ASP.NET MVC Html Helpers Not Using Updated Model Values

If you’re using the Html helpers with the naming convention TypeNameFor (Func<>…) or the TypeName (string name,…), like TextBoxFor or TextBox, you might find some odd behavior when you post back form data to the controller, update your model, and send the model back to the view.  I was pulling teeth trying to figure out why certain input text boxes and drop down lists were not being set to the values I had in the model.

Problem:  ASP.NET MVC uses the post back data to set any drop down lists, text boxes, hidden fields, etc. before it uses a particular model’s values.  Let’s say you have the simple model below.

public class Car {
public Manufacturer Make {get;set;}
public string Color {get;set;}
}

In your view you are trying to use the “For” Html helper methods to display the car’s color input, your markup would be:

<% using (Html.BeginForm()) { %>
...
<%=Html.LabelFor(m => m.Color)%>
<%=Html.TextBoxFor(m => m.Color)%>
...
<% } %>

In the simplest cases, this works very well because it automatically binds the value to you model if you use automatic binding (i.e. you post method takes an instance of Car) or manually bind with TryUpdateModel<T>.  A problem arises when you post back, modify the model value, in our case Color, and re-show the form.  This sort of scenario can arise when you have an add form that once the user is done adding one record, you send them back to the add form with new defaults so they can add another record.  Below shows the simple controller and UI flow that will do this.

public CarsController : Controller {
[HttpGet]
public ActionResult Add() {
// our default we want
Car car = new Car {
Color = "Hot Sexy Pink"
};
return View(car); // return the model we want displayed
}

[HttpPost]
public ActionResult Add(Car car) {
try {
carDao.MakePersistent(car);
return Add(); // once saved, return user to add another
} catch {...}
}
}
The UI sequence will be as follows:
 
1.  You initially arrive at page “/Cars/Add” and Color input is:
 
first_add

 

 

2.  You change the value to the color you want.

after_add

3.  You submit the form, car is saved and controller returns you back to the add form to enter another car, but this time the default is incorrect, well from what you want it to be!  It still shows “Firey Red” for the color when the default should be “Hot Sexy Pink”, because the Add() controller method creates a new Car instance, set’s the Color value, and passes it back to the view.

rrr_add

The framework is setting the color input with the forms posted values (ModelState key’s values) and not the model value (car in our case) that you are passing to your view.  I can understand this because it’s the sticky form concept which is very useful, but not in this kind of scenario.  You want it to be set to your defaults.

Solution:  Clear the ModelState before redisplaying the form.  Since the helpers are using the ModelState key’s values, you can make a call to ModelState.Clear() to clear out the posted values.  If clearing all the values is not an option, you will have to set the keys to the desired default values.  Below shows a modification to our view and controller code that will fix our situation.  In the controller I’ve left a bunch a code out for brevity, like validation and error handling, but the idea is if the save is successful, clear the ModelState, and return user to a new add form with correct defaults.

<% using (Html.BeginForm()) { %>
...
<%=Html.LabelFor(m => m.Color)%>
<%=Html.TextBoxFor(m => m.Color)%>
OR
<label>Car color:</label>
<%=Html.TextBox("Color", Model.Color)%>
...
<% } %>

public CarsController : Controller {
[HttpPost]
public ActionResult Add(Car car) {
try {
carDao.MakePersistent(car);
ModelState.Clear(); // clear model state so new add form will not have old posted data
return Add(); // once saved, return user to add another
} catch {...}
}
}

Notice that I named the text box’s name property to the same name as the model property in the case of using Html.TextBox.  This ensures binding sets the correct properties on your model when posting back.  For example if the Car class had a decimal property like “Engine.Displacement”, the text box name would be “Engine.Displacement”.  This is very convenient, but there is a caveat with this as well which I talk about in this post, ASP.NET MVC Html.DropDownList Not Showing Selected Value.

8 comments:

Anonymous said...

Good Find. Thanks.

Anonymous said...

You kept me from ripping out the last patch of my thinning hair. Thanks so much.

Nico said...

Many thanks for this article ! I was stuck with the same situation since half an hour.

Anonymous said...

It's easy to miss this call. Thanks for pointing it out!

-Jan

Anonymous said...

Thanks for pointing this out, just came across the same issue, this post helped fix it!

Arunkumar_Mcsd said...

Hi This is really a great article. I was sticking with this issue for few days .finally i have resolved .

Thanks
Arunkumar_Mcsd

Anonymous said...

Glad I came across this post! It helped me greatly, thanks!

Unknown said...

Sthanks so much!