Understanding T4: <#@ parameter #> directive
Visual Studio 2010 introduced a new T4 directive called parameter. This directive provides a standard method for defining input parameters in text templates as well as passing parameter values at run time.
Syntax
<#@ parameter name="…" type="…" #>
name
Specifies name of the parameter and must be a valid C# or Visual Basic identifier.
type
Specifies type of the parameter and must include the complete namespace, such as System.String or System.Int32. A custom type can be used as well, as long as the assembly it is defined in is referenced with an assembly directive in the template.
Usage
During template transformation, parameters are available in template code as strongly-typed, read-only properties.
MyTemplate.tt
<#@ template language="C#" #> <#@ parameter name="MyParameter" type="System.String" #> Parameter in expression block: <#= MyParameter #> Parameter in statement block: <# Write(MyParameter) #>
Parameter values can be supplied via remoting CallContext or the template transformation Session. Typically, this would be done in a Visual Studio extension that hosts its own instance of T4 engine or relies on the standard ITextTemplating service. While Visual Studio extensions are a fascinating subject, its discussion is outside of scope of this article. Instead, we will illustrate the technique of passing parameter values using template code itself.
Remoting CallContext
CallContext allows you to define global variables whose scope is limited to a single thread, also known as thread-local variables. While on the surface the CallContext is similar to Thread Local Storage in Windows, when a remoting call is made, whether across the network or simply from one AppDomain to another, the LogicalCallContext travels with the call. In other words, CallContext allows you to define thread-local variables that will work even with remote calls.
In Visual Studio, T4 templates are normally executed in a separate AppDomain, which is periodically unloaded to free memory. Therefore, remoting CallContext is a great way to specify template parameter values. The following example illustrates this can be done for the template shown above.
CallContextTemplate.tt
<#@ template language="C#" hostspecific="true" #> <#@ output extension=".txt" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Runtime.Remoting.Messaging" #> <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #> <# string templateFile = this.Host.ResolvePath("MyTemplate.tt"); string templateContent = File.ReadAllText(templateFile); CallContext.LogicalSetData("MyParameter", "CallContextValue"); Engine engine = new Engine(); string generatedContent = engine.ProcessTemplate(templateContent, this.Host); CallContext.FreeNamedDataSlot("MyParameter"); this.Write(generatedContent); #>
Template Transformation Session
In Visual Studio 2010, T4 also allows you to access template transformation Session similar to the Session in ASP.NET. Template transformation session allows you to define variables global for a particular template. Session variables can be passed from the transformation host to the template and can be used to define template parameter values. Here is how this can be done for the template shown above.
SessionTemplate.tt
<#@ template debug="true" hostspecific="true" language="C#" #> <#@ output extension=".txt" #> <#@ import namespace="System.IO" #> <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #> <# string templateFile = this.Host.ResolvePath("MyTemplate.tt"); string templateContent = File.ReadAllText(templateFile); TextTemplatingSession session = new TextTemplatingSession(); session["MyParameter"] = "SessionValue"; var sessionHost = (ITextTemplatingSessionHost) this.Host; sessionHost.Session = session; Engine engine = new Engine(); string generatedContent = engine.ProcessTemplate(templateContent, this.Host); this.Write(generatedContent); #>
Under the Hood
T4 engine generates a private, read-only property for each parameter directive in your template. In particular, here is how the template shown above is compiled by T4.
public partial class MyTemplate : TextTransformation { private string _MyParameterField; private string MyParameter { get { return this._MyParameterField; } } public override string TransformText() { this.GenerationEnvironment = null; this.Write("Parameter in expression block: "); this.Write(this.ToStringHelper.ToStringWithCulture(MyParameter)); this.Write("\r\nParameter in statement block: "); Write(MyParameter); return this.GenerationEnvironment.ToString(); } // … }
Notice that MyParameter property is backed by a private field. The field is initialized by code in the Initialize method, invoked by T4 before the TransformText method which performs the actual code generation.
public partial class MyTemplate : TextTransformation { // … public override void Initialize() { base.Initialize(); if (this.Errors.HasErrors == false) { bool MyParameterValueAcquired = false; if (this.Session.ContainsKey("MyParameter")) { if (typeof(string).IsAssignableFrom(this.Session["MyParameter"].GetType()) == false) { this.Error("The type \’System.String\’ of the parameter \’MyParameter\’ did not match the type of" + " the data passed to the template."); } else { this._MyParameterField = (string)this.Session["MyParameter"]; MyParameterValueAcquired = true; } } if (MyParameterValueAcquired == false) { object data = CallContext.LogicalGetData("MyParameter"); if (data != null) { if (typeof(string).IsAssignableFrom(data.GetType()) == false) { this.Error("The type \’System.String\’ of the parameter \’MyParameter\’ did not match the type of" + " the data passed to the template."); } else { this._MyParameterField = (string)data; } } } } } // … }
As you can see, the template will first try to retrieve the parameter value from its Session, and only if it doesn’t exist there, it will try the remoting CallContext.
Closing Thoughts
Parameter directive closes an important gap in the integration story of T4. In the past, most notably in ASP.NET MVC, this gap lead the tool developers using T4 down the wrong path of creating a custom ITextTemplatingEngineHost just to be able to pass parameters from the tool to the template. Although the remoting CallContext was available before and template parameters are just a thin wrapper around it, this new mechanism will be difficult to miss now that it is a part of the template syntax. Unfortunately, this may have come too late for MVC, which is bound by backward compatibility chains to the old custom host, making it impossible to use any but the limited, MVC-specific templates with this tool.
References
- T4 Templates Natively Support Parameters by Kathleen Dollard
- What’s new in T4 in Visual Studio 2010 by Gareth Jones



[…] Understanding T4: <#@ parameter #> directive (Oleg Sych) […]