Sunday, March 18, 2012

Associated Metadata (Buddy) classes in C#

Partial classes allow us to define a class in different parts. At compile time, all these pieces are joined together and a type is generated. Now if we need to add something else to the class definition then we add another part and it is automatically added to type definition. Partial classes allow us to add new members to the same types. It does not directly allow us to be adding further description of already existing members.


Consider our example of WCF Service. It is exposing a list of students. Student type is provided to the client as a DataContract. It has two properties, FirstName and LastName. Both are of string type and decorated with DataMember attribute.
namespace Institute.Data
{
    using System.Runtime.Serialization;

    [DataContract]
    public partial class Student
    {
        [DataMember]
        public string FirstName { get; set; }

        [DataMember]
        public string LastName { get; set; }
    }
}
This is the same service as we defined in a previous post. You can find the definition of service here:

http://www.shujaat.net/2012/03/visual-studio-hosting-wcf-services.html

What if we want to allow the client of this service to be able to provide additional members. That is why we are providing it as a partial class. We might want to further decorate the FirstName and LastName properties with additional attributes. We can then do data validation based on these Data Annotation attributes. This can be done with another feature of .net framework, Associated Metadata classes. Developers also refer to it as Buddy Classes.

Let's add a service reference of InstituteDataService to our console application as follows:


We add a new class to the client. Let's call it StudentMetadata. Here we are just defining additional data annotation attributes to the same property names as the original partial class that we want additional attributes to be added to.
public sealed class StudentMetadata
{
    [Required]
    public string FirstName { get; set; }

    [StringLength(10)]
    public string LastName { get; set; }
}
In order to add these attributes, we need to add System.ComponentModel.DataAnnotations assembly reference. Now we need to map this buddy class [StudenMetadata] to the Student class. We need to add MetadataTypeAttribute to the Student class definition and specifying StudentMetadata as the buddy class as below:

[MetadataType(typeof(StudentMetadata))]
public partial class Student
{
}
We also need to add the mapping to the Type Descriptor definitions. This code can be executed at the startup of the client code.
TypeDescriptor.AddProvider(
        new AssociatedMetadataTypeTypeDescriptionProvider(
            typeof(Student)
            ), typeof(Student));
Now we just need to see how the description of the properties is affected after this.
foreach (PropertyDescriptor descriptor in
            TypeDescriptor.GetProperties(typeof(Student)))
{
    Console.WriteLine("*******************************");
    Console.WriteLine(string.Format("Property Name: {0}", descriptor.Name));
    foreach (Attribute attribute in descriptor.Attributes)
    {
        Console.WriteLine(string.Format("Attribute Name: {0}", attribute));
    }
}
The above code iterates through the property descriptors of all the properties of Student type. It then writes all the attributes to the console. This can result in the following output:


Please remember that if we access the information of PropertyInfo for the Student type, it would still return the same information. This is because this is not part of type definition but separate description of type is added. Let's add the following code:
PropertyInfo[] properties = typeof(Student).GetProperties();
foreach (var propertyInfo in properties)
{
    Console.WriteLine("*******************************");
    Console.WriteLine("Propery Name: {0}", propertyInfo.Name);
    foreach (Attribute attrib in propertyInfo.GetCustomAttributes(false))
    {
        Console.WriteLine("Attribute: {0}", attrib);
    }
}
This would result in the following output:


Please note that it has just considered DataMemberAttribute which is the definition of type from service reference. Framework developers can use the type description for generating proxies as a decoration of these types based on these additional type description attributes.

Download Code:

2 comments:

Vijay said...

Do you have any idea what is the use of the AssociatedMetaDataProvider in MVC? Is that class there because of the buddy classes?

Muhammad Shujaat Siddiqi said...

Hi Vijay,

You can certainly inherit from this class to write attribute based custom validators. You can see futher details about this here:
http://msdn.microsoft.com/en-us/library/system.web.mvc.associatedmetadataprovider%28v=vs.108%29.aspx

Thanks,
Muhammad