1: public class MetadataProvider : DataAnnotationsModelMetadataProvider
2: {
3: protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
4: {
5: var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
6: var linkText = attributes.OfType<LinkText>().FirstOrDefault();
7: if(linkText != null)
8: metadata.AdditionalValues.Add("LinkText",linkText);
9:
10: return metadata;
11: }
12:
13: }
I just wanna have some interface that builds up a ModelMetaData. For simplicity sake, I'll just give it the same signature as DataAnnotationsModelMetadataProvider, but just allow you to send in the ModelMetaData that's already built.
1: public interface IModelMetaBuilder
2: {
3: ModelMetadata BuildUp(ModelMetadata metaData, IEnumerable<Attribute> attributes, Type containerType,
4: Func<object> modelAccessor, Type modelType, string propertyName);
5: }
Then I'll make the MetadataProvider just get all instances of those from the container, loop through em all, and build up. That way we can add new behavior without opening it back up (yeah, that's OCP).
First, lets make my LinkText stuff from my last blog and move it into one of these,
1: public class LinkTextModelMetadataBuilder : IModelMetaBuilder
2: {
3: public ModelMetadata BuildUp(ModelMetadata metadata, IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
4: {
5: var linkText = attributes.OfType<LinkText>().FirstOrDefault();
6: if (linkText != null)
7: metadata.AdditionalValues.Add("LinkText", linkText);
8: return metadata;
9: }
10: }
Now... Lets go fix my MetaDataProvider to use my IOC container of choice (StructureMap). To be honest, this is so simple, I'm kinda embarrased that I'm making a blog out of it - but hey, I've got no pride (yeah right).
1: public class MetadataProvider : DataAnnotationsModelMetadataProvider
2: {
3: private readonly IContainer _container;
4:
5: public MetadataProvider(IContainer container)
6: {
7: _container = container;
8: }
9:
10: protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
11: {
12: var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
13: var allBuilders = _container.GetAllInstances<IModelMetadataBuilder>();
14: foreach (var builder in allBuilders)
15: {
16: builder.BuildUp(metadata, attributes, containerType, modelAccessor, modelType, propertyName);
17: }
18: return metadata;
19: }
20:
21: }
Next up, lets write a cute little registry for StructureMap that'll go get em all. Oh yeah, I renamed that interface to IModelMetadataBuilder. Here it is.
1: public class ModelMetadataProviderRegistry : Registry
2: {
3: public ModelMetadataProviderRegistry()
4: {
5: Scan(scanner =>
6: {
7: scanner.AssemblyContainingType(GetType());
8: scanner.AddAllTypesOf<IModelMetadataBuilder>();
9: });
10: }
11: }
And there we go... Now we just gotta change the App_Start stuff that wired up the ModelMetadataProvider to inject the Container, or better yet, just go get the instance from the container. Like so (well like the last line here - line 13).
1: protected void Application_Start()
2: {
3:
4: StructureMapInitilizer.Initilize();
5:
6: new Bootstrapper(ObjectFactory.Container).BootstrapApplication();
7: //TODO: make this stuff bootstrap classes
8: AreaRegistration.RegisterAllAreas();
9: RegisterGlobalFilters(GlobalFilters.Filters);
10: RegisterRoutes(RouteTable.Routes);
11: Db = OpenDatabase();
12: ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory(ObjectFactory.Container));
13: ModelMetadataProviders.Current = ObjectFactory.Container.GetInstance<MetadataProvider>();
14:
15: }
Cool... and everything is still passing! Next up... I'm gunna go steal all the cool ModelMetaData stuff from everyone, but implement them in their own builders. First up is Brad Wilsons, because it's a whole lot of awesome.
All I need to do is to instead of inheriting the DataAnnotationsModelMetaDataProvider, I'll implement IModelMetadataBuilder. Since the signatures are almost exactly the same this is really easy. Here's what I ended up with.
1: public class WilsonModelMetadataBuilder : IModelMetadataBuilder
2: {
3: public ModelMetadata BuildUp(ModelMetadata metadata,IEnumerable<Attribute> attributes,
4: Type containerType,
5: Func<object> modelAccessor,
6: Type modelType,
7: string propertyName)
8: {
9:
10:
11:
12: // Prefer [Display(Name="")] to [DisplayName]
13: DisplayAttribute display = attributes.OfType<DisplayAttribute>().FirstOrDefault();
14: if (display != null)
15: {
16: string name = display.GetName();
17: if (name != null)
18: {
19: metadata.DisplayName = name;
20: }
21:
22: // There was no 3.5 way to set these values
23: metadata.Description = display.GetDescription();
24: metadata.ShortDisplayName = display.GetShortName();
25: metadata.Watermark = display.GetPrompt();
26: }
27:
28: // Prefer [Editable] to [ReadOnly]
29: EditableAttribute editable = attributes.OfType<EditableAttribute>().FirstOrDefault();
30: if (editable != null)
31: {
32: metadata.IsReadOnly = !editable.AllowEdit;
33: }
34:
35: // If [DisplayFormat(HtmlEncode=false)], set a data type name of "Html"
36: // (if they didn't already set a data type)
37: DisplayFormatAttribute displayFormat = attributes.OfType<DisplayFormatAttribute>().FirstOrDefault();
38: if (displayFormat != null
39: && !displayFormat.HtmlEncode
40: && String.IsNullOrWhiteSpace(metadata.DataTypeName))
41: {
42: metadata.DataTypeName = DataType.Html.ToString();
43: }
44:
45: return metadata;
46: }
47: }
There ya go folks... Have fun with it.
If you pull down my code, just look in myDojo.Infrastructure.Web. It's all there.
Peace out!
E