Using an Associated Metadata Class outside Dynamic Data

A while back, I blogged about how ASP.NET Dynamic Data apps can uses an Associated Metadata class (aka a ‘buddy’ class) to add metadata attributed to properties defined in a generated class.  It’s a mostly ugly thing that was made necessary by limitations of the C# and VB.NET languages: they don’t let you add attributes to properties defined in another partial class.

What I didn’t mention there is that this ‘buddy’ class mechanism is actually not specific to Dynamic Data apps, and can in fact be used anywhere.  Since I’ve recently heard of several cases of users trying to do something similar, I’ll describe how it’s done.  If you’re familiar with TypeDescriptionProviders (which have been around since ancient times), this will look very trivial.

I will illustrate this in a very simple console app to keep all other distractions out of the picture (the full sample is attached to the post).  So the general scenario is that we have a generated class somewhere, e.g.

 // Assume that this is generated code that should never be hand modified.
// Hence metadata attributes can't be added directly here

public partial class Product {
    public string Name { get; set; }

    public int UnitsInStock { get; set; }
}

Instead, the buddy provider let’s you write:

 [MetadataType(typeof(Product_Metadata))]
public partial class Product {
}

class Product_Metadata {
    [DisplayName("The Units In Stock")]
    public object UnitsInStock { get; set; }
}

This works in Dynamic Data, but if we’re in some other context, no one will find our Product_Metadata ‘buddy’.  In our to hook it up ourselves, we just need to make one call!

 TypeDescriptor.AddProvider(
    new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Product)),
    typeof(Product));

Pretty trivial stuff: we instantiate a TypeDescriptionProvider (with a somewhat scary name, I’ll give you that) and we register it by calling TypeDescriptor.AddProvider.

Once we do that, the attributes on the ‘buddy’ class magically show up as if they were defined on the real class:

 // Get the property descriptor for UnitsInStock
PropertyDescriptor propDesc = TypeDescriptor.GetProperties(
    typeof(Product)).Find("UnitsInStock", true);

// Get the display name attribute, which is not actually on the property,
// but on its counterpart in the 'buddy' class
var displayName = propDesc.Attributes.OfType<DisplayNameAttribute>().First();

And that’s pretty much it.  One important thing to notice here is that we are not using the standard reflection API (which would look like typeof(Product).GetCustomAttributes()), but we are instead using the TypeDescriptor API from the System.ComponentModel model namespace.

So to summarize, you can easily use this ‘buddy’ class mechanism anytime you deal with generated code that you need to annotate with attributes.  And even though it’s not pretty to have to use that extra class, it’s comes very handy when there is no alternative.  Maybe the day will come when C# will support doing this more cleanly, but in the meantime that’ll have to do!

SimpleBuddyTypeSample.zip

Comments

  • Anonymous
    July 27, 2009
    The comment has been removed

  • Anonymous
    July 27, 2009
    Craig: this applies to cases where you can't extend the classes via inheritance. e.g. when you use Linq To Sql, it generates some entity classes (Product, Category), and there are the classes it will create, not anything derived from it. The goal here is to add attributes to the properties on those generated classes, which is what the buddy class achieves.

  • Anonymous
    July 27, 2009
    Gotcha.  I read a little too quickly in my haste to get through the weekend blogs this morning.  So much to learn, so little time... I glossed over the "...add attributes to properties..." phrase while skimming, which is crucial to the discussion.  I should have read more thoroughly when the confusion arose.  My bad...

  • Anonymous
    August 04, 2009
    It appears that there is a bug in your third code sample. You aren't specifying the type of the associated type. Shouldn't it read as follows: TypeDescriptor.AddProvider(  new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Product)),  typeof(Product_Metadata));

  • Anonymous
    August 04, 2009
    @Jim: The code is actually correct.  TypeDescriptor.AddProvider never needs to be told about the buddy class, as that's encapsulated by the provider. The provider itself finds the buddy class in one of two ways:

  1. Passed explicitly to the ctor (public AssociatedMetadataTypeTypeDescriptionProvider(Type type, Type associatedMetadataType))
  2. Inferred via the attribute. The sample above only shows #2.  
  • Anonymous
    September 19, 2009
    I have a Core Library in my project that I've added the reference to the dataannotations to etc.. and the [MetadataType as well as the displayname never get highlighted like they're being compiled properly, any ideas? If I add them in my main windows forms project I have 0 issues I don't get any compile time errors and I have the using System.ComponentModel.DataAnnotations at the top

  • Anonymous
    September 19, 2009
    Nevermind my post, file was marked not to compile lol Thanks for the great article, is there any plans for MS to support dynamic data in winforms?

  • Anonymous
    September 19, 2009
    The comment has been removed

  • Anonymous
    May 14, 2013
    Very nice article. Though I'm late in asking this but do you know how EF4.2 handles this? Doesn't Entity Framework has the same mechanism or relies on something different? Thanks

  • Anonymous
    May 14, 2013
    @Sameer: I haven't played with recent EF builds much, so I don't know. I'd suggest posting question on social.msdn.microsoft.com/.../threads.

  • Anonymous
    August 27, 2015
    This was an extremely helpful post. I don't understand why Microsoft doesn't recommend this technique in their Data Annotation documentation, or along with anything related to Entity. The only information I did find from Microsoft was "you cannot annotate anywhere other than the declaration". Clearly whoever wrote that didn't know about this method. Thank you!