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.
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
I scratched my head for a couple of hours and came up with this:
Much cleaner! So what’s different that’s enabled us to dispense with the intermediate ‘Templates’ project?
Here’s how that inheritance hierarchy looks:
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.
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.