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() #>
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() #>






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