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.
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
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
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
Some of the
DataAnnotations attributes (such as
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
- 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,
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.
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
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.
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:
Figure 4: 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
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,
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.
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.
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.
Please login to rate or to leave a comment.