Blazor Reset Model to Initial State when Click on Cancel (Reset Button) in Editform - blazor-server-side

I have a crud operation using Blazor Server Side and Editform. Everything works great except for when I try to reset the form after editing an existing record.
When I change something in a form control and then click the reset button, it closes the form. The data that I change is updated to the HTML table, but it's not updated in the database.
Is there anyway I can prevent this?
Here is my model:
public class Address
{
public string province { get; set; }
public string address { get; set; }
public string contact_name { get; set; }
public string phone_number { get; set; }
}
This is my EditForm:
<EditForm Model="#model" OnValidSubmit="#HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group">
<InputText #bind-Value="#model.province" />
</div>
<div class="form-group">
<InputText #bind-Value="#model.contact_name" />
</div>
<div class="form-group">
<InputText #bind-Value="#model.phone_number" />
</div>
<div class="form-group">
<InputText #bind-Value="#model.address" />
</div>
<button type="submit" class="btn btn-primary">Save</button>
<button type="reset" class="btn btn-warning">Cancel</button>
</EditForm>
Here is my HTML Table:
#if (address_list== null)
{
<p>Loading</p>
}
else
{
<table class="table table-striped text-center">
<thead>
<tr>
<th scope="col" class="text-center">Province</th>
<th scope="col" class="text-center">Contact Name</th>
<th scope="col" class="text-center">Phone</th>
<th scope="col" class="text-center">Address</th>
<th scope="col" class="text-center">Edit</th>
<th scope="col" class="text-center">Delete</th>
</tr>
</thead>
<tbody>
#foreach (var d in address_list)
{
<tr>
<td>#d.province</td>
<td>#d.contact_person</td>
<td>#d.phone_number</td>
<td>#d.address</td>
<td><button type="button" class="btn btn-link" #onclick="#(() => Edit(d))">Edit</button></td>
<td><button type="button" class="btn btn-link" #onclick="#(() => Delete(d))">Delete</button></td>
</tr>
}
</tbody>
</table>
}

You don't show your server-side code related to the edit form, but I assume it looks something like this:
#code {
public Address model { get; set; }
}
You can actually implement the getter and setter. In doing so, create a typical backing field, but then also create a clone field to capture your initial data. Also create a reset function that captures the values from the clone field and puts it back in to the current model state.
#code {
public Address model {
get { return _model; }
set {
_model = value;
_clone = new Address {
province = value.province,
address = value.address,
contact_name = value.contact_name,
phone_number = value.phone_number
}
}
Address _model;
Address _clone;
public void reset () {
_model.province = _clone.province;
_model.address = _clone.address;
_model.contact_name = _clone.contact_name;
_model.phone_number = _clone.phone_number;
}
}
Then, in your edit form, instead of a 'reset' type button, do:
<button class="btn btn-warning" #onclick="reset" #onclick:preventDefault>Cancel</button>
This will last you until the guys at Microsoft implement something like:
<InputText #firstNonBlankAsInitial="true" #bind-Value="#model.province" />

Extending to the answer provided by #pwilcox
You can use the serializer
var modelString = JsonConvert.SerializeObject(model);
_clone = JsonConvert.DeserializeObject< Address >(modelString);
and then in the reset function do the reverse
var modelString = JsonConvert.SerializeObject(_clone);
model = JsonConvert.DeserializeObject< Address >(modelString);

There can't be any easy solution since the problem is conceptual. You can't reset form to initial values with a button type reset. Reset button, reset all fields to their initial values, that is to the value they had when the HTML form was created.
However, the Html form may be destroyed and created several time during a single Edit because of automatic Blazor creation and destruction of components. For instance, if some fields or the whole form are within a TabItem of a Tab control fields and/or form is created/destroyed whenever you change the selected tab. For this reason form "initial values" differ from the initial values appearing the first time the form showed up, and are meaningless values.
Thus you can't act on Html fields or forms, but you need to act on the model that is bound to the form since just the underlying model is ensured to survive to all Blazor re-rendering.
Summing up the only solution is to store the initial property values of your model somewhere, and to copy on your model these initial values whenever the user cancel the operation either with a reset button or by closing a modal, and so on.

The "changed" data is not in sync anymore with the actual data in the database.
A quick (but not the most elegant) solution is to requery the data from the database.

Related

How to process forms in ASP.NET Core with unknown fields

I want to create an Http post method that will be able to process html forms of different inputs.
[HttpPost]
public async Task<IActionResult> Create(string content)
{
// process content here.
return View("Success");
}
My method should be able to support this form
<form action="url" method="POST">
<input type="text" name="firstName">
<input type="text" name="lastName">
<button type="submit">Submit</button>
</form>
as well as this form
<form action="url" method="POST">
<input type="text" name="name">
<input type="text" name="surname">
<input type="text" name="age">
<button type="submit">Submit</button>
</form>
How can I modify my method so that the content will be populated for both of these cases?
To process forms with unknown fields, you can use the FormCollection type as a parameter in the Create method. The FormCollection type represents a collection of keys and values that are sent as the HTTP request body when the form is submitted. You can use this type to capture all the form data, regardless of the number or names of the fields.
To access the form data, you can use the formData object. For example, you can access a field with the name "firstName" using the following syntax: formData["firstName"].
PdfController.cs:
public class PdfController: Controller
{
[HttpPost]
public IActionResult Create(IFormCollection formData)
{
// process form data here
var surName= formData["surname"].ToString();
return View("Create", (FormCollection)formData);
}
}
Form:
<form action="/pdf/create" method="POST">
<input type="text" name="name">
<input type="text" name="surname">
<input type="text" name="age">
<button type="submit">Submit</button>
</form>
Create.cshtml:
#model FormCollection
<h1>Form Data</h1>
<table>
#foreach (var key in Model.Keys)
{
<tr>
<td>#key</td>
<td>#Model[key]</td>
</tr>
}
</table>
View:
How can I modify my method so that the content will be populated for
both of these cases?
Well, based on your scenario and requirement,we have two option other than [FormBody] pattern,to deal with completely dynamic value, we can use IFormCollection and a Dictionary.
Our goal is to handle any dynamic data which we will finally add into our Dictionary using its Key and Value.
Completely Dynamic Form Request Example:
[HttpPost]
public IActionResult Create(IFormCollection dynamicData)
{
var dynamicFormDataDictionary = new Dictionary<string, object>();
foreach (var item in dynamicData)
{
dynamicFormDataDictionary.Add(item.Key, item.Value);
dynamicFormDataDictionary.Remove("__RequestVerificationToken");
}
return View("DynamicOutput", dynamicFormDataDictionary);
}
Note: We cannot use FormCollection because it will encounter runtime exception as it has Interface of IFormCollection type. .
Bind Dynamic Data And Display in View:
Controller:
public IActionResult DynamicOutput()
{
return View();
}
View:
#model Dictionary<string, object>
<h4>Dynamic Data</h4>
<table class="table">
#foreach (var key in Model.Keys)
{
<tbody>
<tr>
<th>
#key
</th>
<td>
#Model[key]
</td>
</tr>
</tbody>
}
</table>
Output:
Alternative Way:
We have another way, which is using Request.Form but here we need to define our data fields. In following way we can get the data:
var input1 = Request.Form["firstName"].GetValue();
var input2 = Request.Form["name"].GetValue();
Note: Point to remember, in this way, let's say, you have two request from we would define all the property together and allow null-value. Therefore, we would get our desired value from one request form while other we would remain empty.

Add Comment Section in View DetailPage

I am working on Bookevent application I want to add comment section on eventdetail page where I am already using EventViewModel to display information of event detail and I also what to add comment section so user can post the comment.
So basically, My question is how can I use comment model as well on same view page. I searched for it and I found that we can use tuple. But I think that's not good practice in this case. Can Someone suggest any other way please.
CommentViewModel.cs
public class CommentViewModel
{
[Required]
public string CommentAdded { get; set; }
[Required]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime Date { get; set; }
[Required]
public int EventId { get; set; }
}
EventDetail.cshtml
#model EventViewModel
<div class="container">
<div class="row">
<h2 class="mt-5 mb-5 text-center">Event Details</h2>
<div class="table-responsive field ms-5 mt-3">
<table class="table" id="stu_table">
<tbody>
<tr>
<th>Title</th>
<td>Title</td>
</tr>
<tr>
<th>Date</th>
<td>Date</td>
s
</tr>
<tr>
<th>Location</th>
<td>Location</td>
</tr>
<tr>
<th>StartTime</th>
<td>StartTime</td>
</tr>
<tr>
<th>Type</th>
<td>Type</td>
</tr>
<tr>
<th>Duration</th>
<td>Duration</td>
</tr>
<tr>
<th>Description</th>
<td>Description</td>
</tr>
<tr>
<th>OtherDetails</th>
<td>OtherDetails</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<form asp-controller="Comment" asp-action="CreateComment">
<div>
<label asp-for="CommentAdded"class="control-label">Add Comment</label>
<textarea asp-for="CommentAdded"rows="4" cols="50"></textarea>
<input class="btn-btn-default" value="Create" type="submit"/>
</div>
</form>
</div>
</div>
"I want to add comment section on eventdetail page where I am already using EventViewModel"
Okay, so you have 2 options.
Use ViewBag or ViewData[] to pass the additional data from the controller to the View and then display it, you can still use your EventViewModel as well without changing anything in it.
In this case though you can't use "asp-for=" for the things you got from the ViewBag/ViewData[].
If you want to send the data back to the controller after submitting the form, use "name=" and make a parameter in your controller action with the same string name.
The method I prefer is to just put everything that you need for a particular view in its own view model. If you already have a view model EventViewModel, just add CommentViewModel inside of it (view model that has another view model).
Your code will look like this:
public class EventViewModel
{
public CommentViewModel CommentViewModel { get; set; }
// the usual properties you have for EventViewModel
}

Checkbox is not retaining its state while using ViewBag in .net core

I am using ViewBag to store the state of checkbox. It is always showing false. When I remove value="isCurrentFilter"then It is showing enabled when I check the checkbox.
Can anyone guide me where I am going wrong?
Please advise me.
Controller
Public async Task<IActionResult> Index(bool searchText, bool currentFilter, int? page)
{
int selectedPage = page ?? 1;
int bypassCount = (selectedPage - 1) * _pagingOptions.PageSize;
if (searchText != false)
{
page = 1;
}
else
{
searchText = currentFilter;
}
ViewBag.CurrentFilter = searchText;
}
Index.cshtml
<form asp-action="Index" method="get">
#{ bool isCurrentFilter = ViewBag.CurrentFilter; }
<input type="checkbox" asp-for="searchText" value="isCurrentFilter" class="form-control" />
<div class="col-md-12">
<button class="btn btn-primary" type="submit">Search</button>
</div>
</form>
<table class="table">
<thead>
<tr >
<th>Message Id</th>
<th>Status</th>
<th>Resent</th>
<th>Resent Date</th>
<th>Created Date</th>
</tr>
</thead>
<tbody>
#if (Model.Items.TotalItemCount > 0)
{
#foreach (var item in Model.Items.ToList())
{
<td>#Html.DisplayFor(modelItem => item.MessageId)</td>
<td>#Html.DisplayFor(modelItem => item.Status)</td>
<td>#Html.DisplayFor(modelItem => resentString)</td>
<td>#Html.DisplayFor(modelItem => resentDateString)</td>
<td>#Html.DisplayFor(modelItem => createdDateString)</td>
</tr>
}
}
</tbody>
</table>
</div>
using ViewBag to store the state of checkbox. It is always showing false.
If you check the source code of checkbox in browser, you would find that it might be rendered as below, and the value property of checkbox is false, so you always get false even if you checked it and submit the form.
When I remove value="#isCurrentFilter" then It is showing enabled when I check the checkbox.
Without explicitly set value property for your checkbox <input type="checkbox" asp-for="searchText" class="form-control"/>, the value property would be true.
And the rendered HTML as above will also include two fields (checkbox and hidden) for the searchText property. If the checkbox is checked, the posted value will be searchText=true&searchText=false. The model binder will correctly extract true from the value. Otherwise it will be false. SO as you said, removing value="#isCurrentFilter" will work.
You may use #ViewBag.CurrentFilter instead
Like this
<input type="checkbox" asp-for="searchText" value="#ViewBag.CurrentFilter" class="form-control" />

View is posting a empty object on submit when expecting the modified object

I have a View that lets users edit data that is fetched from a database and converted into a DataTable (for simplicity since the data can get really complicated and deep).
The issue is that when I POST the data back to the responsible Controller the controller receives the DataTable object but it's empty, e.g. the changes made by a user never get back to the Controller and cannot be saved to Database.
I am at most intermediate at web programming so I appreciate straight answers or direct pointers.
View:
#model System.Data.DataTable
#{
ViewData["Title"] = "Edit";
Layout = "~/Views/Shared/_LayoutAdminlte.cshtml";
}
#using (Html.BeginForm("EditSave", "Recipe", FormMethod.Post, new { #id = "Properties-Form" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<h2>#Html.DisplayFor(m => Model.TableName)</h2>
</div>
<div class="box box-primary">
<table id="Properties" class="table table-condensed">
<thead>
<tr>
#foreach (System.Data.DataColumn col in Model.Columns)
{
<th>
#Html.DisplayFor(m => col.Caption)
</th>
}
</tr>
</thead>
<tbody>
#foreach (System.Data.DataRow row in Model.Rows)
{
<tr>
#foreach (var cell in row.ItemArray)
{
<td>
#if (row.ItemArray.ToList().IndexOf(cell) == 0)
{
#cell.ToString()
}
else
{
<input asp-for="#cell" />
}
</td>
}
</tr>
}
</tbody>
</table>
</div>
<div class="box-footer">
<!-- id="Save" -->
<input class="btn btn-primary pull-right" type="submit" value="Spara" id="Save" />
<i class="fa fa-show"></i> Visa
<i class="fa fa-show"></i> Avbryt
</div>
</div>
}
#section Scripts{
#await Html.PartialAsync("_ValidationScriptsPartial")
}
Controller:
[HttpPost]
public ActionResult EditSave(DataTable model)
{
Debugger.Break();
return View("Edit", model);
}
This <input asp-for="#cell" /> is your problem.
If you run your program and use the developer tools to inspect the html generated for the input fields, you will notice the generated html attributes.
the particular one that you should pay attention to is the name attribute.
The way model binding works in .net core when you send data to the controller.
public IActionResult DoSomething(Model model) { ... }
you would have match the name attribute to the property of the object. Example:
<input name="model.Level" value="8999" />
so then you would get var level = model.Level and level will be 8999.
So you have to take care when you use the asp-for it's not doing as much heavy lifting as you think. Always check the html generated.
PS
Don't use DataTables, other developers will throw rocks at you. Don't be lazy map to an actual object or use an ORM, programs quickly become unmaintainable if you use them to dynamically store data.

MVC 4 Model not returned to controller

I have three cascading dropdowns using Ajax to populate themselves on a view. Also in the view I call a partial view that iterates a container in the model and for each item calls another partial to display for editing the model properties appropriate according to the selected items in the dropdowns. I have built a complex model that contains the dropdown choices as well as the properties to be edited, and pass the portion of the model to the lowest level partial necessary to display the properties to be edited.
I want to update the db when the user clicks the submit button, through a normal Html.BeginForm, not by using Ajax. So I must wrap only the partials that display the properties in the form so that the existing Ajax functionality does not post to the controller. The problem is that although I can build this all, and the submit button connects to the controller as expected, the model returns to the controller null.
Does the model not come back from the partials up through the path that built them? Or more correctly stated, does the model not persist on the page even if it is built using partials?
I am sure someone is going to suggest that I post back using Ajax but that is not a best option for me. Or someone might ask what the html looks like on the page. Oddly, I can only see the complete html using browser developer tools, it does not show in a View Source selection.
Any ideas?
Moving this to where it belongs:
I'm not certain I understand. I get that the returning model needs to match the expected model but I don't get the explanation above. You say "So if your controller looks like this:", and of course it does, then what? That's wrong?
I have to look in dev tools for Chrome to see the actual html output and I see this in one case:
<input class="text-box single-line" id="status_Comments" name="status.Comments" type="text" value="Last Install to be completed this weekend">
So if the 'name' tag needs to look proper, I think it does. Am I wrong?
My date fields look like this:
<input type="text" value="8/19/2014" class="datepicker hasDatepicker" id="dp1391795955619">
So there's no 'name' tag but does have an id. Do I need to add a name?
Here's the code that generates the above:
#foreach (Status status in Model) {
string date = status.Date.HasValue ? status.Date.Value.ToShortDateString() : string.Empty;
<tr>
<td style="width: 175px;">#Html.DisplayFor(model => status.Name)</td>
<td style="width: 75px;">#Html.DisplayDropdownColorFor(model => status.StatusValue)</td>
<td style="width: 80px;"><input type="text" value="#date" class="datepicker" /></td>
<td style="width: 80px;"><input type="text" value="" class="datepicker" /></td>
<td style="width: 375px;">#Html.EditorFor(model => status.Comments)</td>
</tr>
}`
Geez, I sound like a desperate moron.
Thanks for your help.
Let's say you had a couple of classes like this:
public class SomeClass1
{
public string Name { get; set; }
public SomeClass2 class2Value { get; set; }
}
public class SomeClass2
{
public string Description { get; set; }
}
And you have a form in a Razor view:
#model SomeClass1
#using (Html.BeginForm()) {
<fieldset>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.class2Value.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.class2Value.Description)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
The actual HTML code is going to look something like this:
<form id="form1" action="/pageurl" method="POST">
<fieldset>
<div class="editor-label">
Name
</div>
<div class="editor-field">
<input type="text" name="Name" id="Name" value="Name val 1" />
</div>
<div class="editor-label">
Description
</div>
<div class="editor-field">
#Html.EditorFor(model => model.class2Value.Description)
<input type="text" name="class2Value.Description" id="class2Value_Description" value="Desc val 2" />
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
If you notice, the name attribute of the form fields are set by MVC to follow the model. In the case of the nested object, it uses the underscore in the ID and period in the name to keep track of the where in the post model the values should go.
So if your controller looks like this:
[HttpPost]
protected ActionResult Save(SomeClass1 model)
{
//Then
// SomeClass1.Name == "Name val 1"
// and
// SomeClass1.class2Value.Description == "Desc val 2"
}
That's what #David was getting at in the comments... No matter how complex your page, when the <form> posts back to the server, the name attributes of the fields you are returning have to line up with the model that the controller action is expecting... or you will get null values.
You can even create a custom class for the post model, and as long as the name attributes line up with the model, and even though it was not the same as the #model at the top of the Razor view, MVC will use the key/value pairs and populate the data in your post model.
UPDATE
I updated the above to reflect a correction. The underscore is the delimiter in the ID attribute, and the period is used in the NAME attribute.
#leemid: An HTML form only posts fields with a name attribute. Only the name'ed inputs, selects, textareas, etc. will be passed back to the server when the form is submitted. And those names have to be inline with the model that is expected by the controller. For the sake of sounding redundant, that date input field you showed the example for, does not have a name attibute, and if the name were the same as the id, there would have to be a property in your model like
public string dp1391795955619 { get; set; }
for it to show up in your in controller action. You can set the name manually, as long as it's named the way MVC is expecting so that it can pass it into the model when posting.
In my example, the parts I was trying to highlight was the relationship between the class and property names, versus the name attributes MVC writes to the HTML document, and how it keeps all that stuff straight. In simple examples, you don't have to think about it. But in your complex scenario, it looks like you're going to have to manage some of the naming conventions manually so that MVC can understand what you're trying to do.