Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET MVC Model Binding and Data Annotation

0.00/5 (No votes)
23 Feb 2013 1  
An introduction to MVC model binding and data annotation.

Introduction

The ASP.NET MVC separates an web application into three main components: the model, the view, and the controller. The MVC framework maps URLs and passes requests to the controllers. It also provides model binding that retrieves data from requests, populates controller action parameters, takes care of the property mapping and type conversion typically involved in working with ASP.NET request data.

In this article, I will talk about model binding and data validation with data annotation. This article is meant to highlight some of the details of model binding and annotation in MVC that developers may face when you develop MVC application.

Model Binding

Before I talk about model binding, let us see how we normally retrieve the user input data. The following example shows how we retrieve data from request object.

public ActionResult Edit()
{
    int id = Int32.Parse(Request["StudentID"]);
    string studentName = Request["StudentName"];
    DateTime birthday = DateTime.Parse(Request["BirthDay"]);
    var student = new Student() { 
        StudentID = id, 
        StudentName = studentName, 
        BirthDay = birthday 
    };
    ...
}

In the controller, we retrieve the user input from the request object. We need to do the data type conversion and map the input values to properties. We also need to do data validation, such as required or checking data format. There are a lot of codes to write in controller. The ASP.NET MVC framework provides the model binding that greatly simplify the process. The framework retrieves the data, maps data to object properties, validates the data and passes the model object to the controller as parameter. The following example shows how we process user input with model binding.

public ActionResult Edit(Student student)
{
    if (ModelState.IsValid)
    {
        // call update 
        return View("Index");
    }
    // display error 
    return View();
}

In the controller, the model object is created by the model binder and passed as parameter to the controller. We check if the data is valid by calling ModelState.IsValid. If data is valid, we can continue otherwise we will render the view with error message back to the user. The model binder handles all the property mapping and validation.

Default Model Binder

The ASP.NET MVC framework provides a very powerful default model binder that can bind most of the data types, from primitive types, array, collection to complex objects. The following explains binding in more details.

Binding to Primitive Values

The default model binder can bind the request to primitive type as parameters to controller. In following example, we retrieve three values from request.

public ActionResult Edit(int StudentID, string StudentName, DateTime BirthDay)
{
    return View();
}

Binding to Collection

The default model binder can bind to a collection of primitive data type. The collection can be simply an array, or a collection like IEnumerable<T>, ICollection<T>, and IList<T>. The type IDictionary<TKey, TValue> can also be binded. For collection binding, the controller parameter may look like following:

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

public ActionResult Edit(IList<int> id)
{
    return View();
}

To have parameters like above passed to controller, we need to append index to input name to mark input as collection. We will need to have html input like following in html form:

<input type="text" name="id[0]" value="1" />
<input type="text" name="id[1]" value="2" />
<input type="text" name="id[2]" value="3" />

Binding to Simple Objects

The default model binder can bind to a simple object like Student. The Student class has some primitive date type properties and it may look like following:

public class Student
{
    public int StudentID { get; set; }
    public string StudentName { get; set; }
}

The model binder can bind request data to Student object and pass it as parameter to controller.

public ActionResult Edit(Student student) 
{ ... }

To have Student object passed to controller, we need html input like following in your form:

<input type="text" name="StudentID" value="1" />
<input type="text" name="StudentName" value="alex" />

Binding to Collection of Objects

The default model binder can bind to a collection of objects, just like it does for primitive data type. The controller may have signature like following:

public ActionResult Edit(IList<Student> student)
{ ... }

The html input may look like following:

<input type="text" name="student[0].StudentID" value="1" />
<input type="text" name="student[0].StudentName" value="alex" />
<input type="text" name="student[1].StudentID" value="2" />
<input type="text" name="student[1].StudentName" value="joe" />

To bind to a dictionary, the controller may have signature like following:

public ActionResult Edit(IDictionary<string, Student> student)
{ ... }

The html input may look like following:

<input type="text" name="student[0].Key" value="1" />
<input type="text" name="student[0].StudentID" value="1" />
<input type="text" name="student[0].StudentName" value="alex" />
<input type="text" name="student[1].Key" value="2" />
<input type="text" name="student[1].StudentID" value="2" />
<input type="text" name="student[1].StudentName" value="joe" />

Binding to Complex Objects

In previous Student example, the object just has two simple properties, but we can add other object types to it and form a complex object graph. The default model binder can recursively traverses entire complex object graphs and populates nested property values. For example, we have following two classes:

public class Student
{
    public int StudentID { get; set; }
    public string StudentName { get; set; }
    public Phone ContactPhone { get; set; }
}

public class Phone
{
    public string PhoneNumber { get; set; }
}

The controller may have signature like following:

public ActionResult Edit(Student student)
{ ... }

To bind nested properties, we will need to use dot notation to specify the complex type. The format is [ParentProperty].[Property]. The html input may look like following:

<input type="text" name="StudentID" value="1" />
<input type="text" name="StudentName" value="alex" />
<input type="text" name="ContactPhone.PhoneNumber" value="123-000-0000" />

Binding Attribute

We can add binding attribute to instruct model binder when to bind property and when to exclude property. In the following example, we exclude StudentID in binding and include StudentName in binding.

public ActionResult Create([Bind(Exclude="StudentID", Include="StudentName")]Student student)
{ ... }

Custom Model Binder

The default model binder is very powerful and can bind most of the data types. You do not have the need to write your own binder. In case if you really want to bind yourself, you can either implement the IModelBinder interface or derive from the DefaultModelBinder. The following example shows how you bind your custom object by implementing the IModelBinder interface.

public class ProductModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var product = new Product()
        {
            productID = Int32.Parse(bindingContext.ValueProvider.GetValue("productID").AttemptedValue),
            productName = bindingContext.ValueProvider.GetValue("productName").AttemptedValue
        };
        return product;
    }
}

After creating your binder, you need to register the binder. In Application_Start(), you need the following code to register binder for your custom object type.

ModelBinders.Binders.Add(typeof(Product), new ProductModelBinder());

Razor View Engine

We have talked about html form format and data binding so far, but how do we generate the html from the view? To generate the html input shown above, we need to write the MVC view script to do that. I will use razor view engine as examples.

View for Collection

Remember we have the following html input for simple collection.

<input type="text" name="id[0]" value="1" />

In the view, we need to call the html helper function to render the model. I have written a custom extension method for it. The source code for the extension method is included in the download zip file and you can modify it to fit your need. To render the html input, we call the extension method EditorForCollection in the following view "id.cshtml" file.

id.cshtml:
    @Html.EditorForCollection(model => Model, "id")

In above example, we pass view model and variable name to EditorForCollection. The helper function will handle name index in html form.

View for Collection of Objects

For object collection, we can call the same html helper function to render the model.

studentlist.cshtml:
    @Html.EditorForCollection(model => Model, "student")

View for Complex Object

When we add Phone class to Student class, we have used a nested object as its properties. We need to use dot notation to specify the nested relationship. We need to use template for each objects and call EditorFor for each nested objects.

Edit.cshtml:
    @Html.EditorForModel()

Student.cshtml:
    @Html.TextBoxFor(m => m.StudentID)
    @Html.TextBoxFor(m => m.StudentName)
    @Html.EditorFor(m => m.ContactPhone)

Phone.cshtml:
    @Html.TextBoxFor(m => m.PhoneNumber)

In above example, the Student and Phone are the view templates. We call EditorFor for the nested property and it will use dot notation to format the html.

View for Nested Object Collection

When a student has more than one phone numbers, we will need to use collection object as its property as in following example.

public class Student
{
    public int StudentID { get; set; }
    public string StudentName { get; set; }
    public IList<Phone> ContactPhone { get; set; }
}

We can use same view above and call EditorFor for the nested collection property.

Model Data Annotation

The MVC framework not just provide us the model binder to simplify the data retrieve from request, but also provide us the rich support to use data annotation to describe the model for data validation and visual hint.

Data validation

The following examples are annotation for common data validation.

  [Required]
  public int ID { get; set; } 

  [StringLength(50, MinimumLength = 2)]
  public string name { get; set; }

  [Range(15, 100)]
  public int age { get; set; }

Data type

The data type annotation can be used to specify the data type for validation. The information may also be used as UI hint later in rendering process.

  [Url]
  public string url { get; set; }

  [Phone]
  public string phone { get; set; }

  [DataType(DataType.Date)]
  public DateTime updatedate { get; set; }

  [DataType(DataType.PhoneNumber)]
  public string phone { get; set; }

The following is a list of commonly used data type.

  Type           Description
  ----------------------------------------------------------------------------------
  DateTime       Represents an instant in time, expressed as a date and time of day.
  Date           Represents a date value.
  Time           Represents a time value.
  PhoneNumber    Represents a phone number value.
  Currency       Represents a currency value.
  EmailAddress   Represents an e-mail address.
  Password       Represent a password value.
  Url            Represents a URL value.
  CreditCard     Represents a credit card number.
  PostalCode     Represents a postal code.

UI hint

The following examples are annotation for common display UI hint.

  [Display(Name = "Student ID")]
  public int ID { get; set; } 

  [DisplayFormat(DataFormatString = "{0:d}")]
  public DateTime myDate { get; set; }

  [UIHint("CustomDateTime")]        // use template to display date
  public DateTime updateDate { get; set; }

Summary

I have talked about some of the details of model binding and data validation in MVC. Please mark your votes and suggestions. I hope you have enjoyed this article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here