[In addition to blogging, I am also now using Twitter for quick updates and to share links. Follow me at: twitter.com/scottgu]
This is the second in a series of blog posts I’m doing on the upcoming ASP.NET MVC 2 release. This blog post covers some of the validation improvements coming with ASP.NET MVC 2.
ASP.NET MVC 2 Validation
1) Developers can easily take advantage of the DataAnnotation validation support built-into the .NET Framework. DataAnnotations provide a really easy way to declaratively add validation rules to objects and properties with minimal code.
2) Developers can optionally integrate either their own validation engine, or take advantage of existing validation frameworks like Castle Validator or the EntLib Validation Library. ASP.NET MVC 2’s validation features are designed to make it easy to plug-in any type of validation architecture – while still taking advantage of the new ASP.NET MVC 2 validation infrastructure (including client-side validation, model binding validation, etc).
This means that enabling validation is really easy for common application scenarios, while at the same time still remaining very flexible for more advanced ones.
Enabling Validation with ASP.NET MVC 2 and DataAnnotations
Let’s walkthrough a simple CRUD scenario with ASP.NET MVC 2 that takes advantage of the new built-in DataAnnotation validation support. Specifically, let’s implement a “Create” form that enables a user to enter friend data:
We want to ensure that the information entered is valid before saving it in a database – and display appropriate error messages if it isn’t:
Below I’m going to be using VS 2010 to implement the above scenario using ASP.NET MVC 2. You could also implement the exact same scenario using VS 2008 and ASP.NET MVC 2 as well.
Step 1: Implementing a FriendsController (with no validation to begin with)
We’ll begin by adding a simple “Person” class to a new ASP.NET MVC 2 project that looks like below:
It has four properties (implemented using C#’s automatic property support, which VB in VS 2010 now supports too – woot!).
We’ll then add a “FriendsController” controller class to our project that exposes two “Create” action methods. The first action method is called when an HTTP-GET request comes for the /Friends/Create URL. It will display a blank form for entering person data. The second action method is called when an HTTP-POST request comes for the /Friends/Create URL. It maps the posted form input to a Person object, verifies that no binding errors occurred, and if it is valid will eventually save it to a database (we’ll implement the DB work later in this tutorial). If the posted form input is invalid, the action method redisplays the form with errors:
After we’ve implemented our controller, we can right-click within one of its action methods and choose the “Add View” command within Visual Studio – which will bring up the “Add View” dialog. We’ll choose to scaffold a “Create” view that is passed a Person object:
Visual Studio will then generate a scaffolded Create.aspx view file for us under the \Views\Friends\ directory of our project. Notice below how it takes advantage of the new strongly-typed HTML helpers in ASP.NET MVC 2 (enabling better intellisense and compile time checking support):
And now when we run the application and hit the /Friends/Create URL we’ll get a blank form that we can enter data into:
Because we have not implemented any validation within the application, though, nothing prevents us from entering bogus input within the form and posting it to the server.
Step 2: Enabling Validation using DataAnnotations
Let’s now update our application to enforce some basic input validation rules. We’ll implement these rules on our Person model object – and not within our Controller or our View. The benefit of implementing the rules within our Person object is that this will ensure that the validation will be enforced via any scenario within our application that uses the Person object (for example: if we later added an edit scenario). This will help ensure that we keep our code DRY and avoid repeating rules in multiple places.
ASP.NET MVC 2 enables developers to easily add declarative validation attributes to model or viewmodel classes, and then have those validation rules automatically be enforced whenever ASP.NET MVC performs model binding operations within an application. To see this in action, let’s update our Person class to have a few validation attributes on it. To do this we’ll add a “using” statement for the “System.ComponentModel.DataAnnotations” namespace to the top of the file – and then decorate the Person properties with [Required], [StringLength], [Range], and [RegularExpression] validation attributes (which are all implemented within that namespace):
Note: Above we are explicitly specifying error messages as strings. Alternatively you can define them within resource files and optionally localize them depending on the language/culture of the incoming user. You can learn more about how to localize validation error messages here.
Now that we’ve added the validation attributes to our Person class, let’s re-run our application and see what happens when we enter bogus values and post them back to the server:
Notice above how our application now has a decent error experience. The text elements with the invalid input are highlighted in red, and the validation error messages we specified are displayed to the end user about them. The form is also preserving the input data the user originally entered – so that they don't have to refill anything. How though, you might ask, did this happen?
To understand this behavior, let’s look at the Create action method that handles the POST scenario for our form:
When our HTML form is posted back to the server, the above method will be called. Because the action method accepts a “Person” object as a parameter, ASP.NET MVC will create a Person object and automatically map the incoming form input values to it. As part of this process, it will also check to see whether the DataAnnotation validation attributes for the Person object are valid. If everything is valid, then the ModelState.IsValid check within our code will return true – in which case we will (eventually) save the Person to a database and then redirect back to the home-page.
If there are any validation errors on the Person object, though, our action method redisplays the form with the invalid Person. This is done via the last line of code in the code snippet above.
The error messages are then displayed within our view because our Create form has <%= Html.ValidationMessageFor() %> helper method calls next to each <%= Html.TextBoxFor() %> helper. The Html.ValidationMessageFor() helper will output the appropriate error message for any invalid model property passed to the view:
The nice thing about this pattern/approach is that it is pretty easy to setup – and it then allows us to easily add or change validation rules on our Person class without having to change any code within our controllers or views. This ability to specify the validation rules one place and have it be honored and respected everywhere allows us to rapidly evolve our application and rules with a minimum amount of effort and keep our code very DRY.
Step 3: Enabling Client-side Validation
Our application currently only performs server-side validation – which means that our end users will need to perform a form submit to the server before they’ll see any validation error messages.
If we enter some text that is not a legal email the error message will immediately change from “Email Required” to “Not a valid email” (which are the error messages we specified when we added the rules to our Person class):
When we enter a legal email the error message will immediately disappear and the textbox background color will go back to its normal state:
Note that for security reasons the server-side validation rules always execute even if you have the client-side support enabled. This prevents hackers from trying to spoof your server and circumvent the client-side rules.
Step 4: Creating a Custom [Email] Validation Attribute
The System.ComponentModel.DataAnnotations namespace within the .NET Framework includes a number of built-in validation attributes that you can use. We’ve used 4 different ones in the sample above - [Required], [StringLength], [Range], and [RegularExpression].
You can also optionally define your own custom validation attributes and use them as well. You can define completely custom attributes by deriving from the ValidationAttribute base class within the System.ComponentModel.DataAnnotations namespace. Alternatively, you can choose to derive from any of the existing validation attributes if you want to simply extend their base functionality.
For example, to help clean up the code within our Person class we might want to create a new [Email] validation attribute that encapsulates the regular expression to check for valid emails. To do this we can simply derive it from the RegularExpression base class like so, and call the RegularExpression’s base constructor with the appropriate email regex:
We can then update our Person class to use our new [Email] validation attribute in place of the previous regular expression we used before – which makes the code more clean and encapsulated:
In addition to creating validation attributes that apply to individual properties on an object, you can also apply validation attributes at the class level – which allows you to perform validation logic across multiple properties within an object. For an example of this in action, you can review the “PropertiesMustMatchAttribute” custom attribute that is included in the AccountModels.cs/vb file within the default ASP.NET MVC 2 application project template (just do a File->New ASP.NET MVC 2 Web Project within VS 2010 and look for this class).
Step 5: Persisting to a Database
Let’s now implement the logic necessary to save our friends to a database.
Right now we are simply working against a plain-old C# class (sometimes referred to as a “POCO” class - “plain old CLR (or C#) object”). One approach we could use would be to write some separate persistence code that maps this existing class we’ve already written to a database. Object relational mapping (ORM) solutions like NHibernate support this POCO / PI style of mapping today very well. The ADO.NET Entity Framework (EF) that ships with .NET 4 will also support POCO / PI mapping, and like NHibernate will also optionally enable the ability to define persistence mappings in a “code only” way (no mapping file or designers required).
If our Person object was mapped to a database in this way then we wouldn’t need to make any changes to our Person class or to any of our validation rules – it would continue to work just fine.
But what if we are using a graphical tool for our ORM mappings?
Many developers using Visual Studio today don’t write their own ORM mapping/persistence logic – and instead use the built-in designers within Visual Studio to help manage this.
One question that often comes up when using DataAnnotations (or any other form of attribute based validation) is “how do you apply them when the model object you are working with is created/maintained by a GUI designer”. For example, what if instead of having a POCO style Person class like we’ve been using so far, we instead defined/maintained our Person class within Visual Studio via a GUI mapping tool like the LINQ to SQL or ADO.NET EF designer:
Above is a screen-shot that shows a Person class defined using the ADO.NET EF designer in VS 2010. The window at the top defines the Person class, the window at the bottom shows the mapping editor for how its properties map to/from a “People” table within a database. When you click save on the designer it automatically generates a Person class for you within your project. This is great, except that every time you make a change and hit save it will re-generate the Person class – which would cause any validation attribute declarations you make on it to be lost.
One way you can apply additional attribute-based meta-data (like validation attributes) to a class that is auto-generated/maintained by a VS designer is to employ a technique we call “buddy classes”. Basically you create a separate class that contains your validation attributes and meta-data, and then link it to the class generated by the designer by applying a “MetadataType” attribute to a partial class that is compiled with the tool-generated class. For example, if we wanted to apply the validation rules we used earlier to a Person class maintained by a LINQ to SQL or ADO.NET EF designer we could update our validation code to instead live in a separate “Person_Validation” class that is linked to the “Person” class created by VS using the code below:
The above approach is not as elegant as a pure POCO approach – but has the benefit of working with pretty much any tool or designer-generated code within Visual Studio.
Last Step – Saving the Friend to the Database
Our last step – regardless of whether we use a POCO or tool-generated Person class – will be to save our valid friends into the database.
Doing that simply requires us to replace the “Todo” placeholder statement within our FriendsController class with 3 lines of code that saves the new friend to a database. Below is the complete code for the entire FriendsController class - when using ADO.NET EF to do the database persistence for us:
And now when we visit the /Friends/Create URL we can easily add new People to our friends database:
Validation for all the data is enforced on both the client and server. We can easily add/modify/delete validation rules in one place, and have those rules be enforced by all controllers and views across our application.
ASP.NET MVC 2 makes it much easier to integrate validation into web applications. It promotes a model-based validation approach that enables you to keep your applications very DRY, and helps ensure that validation rules are enforced consistently throughout an application. The built-in DataAnnotations support within ASP.NET MVC 2 makes supporting common validation scenarios really easy out of the box. The extensibility support within the ASP.NET MVC 2 validation infrastructure then enables you to support a wide variety of more advanced validation scenarios – and plugin any existing or custom validation framework/engine.
Hope this helps,