In the last article we introduced the ContextConfiguration class and its MetadataProviderFactory property. We also saw that if a provider factory isn’t explicitly set then an instance of the AssociatedMetadataTypeTypeDescriptorProvider class will be used. What we didn’t discuss is what an “associated metadata type” is, and why we need a type descriptor for working with it. Before going into those exact questions, we need to take a step back and examine the way some models are constructed.
We can all agree that the term “model” is pretty overloaded and has many different meanings/interpretations. You have domain models, presentation models, view models, super models, model trains, and so forth and so on. This series isn’t tied to any specific model implementation methodology per se, but rather on the intent of creating web applications that are focused around data in a RAD fashion. Because there are numerous ways of constructing an object model, we’d need a mechanism for discovering metadata from models that assume different shapes.
If you hand-craft your object model and perform the database-mapping yourself you could easily place data annotations on your entity classes and go on with it (as you might do if using NHibernate). While that is a totally valid scenario, it isn’t necessarily the right one for a data-driven application where you most likely already have a database in place and are looking to reverse engineer a model from it’s schema. For cases like that you might use something like LINQ To SQL or the Entity Framework, both of which provide designers/wizards for generating a mapped model to an existing database.
If I create a new Entity Data Model and point its wizard at an AdventureWorks database, I’ll end up with a whole bunch of generated entity classes that look something like this:
[global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="AdventureWorks", Name="Address")] [global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)] [global::System.Serializable()] public partial class Address : global::System.Data.Objects.DataClasses.EntityObject { ... }
Because that class lives within the confines of my EDMX, which is template-generated code, I don’t exactly want to go mucking around in there. If I later make a change with the designer, it would wipe out any changes I manually made to the entity classes, which would lead to a lot of pain and frustration. Luckily though, the entities are generated as partial classes which means we can make modifications to them from outside of the EDMX’s code files. This gives us the ability to leverage the designer and still have some level of flexibility over the codebase moving forward.
I can easily create a new partial class for my Address entity and begin placing some data annotations on it. Let’s say that I want to specify that the display name for it should be “Customer Address” and that the column (property) that should be used to represent it’s display value should be “Country”. I could easily do this:
[DisplayColumn("Country")] [DisplayName("Customer Address")] public partial class Address { }
Because we’re using partial classes (which are combined at compile time), we’re still annotating the entity class itself, just using a different approach. We could easily retreive this metadata using standard reflection without requiring the need for a custom type descriptor. But what if I wanted to add metadata to one of the properties of my Address entity now? Let’s say that I wanted to specify that the Address2 property is required (even though it isn’t at the database level), PostalCode should be constrained by a regular expression, and that CountryRegion’s display name should just be “Country”. Because I don’t want to mess with the generated code of the EDMX I can’t go place data annotations directly on those properties. I definitely can’t do the following, since that would be declaring the properties twice on the same class:
[DisplayColumn("Country")] [DisplayName("Customer Address")] public partial class Address { public string Address2 { get; set; } public string CountryRegion { get; set; } public string PostalCode { get; set; } }
It might seem like we’re out of luck at this point and have to settle with changing the generated EDMX code. What if though, we could create a class whose sole purpose was to contain properties that we could annotate with metadata for another entity and then associate the two together? That would get us the functionality we need and work around the “issue” we’re dealing with between partial classes and generated code. Let’s say we created the following AddressMetadata class:
private class AddressMetaData { public string Address2 { get; set; } public string CountryRegion { get; set; } public string PostalCode { get; set; } }
We don’t get any compiler errors (obviously) so we’re moving in the right direction. How can we then associate this new type with our original Address entity class?
[MetadataType(typeof(AddressMetaData))] public partial class Address {
Now we can place any entity-level metadata on our Address partial class, and any property-level metadata on our new AddressMetaData class, because the two are associated. You can place/name your metadata type anywhere you want, but I prefer to make it a private/embedded class of the partial entity class itself. I can now add the metadata I want to my properties.
[DisplayColumn("Country")] [DisplayName("Customer Address")] [MetadataType(typeof(AddressMetaData))] public partial class Address { private class AddressMetaData { [Required] public object Address2 { get; set; } [DisplayName("Country")] public object CountryRegion { get; set; } [RegularExpression(@"\d{5}(-\d{4})?")] public object PostalCode { get; set; } } }
Notice that I changed the type of the three properties to “object” because the property type no longer matters. Within the metadata type, we only need to be concerned with naming the property correctly.
We established that standard reflection could handle the discovery of the entity-level metadata, but now that we’ve created this “metadata type” which has its own set of properties and metadata, reflection would have no clue of its semantic association with the Address entity. This is where the AssociatedMetadataTypeTypeDescriptionProvider comes in.
The AssociatedMetadataTypeTypeDescriptionProvider class is capable of looking at a type and finding any of its class-level attributes as well as looking to see if that type has an associated metadata type (via the MetadataType attribute) and if it does, grabbing any metadata from the metadata type’s properties. This is a fairly roundabout approach, but it gets us around in the meantime. In the future you might see the notion of “partial properties” that would remove the need for the metadata type altogether.
So now it’s clear why the AssociatedMetadataTypeTypeDescriptionProvider exists and is the default behavior. If our model is able to apply all metadata directly to the entity classes, then it will work great. If our model requires the use of a “associated metadata type”, then it will also pick up on that as well. If we had an alternate style of model, that required a different type of metadata discovery, then we could implement a TypeDescriptionProvider and set it via the ContextConfiguration’s MetadataProviderFactory property. Dynamic Data uses the specified provider throughout its entire API, making it extremely easy to swap our your model metadata discovery logic.
Now that we’ve got our model created and we’ve begun adding metadata to it, how can we start seeing the fruits of our labor take effect within our web application’s UI? In the next article I’ll begin showing how Dynamic Data provides a set of extensions to ASP.NET that can take advantage of your annotated models and the MetaModels derived from them.