T4 Tutorial: Integrating Generated Files in Visual Studio Projects


This post is a part of the series that introduces code generation with Text Templates (also known as T4 Templates) in Visual Studio using C# and Visual Basic; explains how to create reusable templates and combine them in complex code generators. In order to follow examples in this article, you need to have Visual Studio 2008 SP1 or later, T4 Toolbox and T4 Editor installed on your computer.

Output Integration Capabilities of T4 Toolbox

T4 Toolbox offers rich capabilities for integrating generated output files in Visual Studio projects, including automatically adding generated files to multiple projects, specifying project item attributes, adding assembly references to target projects and more. The framework allows specifying these integration details declaratively, takes care of the plumbing and helps developers to concentrate on the code generation itself.

Integration capabilities are exposed through the Output property of Template class in T4 Toolbox. As you remember, Template class represents a single logical unit of code generation. For example, you might create a template that generates a single .NET class or a SQL stored procedure. Here is what it might look like.

C#
<#+
public class TableTemplate : Template
{
    public override string TransformText()
    {
#>
CREATE TABLE [dbo].[TableCs]
(
    column_1 int NOT NULL,
    column_2 int NULL
)
<#+
        return this.GenerationEnvironment.ToString();
    }
}
#>
VB
<#+
Public Class TableTemplate
    Inherits Template

    Public Overrides Function TransformText() As String
#>
CREATE TABLE [dbo].[TableVb]
(
    column_1 int NOT NULL,
    column_2 int NULL
)
<#+
        Return Me.GenerationEnvironment.ToString()
    End Function

End Class
#>

Having defined the template, we can use it to generate code like so.

C#
<#@ template language="C#" hostspecific="True" debug="True" #>
<#@ output extension="sql" #>
<#@ include file="T4Toolbox.tt" #>
<#@ include file="TableTemplate.tt" #>
<#
    TableTemplate t = new TableTemplate();
    t.Render();
#>
VB
<#@ template language="VB" hostspecific="True" debug="True" #>
<#@ output extension="sql" #>
<#@ include file="T4Toolbox.tt" #>
<#@ include file="TableTemplate.tt" #>
<#
    Dim t as TableTemplate = New TableTemplate()
    t.Render()
#>
Output
CREATE TABLE [dbo].[TableCs]
(
    column_1 int NOT NULL,
    column_2 int NULL
)

Template.Output property returns an instance of OutputInfo class which you can use to declaratively specify how output generated by the template will be integrated by the T4 Toolbox framework.

Output.File property

File property of the OutputInfo class specifies name of the file where generated output will be saved. By default, it is empty, meaning that generated output will be added to the standard output file generated for the root .tt file by Visual Studio. Here is an example of setting this property to save generated output in a separate file.

C#
<#
  TableTemplate t = new TableTemplate();
  t.Output.File = "Table.sql";
  t.Render();
#>
VB
<#
    Dim t as TableTemplate = New TableTemplate()
    t.Output.File = "Table.sql"
    t.Render()
#>

Output.File property value can also include file path. When it does, T4 Toolbox will save the file in the specified folder. When the generated file is not located in the same folder with the root .tt template, T4 Toolbox will also create a code generation log file (Script.tt.log in this example) for keeping track of the generated file, which is now stored away from the root .tt file. T4 Toolbox keeps track of all generated files in order to automatically remove them when they are no longer regenerated.

C#
<#
  TableTemplate t = new TableTemplate();
  t.Output.File = @"SQL Scripts\Table.sql";
  t.Render();
#>
VB
<#
    Dim t as TableTemplate = New TableTemplate()
    t.Output.File = "SQL Scripts\Table.sql"
    t.Render()
#>

Output of multiple templates can be combined in a single file. When the File property is empty for multiple templates, their outputs are combined in the standard output file of the root .tt file. Similarly, when the File property of several templates is set to the same file name, their outputs will be combined in the specified, separate file. This capability allows you to design a code generator to be granular by default, but allow combining the outputs when managing a single generated file is desired. The following example illustrates a code generator that combines generated table and stored procedure in a single SQL script.

C#
<#
  var t = new TableTemplate();
  t.Output.File = "Database.sql";
  t.Render();

  var p = new ProcedureTemplate();
  p.Output.File = "Database.sql";
  p.Render();
#>
VB
<#
  Dim t as New TableTemplate()
  t.Output.File = "Database.sql"
  t.Render()

  Dim p as New ProcedureTemplate()
  p.Output.File = "Database.sql"
  p.Render()
#>
Output
CREATE TABLE [dbo].[TableCs]
(
    column_1 int NOT NULL,
    column_2 int NULL
);
CREATE PROCEDURE [dbo].[ProcedureCs]
    @param1 int = 0,
    @param2 int
AS
    SELECT @param1, @param2
RETURN 0;

Output.Encoding property

Encoding property of the OutputInfo class specifies the Encoding that will be used to save the output file. This property is an equivalent of the encoding attribute in the standard output directive in T4. It is set to ANSI by default. One of examples of when you may want to explicitly specify encoding of the output file is when your template generates XML and specifies encoding in the file content, which needs to match.

C#
<#@ import namespace="System.Text" #>
<#
  var t = new XmlTemplate();
  t.Output.File = "File.xml";
  t.Output.Encoding = Encoding.UTF8;
  t.Render();
#>
VB
<#@ import namespace="System.Text" #>
<#
  Dim t as New XmlTemplate()
  t.Output.File = "File.xml"
  t.Output.Encoding = Encoding.UTF8
  t.Render()
#>

Output.Project property

Project property of the OutputInfo class specifies the project file to which the output file will be added. You will usually set this property to a path relative from the .tt file to the target project file. In addition, you will also typically set the File property to specify name and location of the output file within the target project. There is an important different between the File and Project properties in the way they treat relative paths. Project property always assumes that path is relative to the location of the root .tt file, whereas File property assumes that path is relative to the location of the target project file when the Project property is specified.

C#
<#
  TableTemplate t = new TableTemplate();
  t.Output.Project = @"..\Database\Database.dbproj";
  t.Output.File = @"Schema Objects\Schemas\dbo\Tables\TableCs.table.sql";
  t.Render();
#>
VB
<#
  Dim t as TableTemplate = New TableTemplate()
  t.Output.Project = "..\Database\Database.dbproj"
  t.Output.File = "Schema Objects\Schemas\dbo\Tables\TableVb.table.sql"
  t.Render()
#>

Output.BuildAction property

BuildAction property of the OutputInfo class specifies the build action that will be assigned for the output file in the target project. By default, this property is not assigned, allowing Visual Studio to pick the build action appropriate for the file type. Because build actions can be customized by importing MSBuild target files, the type of BuildAction property is not an enumeration, but a string. Common build actions, such as Content, Compile and EmbeddedResource are available as constants in a static class called BuildAction.

C#
<#
  TableTemplate t = new TableTemplate();
  t.Output.File = "TableCs.sql";
  t.Output.BuildAction = BuildAction.Content;
  t.Render();
#>
VB
<#
  Dim t as TableTemplate = New TableTemplate()
  t.Output.File = "TableVb.sql"
  t.Output.BuildAction = BuildAction.Content
  t.Render()
#>

Output.CopyToOutputDirectory property

CopyToOutputDirectory property of the OutputInfo class specifies the value that will be assigned to “Copy to Output Directory” attribute of the output file in the target project. The type of CopyToOutputDirectory property is an enumeration also called CopyToOutputDirectory, with items like CopyAlways, CopyIfNewer and DoNotCopy (default).

C#
<#
  TableTemplate t = new TableTemplate();
  t.Output.File = "TableCs.sql";
  t.Output.CopyToOutputDirectory = CopyToOutputDirectory.CopyAlways;
  t.Render();
#>
VB
<#
  Dim t as TableTemplate = New TableTemplate()
  t.Output.File = "TableVb.sql"
  t.Output.CopyToOutputDirectory = CopyToOutputDirectory.CopyAlways
  t.Render()
#>

Output.CustomTool and Output.CustomToolNamespace properties

CustomTool and CustomToolNamespace properties of the OutputInfo class specify the value that will be assigned to the “Custom Tool” and “Custom Tool Namespace” attributes of the output file. These properties are of type string. They are null by default, which leaves Visual Studio to pick appropriate defaults based the file type. The following example illustrates generating a resource file, assigning ResXFileCodeGenerator to automatically generate a strongly-typed .NET wrapper for the resource in a particular namespace.

C#
<#
  var t = new ResourceTemplate();
  t.Output.File = "Resource.resx";
  t.Output.CustomTool = "ResXFileCodeGenerator";
  t.Output.CustomToolNamespace = "CustomNamespace";
  t.Render();
#>
VB
<#
  Dim t as New ResourceTemplate()
  t.Output.File = "Resource.resx"
  t.Output.CustomTool = "ResXFileCodeGenerator"
  t.Output.CustomToolNamespace = "CustomNamespace"
  t.Render()
#>

Output.BuildProperties property

For some types of files, such as resource files, not all project item attributes are exposed in Visual Studio user interface. In particular, resource files have a LogicalName attribute that determines the name under which the resource will be embedded in a .NET assembly. BuildProperties property of the OutputInfo class allows you to specify this and other MSBuild properties for the output file. BuildProperties is a string dictionary, you will need to know names of the properties you want to set.

C#
<#
  var t = new ResourceTemplate();
  t.Output.File = "Resource.resx";
  t.Output.BuildProperties["LogicalName"] = "MyResource";
  t.Render();
#>
VB
<#
  Dim t as New ResourceTemplate()
  t.Output.File = "Resource.resx"
  t.Output.BuildProperties("LogicalName") = "MyResource"
  t.Render()
#>

Output.References property

References property of the OutputInfo class specifies the assembly references that needed for the generated file to compile. References is a collection of strings, empty by default. The following example illustrates a scenario where the generated output uses Oracle client and needs a reference to System.Data.OracleClient assembly to compile. When the template is rendered, T4 Toolbox will automatically add the specified reference(s) to the target project.

C#
<#
  var t = new OracleTemplate();
  t.Output.File = "OracleCode.cs";
  t.Output.References.Add("System.Data.OracleClient");
  t.Render();
#>
VB
<#
  Dim t as New OracleTemplate()
  t.Output.File = "OracleCode.vb"
  t.Output.References.Add("System.Data.OracleClient")
  t.Render()
#>
image

Output.PreserveExistingFile property

PreserveExistingFile is a boolean property of the OutputInfo class. It is false by default, which means that T4 Toolbox will treat the output file as automatically generated, under full control of the generator. When PreserveExistingFile is true, T4 Toolbox will treat the output file as optionally generated, under full control of the developer. If the file already exists before code generation, the framework will assume that developer created it manually and will not overwrite it. When the file is no longer generated, the framework will again assume that developer modified it and will not delete the file. This logic is designed to preserve code written by hand.

Setting this property to true allows you to generate scaffolding code - code meant to serve as a sample or starting point for a developer to complete manually, such as partial classes or stubs.

C#
<#
  var t = new SampleTemplate();
  t.Output.File = "Sample.cs";
  t.Output.PreserveExistingFile = true;
  t.Render();
#>
VB
<#
  Dim t as New SampleTemplate()
  t.Output.File = "Sample.vb"
  t.Output.PreserveExistingFile = True
  t.Render()
#>

Download

Source code, C# and Visual Basic.


Write a Comment

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

Reader Comments

[…] T4 Tutorial: Integrating Generated Files in Visual Studio Projects (Oleg Sych) […]

Nice post, never enough of practicle examples. Thanks

Love that ! :)

Thanks oleg.

I’m using your toolbox every 2 days since june 2009 now.

Be aware of encoding!
Had a problem with encoding. The output contained unrecognized characters, even though I hab UTF8 set.

The reason wss, that the file containig the text for the template was saved as ASCII. So when retrieving the template text, there were allready unrecognized characters in it there where written “as is” to the output file.

Can we set the “DependentUpon” in Output.BuildProperties?

template.Output.File = “Entity.g.cs”;
template.Output.BuildProperties[”DependentUpon”] = “Entity.cs”;
template.Render();

I would expect Entity.g.cs to be placed under my existing Entity.cs file that is my custom code in a partial class.

It doesn’t seem to work though.

Francois,

DependentUpon is a special case in Visual Studio Automation API. T4 Toolbox doesn’t support it at this time: http://t4toolbox.codeplex.com/Thread/View.aspx?ThreadId=68981

Oleg

Hi Oleg,
thanks a lot for your fantastic work!

Is there a way for to get the full qualified file name of the
t.Output.File property?

I need to check if the output file is there, if so I need to read them to preserve the developers changes and extend the content with some updates (it is a resource file (.resx)).

- Sörnt

Thanks Sörnt. You can use TransformationContext.Host.ResolvePath to convert relative path in Output.File to absolute path.

Oleg

Oleg,
Thank you for the T4 toolbox and great articles.
I am using t4toolbox to generate files going to multiple projects (template project is in the same solution).
I have config file which controls which projects to be generated. Everything works except that when I disable some of the projects, their output is being deleted.
I have one bootstrap template which executes separate generators depending on the configuration.
Is there any way to solve this? I am thinking about using PreserveExistingFile=true and deleting manually…

Ivo,

PreserveExistingFile option is meant to address the single-time code generation scenarios where developers will be modifying the generated scaffolding code. If you need to regenerate target projects, you may also want to consider creating a separate template for each target project.

Oleg

Thank you for this fantastic tool that more than makes up for VS shortcomings.

Do you have any idea how I can set the BuildAction of an output template to “Build” for a VS2010 Database project (.dbproj?). I just get a warning telling me that “Build is not an allowed option” when I follow the instructions?!

However, in the properties pane I can set it to “Build” no problem. I looked in the .dbproj file, and it seems that it’s actually a “subtype”, so I am not sure what is going on here.

Pls help as I keep having to manually reset 40 files to “Build” whenever I generate new templates and it is killing me!!

Thanks!

The current version of the T4 Toolbox only supports setting build action output files in C# and Visual Basic projects. It would be a nice enhancement though.

Oleg

Thanks oleg, I might have a go at contributing a patch.

Could you drop me some details in an email, specifically what is different about build action in a .dbproj that means it doesn’t work? I have not tried vs automation before so if there are any gotchas a quick heads up would be fantastic

Hi
With T4 can you have custom code blocks, that should not be overwritten at generation?
Like PreserveExistingFile but for a block of code in the generated file.
I can’t use partial class in my scenario

This is not a built-in feature. The general rule of thumb is to use partial files to achieve the same effect. You could also implement it in custom code.

Can you give me some more information about “implement it in custom code”?

Rodrigo, I haven’t given much thought to it for my work and don’t have a code sample to share. The general idea is to somehow identify a “generated” block in your target file and instead of letting T4 overwrite the entire file, inject the generated code in this block. How you identify the “generated” block will depend on the format of this file, you could use regions in VB or C#, processing directives in XML and comments in files that store program code in general.

Thanks a lot!
I will try to implement some solution …
I post a question in stackoverflow
http://stackoverflow.com/questions/4546715/t4-custom-code-blocks

A great tool.

Is possible deactivate the log file when the generated file is not located in the same
folder with the root .tt template?

thanks

T4 Toolbox is optimized for continuous (as opposed to single-time) code generation. The log file is required to keep track of files generated outside of the template’s folder.

Excellent series of articles! I was wondering how can we generate a project using T4 before using OutputProject property. Can you share a code sample for doing so or point to some example somewhere!

When I generate various files that are of a project that is in Source Safe, for each, Source Safe asks if I want to erase it and I say no. Is there any way of Source Safe not ask this question?

thanks

The toolbox helps me greatly. Really appreciate it.

I am wondering if I can add project reference (referenced project in the same solution as tt) through Ouput?

Thanks

Hi Oleg,

I’m pretty new to the T4 concept. Thus far I’m enthusiastic about it. There’s one thing I don’t get from your example. The ‘TransformText’ method in your example generates the example but how can I make it more flexible. That is: change the class generation based on external parameters.

Thanks in advance,

Andre

[…] Tom Dykstra: Creating an Entity Framework Data Model for an ASP.NET MVC Application Oleg Sych: T4 Tutorial: Integrating Generated Files in Visual Studio Projects MSDN: Code Generation and T4 Text […]

Nice Article!

Actually I am stuck in running the C# code that I have generated with the t4 template. Does anyone have an idea how this could work?

It keeps saying that I don’t have a suitable main-mothod even if I DO have one in the generated cs file.

I tried to create a new cs class and paste the generated code in it and it worked!!

Do I have a problem with the generated file. I am also wondering, why does the generated file have another icon that the normal c# class file green icon?

thanks for helping