T4文本模板,即一种自定义规则的代码生成器。根据业务模型可生成任何形式的文本文件或供程序调用的字符串。(更多基础知识请参见《你必须懂的 T4 模板:深入浅出》)
文本模板运行在 Visual Studio 本身提供的一套基于T4模板引擎中,只有了解 T4 模板引擎的运作才能让你更好地使用 T4 ,并在适当的时候进行重写自定义。(如下针对“设计时模板”转换过程进行说明)
VS本身只提供一套基于T4引擎的代码生成的执行环境,由下面程序集构成:
Microsoft.VisualStudio.TextTemplating.10.0.dll
Microsoft.VisualStudio.TextTemplating.Interfaces.10.0.dll
Microsoft.VisualStudio.TextTemplating.Modeling.10.0.dll
Microsoft.VisualStudio.TextTemplating.VSHost.10.0.dll
模板转换过程
这里提供一幅协作图,用于阐释模板转换过程及该过程中各个参与对象。下面的段落将一步一步解释文本转换过程。
1. Custom Tool
模板转换过程起点:在 Visual Studio 的解决方案资源管理器中右击模板文件 (.tt) 并在弹出的上下文菜单中选择“运行自定义工具”。(更多启动方式)
自定义工具是一个实现了 Visual Studio 定义的IVsSingleFileGenerator接口的特殊组件。T4 自定义工具由Microsoft.VisualStudio.TextTemplating.VSHost.TemplatedCodeGenerator类实现并且在注册表中注册在Visual Studio 下命名为TextTemplatingFileGenerator。Visual Studio 将使用该名称查找自定义工具、创建 COM 对象并调用其IVsSingleFileGenerator.Generate 方法。(step 1)
a) IVsSingleFileGenerator
任何一个 Custom tool 必须实现该接口,其Generate方法执行将单个输入文件转化为能被编译或可添加到项目中的单个输出文件。
b) 注册表:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\10.0\Generators
2. ITextTemplateing Service
自定义工具使用 Visual Studio 提供的Microsoft.VisualStudio.OLE.Interop.IServiceProvider.QueryServic() 方法来定位 T4 提供的ITextTemplating服务。ITextTemplating服务由Microsoft.VisualStudio.TextTemplating.VSHost.TextTemplatingService实现。一旦定位到服务,自定义工具调用ITextTemplating.ProcessTemplate方法,并传递相应的模板内容和参数。(step 1.1)
IServiceProvider定义
namespace Microsoft.VisualStudio.OLE.Interop
{
[Guid("6D5140C1-7436-11CE-8034-00AA006009FA")]
[InterfaceType(1)]
publicinterfaceIServiceProvider
{
intQueryService(refGuidguidService, refGuidriid, outIntPtrppvObject);
}
}
方法步骤1:
IVsSingleFileGenerator.Generate() ->GenerateCode() ->ITextTemplating.ProcessTemplate()
3. Host
Microsoft.VisualStudio.TextTemplating.VSHost.10.0.dll 的TextTemplatingService服务实现Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost接口,为代码生成引擎提供 Host (宿主)。在模板转换过程中: Engine 负责代码生成;Host负责提供转换过程中行为具体实现及所需要的的所有外部资源,ITextTemplatingEngineHost和模板内容一起传递到Engine.ProcessTempalte方法中用于代码生成。(step 1.2)
方法步骤2:
ITextTemplating.ProcessTemplate()–>ITextTemplatingEngine.ProcessTemplate()
4. Engine
T4 引擎负责解析模板(step2),生成并编译GeneratedTextTransformation类(steps3,4,5),运行生成类的TransformText() 并将输出内容返回给 Host (step 6)。解析模板内容后(step 2),引擎开始处理用于模板的 T4 指令。当处理内置指令时(step 3),引擎调用 Host 相应方法来执行所需的操作,eg:引擎通过调用 Host 的ITextTemplatingEngineHost.LoadIncludeText方法来处理 include 指令(step 3.1)。
由图可清晰知道:Engine是模板转换过程中主要对象,解析模板、处理指令、编译生成类等等。
方法步骤3:
ITextTemplatingEngine.ProcessTemplate() ->Engine.ProcessTemplateImplementation() ->Engine.CompileAndRunCode()
5. DirectiveProcessor
当处理自定义指令时(step 4),引擎首先调用 Host 的ITextTemplatingEngineHost.ResolveDirectiveProcessor来确定每个DirectiveProcessor(具体指令处理器的抽象基类) 的类型(step 4.1)。指令处理器通过在生成的GeneratedTextTransformation类中添加代码进行工作。Eg:<#@ parameter #> 指令创建一个属性用于模板与外部的参数传递(更多请参见《(译)理解 T4 模板:<#@ parameter #> 指令》)。一旦处理器确定所有自定义指令的类型后,该引擎实例化并为每个自定义指令调用其对应处理器的DirectiveProcessor.ProcessDirective方法(step 4.2)。
6. GenerateTextTransformation
Engine 组合解析的模板块和自定义指令来生成GeneratedTextTransformation类,这个类继承自Microsoft.VisualStudio.TextTemplating.TextTransformation类(step 5)。
Engine 调用Host 的ITextTemplatingEngineHost.ResolveAssemblyReference方法查找模板引用的所有程序集并进行编译(step 5.1)。然后再调用 Host 的ITextTemplatingEngineHost.ProvideTemplatingAppDomain方法来获取运行所生成转换类的应用程序域(step 5.2)。Visual Studio 宿主将创建一个命名为“TemplatingAppDomain”新AppDomain(step 5.3).如果“TemplatingAppDomain域”已经存在,Visual Studio 宿主将重用它。
获得TemplatingAppDomain之后,Engine 使用CodeDomProvider将生成的GeneratedTextTransformation类编译到一个内存中的临时程序集(step 5.4)。临时程序集会缓存在TemplatingAppDomain中,如果再次转换相同的模板,Engine将从缓存中查找而不是重新生成一个。
一旦GeneratedTextTransformation编译好,Engine创建此类的实例并调用TransformText方法(step 6)。TransformText方法返回由模板生成的内容,并传回到Engine,Host,Custom Tool 最后传递到 Visual Studio。
说说 T4 体系的优劣
1. T4 旨在设计用于生成输出文件的模板
由前面几节讲述IVsSingleFileGenerator.Generate()到ITextTemplating.ProcessTemplate()可知Custom Tool仅仅负责生成内容。Custom Tool 返回生成的内容到 Visual Studio ,由Visual Studio将内容保存到文件并将输出文件添加到当前项目并将其做为源代码的管理。这一伟大设计并不需要 custom tool 对Visual Studio 内核深入了解,但是,也限制了每个 T4 模板只能生成单个输出文件。T4 模板需要使用特殊的变通方法来从单个模板生成多个输出文件。(生成多个文件这块涉及到示例,后续我整示例程序的时候再整理生成多个文件的方法,有兴趣的先看老外原文)
2. 在 Visual Studio 加载项中,可以使用ITextTemplating服务
使用ITextTemplating服务并不只限于 T4 自定义工具。也可以直接从 Visual Studio 加载此服务(引用Microsoft.VisualStudio.TextTemplating.10.0.dll )。例如,DSL 设计器能直接通过域模型使用一个或多个“独立模板”生成代码,而不需要用户添加和管理项目中的 .tt文件。
a) DSL(Domain-Specific Languages 领域特定语言)
是指软件开发中出于某特定考虑而设计的小的、目标明确的语言。它们的作用是,使用某特定领域里惯用的符号或者表示法对该领域的实体或者流程进行建模。这就是它们区别于那些提供统一符号表示的通用建模语言(如UML)的地方。另外一个区别是UML经常被用作于文档分析或者设计交付,而领域特定语言既可以用作建模工具,还可以用作代码生成器(代码生成是基于TextTemplate(.tt)文件的)。
b) 独立模板
是一个可以重复使用的模板,可变部分通过参数进行传入。Eg:模板A在调用模板B时(Microsoft.VisualStudio.TextTemplating.Engine.ProcessTemplate()),模板A将需要的参数值做为CallContext或Session 传递给模板B的<#@ parameter>,并且使用 T4 引擎为模板B生成中间代码并运行输出结果。示例可参考《(译)理解 T4 模板:<#@ parameter #>指令》
3. 通过实现自定义 Host 改变内置指令的行为
请记住,Engine处理内置指令无需额外创建指令处理器。Eg:<#@output #>指令调用ITextTemplatingEngineHost .SetFileExtension和ITextTemplatingEngineHost .SetOutputEncoding方法。要改变内置指令的行为你必须创建自定义的宿主(实现ITextTemplatingEngineHost)。
4. 在不同宿主中 T4 指令的行为存在差异
创建自定义 T4 宿主的能力是一把双刃剑。因为不同宿主的实现提供的功能差别很大(eg:Microsoft-Visual Studio 和 TextTransform.exe 命令行工具),影响了如assembly,include等指令的行为,所以相同的模板很难重用在不同的宿主上。
你能为 T4 模板创建一个自定义指令处理器并通过设置注册表项注册指令处理器。不幸的是,TextTransform.exe 命令行工具不支持相同的注册机制,可能会限制你使用自定义指令的能力。
6. T4 模板引入的程序集将被锁定
在 Visual Studio 中重用“TemplatingAppDomain域”能提高性能,不幸的是,这是要付出代价的。一旦包含GeneratedTextTransformation类的程序集(eg:程序集A)加载到“TemplatingAppDomain域”中,.NET Framework 会锁定当前引用的所有程序集(包括程序集A)。如果你的模板放在这些将被锁定的程序集中,那么在运行模板后这些程序集将因锁定而无法重编译。还存在一个问题,你的模板不能直接从被锁定的程序集中调用执行代码,但是可以通过反射或CodeModel简单的访问类型元素据。
7. 在 Visual Studio 进程中运行的GeneratedTextTransformation
最后,GeneratedTextTransformation加载到“TemplatingAppDomain域”并运行在 Visual Studio 进程中(devenv.exe)。这种设计的两个副作用
a) 调试的时候必须启动另外一个 Visual Studio 实例来进行断点调试。
b) Visual Studio 进程定义Environment.CurrentDirectory来运行GenerateTextTransformation,不匹配模板本身所在的位置。这使得在 T4 模板中更难以使用相对路径来引用外部文件,可在 T4 模板中使用ITextTemplatingEngineHost. ResolvePath方法来解决此问题。
8. 在模板中访问宿主
通过在<#@ template #>指令中添加hostspecific=”True”属性使可在模板中访问宿主。GeneratedTextTransformation类中会生成一个 Host 属性提供给模板访问。Eg,在模板中使用this.Host.ResolvePath() 将相对路径转化为绝对路径。
9. 在模板中访问 Visual Studio 服务
Visual Studio Host实现了IServiceProvider接口,可以通过强制类型转换ITextTemplatingEngineHost接口实例 Host来得到IServiceProvider接口实例。通过 IServiceProvider.GetService() 方法可以访问 Visual Studio 提供的服务。
Eg:
IServiceProviderserviceProvider = (IServiceProvider)this.Host;
EnvDTE.DTEdte = (EnvDTE.DTE) serviceProvider.GetService(typeof(EnvDTE.DTE));
本想整个示例的,发现还有诸多不便,了解了一些在示例中可能会用到的优雅方案,所以接下来会整理:1、单个模板生成多个文件; 2、自定义模板指令; 3、示例 (有兴趣的园友可以关注下我,以方便得到最新信息)
参考链接: