Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asp.net MVC ModelState.Clear

I think is a bug in MVC. I struggled with this issue for hours today.

Given this:

public ViewResult SomeAction(SomeModel model) 
{
    model.SomeString = "some value";
    return View(model); 
}

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:

public ViewResult SomeAction(SomeModel model) 
{
    var newModel = new SomeModel { SomeString = "some value" };
    return View(newModel); 
}

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:

public ViewResult SomeAction(SomeModel model) 
{
    var newModel = new SomeModel { SomeString = "some value" };
    ModelState.Clear();
    return View(newModel); 
}

Works as expected.

I don't think this is a "feature," is it?


Update:

  • This is not a bug.
  • Please stop returning View() from a POST action. Use PRG instead and redirect to a GET if the action is a success.
  • If you are returning a 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()
  • If you're using this action to return ajax for a SPA, use a web api controller and forget about 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.


If you want to clear a value for an individual field then I found the following technique useful.

ModelState.SetModelValue("Key", new ValueProviderResult(null, string.Empty, CultureInfo.InvariantCulture));

Note: Change "Key" to the name of the field that you want to reset.


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.

TryUpdateModel();
UpdateModel();

or like a parameter in the ActionResult

public ActionResult Create(Person person)

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.

ModelState["a"].Value.AttemptedValue

I had an instance where I wanted to update the model of a sumitted form, and did not want to 'Redirect To Action' for performanace reason. Previous values of hidden fields were being retained on my updated model - causing allsorts of issues!.

A few lines of code soon identified the elements within ModelState that I wanted to remove (after validation), so the new values were used in the form:-

while (ModelState.FirstOrDefault(ms => ms.Key.ToString().StartsWith("SearchResult")).Value != null)
{
    ModelState.Remove(ModelState.FirstOrDefault(ms => ms.Key.ToString().StartsWith("SearchResult")));
}

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 what key should be, especially for nested models. Here are a couple methods I came up with to assist with this.

The RemoveStateFor method will take a ModelStateDictionary, 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).

/// <summary>
/// Returns a hidden input field for the specified property. The corresponding value will first be removed from
/// the ModelState to ensure that the current Model value is shown.
/// </summary>
public static MvcHtmlString HiddenForModel<TModel, TProperty>(this HtmlHelper<TModel> helper,
    Expression<Func<TModel, TProperty>> expression)
{
    RemoveStateFor(helper.ViewData.ModelState, helper.ViewData.Model, expression);
    return helper.HiddenFor(expression);
}

/// <summary>
/// Removes the ModelState entry corresponding to the specified property on the model. Call this when changing
/// Model values on the server after a postback, to prevent ModelState entries from taking precedence.
/// </summary>
public static void RemoveStateFor<TModel, TProperty>(this ModelStateDictionary modelState, TModel model,
    Expression<Func<TModel, TProperty>> expression)
{
    var key = ExpressionHelper.GetExpressionText(expression);

    modelState.Remove(key);
}

Call from a controller like this:

ModelState.RemoveStateFor(model, m => m.MySubProperty.MySubValue);

or from a view like this:

@Html.HiddenForModel(m => m.MySubProperty.MySubValue)

It uses System.Web.Mvc.ExpressionHelper to get the name of the ModelState property.