Published: 09 Nov 2009
By: Ben Scheirman
Download Sample Code

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.

Contents [hide]

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

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

Our form renders using the conventions we defined

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

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.

Figure 4: Client-side validation automatically driven from our attributes

Client-side validation automatically driven from our attributes

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.

Figure 5: The AssociatedValidatorProvider provides an easier extension point for simple property & attribute validation

The AssociatedValidatorProvider provides an easier extension point for simple property & attribute validation

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

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.

<<  Previous Article Continue reading and see our next or previous articles Next Article >>

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.

Other articles in this category


Code First Approach using Entity Framework 4.1, Inversion of Control, Unity Framework, Repository and Unit of Work Patterns, and MVC3 Razor View
A detailed introduction about the code first approach using Entity Framework 4.1, Inversion of Contr...
jQuery Mobile ListView
In this article, we're going to look at what JQuery Mobile uses to represent lists, and how capable ...
Exception Handling and .Net (A practical approach)
Error Handling has always been crucial for an application in a number of ways. It may affect the exe...
JQuery Mobile Widgets Overview
An overview of widgets in jQuery Mobile.
Book Review: SignalR: Real-time Application Development
A book review of SignalR by Simone.

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
Top
 
 
 

Discussion


Subject Author Date
placeholder Visual Studio 2010 Tomas McGuinness 11/10/2009 7:20 AM
RE: Visual Studio 2010 Sonu Kapoor 11/10/2009 10:36 AM
placeholder Validation Josh Williams 4/5/2010 12:28 PM

Please login to rate or to leave a comment.