Customizing ASP.NET MVC 2 - Metadata and Validation

In this article, you will learn about two major extensibility points of ASP.NET MVC 2, the ModelMetadataProvider and the ModelValidatorProvider. These two APIs control how templates are rendered, as well as server-side & client side validation of your model objects.

Model Metadata

In the first article in this series, A First Look at ASP.NET MVC 2, we looked at the new templated input helpers. To recall, this means you can simply do:

Preview 2 introduced a slightly friendlier syntax for this...

With this the framework will generate scaffolding for you. The built-in scaffolding is very basic; however you can customize it by providing your own templates for various Types or by providing attributes (metadata) to decorate your model. Here is an example:

These attributes come from System.ComponentModel.DataAnnotations (with the exception of HiddenInput, which lives in System.Web.Mvc) and they influence how the templates are rendered.

First, you don't want anyone modifying the primary key of your record, so the Id property gets the [HiddenInput] attribute. When the template is rendered it will not be shown, instead it will be rendered as a hidden input tag on the form.

The DaysLeftToServe property is just an integer, and by default will get a textbox. By providing a [UIHint] attribute with the name of another template (a view user control or partial) you can have something better, perhaps up & down arrows that allow the user to just click to add or remove days. Of course you'd have to provide this yourself. We also tell the framework what label to generate for this field. Providing a human-friendly name is nice and makes your UI feel less mechanical.

Lastly, when you're displaying an edit template, the framework can't know how to render this IList<Offenses> property, so we can choose to just ignore it for the automatic template. You can do this by providing the [ScaffoldColumn(false)] attribute.

My model has properties that I didn't create! How can I add attributes to those to ignore them from the template?

If you're using something like Entity Framework and you allow the MVC framework to generate your entities off of a database, then you're likely stuck with a bit of baggage on your objects. In practice, this means you'll see fields like EntitySet and EntityKey show up in your templates.

To provide metadata for properties that you don't have control over, you can create a separate metadata class that has those same property names. You can tell the framework to use this class instead of your model object by providing the [MetadataType] attribute, as shown in listing 1.

With the separate metadata class you can eliminate putting all this attribute noise on your model. It is also useful in code generation scenarios or other areas where you are inheriting from a class that provides properties on your model.

Listing 1: Example of metadata class

This metadata that we've defined all comes from the System.ComponentModel.DataAnnotations attributes. The class that provides all of this behavior is DataAnnotationsModelMetadataProvider. This is of course configurable, which leads us to the new ModelMetadataProvider API.

Note

Some of the DataAnnotations attributes (such as Range and Regex) control validation, not metadata. It's easy to confuse the two. Required is more commonly used for validation, however it can be treated as metadata as well, for example if you wanted to bold all of your required field labels. We will discuss validation later in this article. For now, remember that metadata is only there to drive the display and edit templates.

To create a custom ModelMetadataProvider, you have a couple of options. You can inherit directly from the ModelMetadataProvider class and that gives you ultimate control. However, if you're doing simply property/attribute checking similar to DataAnnotations, then it's best to start with the friendlier AssociatedModelMetadataProvider. This is, in fact, the class that DataAnnotationsModelMetadataProvider inherits from.

Figure 1: The AssociatedMetadataProvider usually provides an easier starting point for customizing your own metadata provider

Notice that in the AssociatedMetadataProvider, only one abstract method exists (CreateMetadata). In this method you need to return the appropriate ModelMetadata class that represents the property in question.

Let's create a new (simplistic) provider that will use conventions to determine the metadata. Here are our conventions:

Any property ending with "Id" will automatically be marked as HiddenInput

All properties will have a display name applied that breaks the name up by capital letter (SomeCoolProperty becomes "Some Cool Property")

Property names that end with X will be marked as required. This won't control validation, just the display of the template (for example if you want to make all required fields bold). This is a silly convention, but it will illustrate the example quite nicely.

Let's start out by creating our new class, ConventionMetadataProvider.

The job of the CreateMetadata() method is to look at the context and provide a custom ModelMetadata instance that represents the property. You can see that we inspect the property name to make our decision on what to set on our metadata class. You can create your own derived class for this, however in our case the ModelMetadata class works just fine.

The Wordify() method above is a simple way of splitting apart a string based on capital letters. Here is the extension method:

Now that we have our provider finished, we need to set it up as our default metadata provider for the application. In your Global.asax, add this line to Application_Start:

Let's try this out, shall we? In your Models folder, add a new class called Customer. Define it like this:

We'll also create a simple create Contact action:

...and the associated view:

If we've done our job correctly then we should see the following:

Properties with PascalCasedWords should be split apart with a space

No field label should have X (it should be stripped)

Id should not be displayed

A hidden field for Id should exist

Let's build & run this and see what we've got!

Figure 2: Our form renders using the conventions we defined

Figure 3: The hidden field is rendered for Id (as expected)

You've now seen how metadata can help drive the templated helpers in MVC 2. You have complete access to this metadata from your own templates as well. You could use this to provide a different UI style to required fields. For more information on creating and customizing the templates, check out this informative 5-part blog post series from Brad Wilson.

Validation

The next major enhancement in MVC 2 (and one most likely to be customized) is model validation. Now that Preview 2 supports client-side validation as well as server side (on the same set of rules) you have a much nicer picture of validation in your applications.

Out of the box, validation is driven by System.ComponentModel.DataAnnotations attributes. Let's take our Contact Model and add some validation-related attributes to it:

Also ensure that you enable client side validation in your New.aspx view:

This will cause a bit of JavaScript validation magic to be rendered on your view. Armed with only these changes, if we render the page again and try to submit the form empty, we should see an error.

You'd also notice, if you were to turn off client script in your browser, that the server side validation works as well. Typically you'll have some code like this that checks ModelState and re-renders the view if it is found to be invalid:

You can customize the validation and use something different, say perhaps Castle Validation attributes or maybe the Enterprise Library Validation Application Block. Customizing this is similar to how you customized the model metadata provider.

Let's stay simple, and we'll leverage our existing convention of appending "X" to the properties that are required. We can create a custom ConventionValidatorProvider that understands this convention (and perhaps others) to drive validations for us.

Again, notice that the AssociatedValidatorProvider is a better starting place than the basic ModelValidatorProvider for basic attribute/property validation.

When you inherit from AssociatedValidatorProvider, you have one method to override.

Since we have complete access to the model metadata, we could re-use the required rule and simply check: metadata.IsRequired. This will all depend on your use.

Tip: Provide more metadata!

One interesting thing you can do in your ModelMetadataProvider is return a custom class derived from ModelMetadata in order to add more aspects of metadata that you want to track. This same instance is piped through the ModelValidatorProvider API. You'd have to apply a cast here to access all of the properties, but it can enable some interesting scenarios for re-use amongst the metadata provider and the validator provider.

In the above code sample, we created a MyRequiredValidator instance and returned it. This is a nested class, defined as:

Any custom validator that you create must inherit from ModelValidator. This provides two methods to override, one for server side model validation, and one to add the client-side validation rule.

For the server side validation error case, we return a ModelValidationResult that indicates which field had the problem and what error message to display on the UI.

For the client-side validation rules, we simply return a built-in class, ModelClientValidationRule that controls the actual Javascript that is emitted on the page. There are other client validation rules, as shown below.

System.Web.Mvc.ModelClientValidationRule (abstract)

System.Web.Mvc.ModelClientValidationRequiredRule

System.Web.Mvc.ModelClientValidationRangeRule

System.Web.Mvc.ModelClientValidationStringLengthRule

System.Web.Mvc.ModelClientValidationRegexRule

There seems to be no easy way of creating custom client validation rules that I can tell as these classes have no behavior. It is my guess that eventually you'll be able to extend this behavior.

To wrap up this example, let's recap what we've done. We've created a custom validator provider (using AssociatedValidatorProvider as a base class) and implemented our 1st validation convention: If a property ends in "X" it is considered required. We've also returned the necessary client-side validation rule in order to drive client-side script for our rule.

The last thing we need to do is tell the ASP.NET MVC Framework to use our new provider. In the global.asax, just below the line we wrote earlier, go ahead and wire it up:

If we run the application, go to the New Contact page and try to submit it, we should see our required field errors kick in.

Figure 6: Our form is now validated based on our convention

And it works! You have now customized the validation to meet our perhaps unusual needs.

Summary

The new templated helpers are a great addition to the ASP.NET MVC Framework. Metadata and validation are two important pieces of this to help you build functional display & edit screens rapidly. You probably won't choose to utilize our "crazy X" convention, but should you have the need to conform to a different API for validation and/or metadata hopefully this article gives you the information you need to succeed.

About Ben Scheirman

Ben Scheirman is a software developer specializing in .NET. He has worked extensively on the web on various platforms and languages. Ben is a Microsoft MVP, Microsoft ASP Insider, and Certified ScrumMaster. When not programming, Ben enjoys speaking, blogging, spending time with his wife and five won...

This author has published 4 articles on DotNetSlackers. View other articles or the complete profile here.

You might also be interested in the following related blog posts

MvcContrib working on Portable Areas
read more
MvcContrib version control has moved to GitHub
read more
Validation - Part 3 - Server-Side
read more
Validation - Part 1 - Getting Started
read more
You should NOT use ASP.NET MVC if. . .
read more
Is ASP.NET MVC a half-baked solution?
read more
MvcContrib v1.0 Released! Download now
read more
New class: ASP.NET MVC Boot Camp developer training
read more
MvcContrib Release Candidate posted to CodePlex - now with more consolidated packaging
read more
ASP.NET MVC Release Candidate 2
read more

I am using EF and have still not been able to get any validation working. I used the "Buddy class" - as described above - yet when editing or creating a UserProfile (EF Table/Class) - no validation occurs and ModelState.IsValid returns true. What could I be doing wrong? Is there somewhere else I have to tell mvc / EF to use my new metadata class?