Using T4 to Create an AppSettings Wrapper, Part 4
In the previous article we finished the basic appsettings template that allowed us to generate a strongly typed class from the settings in a configuration file. We now want to expand the template to allow it to be customized depending upon the needs of the project, a later article. Before we get there though it is time to refactor the code to make it more reusable and easier to maintain. That is the focus of this article.
Under the hood a template is nothing more than a compiled class that implements a method to render the template. Up to this point we have been using the default class because our template wasn't that complicated but we want to be able to customize the template so it is time to move away from the default settings. The T4 Toolbox is a handy set of extensions to T4 that allows us to work with the template as a class rather than as a text file. By switching to a class approach we can take advantage of all the features we are used to in normal code but still ultimately generate a template. There is nothing we are going to implement that you couldn't do by hand or with another library but the T4 Toolbox makes things easier so we'll use it. You'll need to download and install it before moving on.
To make the best use of T4 Toolbox we need to move our template into a class that derives from Template. We can then implement the TransformText method to generate our template. First ensure that the T4 Toolbox is installed through Extension Manager. Normally if you're creating a new template you will add a new template file using T4 Toolbox\Template but since we already have one we'll just modify our existing template file.
T4 allows you to include text files inside templates using the include directive. It works similar to C++ #includes in that the contents of the file are inserted into the template file whereever the directive resides. This is useful for sharing common functionality across multiple templates. The included file must be either in the same directory as the template, a directory relative to the template or in a path that T4 will search.
Open the template file and add an include for the T4 Toolbox.
<#@ include file="T4Toolbox.tt" #>
This include brings in the infrastructure needed by T4 Toolbox. I normally place it after the assembly directives but before the import directives. A word of warning, T4 complains if it finds multiple assembly or import statements for the same string so try to keep these directives down to a single file.
Creating the Template Class
Now it is time to move the template into a Template class. Right after the import directives start a new class block. Define a new class that derives from CSharpTemplate. The class name is not that relevant.
<#@ output extension=".generated.cs" #>
public class AppSettingsTemplate : CSharpTemplate
Move any methods from the original template inside the class body. They should probably be private since they are used only for generating the template. The last method in the class should be the override for the TransformText. You can generate the template body by making method calls but the easier approach is to simply end the class block and starting the template text. After the template text will be another class block that finishes the TransformText method. The call to GenerationEnvironment is what returns the template text from the method. The template text outside of the class block is automatically added to the returned text.
public override string TransformText ()
var className = MakeIdentifier(Path.GetFileNameWithoutExtension(Host.TemplateFile));
Remember that indentation and blank lines can be confusing in templates so it might be necessary to play around with the template to get them right. The goal isn't to make the template be styled correctly but rather the generated code.
Notice that the className variable that was in a statement block before has been moved inside the method. Variables defined in the method are accessible to the template text. Also note that any statement blocks used inside the template text need to be switched to class blocks because a statement block cannot follow a class block.
Removing Unneeded Code
When we were writing the template in previous articles we defined a few helper methods. Some of these can go away because T4 Toolbox provides them for us.
MakeIdentifier() was used to create a valid identifier given a string. It can be replaced by a call to PropertyName() if you want a Pascal cased identifier or FieldName() if you want a camel cased identifier. Replace the calls and remove the method.
GetNamespaceForTemplate() was used to determine the namespace to use for the type. It can be replaced by a call to the property DefaultNamespace which should provide the same information. Replace the calls and remove the method.
Host was used to access the host information. T4 Toolbox wraps the host to better provide isolation from the underlying implementation. Replace references of Host with TransformationContext.Current.Host.
If you run the template at this point you'll see that the generated file is empty. What's going on? If you look at your template file you'll realize that all you've done is defined a template class. There isn't anything that is actually causing the template to run. Time to fix that.
A nested template is a template inside a template. It is one of the key techniques that we can use to both encapsulates a template and make it configurable. The issue with the current template is that someone would need to know how the template works before they could customize it. Additionally they would need to change the template file to make any customizations. If we later release a new version of our template then they would either need to manually merge the changes in or redo their customizations. A nested template allows us to expose a simple template that exposes the customizable parts of the template (generally through properties) while calling the real template to do the actual work. This allows us to separate the real template from the customizable parts. Let's break our template up into the customizable part and the template part.
I like for my customizable template to bear the name of the template as will be shown in the Add New Items dialog and the real template to match the template class name so rename the existing template file to AppSettingsTemplate.tt. Then create a new template file called AppSettings.tt which will be the customizable part.
The customizable template represents what will get generated so it needs the template directive, the output directive and an include of the real template.
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".generated.cs" #>
<#@ include file="AppSettingsTemplate.tt" #>
The real template doesn't need the template or output directives anymore. In fact it would be an error to leave them in. Instead it just defines the assemblies and namespaces needed to generate the template. Additionally we need to tell VS not to treat it as a template file anymore so view the properties of the file and remove the custom tool setting. A caveat to this is that VS will not automatically regenerate the output if you modify this template anymore. If you make any changes to it you will need to regenerate it manually.
The customizable template needs to create an instance of the real template, call any customization members that it needs to and then render the template. Since we have no customization yet it would boil down to this.
var template = new AppSettingsTemplate();
//Do customization here
We've now set the stage to allow our template to be customized. In the next article we'll expose customization points to make our template truly useful in real world projects.