本文转自:http://www.olegsych.com/2008/03/how-to-generate-multiple-outputs-from-single-t4-template/

Update: A new version of the code described in this article is available in T4 Toolbox. For details, clickhere.

Overview

For some code generation tasks, the exact number of code artifacts to be generated may not be known upfront. For example, when generating CRUD stored procedure for a given database table, you may want to have one SELECT stored procedure created for each index. As new indexes are added to the table, additional SELECT procedures need to be generated.

Unfortunately, T4 was designed to generate a single output file per template. This forces developer to either generate all code artifacts in a single file, or create an additional T4 template for each new artifact that needs to be generated. In the CRUD stored procedure example, we need to either generate all stored procedures in a single .sql file, or add a new template to the code generation project for each additional SELECT stored procedure.

Both of these approaches are less than ideal. Generating a single source file with multiple artifacts tends to produce large files, which are difficult to understand and track changes in. Many development teams adopted a practice of creating a separate source file per code artifact (i.e. one C# class per .cs file, one stored procedure per .sql file, etc.) This allows to bring physical structure of a project (source files and folders on hard drive) closer to its logical structure (types and namespaces) making it easier to understand. Changes made in smaller files are also easier to track with version control tools. Most Visual Studio project item templates encourage this practice and Database Professional edition in particular is strongly geared toward using a single .sql file per database object.

Strongly-typed DataSet generator compromised between having one source file per code artifact and producing multiple artifacts from a single project item by generating a single DataSet class with multiple DataTable and DataRow classes nested in it. This can produce huge source files for non-trivial DataSets and long class names that don’t fit on a single line. LINQ DataContext generator addresses the problem with long class names by generating multiple classes in a single source file without nesting, but stops short of splitting individual generated classes into separate source files.

Ideally, a code generation tool should allow producing multiple output files from a single Visual Studio project item. Additional output files should be automatically added to the Visual Studio project to which the original project item belongs and previously generated output files that are no longer needed should be automatically removed from the project.

This article demonstrates how to accomplish this goal with T4 text templates. It discusses alternative approaches of generating multiple outputs and briefly describes code required to automatically add/remove output files from a Visual Studio project. Ready-to-use code is included at the end of the article with step-by-step usage instructions.

Producing multiple outputs from a single T4 template

T4 compiles a text template into a class descending from TextTransformation and calls its TransformTextmethod. This method returns a string that contains output produced by the template, which is then written to the output file by the T4 engine. The only way to produce multiple output files is to handle this explicitly, in the code of the template itself. While writing a code block that creates a file is trivial, the main challenge is to take advantage of T4 capabilities to generate its contents.

Saving accumulated content

Compiled TextTransformation accumulates output of text blocksexpression blocksWrite and WriteLinemethods by appending it to an internal StringBuilder object exposed by its GenerationEnvironmentproperty. We can write code that save all currently accumulated content to a file. Here is a helper method that does that:

SaveOutput.tt
<#@template language=“C#” hostspecific=“true” #>
<#@import namespace=“System.IO” #>
<#+
void SaveOutput(string outputFileName)
{
string templateDirectory = Path.GetDirectoryName(Host.TemplateFile);
string outputFilePath = Path.Combine(templateDirectory, outputFileName);
File.WriteAllText(outputFilePath, this.GenerationEnvironment.ToString()); this.GenerationEnvironment.Remove(0, this.GenerationEnvironment.Length);
}
#>

This template turns on the hostspecific option to make T4 generate the Host property, which SaveOutput method uses to determine output directory.

Here is a template that uses this helper method to generate two output files.

Example1.tt
<#@include file=“SaveOutput.tt” #>
<#
GenerateFile1();
SaveOutput(”File1.txt”); GenerateFile2();
SaveOutput(”File2.txt”);
#>
<#+
void GenerateFile1()
{
#>
This is file 1
<#+
} void GenerateFile2()
{
#>
This is file 2
<#+
}
#>

Template in Example1.tt uses class feature blocks to separate generation of content for different files into different methods - GenerateFile1 and GenerateFile2. In this example, these methods are very simple and contain a single text block each. However, we can easily extend them by adding method parameters, expanding the methods with additional text blocks and expression blocks, calling other helper methods, etc. As the example below illustrates, we can move GenerateFile1 and GenerateFile2 methods into separate template files (File1.tt and File2.tt) and use the include directive to make them available in the main template (Example2.tt). This would be beneficial if the individual methods become too complex or if we need to reuse them in several multi-output templates.

Example2.tt
<#@include file=“SaveOutput.tt” #>
<#@include file=“File1.tt” #>
<#@include file=“File2.tt” #>
<#
GenerateFile1(”parameter 1″);
SaveOutput(”File1.txt”); GenerateFile2(”parameter 2″);
SaveOutput(”File2.txt”);
#>
File1.tt
<#+
void GenerateFile1(string parameter)
{
#>
This is file 1, <#= parameter #>
<#+
}
#>
File2.tt
<#+
void GenerateFile2(string parameter)
{
#>
This is file 2, <#= parameter #>
<#+
}
#>

Saving accumulated content is an effective method for generating multiple files. It is simple, but can scale up to handle complex code generation scenarios. On the other hand, this approach doesn’t allow reuse of standalone templates. In other words, a T4 template that already produces a particular output file cannot be reused “as is” in a multi-output template and needs to be converted into an “include” template with aclass feature block that defines a “template” method. And vice versa, an “include” template cannot be used in standalone mode to produce a single output file.

Calling standalone template

Instead of saving output accumulated by a single compiled template (TextTransformation class), we can have T4 engine compile and run another, separate template; collect it’s output and save it to a file. We can repeat this process as many times as necessary. Here is a helper method that does that.

ProcessTemplate.tt
<#@template language=“C#” hostspecific=“True” #>
<#@import namespace=“System.IO” #>
<#@import namespace=“Microsoft.VisualStudio.TextTemplating” #>
<#+
void ProcessTemplate(string templateFileName, string outputFileName)
{
string templateDirectory = Path.GetDirectoryName(Host.TemplateFile);
string outputFilePath = Path.Combine(templateDirectory, outputFileName); string template = File.ReadAllText(Host.ResolvePath(templateFileName));
Engine engine = new Engine();
string output = engine.ProcessTemplate(template, Host); File.WriteAllText(outputFilePath, output);
}
#>

This template also turns on the hostspecific option to generate the Host property. ProcessTemplatemethod uses this property to determine full path of the standalone template file as well as the output directory. ProcessTemplate method creates a new instance of T4 Engine class, which it uses to compile and run the standalone template.

Here is a template that uses this helper method to generate two output files from two standalone templates.

Example3.tt
<#@include file=“ProcessTemplate.tt” #>
<#
ProcessTemplate(”Standalone1.tt”, “StandaloneOutput1.txt”);
ProcessTemplate(”Standalone2.tt”, “StandaloneOutput2.txt”);
#>
Standalone1.tt
<#@output extension=“txt” #>
This is file 1
Standalone2.tt
<#@output extension=“txt” #>
This is file 2

Unfortunately, there is no standard way to provide parameters to a standalone template built into T4 engine itself. GAX includes a custom T4 host that provides <#@ property #> directive processor and T4 editor installs a standalone <#@ property #> directive processor which can be used with standard T4 host. Discussion of these options is outside of scope of this article since both of them require having additional components, which are not included with Visual Studio 2008 by default.

One possible way to pass parameters to a standalone T4 template is via remoting CallContext. Here is how previous example can be expanded to achieve that.

Example4.tt
<#@import namespace=“System.Runtime.Remoting.Messaging” #>
<#@include file=“ProcessTemplate.tt” #>
<#
CallContext.SetData(”Standalone1.Parameter”, “Value 1″);
ProcessTemplate(”Standalone1.tt”, “StandaloneOutput1.txt”); CallContext.SetData(”Standalone2.Parameter”, “Value 2″);
ProcessTemplate(”Standalone2.tt”, “StandaloneOutput2.txt”);
#>
StandaloneWithParameter1.tt
<#@template language=“C#” #>
<#@output extension=“.txt” #>
<#@import namespace=“System.Runtime.Remoting.Messaging” #>
This is file 1 <#= Parameter #>
<#+
string Parameter
{
get { return (string)CallContext.GetData(”Standalone1.Parameter”); }
}
#>
StandaloneWithParameter2.tt
<#@template language=“C#” #>
<#@output extension=“.txt” #>
<#@import namespace=“System.Runtime.Remoting.Messaging” #>
This is file 2 <#= Parameter #>
<#+
string Parameter
{
get { return (string)CallContext.GetData(”Standalone1.Parameter”); }
}
#>

Compared to saving accumulated content to multiple output files, calling standalone templates is more flexible, but also more complex. It is more flexible because it allows authoring templates that can be used in standalone mode to produce a single output file, but can also be called from another template which produces multiple output files. This approach is more complex because it requires custom code or additional (third-party) components to pass parameters from calling template to the standalone template. Calling a standalone template is also slower than saving accumulated output, because T4 has to compile standalone template separately from the calling template. Ad-hoc tests show that saving of accumulated content appears to be 50% faster than execution of a similar standalone template. Actual impact on performance will depend on complexity of the templates, frequency of their compilation and may not be noticeable.

Adding/removing files from a Visual Studio project

Adam Langley provides a good overview of creating a custom code generator that adds multiple output files to a Visual Studio project in his article on CodeProject. While it is certainly possible to build your own custom tool that generates multiple output files from T4 templates, it requires a separate Visual Studio project and has to be installed on each developer’s computer. Fortunately, it is possible to access Visual Studio extensibility APIs from a T4 template directly, which allows us to put the entire code generation solution into T4 template files that developer can get with the rest of the application source from theirversion control system.

As Adam Langley explains in his article, the task of adding a generated file to a Visual Studio project boils down to obtaining a ProjectItem that, in our case, represents the T4 template which generates multiple output files. Then it’s a simple matter of calling AddFromFile method to add each output file as a nested sub-item to the project.

MultiOutput.tt template contains Adam’s code adapted for execution within a T4 code block. The only significant change that was required was in the way it obtains the DTE service. Instead of callingPackage.GetGlobalService, which from a T4 code block returns null, it needs to request it through the Hostproperty:

IServiceProvider hostServiceProvider = (IServiceProvider)Host;
EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));

For complete details, please refer to Adam’s article and __getTemplateProjectItem helper method in the attached MultiOutput.tt. Here is an updated version of SaveOutput and ProcessTemplate helper methods.

<#+
List<string> __savedOutputs = new List<string>();
Engine __engine = new Engine(); void DeleteOldOutputs()
{
ProjectItem templateProjectItem = __getTemplateProjectItem();
foreach (ProjectItem childProjectItem in templateProjectItem.ProjectItems)
{
if (!__savedOutputs.Contains(childProjectItem.Name))
childProjectItem.Delete();
}
} void ProcessTemplate(string templateFileName, string outputFileName)
{
string templateDirectory = Path.GetDirectoryName(Host.TemplateFile);
string outputFilePath = Path.Combine(templateDirectory, outputFileName); string template = File.ReadAllText(Host.ResolvePath(templateFileName));
string output = __engine.ProcessTemplate(template, Host);
File.WriteAllText(outputFilePath, output); ProjectItem templateProjectItem = __getTemplateProjectItem();
templateProjectItem.ProjectItems.AddFromFile(outputFilePath); __savedOutputs.Add(outputFileName);
} void SaveOutput(string outputFileName)
{
string templateDirectory = Path.GetDirectoryName(Host.TemplateFile);
string outputFilePath = Path.Combine(templateDirectory, outputFileName); File.WriteAllText(outputFilePath, this.GenerationEnvironment.ToString());
this.GenerationEnvironment = new StringBuilder(); ProjectItem templateProjectItem = __getTemplateProjectItem();
templateProjectItem.ProjectItems.AddFromFile(outputFilePath); __savedOutputs.Add(outputFileName);
}
#>

As you can see, ProcessTemplate and SaveOutput methods add generated file to the current Visual Studio project as a nested item under the template. They also save the name of the output file in a list, which is then used by DeleteOldOutputs method which removes all nested project items that weren’t generated by the current template transformation. DeleteOldOutputs helper method allows templates that generate a varying number of output files to automatically remove obsolete files from the project. For example, if the template generates multiple SQL files with a SELECT stored procedure for each index in a given database table, calling DeleteOldOutputs will automatically remove obsolete SELECT stored procedure when an index is removed from the table.

Usage

  • Add MultiOutput.tt from the attached ZIP archive to the Visual Studio project you are using for code generation.
  • If you are using the SaveOutput approach, add to the Visual Studio project a new .tt file similar to the example below. Examples of File1.tt and File2.tt templates are available above.
<#@output extension=“txt” #>
<#@include file=“MultiOutput.tt” #>
<#@include file=“File1.tt” #>
<#@include file=“File2.tt” #>
<#
GenerateFile1(”parameter 1″);
SaveOutput(”File1.txt”); GenerateFile2(”parameter 2″);
SaveOutput(”File2.txt”); DeleteOldOutputs();
#>
  • If you are using the ProcessTemplate approach, add to the Visual Studio project a new .tt file similar to the example below. Examples of Standalone.tt and Standalone2.tt templates are available above.
<#@output extension=“txt” #>
<#@import namespace=“System.Runtime.Remoting.Messaging” #>
<#@include file=“MultiOutput.tt” #>
<#
CallContext.SetData(”Standalone1.Parameter”, “Value 1″);
ProcessTemplate(”Standalone1.tt”, “StandaloneOutput1.txt”); CallContext.SetData(”Standalone2.Parameter”, “Value 2″);
ProcessTemplate(”Standalone2.tt”, “StandaloneOutput2.txt”); DeleteOldOutputs();
#>
  •  You should see the generated output files listed under their templates in Solution Explorer.

Download

References

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.

For more information about T4, check out my previous article.

[转] How to generate multiple outputs from single T4 template (T4 输出多个文件)的更多相关文章

  1. [转]Multiple outputs from T4 made easy

    本文转自:http://damieng.com/blog/2009/01/22/multiple-outputs-from-t4-made-easy One of the things I wante ...

  2. Multiple outputs from T4 made easy – revisited » DamienG

    Multiple outputs from T4 made easy – revisited » DamienG Multiple outputs from T4 made easy – revisi ...

  3. Multiple websites on single instance of IIS

    序幕 通常需要在单个IIS实例上托管多个网站,主要在开发环境中,而不是在生产服务器上.我相信它在生产服务器上不是一个首选解决方案,但这至少是一个可能的实现. Web服务器单实例上的多个网站的好处是: ...

  4. 转 Multiple outputs from T4 made easy t4生成多文件

    原文:http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited Usage Initializat ...

  5. C# Meta Programming - Let Your Code Generate Code - Introduction of The Text Template Transformation Toolkit(T4)

    <#@ template language="C#" #> <#@ output extension=".cs" #> <#@ a ...

  6. C:\Program Files (x86)\Common Files\microsoft shared\TextTemplating\11.0

    Generating Files with the TextTransform Utility \Program Files\Common Files\Microsoft Shared\TextTem ...

  7. t4-editor使用方法 Visual T4

    原文发布时间为:2011-05-17 -- 来源于本人的百度文章 [由搬家工具导入] http://visualstudiogallery.msdn.microsoft.com/40a887aa-f3 ...

  8. Uniform synchronization between multiple kernels running on single computer systems

    The present invention allocates resources in a multi-operating system computing system, thereby avoi ...

  9. salesforce lightning零基础学习(十三) 自定义Lookup组件(Single & Multiple)

    上一篇简单的介绍了自定义的Lookup单选的组件,功能为通过引用组件Attribute传递相关的sObject Name,捕捉用户输入的信息,从而实现搜索的功能. 我们做项目的时候,可能要从多个表中获 ...

随机推荐

  1. sqlite 使用记录

    2014年8月13日 18:20:52 SQLite中创建自增字段: 简单的回答:一个声明为 INTEGER PRIMARY KEY 的字段将自动增加. 从 SQLite 的 2.3.4 版本开始,如 ...

  2. ffplay 2.5.3 媒体播放器

    下载地址 http://pan.baidu.com/s/1bnlMYB1 一定要解压到 D:\ffmpeg\ 目录下 双击 OpenWith_FFPlay.reg 注册ffplay 在视频文件名上面, ...

  3. codeforces B. Sereja and Stairs 解题报告

    题目链接:http://codeforces.com/problemset/problem/381/B 题目意思:给定一个m个数的序列,需要从中组合出符合楼梯定义 a1 < a2 < .. ...

  4. [Android UI] ProgressBar自定义

    转载自:http://gundumw100.iteye.com/blog/1289348 1: 在JAVA代码中 在java代码中 ProgressBar      继承自View, 在android ...

  5. p235习题1

  6. C# Window Form播放音乐的4种方式

    C#播放背景音乐通常有四种方式: 1.播放系统事件声音 2.使用System.Media.SoundPlayer播放wav------------------------仅仅是对波形音乐 3.使用MC ...

  7. WPF中的常用类汇总:

    1.FrameworkElement: WPF中大部分的控件都可以转化成FrameworkElement,利用FrameworkElement属性获取相应的值: 2.WPF获取当前工作区域的宽度和高度 ...

  8. XtraScrollableControl 滚动条控件随鼠标滚动

    using System; using System.Windows.Forms; using DevExpress.XtraEditors; namespace WindowsFormsApplic ...

  9. Xamarin.Android开发实践(八)

    Xamarin.Android其他类型的服务 一.前言 前面我们已经学了关于服务的很多知识,但是对于真实的开发那些远远不够,通过这节我们将学习其他类型的服务,比如前台服务.IntentService和 ...

  10. Sql server之路 (六)上传服务器图片

    原理: 上传图片的名字 插入到数据库里 上传图片的内容(二进制数据) 写到服务器指定的目录下 下次读取图片的时候 从数据库里的指定字段里读取图片文件名 从数据库的指定路径下 拼串成完成的路径 就可以下 ...