[转] How to generate multiple outputs from single T4 template (T4 输出多个文件)
本文转自: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 blocks, expression blocks, Write 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
- Source code
- T4 Toolbox (contains a new version of the code described in here)
References
- Generate many files from one template by Gabriel Kevorkian
- Testing T4 templates using the GAX host by Jose Escrich
- Creating a Custom Tool to Generate Multiple Files in Visual Studio 2005 by Adam Langley
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 输出多个文件)的更多相关文章
- [转]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 ...
- Multiple outputs from T4 made easy – revisited » DamienG
Multiple outputs from T4 made easy – revisited » DamienG Multiple outputs from T4 made easy – revisi ...
- Multiple websites on single instance of IIS
序幕 通常需要在单个IIS实例上托管多个网站,主要在开发环境中,而不是在生产服务器上.我相信它在生产服务器上不是一个首选解决方案,但这至少是一个可能的实现. Web服务器单实例上的多个网站的好处是: ...
- 转 Multiple outputs from T4 made easy t4生成多文件
原文:http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited Usage Initializat ...
- C# Meta Programming - Let Your Code Generate Code - Introduction of The Text Template Transformation Toolkit(T4)
<#@ template language="C#" #> <#@ output extension=".cs" #> <#@ a ...
- C:\Program Files (x86)\Common Files\microsoft shared\TextTemplating\11.0
Generating Files with the TextTransform Utility \Program Files\Common Files\Microsoft Shared\TextTem ...
- t4-editor使用方法 Visual T4
原文发布时间为:2011-05-17 -- 来源于本人的百度文章 [由搬家工具导入] http://visualstudiogallery.msdn.microsoft.com/40a887aa-f3 ...
- Uniform synchronization between multiple kernels running on single computer systems
The present invention allocates resources in a multi-operating system computing system, thereby avoi ...
- salesforce lightning零基础学习(十三) 自定义Lookup组件(Single & Multiple)
上一篇简单的介绍了自定义的Lookup单选的组件,功能为通过引用组件Attribute传递相关的sObject Name,捕捉用户输入的信息,从而实现搜索的功能. 我们做项目的时候,可能要从多个表中获 ...
随机推荐
- Android Design 4.4中文版发布
“两年前的今天,我们发布了 Android Design 中文版(旧闻链接). 随着 Android 系统的发展,界面和设计语言都不断的发生变化.韶华易逝.光阴苒冉,Android 进化到了 4.4 ...
- Human Gene Functions(poj 1080)
题目大意是:给定两组DNA序列,要你求出它们的最大相似度 每个字母与其他字母或自身和空格对应都有一个打分,求在这两个字符串中插入空格,让这两个字符串的匹配分数最大 /* 思路是很好想的,设f[i][j ...
- 标准C实现基于TCP/IP协议的文件传输
上学期集成程序设计的课堂作业,对于理解TCP/IP实现还是挺有帮助的. TCP/IP编程实现远程文件传输在LUNIX中一般都采用套接字(socket)系统调用. 采用客户/服务器模式,其程序编写步骤如 ...
- jquery easy ui 1.3.4 事件与方法的使用(3)
3.1.easyui事件 easyui事件可以在构建的时候就通过属性方式添加上去,比如在panel收缩的时候添加一个事件,在构建的时候代码如下 onCollapse: function () { al ...
- oracle11g客户端 安装图解
软件位置:我的网盘 -- oracle空间 -- oracle工具 -- win64_11gR2_database_clint(客户端) -- 压缩软件包 先将下载下来的ZIP文件解压,并运行setu ...
- SQL语句优化原则
处理百万级以上的数据提高查询速度的方法: .应尽量避免在 .对查询进行优化,应尽量避免全表扫描,首先应考虑在 .应尽量避免在 .应尽量避免在 or num= 可以这样查询: ...
- 人人都可以开发高可用高伸缩应用——论Azure Service Fabric的意义
今天推荐的文章其实是微软的一篇官方公告,宣布其即将发布的一个支撑高可用高伸缩云服务的框架--Azure Service Fabric. 前两天,微软Azure平台的CTO Mark Russinovi ...
- 【软件工程】week5-个人作业-敏捷开发方法初窥
敏捷开发方法初窥 引言:本周的软件工程个人博客作业是阅读关于敏捷开发方法的文章(http://martinfowler.com/agile.html),并撰写自己的读后感.文章内容非常丰富,对敏捷开发 ...
- 【练习】使用接口回调和handler实现数据加载到listview
代码结构 布局: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xml ...
- Android SQLite数据库版本升级原理解析
Android使用SQLite数据库保存数据,那数据库版本升级是怎么回事呢,这里说一下. 一.软件v1.0 安装v1.0,假设v1.0版本只有一个account表,这时走继承SQLiteOpenHel ...