Can anyone give me a succinct definition of the role of ModelState in Asp.net MVC (or a link to one). In particular I need to know in what situations it is necessary or desirable to call ModelState.Clear()
.
Bit open ended huh... sorry, I think it might help if tell you what I'm acutally doing:
I have an Action of Edit on a Controller called "Page". When I first see the form to change the Page's details everything loads up fine (binding to a "MyCmsPage" object). Then I click a button that generates a value for one of the MyCmsPage object's fields (MyCmsPage.SeoTitle
). It generates fine and updates the object and I then return the action result with the newly modified page object and expect the relevant textbox (rendered using <%= Html.TextBox("seoTitle", page.SeoTitle)%>
) to be updated ... but alas it displays the value from the old model that was loaded.
I've worked around it by using ModelState.Clear()
but I need to know why / how it has worked so I'm not just doing it blindly.
PageController:
[AcceptVerbs("POST")]
public ActionResult Edit(MyCmsPage page, string submitButton)
{
// add the seoTitle to the current page object
page.GenerateSeoTitle();
// why must I do this?
ModelState.Clear();
// return the modified page object
return View(page);
}
Aspx:
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyCmsPage>" %>
....
<div class="c">
<label for="seoTitle">
Seo Title</label>
<%= Html.TextBox("seoTitle", page.SeoTitle)%>
<input type="submit" value="Generate Seo Title" name="submitButton" />
</div>
I think is a bug in MVC. I struggled with this issue for hours today.
Given this:
The view renders with the original model, ignoring the changes. So I thought, maybe it does not like me using the same model, so I tried like this:
And still the view renders with the original model. What's odd is, when I put a breakpoint in the view and examine the model, it has the changed value. But the response stream has the old values.
Eventually I discovered the same work around that you did:
Works as expected.
I don't think this is a "feature," is it?
Well the ModelState basically holds the current State of the model in terms of validation, it holds
ModelErrorCollection: Represent the errors when the model try to bind the values. ex.
or like a parameter in the ActionResult
ValueProviderResult: Hold the details about the attempted bind to the model. ex. AttemptedValue, Culture, RawValue.
Clear() method must be use with caution because it can lead to unspected results. And you will lose some nice properties of the ModelState like AttemptedValue, this is used by MVC in the background to repopulate the form values in case of error.
Update:
View()
from a POST action. Use PRG instead and redirect to a GET if the action is a success.View()
from a POST action, do it for form validation, and do it the way MVC is designed using the built in helpers. If you do it this way then you shouldn't need to use.Clear()
ModelState
since you shouldn't be using it anyway.Old answer:
ModelState in MVC is used primarily to describe the state of a model object largely with relation to whether that object is valid or not. This tutorial should explain a lot.
Generally you should not need to clear the ModelState as it is maintained by the MVC engine for you. Clearing it manually might cause undesired results when trying to adhere to MVC validation best practises.
It seems that you are trying to set a default value for the title. This should be done when the model object is instantiated (domain layer somewhere or in the object itself - parameterless ctor), on the get action such that it goes down to the page the 1st time or completely on the client (via ajax or something) so that it appears as if the user entered it and it comes back with the posted forms collection. Some how your approach of adding this value on the receiving of a forms collection (in the POST action // Edit) is causing this bizarre behaviour that might result in a
.Clear()
appearing to work for you. Trust me - you don't want to be using the clear. Try one of the other ideas.Generally, when you find yourself fighting against a framework standard practices, it is time to reconsider your approach. In this case, the behavior of ModelState. For instance, when you don't want model state after a POST, consider a redirect to the get.
EDITED to answer culture comment:
Here is what I use to handle a multi-cultural MVC application. First the route handler subclasses:
And here is how I wire up the routes. After creating the routes, I prepend my subagent (example.com/subagent1, example.com/subagent2, etc) then the culture code. If all you need is the culture, simply remove the subagent from the route handlers and routes.
Well lots of us seem to have been bitten by this, and although the reason this happens makes sense I needed a way to ensure that the value on my Model was shown, and not ModelState.
Some have suggested
ModelState.Remove(string key)
, but it's not obvious whatkey
should be, especially for nested models. Here are a couple methods I came up with to assist with this.The
RemoveStateFor
method will take aModelStateDictionary
, a Model, and an expression for the desired property, and remove it.HiddenForModel
can be used in your View to create a hidden input field using only the value from the Model, by first removing its ModelState entry. (This could easily be expanded for the other helper extension methods).Call from a controller like this:
or from a view like this:
It uses
System.Web.Mvc.ExpressionHelper
to get the name of the ModelState property.I wanted to update or reset a value if it didn't quite validate, and ran into this problem.
The easy answer, ModelState.Remove, is.. problematic.. because if you are using helpers you don't really know the name (unless you stick by the naming convention). Unless perhaps you create a function that both your custom helper and your controller can use to get a name.
This feature should have been implemented as an option on the helper, where by default is does not do this, but if you wanted the unaccepted input to redisplay you could just say so.
But at least I understand the issue now ;).