Only showing posts tagged with "ASP.NET MVC 2"
April 14, 2010 5:33 PM by Daniel Chambers (last modified on April 14, 2010 6:15 PM)
One of the big ticket features of ASP.NET MVC 2 is its ability to perform model validation at the model binding stage in the controller. It also has the ability to store metadata about your model itself (such as what properties are required ones, etc) and therefore can be more intelligent when it automatically renders an HTML interface for your data model.
Unfortunately, the way ASP.NET MVC 2 handles validation (in the controller) is at odds with my preferred way to handle validation (in the business layer, with validation errors batched and thrown up as exceptions back to the controller. I used xVal). In order to understand ASP.NET MVC 2’s validation I wanted to know how it worked, not just how to use it. Unfortunately, at time of writing, there seemed to be very few sources of information that described the way MVC 2’s model metadata and validation worked at a high, architectural level. The ASP.NET MVC MSDN class documentation is pretty horrible (it’s anaemic, in terms of detail). Sure, there are blog articles on how to use it, but none (that I could find, anyway) on how it really all works together under the covers. And if you don’t know how it works, it can be quite difficult to know how to use it most effectively.
To resolve this lack of documentation, this blog post attempts to explain the architecture of the ASP.NET MVC 2 metadata and validation features and to show you, at a high level, how they work. As I am not a member of the MVC team at Microsoft, I don’t have access to any architecture documentation they may have, so keep in mind everything in this blog was deduced by reading the MVC source code and judicious application of .NET Reflector. If I’ve made any mistakes, please let me know in the comments.
ASP.NET MVC 2 has the ability to create metadata about your data model and use this for various tasks such as validation and special rendering. Some examples of the metadata that it stores are whether a property on a data model class is required, whether it’s read only, and what its display name is. As an example, the “is required” metadata would be useful as it would allow you to automatically write HTML and JavaScript to ensure that the user enters a value in a textbox, and that the textbox’s label is appended with an asterisk (*) so that the user knows that it is a required field.
Figure 1. ModelMetadata and Providers Class Diagram
The ASP.NET MVC Model Metadata system stores metadata across many instances of the ModelMetadata class (and subclasses). ModelMetadata is a recursive data structure. The root ModelMetadata instance stores the metadata for the root data model object type, and also stores references to a ModelMetadata instance for each property on that root data model type. Figure 1 shows visual example of this for an example “Book” data model class.
Figure 2. Model Metadata Recursive Data Structure Diagram (note that not all properties on ModelMetadata are shown)
As you can see in Figure 2, there is a root ModelMetadata that describes the Book instance. You’ll notice that its ContainerType and PropertyName properties are null, which indicates that this is the root ModelMetadata instance. This root ModelMetadata instance has a Properties property that provides lazy-loaded access to ModelMetadata instances for each of the Book type’s properties. These ModelMetadata instances do have ContainerType and PropertyName set, because since they are for properties, they have a container type (ie. Book) and a name (ie. Title).
So how do we get instances of ModelMetadata for a data model, and how is the model’s metadata divined in the first place? This is where ModelMetadataProvider classes come in (see Figure 1). ModelMetadataProvider classes are able to create ModelMetadata instances. However, ModelDataMetadataProvider itself it is an abstract class, and delegates all its functionality to subclasses. The DataAnnotationsModelMetadataProvider is a implementation that is able to create model metadata based off the attributes in the System.ComponentModel.DataAnnotations namespace that are applied to data model classes and their properties. For example, the RequiredAttribute is used to determine whether a property is required or not (ie. it causes the ModelMetadata.IsRequired property to be set).
You’ll notice that the DataAnnotationsModelMetadataProvider does not inherit directly from ModelMetadataProvider (see Figure 1). Instead, it inherits from the AssociatedMetadataProvider abstract class. The AssociatedMetadataProvider class actually performs the getting of the attributes off the class and its properties by using an ICustomTypeDescriptor behind the scenes (which turns out to be the internal AssociatedMetadataTypeTypeDescriptor class, which is constructed by the AssociatedMetadataTypeTypeDescriptionProvider class). The AssociatedMetadataProvider then delegates the actual responsibility of building a ModelMetadata instance using the attributes it found to a subclass. The DataAnnotationsModelMetadataProvider inherits this responsibility.
As you can see, the whole metadata-creation system is very modular and allows you to generate model metadata in any way you want by just using a different ModelMetadataProvider class. The Provider class that the system uses by default is set by creating an instance of it and putting it in the static property ModelMetadataProviders.Current. By default, an instance of DataAnnotationsModelMetadataProvider is used.
ASP.NET MVC 2 includes a very modular and powerful validation system. Unfortunately, it’s very coupled to ASP.NET MVC as it uses ControllerContexts throughout itself. This means you can’t move its use into your business layer; you have to keep it in the controller. The modularity and power comes with a trade-off: architectural complexity. The validation system architecture contains quite a lot of indirection, but when you step through it you can see the reasons why the indirection exists: for extensibility.
Figure 3 shows an overview of the validation system architecture. Key to the system are ModelValidators, which when executed, are able to validate a part of the model in a specific way and return ModelValidationResults, which describe any validation errors that occurred with a simple text message. ModelValidators also have a collection of ModelClientValidationRules, which are simple data structures that represent the client-side JavaScript validation rules to be used for that ModelValidator.
ModelValidators are created by ModelValidatorProvider classes when they are asked to validate a particular ModelMetadata object. Many ModelValidatorProviders can be used at the same time to provide validation (ie. return ModelValidators) on a single ModelMetadata. This is illustrated by the ModelValidatorProviders.Providers static property, which is of type ModelValidatorProviderCollection. This collection, by default, holds instances of the ClientDataTypeModelValidatorProvider, DataAnnotationsModelValidatorProvider and DataErrorInfoModelValidatorProvider classes. This collection has a method (GetValidators) that allows you to easily execute all contained ModelValidatorProviders and get all the returned ModelValidators back. In fact, this is what is called when you call ModelMetadata.GetValidators.
The DataAnnotationsModelValidatorProvider is able to create ModelValidators that validate a model held in a ModelMetadata object by looking at System.ComponentModel.DataAnnotations attributes that have been placed on the data model class and its properties. In a similar fashion to the DataAnnotationsModelMetadataProvider, the DataAnnotationsModelValidatorProvider leaves the actual retrieving of the attributes from the data types to its base class, the AssociatedValidatorProvider.
The ClientDataTypeModelValidator produces ModelValidators that don’t actually do any server-side validation. Instead, the ModelValidators that it produces provide the ModelClientValidationRules needed to enforce data typing (such as numeric types like int) on the client. Obviously no server-side validation is needed to be done for data types since C# is a strongly-typed language and only an int can be in an int property. On the client-side, it is quite possible for the user to type a string (ie “abc”) into a textbox that requires an int, hence the need for special ModelClientValidationRules to deal with this.
The DataErrorInfoModelValidatorProvider is able to create ModelValidators that validate a data model that implements the IDataErrorInfo interface.
You may have noticed that ModelValidator is an abstract class. ModelValidator delegates the actual validation functionality to its subclasses, and it’s these concrete subclasses that the different ModelValidatorProviders create. Let’s take a deeper look at this, with a focus on the ModelValidators that the DataAnnotationsModelValidatorProvider creates.
As you can see in Figure 4, there is a ModelValidator concrete class for each System.ComponentModel.DataAnnotations validation attribute (ie. RangeAttributeAdapter is for the RangeAttribute). The DataAnnotationsModelValidator provides the general ability to execute a ValidationAttribute. The generic DataAnnotationsModelValidator<TAttribute> simply allows subclasses to access the concrete ValidationAttribute that the ModelValidator is for through a type-safe property.
You’ll notice in Figure 4 how each DataAnnotationsModelValidator<TAttribute> subclass has a matching ModelClientValidationRule. Each of those subclasses (ie. ModelClientValidationRangeRule) represents the client-side version of the ModelValidator. In short, they contain the rule name and whatever data needs to go along with it (for example, for the ModelClientValidationStringLengthRule, the specific string length is set inside that object instance). These rules can be serialised out to the page so that the page’s JavaScript validation logic can hook up client-side validation. (See Phil Haack’s blog on this for more information).
The DataAnnotationsModelValidatorProvider implements an extra layer of indirection that allows you to get it to create custom ModelValidators for any custom ValidationAttributes you might choose to create and use on your data model. Internally it has a Dictionary<Type, DataAnnotationsModelValidationFactory>, where the Type of a ValidationAttribute is associated with a DataAnnotationsModelValidationFactory (a delegate) that can create a ModelValidator for the attribute type it is associated with. You can add to this dictionary by calling the static DataAnnotationsModelValidatorProvider.RegisterAdapterFactory method.
The CompositeModelValidator is a private inner class of ModelValidator and is returned when you call the static ModelValidator.GetModelValidator method. It is a special ModelValidator that is able to take a ModelMetadata class, validate it by using its GetValidators method, and validate its properties by calling GetValidators on each of the ModelMetadata instances returned for each model property. It collects all the ModelValidationResult objects returned by all the ModelValidators that it executes and returns them in one big bundle.
Now that we’re familiar with the workings of the ASP.NET MVC 2 Metadata and Validation systems, let’s see how we can actually use them to validate some data. Note that you don’t need to actually cause validation to happen explicitly, as ASP.NET MVC will trigger it to happen during model binding. This section just shows how the massive system we’ve looked at is kicked into execution.
The Controller.TryValidateModel method is a good example of showing model metadata creation and model validation in action:
protected internal bool TryValidateModel(object model, string prefix) { if (model == null) throw new ArgumentNullException("model"); ModelMetadata modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()); ModelValidator compositeValidator = ModelValidator.GetModelValidator(modelMetadata, base.ControllerContext); foreach (ModelValidationResult result in compositeValidator.Validate(null)) { this.ModelState.AddModelError(DefaultModelBinder.CreateSubPropertyName(prefix, result.MemberName), result.Message); } return this.ModelState.IsValid; }
The first thing we do in the above method is create the ModelMetadata for the root object in our model (which is the model object passed in as a method parameter). We then get a CompositeModelValidator that will internally get and execute all the ModelValidators for the validation of the root object type and all its properties. Finally, we loop over all the validation errors gotten from executing the CompositeModelValidator (which executes all the ModelValidators it fetched internally) and add each error to the controller’s ModelState.
The CompositeModelValidator really hides the actual work of getting and executing the ModelValidators from us. So let’s see how it works internally.
public override IEnumerable<ModelValidationResult> Validate(object container) { bool propertiesValid = true; foreach (ModelMetadata propertyMetadata in Metadata.Properties) { foreach (ModelValidator propertyValidator in propertyMetadata.GetValidators(ControllerContext)) { foreach (ModelValidationResult propertyResult in propertyValidator.Validate(Metadata.Model)) { propertiesValid = false; yield return new ModelValidationResult { MemberName = DefaultModelBinder.CreateSubPropertyName(propertyMetadata.PropertyName, propertyResult.MemberName), Message = propertyResult.Message }; } } } if (propertiesValid) { foreach (ModelValidator typeValidator in Metadata.GetValidators(ControllerContext)) foreach (ModelValidationResult typeResult in typeValidator.Validate(container)) yield return typeResult; } }
First it validates all the properties on the object that is held by the ModelMetadata it was created with (the Metadata property). It does this by getting the ModelMetadata of each property, getting the ModelValidators for that ModelMetadata, then executing each ModelValidator and yield returning the ModelValidationResults (if any; there will be none if there were no validation errors).
Then, if the all the properties were valid (if there were no ModelValidationResults returned from any of the ModelValidators) it proceeds to validate the root ModelMetadata by getting its ModelValidators, executing each one, and yield returning any ModelValidationResults.
The ModelMetadata.GetValidators method that the CompositeModelValidator is using simply asks the default ModelValidatorProviderCollection to execute all its ModelValidatorProviders and returns all the created ModelValidators:
public virtual IEnumerable<ModelValidator> GetValidators(ControllerContext context) { return ModelValidatorProviders.Providers.GetValidators(this, context); }
The only criticism I have is levelled against the Model Validation system that for some reason requires a ControllerContext to be passed around internally (see the ModelValidator constructor). As far as I can see, that ControllerContext is never actually used by any out of the box validation component, which leads me to question why it needs to exist. The reason I dislike it is because it prevents me from even considering using the validation system down in the business layer (as opposed to in the controller) without coupling the business layer to ASP.NET MVC. However, there may be a good reason as to why it exists that I am not privy to.
In this blog post we’ve addressed the problem of a serious lack of documentation that explains the inner workings and architecture of the ASP.NET MVC 2 Model Metadata and Validation systems (that existed at least at the time of writing). We’ve systematically deconstructed and looked at the architecture of the two systems and seen how the architecture enables you to easily generate metadata about your data model and validate it, and how it provides the flexibility to plug in, change and extend the basic functionality to meet your specific use case.