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 {...}
}
}
2. You change the value to the color you want.
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.
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 {...}
}
}