Default Model Binding Behavior in MVC2

This article explains the key concepts to be aware of with model binding in MVC2.

When an HTTP request is made to the server, request routing directs it to a Controller for execution. The Controller collects HTTP values in the request and loads them into a ModelState dictionary, which is used throughout the MVC request lifecycle. In this article, various ways in which ModelState can be consumed within the Action are described. ModelBinders provide the logic for converting the ModelState data into simple and complex types variables. The default ModelBinder used by MVC is very versatile.

For example, model binding is automatically performed on Action method parameters:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Product product)
{
    // ...
}

In this case, the properties of the product object are initialized from HTTP values with matching names.

You can also handle the assignments yourself:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection formValues)
{
  var product = new Product();
  product.ProductName = Request.Form["Product.ProductName"];
  decimal unitPrice;
  Decimal.Parse(Request.Form["Product.UnitPrice"], out unitPrice);
  product.UnitPrice = unitPrice;
  // ...
}

This approach is a bit long winded by comparison. Notice that type conversion for the UnitPrice value has to be explicitly written. Also, in this simplified example, there is no error handling for a type conversion failure that would occur if the user enters a non-numeric value in the Product.UnitPrice form field. This raises the question about what happens if a type conversion failure occurs in the first scenario. The answer is that the ModelBinder would leave product.UnitPrice with its initial value of 0, and record the type conversion error as a model validation error within ModelState. The boolean property ModelState.IsValid reports on whether validation errors exist or not.

How about a hybrid of the first two approaches:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection formValues)
{
  var product = new Product();
  UpdateModel(product);
}

In this third approach, the UpdateModel is called explicitly to perform model binding within the Action. If a model validation failure occurs, then UpdateModel throws an exception. If you prefer, you can use TryUpdateModel instead, which returns false instead of throwing an exception.

So, why not always use the first approach? Well, when creating new objects, there is usually no reason not to. However, the third approach is needed when implementing a post Action to update an existing object rather than create it:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues)
{
  var product = GetProduct(id);
  UpdateModel(product);
}

In this scenario the benefit of binding the model with the Action becomes clear. It allows us to work with an existing object rather than automatically creating a new object.

You may observe that the FormCollection parameter seems to serve no purpose. That is quite correct. It can be removed without impacting the behavior of the Action, but in most scenarios its removal does create a different problem, which is a duplicate method declaration error. Without the second parameter, the method signature would look exactly like another Action method with the same name:

public ActionResult Edit(int id) {
    return View (GetProduct(id));
}

This method looks quite similar in its declaration. The key difference is the lack of the AcceptVerbs attribute. That’s because this method is used for the HTTP GET operation which returns the existing product when it is requested. It is a common convention to use the same Action name “Edit” for the method that both fetches the item for display and that accepts the posted update. Only the method responsible for the post action is decorated with the AcceptVerbs(HttpVerbs.Post) attribute. Similarly, the Action name “Create” commonly has a method for creating the empty item and for accepting the posted values that result in submitting the new item to its storage repository.

Since most of the time, you’re not going to use the FormCollection parameter, you may prefer to use a different method name for the post action in conjunction with the the ActionName attribute to explicitly associate the name of the Action to the method. This approach would look like this:

[AcceptVerbs(HttpVerbs.Post), ActionName("Edit")]
public ActionResult EditPost(int id) {
    return View (GetProduct(id));
}

One final binding scenario that you may see in Edit scenarios is to perform model binding automatically on the parameter object, and then within the method copy the property values to a separate instance of the object:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, Product updatedProduct)
{
    var product = GetProduct(id);
    product.ProductName = updatedProduct.ProductName;
    product.UnitPrice = updatedProduct.UnitPrice;
    // ...
}

Selective Binding

By default, binding will assign every property that has a matching posted form element name. In practice, it’s a good idea to limit the properties to be bound to include only those that editable by the user. One way to do this is to use the Bind attribute:

public ActionResult Edit(int id, [Bind(Include="ProductName, UnitPrice")]Product product)

If you’re using using UpdateModel or TryUpdateModel to explicitly invoke the binding, then some of the overloads to this method allow similar binding control:

UpdateModel(product, new string[] {"UnitPrice", "ProductName"});

Both approaches also allow you to define which properties to exclude rather than include if you prefer to do it that way.

Validation

There is quite a bit of information on the web already about using data annotations to implement validation rules, including the MSDN documentation, so I won’t repeat it here. Beyond the basic principles, I recommend reading Brad Wilson’s article on Input Validation vs Model Validation. In addition to describing how validation is performed during model binding, it also serves as an excellent reference for understand what does and does not get bound when working with strongly-typed classes. Also, see Phil Haack’s post on creating custom validations.

2 thoughts on “Default Model Binding Behavior in MVC2”

  1. this is great… but how would i exclude a property of a complex type ?

    ie:

    public ActionResult Details([Bind(Exclude="User.IDUSER")]AccountDetailsModel details)

    doesnt work…

Leave a Reply

Your email address will not be published. Required fields are marked *