DataAnnotations and ASP.NET MVC
In .NET 3.5 SP1, the ASP.NET team introduced a new DLL named System.ComponentModel.DataAnnotations, in conjunction with the ASP.NET Dynamic Data project. The purpose of this DLL is to provide UI-agnostic ways of annotating your data models with semantic attributes like [Required] and [Range]. Dynamic Data uses these attributes, when applied to your models, to automatically wire up to validators in WebForms. The UI-agnostic bit is important, and is why the functionality exists in the System.ComponentModel.DataAnnotations namespace, rather than somewhere under System.Web.
For .NET 4.0, the .NET RIA Services team is also supporting DataAnnotations (which have been significantly enhanced since their initial introduction). This means that models you annotate can end up with automatic validation being performed in both client- and server-side code, supporting WebForms (via Dynamic Data) as well as Silverlight (via RIA Services).
In our exploration of data support in ASP.NET MVC, we wrote a model binder which does server-side validation in MVC by relying on the DataAnnotations attributes. Using a preview of the .NET 4.0 DataAnnotations DLL (the same one that we released with Dynamic Data 4.0 Preview 3).
How Does It Work?
The MVC DefaultModelBinder class has a lot of extensibility points, some of which are designed specifically with validation in mind. The DataAnnotations model binder leverages those extension points to allow DataAnnotations attributes to contribute to the validation of a model.
For example, let’s take a simple model:
public class Contact
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
In a standard MVC application, if I want FirstName and LastName to be required, I have to write custom validation code to make this happen. My action method might look something like this:
public ActionResult Edit(Contact contact)
{
if (String.IsNullOrEmpty(contact.FirstName))
ModelState.AddModelError("FirstName", "First name is required");
if (String.IsNullOrEmpty(contact.LastName))
ModelState.AddModelError("LastName", "Last name is required");
try
{
if (ModelState.IsValid)
{
// Submit the changes to the database here
return Redirect("Index");
}
}
catch(Exception ex)
{
// Log the exception somewhere to be looked at later
ModelState.AddModelError("*", "An unexpected error occurred.");
}
return View(contact);
}
Now let’s take a look at the same model, but using DataAnnotations:
public class Contact
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
And our updated action method:
public ActionResult Edit(Contact contact)
{
try
{
if (ModelState.IsValid)
{
// Submit the changes to the database here
return Redirect("Index");
}
}
catch(Exception ex)
{
// Log the exception somewhere to be looked at later
ModelState.AddModelError("*", "An unexpected error occurred.");
}
return View(contact);
}
Notice how much cleaner the action method is, now that the validation of the model has been moved into the metadata on the model itself. Now the action method can just focus on submission and error handling, without being concerned about how to validate the model.
To make this work, you need to compile the DataAnnotations model binder project, and then added references to the two DLLs in you find in the src\bin\Debug folder (Microsoft.Web.Mvc.ModelBinders.dll and System.ComponentModel.DataAnnotations.dll).
Then, in your Global.asax.cs file, you make the following changes to register the model binder:
void Application_Start() { RegisterRoutes(RouteTable.Routes); RegisterModelBinders(ModelBinders.Binders); // Add this line } public void RegisterModelBinders(ModelBinderDictionary binders) // Add this whole method { binders.DefaultBinder = new Microsoft.Web.Mvc.DataAnnotations.DataAnnotationsModelBinder(); }
Now when you submit forms, the model binder will automatically find instances of the DataAnnotations attributes on your models and run the validations you’ve specified.
Summary:
I like attribute based validation. I suppose using the DataAnnotations Attributes would be limited to Required, Range, RegularExpression and StringLength and other custom validation would be handled by your code within the Edit method. In addition, the Name parameter of the [Display] attribute can be used to influence the default messages. The default message for Required, for example, is something "The {0} field is required.", where {0} is the name of the property; however, if you use [Display(Name=)] to set the name, it will use that instead in your error message.
In other attribute based approaches, you are able to call delegates from attributes and they validate the properties. I'm not sure what you can and can't do from the DataAnnotations.
You can apply validation attributes to the class as well as to properties. Since none of the built-in validations do what you want, you have a couple choices:
1. Write a new attribute which derives from ValidationAttribute and apply it to the class;
2. Apply the [CustomValidation] attribute to the class, pointint to a custom validation method of your writing.
For .NET 4.0, the .NET RIA Services team is also supporting DataAnnotations (which have been significantly enhanced since their initial introduction). This means that models you annotate can end up with automatic validation being performed in both client- and server-side code, supporting WebForms (via Dynamic Data) as well as Silverlight (via RIA Services).
In our exploration of data support in ASP.NET MVC, we wrote a model binder which does server-side validation in MVC by relying on the DataAnnotations attributes. Using a preview of the .NET 4.0 DataAnnotations DLL (the same one that we released with Dynamic Data 4.0 Preview 3).
How Does It Work?
The MVC DefaultModelBinder class has a lot of extensibility points, some of which are designed specifically with validation in mind. The DataAnnotations model binder leverages those extension points to allow DataAnnotations attributes to contribute to the validation of a model.
For example, let’s take a simple model:
public class Contact
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
In a standard MVC application, if I want FirstName and LastName to be required, I have to write custom validation code to make this happen. My action method might look something like this:
public ActionResult Edit(Contact contact)
{
if (String.IsNullOrEmpty(contact.FirstName))
ModelState.AddModelError("FirstName", "First name is required");
if (String.IsNullOrEmpty(contact.LastName))
ModelState.AddModelError("LastName", "Last name is required");
try
{
if (ModelState.IsValid)
{
// Submit the changes to the database here
return Redirect("Index");
}
}
catch(Exception ex)
{
// Log the exception somewhere to be looked at later
ModelState.AddModelError("*", "An unexpected error occurred.");
}
return View(contact);
}
Now let’s take a look at the same model, but using DataAnnotations:
public class Contact
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
And our updated action method:
public ActionResult Edit(Contact contact)
{
try
{
if (ModelState.IsValid)
{
// Submit the changes to the database here
return Redirect("Index");
}
}
catch(Exception ex)
{
// Log the exception somewhere to be looked at later
ModelState.AddModelError("*", "An unexpected error occurred.");
}
return View(contact);
}
Notice how much cleaner the action method is, now that the validation of the model has been moved into the metadata on the model itself. Now the action method can just focus on submission and error handling, without being concerned about how to validate the model.
To make this work, you need to compile the DataAnnotations model binder project, and then added references to the two DLLs in you find in the src\bin\Debug folder (Microsoft.Web.Mvc.ModelBinders.dll and System.ComponentModel.DataAnnotations.dll).
Then, in your Global.asax.cs file, you make the following changes to register the model binder:
void Application_Start() { RegisterRoutes(RouteTable.Routes); RegisterModelBinders(ModelBinders.Binders); // Add this line } public void RegisterModelBinders(ModelBinderDictionary binders) // Add this whole method { binders.DefaultBinder = new Microsoft.Web.Mvc.DataAnnotations.DataAnnotationsModelBinder(); }
Now when you submit forms, the model binder will automatically find instances of the DataAnnotations attributes on your models and run the validations you’ve specified.
Summary:
I like attribute based validation. I suppose using the DataAnnotations Attributes would be limited to Required, Range, RegularExpression and StringLength and other custom validation would be handled by your code within the Edit method. In addition, the Name parameter of the [Display] attribute can be used to influence the default messages. The default message for Required, for example, is something "The {0} field is required.", where {0} is the name of the property; however, if you use [Display(Name=)] to set the name, it will use that instead in your error message.
In other attribute based approaches, you are able to call delegates from attributes and they validate the properties. I'm not sure what you can and can't do from the DataAnnotations.
You can apply validation attributes to the class as well as to properties. Since none of the built-in validations do what you want, you have a couple choices:
1. Write a new attribute which derives from ValidationAttribute and apply it to the class;
2. Apply the [CustomValidation] attribute to the class, pointint to a custom validation method of your writing.
Comments