Understanding T4: <#@ include #> directive


Here is how MSDN documentation defines the include directive.

The include directive processes text from the specified file as if that text were included verbatim in the template currently being processed. In the following example, the name of the directive is include, the parameter name is file, and the parameter value is C:\test.txt: <#@ include file="c:\test.txt" #>

Syntax

<#@ include file="Included.tt" #>

file

File parameter specifies the template file T4 will merge with the contents of the main template. Included file can contain any of the T4 constructs - text blocks, code blocks, processing directives.

If file contains absolute (rooted) path, T4 will try to load the included file directly from the specified location. If file contains a relative path, T4 will determine absolute path to the included file by combining the specified relative path with the absolute path of the main template.

Although the included template may also contain an include directive pointing to a third template, T4 will continue resolving relative paths based on the absolute path of the main template. In other words, T4 does not support relative path references in nested templates. For example, T4 produces "Failed to resolve include text for file:SubFolder\Included2.tt" error when transforming Template.tt.

Templates with nested include directives

Template.tt
<#@ include file="Folder\Included.tt" #>
Template.tt
Folder\Included.tt
<#@ include file="SubFolder\Included2.tt" #>
Included2.tt
Folder\SubFolder\Included2.tt
Included2.tt

Under the hood

Example below contains two templates with text blocks, statement blocks and class feature blocks. One of the templates contains an include directive pointing to the other template. Compiled Template shows the resulting TextTransformation generated by T4.

Template
<#@ template language="C#" #>
Template Text Block 1
<#
    this.IncludedMethod();
    this.TemplateMethod();
#>
Template Text Block 2
<#@ include file="IncludedFile.tt" #>
Template Text Block 3
<#+
    void TemplateMethod()
    {
#>
Template Method
<#+
    }
#>
Include File
Included Text Block 1
<# this.WriteLine("Included statement block"); #>
Included Text Block 2
<#+
    void IncludedMethod()
    {
#>
Included method
<#+
    }
#>
Compiled Template
using System;
using Microsoft.VisualStudio.TextTemplating;  

namespace Microsoft.VisualStudio.TextTemplatingEC68E1517B8101433
{
    public class GeneratedTextTransformation: TextTransformation
    {
        public override string TransformText()
        {
            this.Write("Template Text Block 1\r\n");
            this.IncludedMethod();
            this.TemplateMethod();
            this.Write("Template Text Block 2\r\n");
            this.Write("Included Text Block 1\r\n");
            this.WriteLine("Included statement block");
            this.Write("Included Text Block 2\r\n");
            this.Write("\r\nTemplate Text Block 3\r\n");
            return this.GenerationEnvironment.ToString();
        }  

        void TemplateMethod()
        {
            this.Write("Template Method\r\n");
        }  

        void IncludedMethod()
        {
            this.Write("Included method\r\n");
        }
    }
}
Output
Template Text Block 1
Included method
Template Method
Template Text Block 2
Included Text Block 1
Included statement block
Included Text Block 2
Template Text Block 3

Notice that T4 did not simply insert the text of the included file into the template. It merged their contents, combined text and statement blocks to generate the TransformText method and used class feature blocks from both files to generate helper methods.

Host-specific features

In addition to using location of the main template to resolve relative file references, T4 allows you to specify additional directories for included files. How you do this depends on the T4 host you are using to transform the template.

Visual Studio T4 Host

T4 engine host integrated as custom tool in Visual Studio allows you to specify additional include directories in registry. Here is what it contains by default:

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\TextTemplating\IncludeFolders\.tt]
Include0="C:\Program Files\Microsoft Visual Studio 2008 SDK\VisualStudioIntegration\Tools\DSLTools\TextTemplates"

In the registry path, 9.0 refers to the Visual Studio version number, which can be 8.0 for Visual Studio 2005. .tt is the extension of the template file to which the paths will apply. T4 will not search .tt include paths when transforming a template file with other extensions, such as .t4.

You can add a path to your own directory with included templates to this list by adding a new registry value to this key. For example:

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\TextTemplating\IncludeFolders\.tt]
Include0="C:\Program Files\Microsoft Visual Studio 2008 SDK\VisualStudioIntegration\Tools\DSLTools\TextTemplates"
Include1="C:\My T4 Templates"

T4 will search the directories in the alphabetical order of their value names. In other words, it will look in the "Include0" directory before looking in the "Include1" directory.

Command Line T4 Host

Command line T4 host (TextTransform.exe) allows you to specify a directory to search for included templates using -I parameter.

TextTransform.exe -I "C:\My T4 Templates" Template.tt

About T4

T4 (Text Template Transformation Toolkit) is a template-based code generation engine. It is available in Visual Studio 2008 and as a download in DSL and GAT toolkits for Visual Studio 2005. T4 engine allows you to use ASP.NET-like template syntax to generate C#, T-SQL, XML or any other text files.

Ready for more? Read about <#@ property #> directive or go back to the overview for more information and examples.


Write a Comment

Take a moment to comment and tell us what you think. Some basic HTML is allowed for formatting.

Reader Comments

Added a link to new article for people reading cover to cover - <#@ property #> directive

Hi Oleg,

do you know of a way to implement a custom processor, that injects code into the template class’ TransformText() method at exactly the position where the directive is given, sort of like a macro looking nice to the template author but expanding to something big and ugly.

E.g. I want to allow writing something like (omitted processor attributes):

where the “For” directive does some ugly XPATH stuff and “EndFor” adds a bunch of closing brackets completing the statement.

Because of the bracket stuff I cannot just create class methods.

Currently I am even considering doing a preprocessing step to replace the macro like statements… but that does feel wrong.

Thanks,
Mike

Just notice that in comment #2 your CM system removed the T4 statements. They looked like this

and

That should work.

Sorry about that,
Mike

Mike, I don’t think it’s possible for a directive processor generate code in the TransformText method. Take a look at virtual methods of the DirectoveProcessor class. If I understand correclty, a directive processor can provide class-level code and code for the Initialize method of GeneratedTextTransformation.

Hi, I have a small problem with using the AzMan.tt Template.
My solution is as follows:
- Solution
-> Folder
|-> AzManProject
|->AuthorizationStore.tt

Then I get the error:
The project C:\xxx\xx.csproj does not belong to the solution.

I’m geussing this is due to the fact that it can’t find:

Or, maybe something else ….
Any help would be appreciated

I can email someone a sample project, if they require more info.

Hugo, does it work when the project is directly in the solution and not in a sub-folder?

Yes … It does. But for some reason if it’s in a folder, it doesn’t.

It turns out, this is something I overlooked in T4Toolbox. I’ll write it up as a bug on CodePlex. Thanks for pointing it out.

Cool, thanks !
Could you maybe do me a favor, and email me if the new version is available.

Anyway, Thank you very much for your speedy replies!

Any new comments on this subject? Is the problem fixed yet?

Kind regards ^_^

Hugo, I created work item 13146 for this problem and can fix it in the next release of T4 Toolbox in a couple of months.

Hi,
We want tot execute a t4 template file through our custom host. We can execute it if and only the Main.tt file doesnot includes any other .tt file in the include directives.

The Same file we can perform successfully through Visual studio.

Our code for custom host is as below:

CustomCmdLineHost host = new CustomCmdLineHost();
Engine engine = new Engine();
//Transform the text template.
string output = engine.ProcessTemplate(input, host);

It always gives the error as Loading the include file ‘D:\IncludedFile.tt’ returned a null or empty string.

As per the MSDN this error comes if the included file is empty, however its not.
http://msdn.microsoft.com/en-us/library/bb126242.aspx

Please suggest.

I’m trying to reuse an include file in several projects in one solution. I thought I could be clever and just link the one file to each project so if I made a change it would be seen everywhere.

I can get the location of the include file by scanning the ProjectItems collection and then using get_FileNames(0) but when I try to use a variable name in the include statement it fails.

My short question is: Is there a way to specify a variable file name in the include statement?

No. Include directives are resolved at compile time. This is a scenario that custom directive processors were intended to address.

I’ve been playing around with T4 templates recently to solve some of our config issues.

In case it helps anyone, the statement “T4 does not support relative path references in nested templates” is now incorrect with the introduction of VS2010 which is very, very nice!!

As per Tom Poulton’s comment, nesting relative paths is now supported.

There is however a small issue with the way this is implemented, if you do a lot of template nesting, TextTemplating will append the relative path directories and then place them next to a #line directive in the code it uses to generate the output. The problem with this is that the #line directive has a maximum path length of 260 characters.

If like me you have includes which navigate up directories, this appended path can very quick exceed this length. For this reason I very much advise using the registry entry, even though this is not ideal with source control.

I wrote a batch script which will point the registry entries to the appropriate directory, dependent upon where the working copy is located.