RIA Services T4 template to copy comments from server to client

As you know one of the new features in RIA Services V1 SP1 is support for T4 templates. The T4 template is a design-time artifact that can modify the way RIA Services client-side code generation happens. For more information on T4 templates, check out Jeff Handley’s blog post on the subject.

Recently I was experimenting with RIA Services and I discovered that the IntelliSense comments for my entities were very sparse on the client. For example my Customer entity has a CompanyName property and the IntelliSense comment for that was:

Gets or sets the ‘CompanyName’ value. 

It was clear that this comment was being generated automatically, which was quite annoying because I had used my entity model on the server to carefully create useful comments for each property.

EF designer showing property documentation

I hear EF is actually smart enough that it supports defining these comments in the database itself, and they will get copied over when you create the model, but I haven’t tried it myself.

So I set out to write a T4 template to take those comments from the server types generated by EF and copy them over to my Silverlight project.

The first step was to tell Visual Studio to scrape all the types in my server project and generate a XML file containing their comments, by going to the Build tab of project properties and checking this box: 

Enabling XML comments in project properties

This file is usually used for IntelliSense, but you can open it up and the schema is pretty self-explanatory. Because code comments don’t get compiled into assemblies, this is the only way I know to tell VS to export the comments from my entities.

The next step was to write a simple T4 template using RIA Services extensibility. What this does is that as it is about to generate a type on the client (which happens when you build), it will look up that type name and all its properties in the above XML file, and copy the comments over to the generated file.

[DomainServiceClientCodeGenerator(typeof(CommentsClientCodeGenerator), "C#")]
public class CommentsClientCodeGenerator : CSharpClientCodeGenerator
{
    private XElement comments;

    public CommentsClientCodeGenerator()
    {
        comments = XElement.Load(new FileStream("..\\AddressBook.Web\\bin\\AddressBook.Web.XML", FileMode.Open));
    }

    protected override EntityGenerator EntityGenerator
    {
        get
        {
            return new CommentEntityGenerator(comments);
        }
    }

    public class CommentEntityGenerator : CSharpEntityGenerator
    {
        private XElement comments;

        public CommentEntityGenerator(XElement comments)
        {
            this.comments = comments;
        }

        private string PrintInnerXml(XElement element)
        {
            string[] lines = element.Nodes()
                .Aggregate("", (b, node) => b += node.ToString())
                .Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
            StringBuilder sb = new StringBuilder();
            foreach(string line in lines)
            {
                sb.AppendLine(line.Trim().Insert(0, "///"));
            }
            return sb.ToString().Trim();
        }

        private XElement FindCommentElementByName(string name)
        {
            return comments
                .Descendants("member")
                .Where(x => String.Compare(x.Attributes("name").FirstOrDefault().Value.ToString(), name, true) == 0)
                .FirstOrDefault();
        }

        protected override void GenerateClassDeclaration()
        {
            XElement element = FindCommentElementByName("T:" + this.Type.FullName);
            if (element != null)
            {
                this.WriteLine(PrintInnerXml(element));
            }
            base.GenerateClassDeclaration();
        }

        protected override void GenerateProperty(PropertyDescriptor propertyDescriptor)
        {
            XElement element = FindCommentElementByName("P:" + propertyDescriptor.ComponentType.FullName + "." + propertyDescriptor.Name);
            if (element != null)
            {
                this.WriteLine(PrintInnerXml(element));
            }
            base.GenerateProperty(propertyDescriptor);
        }
    }
}

That’s all I had to do… as soon as I started building this class (in its own project) inside my solution, the code comments I defined on the server started appearing in my Silverlight client.

The code is available here. Note that you need the following installed on your machine for this to work:

Hope you find this useful!