In the last article we discussed how a MetaModel could derive some of its metadata content from its respective provider’s underlying data model (i.e. an EDM), but also mentioned that there is a lot of valuable information that can’t necessarily be deduced from every data model type. Because a data model can come in any shape or form (i.e. EF, L2S, NHibernate, etc) it becomes tricky to try to interpret metadata in any sort of consistent fashion. Providers make an attempt at doing this, but really, their job isn’t to curry metadata back and forth between the MetaModel and underlying data model, but rather to shape the data model in such a way that the MetaModel can understand it.
Without a standard approach for adding metadata to data models (of any kind), data-driven UI frameworks would have a hard time providing the rich functionality we are demanding from them. By introducing the MetaModel, we’ve given the UI layer a robust view of the underlying data model such that it can provide the behavior we expect, so that problem is already solved. Now we just need a mechanism for annotating our model with metadata in such a way that our MetaModel can pick it up.
The .NET Framework 3.5 SP1 release contained an assembly named System.ComponentModel.DataAnnotations. Within that assembly is a collection of attributes that are meant to be associated with data models for the purpose of adding additional metadata and validation to them in a way that is agnostic to any specific UI framework. Notice that the namespace they live in isn’t “System.Web.*” or “System.Windows.*” but rather “System.ComponentModel.*”, which adds to the argument of standard use.
Because these data annotations are simply attributes, we can easily begin attaching them to our entity classes and properties, which adds additional information to our model such as validation constraints (range, regular expression, string length, etc.) and higher level semantics (data type, UI hint, scaffolding, etc.). The question is: how does our MetaModel discover these annotations once we’ve placed them on our model?
[DisplayColumn("FirstName", "LastName")] public class Customer { [Required] public string FirstName { get; set; } [StringLength(20)] public string MiddleName { get; set; } [Required] public string LastName { get; set; } [Range(18, 30)] public int Age { get; set; } }
For any DDD proponents, I realize the above model class would be considered anemic. It’s important to keep in mind that the methodology being employed in this series is that of data-driven applications not domain-driven.
When you register a context/provider with a MetaModel (as we saw in a previous post), an instance of type ContextConfiguration is created and associated with your model. You can either explicitly create a ContextConfiguration instance and use one of the overloaded versions of the RegisterContext method that accepts one, or you can allow the MetaModel to create one for you. ContextConfiguration is an extremely simple class that contains only two properties: ScaffoldAllTables and MetadataProviderFactory. The ScaffoldAllTables property is only applicable if you’re leveraging Dynamic Data for full-blown scaffolding, which is a scenario we’ll touch on later.
// Implict ContextConfiguration... var model = new MetaModel(); model.RegisterContext(typeof(AdventureWorksContext)); // Explicit ContextConfiguration... var model2 = new MetaModel(); model2.RegisterContext(typeof(AdventureWorksContext), new ContextConfiguration { ScaffoldAllTables = false });
The ScaffoldAllTables property defaults to false, so if we aren’t concerned with Dynamic Data’s scaffolding behavior (which in this case we aren’t), we can just omit that property instead of explicitly setting it.
The MetadataProviderFactory property is of type Func<Type, TypeDescriptionProvider>, which might look scarier than it actually is. Func is simply a generic delegate that returns a value and optionally takes up to four parameters (there are five variations of Func). In this case we’re dealing with a Func that will accept a parameter of type Type and return an object of type TypeDescriptionProvider. When we register a context/provider with a MetaModel, it will enumerate through every entity type in our model, calling the delegate within the ContextConfiguration’s MetadataProviderFactory property, which will then return an TypeDescriptionProvider.
model.RegisterContext(typeof(AdventureWorksContext), new ContextConfiguration { MetadataProviderFactory = (type) => new CustomTypeProvider() });
A TypeDescriptionProvider is (as you can probably guess) responsible for providing a type descriptor (more specifically an object that implements ICustomTypeDescriptor or inherits from CustomTypeDescriptor). An ICustomTypeDescriptor implementation is able to determine information about a type such as its properties, events, attributes, etc. This allows applications to use a type descriptor to retrieve metadata about a type instead of using reflection directly. Why is this useful? Because you could create a custom ICustomTypeDescriptor that represented the shape of a type differently then it is in code.
As was mentioned above, the standard data annotations are provided as attribute classes, so the fact that a type descriptor allows you to retrieve attributes from a type (as well as that type’s properties), makes it very useful. By being able to create a custom TypeDescriptionProvider we can effectively implement logic that retrieves our model metadata from any source (i.e. XML, database, etc.), but uses the same standard data annotations, giving us both consistency and flexibility.
If you don’t explicitly set the MetadataProviderFactory property, the Metamodel will internally create an instance of the AssociatedMetadataTypeTypeDescriptionProvider class (that is a hell of a name!). This TypeDescriptionProvider implementation simply retrieves all attributes for a type via reflection. Why is this any better than just using reflection directly? Because it also retrieves any attributes from the requested type’s associated metadata type as well.
What exactly does “associated metadata type” mean? And why is it beneficial that the TypeDescriptionProvider be able to retrieve its attributes? We’ll discuss these two questions and expand on the usage of data annotations in the next article.
Great post… every “but what about this” that popped into my head was answered by the next paragraph
(BTW, thanks for the info about TypeDescriptors… I didn’t understand what they were good for until now).