Here is the controller source code:
public class HomeController : Controller | |
{ | |
public ActionResult Index() | |
{ | |
return View(new HomeIndexViewModel()); | |
} | |
[HttpPost] | |
public ActionResult Index(HomeIndexViewModel viewModel) | |
{ | |
viewModel.Number = viewModel.Number + 1; | |
return View(viewModel); | |
} | |
} | |
public class HomeIndexViewModel | |
{ | |
public int Number { get; set; } | |
} |
and here is the view code:
@model FormValuesCached.Controllers.HomeIndexViewModel | |
@using (Html.BeginForm()) | |
{ | |
@Html.TextBoxFor(m => m.Number) | |
<input type="submit" value="Click Me"/> | |
} |
Run the code and submit the form. Notice after the first submit, the value in the textbox will change from 0 to 1. Upon subsequent POST’s though, that value won’t change from 1. Why is that?
I ran into this oddity earlier today and did some research, but it wasn’t until a colleague of mine found a key bit of knowledge that the puzzle was solved. During my findings, I noticed many people trying to solve “form values cached after POST” by setting the attribute autocomplete=”off”. Specifically that attribute is placed on the <input>, or even the <form> (due to how different browsers handle this attribute, you may need to place it on <form>, since it may not work otherwise). However in my case, regardless of there being an autocomplete attribute on my <form>, the form values were still cached. Huh?
As a plan B, I tried the following view instead:
@model FormValuesCached.Controllers.HomeIndexViewModel | |
@using (Html.BeginForm()) | |
{ | |
<input type="text" id="number" name="number" value="@(Model.Number)" /> | |
<input type="submit" value="Click Me"/> | |
} |
This time, when I POST the form, the textbox value increments as expected – 0, 1, 2, 3… etc. So why does this work, whilst using the HTML helper method TextBoxFor resulted in cached values? It’s all to do with ModelState.
When Html.TextBoxFor() (or Html.TextBox()) executes, it merges values from the ModelState back into the view. It does this so that if there is a validation error, the value causing the error is rendered on the page. In other words, the old value is used, not the new value that is passed from the action method. This was very confusing at first, but if you think about it, it does make sense. If this did not happen, then you’d get validation errors shown, but with the wrong values being highlighted as the problem.
You can get around this by removing a value from ModelState:
[HttpPost] | |
public ActionResult Index(HomeIndexViewModel viewModel) | |
{ | |
ModelState.Remove("Number"); | |
viewModel.Number = viewModel.Number + 1; | |
return View(viewModel); | |
} |