CHAPTER 3
Most of what we learned in Chapter 2 about text templates will still hold true for run time templates, but there a couple of big differences.
The first big difference is the custom tool used. For text templates, the Custom Tool is TextTemplatingFileGenerator. For run time templates, the Custom Tool is TextTemplatingFilePreProcessor.
The second big difference is the output from the template. With text templates, the output is the result of running the template. Behind the scenes, we saw that T4 creates an intermediary class and then compiles this class. The output we get comes from instantiating this class and calling the TransformText method. For text templates, T4 does all of this for us as a single step, and we can see the output in the nested related file.
For run time templates, our job is a bit more involved, while T4 gets off a bit easier. The output of running the Custom Tool on a run time template is the intermediary class. We are expected to instantiate this class at runtime and explicitly call the TransformText method directly to get the results. This gives us a great deal more control over calling the template.
Let’s start by creating a new template that we will call HelpMessage.tt. Make sure to select Run Time Text Template from the Add New Item dialog box:

Adding a New Run Time Template
Also confirm, by looking at the properties for this item in Solution Explorer, that the Custom Tool is properly set as TextTemplatingFilePreprocessor:

Properties on a Run Time Template
Add the following text to the HelpMessage.tt template.
<#@ template language="C#" #> <#@ assembly name="System.Core" #> <#@ assembly name="System.Reflection" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Reflection" #> Message from SENDER to RECEIVER on <#= DateTime.Now #> Need help with <#= Assembly.GetExecutingAssembly().GetName ().Name #> |
Initial HelpMessage.tt
This simple template shows the directives that we are already familiar with, as well a text block and a couple of expression blocks. We will see in future templates that code blocks and feature blocks also continue to work as expected.
Now let’s look at the output from running the Custom Tool:
// -------------------------------------------------------- // <auto-generated> // This code was generated by a tool. // Runtime Version: 12.0.0.0 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </auto-generated> // ------------------------------------------------------------------------------ namespace book { using System.Linq; using System.Text; using System.Collections.Generic; using System.Reflection; using System;
/// <summary> /// Class to produce the template output /// </summary>
#line 1 "E:\t4\T4\project\book\book\HelpMessage.tt" [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "12.0.0.0")] public partial class HelpMessage : HelpMessageBase { #line hidden /// <summary> /// Create the template output /// </summary> public virtual string TransformText() { this.Write("\r\n\r\nMessage from SENDER to RECEIVER on ");
#line 10 "E:\t4\T4\project\book\book\HelpMessage.tt" this.Write(this.ToStringHelper.ToStringWithCulture(DateTime.Now));
#line default #line hidden this.Write("\r\n\r\nNeed help with ");
#line 12 "E:\t4\T4\project\book\book\HelpMessage.tt" this.Write(this.ToStringHelper.ToStringWithCulture(Assembly.GetExecutingAssembly().GetName ().Name));
#line default #line hidden this.Write("\r\n\r\n"); return this.GenerationEnvironment.ToString(); } } |
Output from HelpMessage.tt
This should look familiar from the intermediary file that we saw in Chapter 2. So now the question is, how do we get the output of the template? The answer may already be obvious from the name of the template type: we get this output at run time.
When we created the Console Application project at the beginning of Chapter 2, we also got a Program class that we have so far ignored. Now we need to add the following code to Program.cs:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace book { class Program { static void Main(string[] args) { var template = new HelpMessage(); Console.WriteLine(template.TransformText()); Console.ReadLine(); } } } |
Running the Run Time Template
This will create an instance of our template, execute the TransformText method, and simply display the output to the screen. So far, so good.

Running the Run Time Template
Now we are in control of setting up the environment for running the template. This opens up a lot of possibilities, and a key question. Most importantly, how can we pass data to this template?
The directives that we saw in Chapter 2 continue to work as expected, but now we have a new directive that is applicable only to run time templates.
The Parameter directive was created specifically for passing data to the template when it is called.
Parameter | Description |
type | The data type of the parameter passed in. This should be the full type name to the data type. If necessary, you may need to Assembly or Import directives so that the template understands the type you specify. |
name | The name of the parameter passed in. All of the identifier naming rules for the language being used apply to the parameter name. |
For our sample template, it would be nice to be able to pass in a couple of parameters to specify the sender and receiver. So let’s add two parameter directives.
<#@ template language="C#" #> <#@ assembly name="System.Core" #> <#@ assembly name="System.Reflection" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Reflection" #> <#@ parameter type="System.String" name="sender" #> <#@ parameter type="System.String" name="receiver" #> Message from <#= sender#> to <#= receiver #> on <#= DateTime.Now #> Need help with <#= Assembly.GetExecutingAssembly().GetName ().Name #> |
HelpMessage.tt with Two Parameter Directives
We can now access these parameters with simple expression blocks as if they were local variables. There are a couple of key items to note in the generated class that implements our template. The parameters that we specified are in fact now local properties, and we have a new Initialize method.
Let’s look at the relevant sections from the generated file.
#line 1 "E:\t4\T4\project\book\book\HelpMessage.tt" private string _senderField; /// <summary> /// Access the sender parameter of the template. /// </summary> private string sender { get { return this._senderField; } } private string _receiverField; /// <summary> /// Access the receiver parameter of the template. /// </summary> private string receiver { get { return this._receiverField; } } /// <summary> /// Initialize the template /// </summary> public virtual void Initialize() { if ((this.Errors.HasErrors == false)) { bool senderValueAcquired = false; if (this.Session.ContainsKey("sender")) { this._senderField = ((string)(this.Session["sender"])); senderValueAcquired = true; } if ((senderValueAcquired == false)) { object data = global::System.Runtime.Remoting.Messaging .CallContext.LogicalGetData("sender"); if ((data != null)) { this._senderField = ((string)(data)); } } bool receiverValueAcquired = false; if (this.Session.ContainsKey("receiver")) { this._receiverField = ((string)(this.Session["receiver"])); receiverValueAcquired = true; } if ((receiverValueAcquired == false)) { object data = global::System.Runtime.Remoting.Messaging .CallContext.LogicalGetData("receiver"); if ((data != null)) { this._receiverField = ((string)(data)); } } } } |
Implementation for HelpMessage.tt with Parameter Directives
As you can see here, the Initialize method explicitly sets these local properties based on data from Session or CallingContext.
Let’s run the template.
Unfortunately, we now get an ArgumentNullException because our parameters have not been initialized.

Error Message From Running the Template Without Initializing the Parameters
So we need to change the template to be a bit more resilient.
Add the following code just above the text block in the template:
<# if ((string.IsNullOrEmpty(sender)) || (string.IsNullOrEmpty(receiver))) { return "Could not Transform text because parameters were missing"; } #> |
Making the Template More Resilient
Now when we run the template, we still won’t get the expected output, but at least we will get a meaningful message without throwing an exception.

Helpful Error Message After Running the More Resilient Version of the Template
So we need to rework the code used to call the template to create a session with valid values for our parameters.
Add code to Program.cs to look like this:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace book { class Program { static void Main(string[] args) { var template = new HelpMessage(); template.Session = new Dictionary<string, object>(); template.Session.Add("sender", Environment.UserName); template.Session.Add("receiver", "Help Desk"); template.Initialize(); Console.WriteLine(template.TransformText()); Console.ReadLine(); } } } |
Properly Calling the HelpMessage Template
As you can see, the Session is simply a dictionary that we can explicitly set, and then we must call the Initialize method so that the Session gets evaluated to initialize the local properties.
Now when we run the template, we get the expected results:

Output From Properly Calling the Help Message Template
So far we have glossed over the base class for the class that gets generated as part of the run time template. If we do not specify an inherits attribute in the template directive, T4 will generate a base class for us that has everything we need so that there won’t be a dependency on T4 when we distribute code with a run time template. I won’t list the code for this generated base class here because it is long and the details for the implementation are not relevant to our current discussion, but we may still have uses for it.
Add a new class to the project and call it HelpMessageBase. Copy the entire base class region from the HelpMessage1.cs to this new class.
Now we have a couple of tweaks to make to this class. Wrap it in a namespace, and add a couple of virtual methods.
public virtual string TransformText() { return ""; } public virtual void Initialize(){ } |
Changes Needed to the Base Class
Now we can set the inherits attribute on the template directive:
<#@ template language="C#" inherits="book.HelpMessageBase"#> |
When we run the Custom Tool, the generated output will no longer include a generated base class.
If we run the program now, we will get the same output as before.
At this point we have control over the base class, and can manipulate it as needed, since it is no longer a generated artifact.
There are a couple of changes that we would like to make. Start by removing the two parameter directives. Instead, we will define these as regular properties in the new base class.
Add the following code to the top of HelpMessageBase.cs:
public string sender { get; set; } public string receiver { get; set; } |
Now we can go back to the program.cs and modify it to use these new properties.
static void Main(string[] args) { var template = new HelpMessage(); template.sender = Environment.UserName; template.receiver = "Help Desk"; Console.WriteLine(template.TransformText()); Console.ReadLine(); } |
Calling the HelpMessage Template with the New Base Class
When we run the program, we get the same results without having to mess with Session or calling the Initialize method.
Note: You could also extend the constructor to accept the values as parameters instead of explicitly setting the properties. This would have the advantage of ensuring that the template could not be created without setting these critical values.
Depending on your specific requirements, this may be a much more useful way to pass data to the template.
In this chapter we explored run time templates. As we have seen, these are different from text templates in that the output is a new class that we need to call to get the output we expect from the template.
We explored a couple of different scenarios for passing data to these templates through parameter directives, and by taking control of the base class and adding our own properties.
Taking control of the base class opens up a lot of possibilities for extending reusability, which we will cover in greater detail in Chapter 4.