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)
{
return View("Index");
}
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")] 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.