I promised in the last post that I’d show how to do template extensibility and customization using inheritance with regular templates, rather than the preprocessed kind, so here goes.

The preprocessed solution was a three-layer design.

image

  1. The base preprocessed template, DataClass.tt provided the code generation smarts to spit out a customizable C# class from some type description metadata.
  2. The Author.tt and Book.tt preprocessed templates provided specializations of this template for metadata instances (for Book and Author in some imaginary library system) and allowed application-level customization.
  3. Finally the preprocessed templates were used at an application level via simple harness templates, BookConsumer.tt and AuthorConsumer.tt

This system works quite well, but it’s a bit clumsy, needing as it does harness templates to call the preprocessed ones and smearing application concepts (Book and Author) across two of its layers.  It would be nice to move to just two layers

  1. The base layer – provides application-independent template base classes as preprocessed templates
  2. The application layer – adds application concepts and provides application-wide customizations

I scratched my head for a couple of hours and came up with this:

image

Much cleaner! So what’s different that’s enabled us to dispense with the intermediate ‘Templates’ project?

  • Application-level templates (Book.tt, Author.tt) derive directly from DataClass.tt preprocessed template class.
  • DataClass.tt preprocessed template class derives from custom adapter class TextTransformation.cs
  • TextTransformation.cs derives from T4’s built-in base class TextTransformation.
  • BaseTemplates project references T4’s SDK assembly (You’ll need the Visual Studio 2010 SDK for this variant)

Here’s how that inheritance hierarchy looks:

image

OK, so that’s all the dirty mechanics; let’s take a look at the new, single-layer application templates:

   1: <#@ template debug="false" hostspecific="false" language="C#" inherits="BaseTemplates.DataClass" #>
   2: <#@ assembly name="$(SolutionDir)\BaseTemplates\$(OutDir)\BaseTemplates.dll" #>
   3: <#@ import namespace="BaseTemplates" #>
   4: <#@ include file="ProjectSpecific.t4" #>
   5: <#@ output extension=".cs" #>
   6: <#
   7: this.Description = new TypeDescription
   8:             {
   9:                 Name="Author",  Description="A class to carry data about an author in a library system.",
  10:                 Properties=
  11:                 {
  12:                     new TypePropertyDescription{Name="ID",              Type="string",    Description="The ID of the author."},
  13:                     new TypePropertyDescription{Name="GivenName",     Type="string",    Description="The given name of the author."},
  14:                     new TypePropertyDescription{Name="FamilyName",    Type="string",    Description="The family name of the author."},
  15:                 }
  16:             };
  17: #>
  18: namespace TemplateUse
  19: {
  20:     using System;
  21:  
  22: <#
  23:     PushIndent();
  24:     base.TransformText();
  25:     PopIndent();
  26: #>
  27: }

This is essentially a coalescing of the Book.tt and BookConsumer.tt templates from the previous version of the solution, but with much less T4 infrastructure on show.  Once again, the project-specific customizations for richer comments and Serialization are included from ProjectSpecific.t4, which is unchanged, and all of the application-level code is now in a single, clean layer.

I think this gives us a really nice pattern now.

  1. Package up your reusable, extensible templates as preprocessed templates in a design-time support library.
  2. Use template inheritance from either regular OR preprocessed templates, as your solution demands.

Here’s the updated demo solution :

For the curious, what’s going on under the hood here?  The key to this variation is that regular T4 templates MUST be derived ultimately from the built-in T4 base class, Microsoft.VisualStudio.TextTemplating.TextTransformation (top left in the class diagram).  Preprocessed templates were specifically designed NOT to take a dependency on Visual Studio, so they lift this restriction and instead rely on duck-typing.  They simply require a base class with the same set of methods as the built-in one.

So why can’t DataClass inherit directly from the built-in base?  It turns out that the duck-typing match is not exact and in particular, the calls to a utility class, ToStringHelper, are instance-based in the case of preprocessed templates and static in the case of regular templates. The adapter class simply reroutes instance-based calls to the static class. With this one little trick, regular templates can inherit from any preprocessed template that in turn inherits from the adapter class.

Technorati Tags: ,