As @Malcolm sais, best practice is to put ModelState
in TempData
, but don't do it manually! If you'd do this manually in every controller action where it's relevant, you would introduce immense amounts of repeated code, and increase the maintenance cost vastly.
Instead, implement a couple of attributes that do the job for you. Kazi Manzur has an approach (scroll down to the end of the post) that has been widely spread, and Evan Nagle shows an implementation with tests that is essentially the same as Kazi's, but with different names. Since he also provides unit tests that make sure the attributes work, implementing them in your code will mean little or no maintenance cost. The only thing you'll have to keep track of is that the controller actions are decorated with the appropriate attributes, which can also be tested.
When you have the attributes in place, your controller might look something like this (I deliberately simplified, because I don't know the class you inherit from):
[HttpPost, PassState]
public ActionResult Update(EntityType entity)
{
// Only update if the model is valid
if (ModelState.IsValid) {
base.Update(entity);
}
// Always redirect to Detail view.
// Any errors will be passed along automagically, thanks to the attribute.
return RedirectToAction("Detail", new { id = entity.Id });
}
[HttpGet, GetState]
public ActionResult Detail(int id)
{
// Get stuff from the database and show the view
// The model state, if there is any, will be imported by the attribute.
}
You mention that you feel putting ModelState
in TempData
feels like a "hack" - why? I agree with you that doing it with repeated code in every single controller action seems hacky, but that's not what we're doing here. In fact, this is exactly what TempData
is for. And I don't think the above code looks hacky... do you?
Although there are solutions to this problem that might appear simpler, such as just renaming the action method to preserve the URL, I would strongly advise against that approach. It does solve this problem, but introduces a couple of others - for example, you'll still have no protection against double form submission, and you'll have pretty confusing action names (where a call to Detail
actually changes stuff on the server).