Tutorial: Generate seperate files from a T4 Template

When you run the custom tool of a T4 Template, it generates code in a child node of that template. On StackOverflow I have seen a a lot of devs asking how to write to separate files, or even to a different folder. This is actually easy to achieve, and there are multiple possible solutions. In this article, I will address how I achieved this in my TexTran projects.

Generation environment

In order to have control of the output, we must know how to fetch it. You can access the currently output by calling this.GenerationEnvironment inside <# Control blocks #> . But what is GenerationEnvironment ?

TextTemplate.GenerationEnvironment is the string builder that generation-time code is using to assemble generated output

MSDN

Write to separate file

So, this.GenerationEnvironment is basically a stringBuilder holding the generated string that is our output. We have to write this a new file. For this, just add this simple method to your T4 <#+ Class Feature Block #> .

public void SaveFile(string folder, string fileName, string content)
{
	using (FileStream fs = new FileStream(Path.Combine(folder, fileName.Trim() + ".cs"), FileMode.Create))
    {
        using (StreamWriter str = new StreamWriter(fs))
        {
            str.WriteLine(content);
            str.Flush();
        }
    }
}

This is a simple method that takes the desired path, file name and the filename and uses a StreamWriter object to write your files to a specific location.

Inside your T4 Template, at the bottom (under the last output block) you can add the following lines

<#
	SaveFile(path, fileName, this.GenerationEnvironment.ToString());
	this.GenerationEnvironment.Remove(0, this.GenerationEnvironment.Length);
#>

What this does, is it writes the string content of the GenerationEnvironment StringBuilder to the path specified. Then we remove the full string value from the GenerationEnvironment so that there is no output left for the transformer to write to the child node.

Generate multiple files

One template can generate multiple files. In my TexTran example, I generate multiple Entities from a definition file.

Entities.tt:

... //imports 
<#
//Get current directory
var directory = Path.GetDirectoryName(this.Host.TemplateFile);
var entitiesPath = @"\ProjectName\FolderName";
var regExFilter = "(?<=src).+$";
var entitiesFolder = Regex.Replace(directory, regExFilter, entitiesPath);

// GenerateEntities method is in TransforManagers.ttinclude
List<EntityDefinition> entities = GenerateEntities(entityLines); 

if(entities != null)
{
	foreach (var entity in entities)
	{
#>
// This file is auto generated. Changes to this file will be lost!
using System;
using System.Collections.Generic;
using TextTran.Transformations.Enums;
	
namespace TexTran.Data.Abstractions.Entities
{
<#
	if (!string.IsNullOrEmpty(entity.Summary))
	{
#>
	/// <summary>
	/// <#= Regex.Replace(entity.Summary, "// ", "") #>
	/// </summary>
<#
	}
#>
	public class <#= entity.Name.Trim() #> : BaseEntity
	{
	<#
	foreach (var property in entity.Properties)
	{
		if (property.Nullable)
		{
#>		
		public <#= property.Type #>? <#= property.Name #> { get; set; }
<#
	}
		else
		{
#>		
		public <#= property.Type #> <#= property.Name #> { get; set; }
<#
		}
	}
#>
	}
}
<#
	SaveFile(entitiesFolder, entity.Name, this.GenerationEnvironment.ToString());
	this.GenerationEnvironment.Remove(0, this.GenerationEnvironment.Length);
	}
}
#>

The template above will write a file for each Entity iterated, using the entity.Name property for the filename. Call the SaveFile method at the end of the foreach loop. I added some lines on top that show how I create the path where I want to write to. In my TexTran project, this is located in the TransformSetup.ttinclude file.

Appendix

Just a small note. What would happen if one if your files name changes? It would keep the old file, and write a new one next to it. Add following method to your Class Feature Block, it removes all content from the destination folder:

public void RemoveFilesFromFolder(string path)
{
	Array.ForEach(Directory.GetFiles(path), File.Delete);
}

Simply call this method first in your template, so that the folder is cleared everytime you start re-generating your files.

I hope this tutorial helps! Please leave any feedback or questions in the comments below.

All templates used referenced in this article can be found here.

T4 Tutorial: Execute templates at build time

This is not so hard to do actually. It ensures your transformations are executed when you build your solution, when in some cases you did not click ‘Build > Transform all T4 Templates’ when changes are made to your .tt files.

If one of your projects uses T4 to generate code, and you want it to execute at build time, consider the next steps:

Unload your project

Righ-click on the project in your solution explorer and click ‘unload’.

Open the .csproj of your project

Right-click again and select ‘edit projectname.csproj’

Edit the .csproj file

Add following PropertyGroup at the beginning of your csproj file (for VS2017):

<PropertyGroup>
    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
    <TransformOnBuild>true</TransformOnBuild>
   <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
    <TransformOutOfDateOnly>false</TransformOutOfDateOnly>
</PropertyGroup>

The TransformOnBuild property set tot true ensures the templates to be transformed when you build your project (false by default).

OverwriteReadOnlyOutputFiles forces overwriting of readonly output files.

Set TransformOutOfDateOnly to false to transform files even if the output is up to date.

At the end of your csproj file, add following import:

<Import Project="$(VSToolsPath)\TextTemplating\Microsoft.TextTemplating.targets" />

Now you can reload your project and you are done. All T4 Templates within this project will be executed on each build.