VS2010 SP1: T4 Template Inheritance Part II – The Core Template

Last time, I outlined my scenario – we have a template that produces a very vanilla C# class from metadata and we’d like to customize it to produce something more directly applicable to our current project. Of course, we could always just copy the standard template into our project and hack around a little until it meets our needs. With simple templates, that’s often the right thing to do, however once you start to do a lot of code generation, copy/paste reuse just doesn’t cut it and you need some structure. You can find my complete sample attached to this post.

The first thing I did was to switch to precompiled templates (introduced in Visual Studio 2010) for my core class-generating template. A precompiled template gives me a class that you can instantiate and then call the TransformText() method on. This method will then produce the same output as a regular template and you can use a simple harness to call it and write the output to disk. I’m using a regular T4 template as my harness because it’s the easiest way to get a string written to disk as a file in the project system inside Visual Studio. You can add a preprocessed T4 template from the Visual Studio 2010 Project/Add New Item dialog.

Here’s the regular harness template, using a preprocessed template class called Book, generated from a preprocessed template called Book.tt

I generally put preprocessed templates in a separate project/assembly as you don’t usually want them leaking into your actual application. Here you can see I’ve put them in the Templates and BaseTemplates projects which I’m referring to via VS 2010’s new ability to reference assemblies via project macros such as $(SolutionDir). This style of working is much, much easier now with Visual Studio 2010 SP1, as we’ve fixed the assembly locking issues that meant you had to keep restarting your IDE.

I’m using a helper function from my standard function library (Helpers.t4) to make indenting the code a bit more standardized and customizable – I’ll come back to that as a customization point in another post. The main thrust of this wrapper is that it simply uses WriteLine() to spit out the code from the preprocessed template class.

So what’s in the Book class? Let’s have a look at the preprocessed template that generated it:

You can see straightaway that this is mostly just the metadata set-up we described last time. All of the real work is being done in the base template that’s referenced in the <#@ template inherits=BaseTemplates.DataClass” #> directive. That template defines a contract (in the loosest sense of the word) that says you have to set its Description property to a piece of metadata before calling the base’s TransformText() and it will generate a matching class for you. Here it is:

The key here is structure. The template has an initial control block that validates its metadata and then defines the logic for generating a class from the metadata provided. That logic is written as ordinary procedural code and has no template boilerplate in it at all. I find this separation of core control logic from output text to be key in keeping larger templates maintainable. The control code uses a few simple helpers (OpenBrace, CloseBrace, NewLine) to avoid clumsy WriteLine statements and literal strings. This could form the start of a language-agnostic approach to structuring templates, although I’m not going to go down that path here.

The control code then calls out to various methods defined in class feature blocks that write snippets of the output using standard boilerplate syntax. Notice that each of these methods is defined as a virtual method. Here we have the key to our extensibility story. We can derive from this template class and replace just the pieces of output generation that we want without disturbing any others or the core logic. In fact, it’s probably a good idea to move that core logic into a virtual method of its own as well so it can be independently customized without disturbing the snippets.

Notice that none of the ouput snippet methods do any indenting – they all assume zero external indentation and leave their callers to manage it. That way you can build up a library of output methods and mix and match them from different pieces of control logic. You could also break down their parameters to only use simple types so they were independent of specific forms of metadata – it’s a balance between readability and reuse here.

This time, we’ve seen how the core template works to generate vanilla class code in a structured, extensible manner. Next time we’ll look at taking advantage of that extensibility to create the custom class we asked for in Part I.