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 1: <#@ template debug="false" hostspecific="false" language="C#" #> 2: <#@ output extension=".cs" #> 3: <#@ assembly name="$(SolutionDir)Templates\$(OutDir)Templates.dll" #> 4: <#@ assembly name="$(SolutionDir)BaseTemplates\$(OutDir)BaseTemplates.dll" #> 5: <#@ Import namespace="Templates" #> 6: <#@ Include File="$(SolutionDir)BaseTemplates\Helpers.t4" #> 7: namespace TemplateUse 8: { 9: using System; 10: <# 11: var book = new Book(); 12: this.PushIndent(); 13: this.WriteLine(book.TransformText()); 14: this.PopIndent(); #> 15: } !--crlf-->!--crlf-->!--crlf-->!--crlf-->!--crlf-->!--crlf-->!--crlf-->!--crlf-->!--crlf-->!--crlf-->!--crlf-->!--crlf-->!--crlf-->!--crlf-->!--crlf-->
1: <#@ template debug="false" hostspecific="false" language="C#" #>
2: <#@ output extension=".cs" #>
3: <#@ assembly name="$(SolutionDir)Templates\$(OutDir)Templates.dll" #>
4: <#@ assembly name="$(SolutionDir)BaseTemplates\$(OutDir)BaseTemplates.dll" #>
5: <#@ Import namespace="Templates" #>
6: <#@ Include File="$(SolutionDir)BaseTemplates\Helpers.t4" #>
7: namespace TemplateUse
8: {
9: using System;
10: <#
11: var book = new Book();
12: this.PushIndent();
13: this.WriteLine(book.TransformText());
14: this.PopIndent(); #>
15: }
1: <#@ template debug="false" hostspecific="false" language="C#" inherits="BaseTemplates.DataClass" #>
2: <#@ assembly name="$(SolutionDir)\BaseTemplates\bin\debug\BaseTemplates.dll" #>
3: <#@ import namespace="BaseTemplates" #>
4: <#@ output extension=".cs" #>
5: <#
6: // Copyright (C) Microsoft Corporation. All rights reserved.
7: this.Description = new TypeDescription
9: Name="Book", Description="A class to carry data about a book in a library system.",
10: Properties=
11: {
12: new TypePropertyDescription{Name="Title", Type="string", Description="The title of the book."},
13: new TypePropertyDescription{Name="AuthorID", Type="string", Description="The ID of the author of the book."},
14: new TypePropertyDescription{Name="ISBN", Type="string", Description="The standard ISBN number of the book."},
15: new TypePropertyDescription{Name="CopiesOwned", Type="int", Description="The number of copies the library owns."}
16: }
17: };
18: base.TransformText();
19: #>
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:
1: <#@ template language="C#" #>
2: <#@ import namespace="System.Collections.Generic" #>
3: <#@ include file="CSharpHelpers.t4" #><#
4: // -----------------------------------------------------------------------------
5: // Template to create a simple C# data carrier class from an instance of datatype description metadata
6: // -----------------------------------------------------------------------------
7:
8: if (this.Description == null)
9: {
10: this.Error("Set the Description property on this template to an instance of the class TypeDescription.");
11: }
12: else
13: {
14: // Define the overall structure of a class.
15: // Generally this should have no template boilerplate in it, so the structure is clear and simple.
16: Summary(this.Description.Description);
17: ClassHeader(this.Description);
18: OpenBrace();
19: foreach (var property in this.Description.Properties)
20: {
21: Property(property);
22: NewLine();
23: }
24: CloseBrace();
25: }
26: #>
27: <#+
28:
29: // Control properties that drive the template
30: public TypeDescription Description { get; set; } // Description that this template uses.
31:
32: // -----------------------------------------------------------------------------
33: // Template snippet functions for the individual pieces of a class
34: // Snippets are not indented at all - it is the responsibility of the calling structural method to set correct indenting.
35: // -----------------------------------------------------------------------------
36:
37: /// <summary>
38: /// Generation method to define the template snippet for a summary comment
39: /// </summary>
40: public virtual void Summary(string comment)
41: {
42: #>
43: /// <summary>
44: /// <#= comment #>
45: /// </summary>
46: <#+
47: }
48:
49: /// <summary>
50: /// Generation method to define the template snippet for a property declaration
51: /// </summary>
52: public virtual void Property(TypePropertyDescription property)
53: {
54: Summary(property.Description);
55: #>
56: public <#= property.Type #> <#= property.Name #> { get; set; }
57: <#+
58: }
59:
60: /// <summary>
61: /// Generation method to define the template snippet for the declaration line of a class
62: /// </summary>
63: public virtual void ClassHeader(TypeDescription type)
64: {
65: #>
66: public class <#= type.Name #>
67: <#+
68: }
69: #>