博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
T4模板技术相关 from artech
阅读量:5302 次
发布时间:2019-06-14

本文共 7746 字,大约阅读时间需要 25 分钟。

一、我们的目标是:从XML文件到C#代码

再次重申一下我们需要通过“代码生成”需要达到的目的。无论对于怎么样的应用,我们都需要维护一系列的消息。消息的类型很多,比如验证消息、确认消息、日志消息等。我们一般会将消息储存在一个文件或者数据库中进行维护,并提供一些API来获取相应的消息项。这些API一般都是基于消息的ID来获取的,换句话说,消息获取的方式是以一种“弱类型”的编程方式实现的。如果我们能够根据消息存储的内容动态地生成相应的C#或者VB.NET代码,那么我们就能够以一种强类型的方式来获取相应的消息项了

比如说,现在我们定义了如下一个MessageEntry类型来表示一个消息条目。为了简单,我们尽量简化MessageEntry的定义,仅仅保留三个属性Id、Value和Category。Category表示该消息条目所属的类型,你可以根据具体的需要对其分类(比如根据模块名称或者Severity等)。Value是一个消息真实的内容,可以包含一些占位符({0},{1},…{N})。通过指定占位符对用的值,最中格式化后的文本通过Format返回。

1: public class MessageEntry
2: {
3:     public string Id { get; private set; }
4:     public string Value { get; private set; }
5:     public string Category { get; private set; }
6:
7:     public MessageEntry(string id, string value, string category)
8:     {
9:         this.Id         = id;
10:         this.Value      = value;
11:         this.Category   = category;
12:     }
13:     public string Format(params object[] args)
14:     {
15:         return string.Format(this.Value, args);
16:     }
17: }

现在我们所有的消息定义在如下一个XML文件中,<message>XML元素代码一个具体的MessageEntry,相应的属性(Attribute)和MessageEntry的属性(Property)相对应。

1: 
2: 
3:   
4:   
5:   
6: 

在上面的XML中,定义了两个类别(Validation和Confirmation)的三条MessageEntry。我们需要通过我们的代码生成工具生成一个包含如下C#代码的CS文件

1: public static class Messages
2: {
3:     public static class Validation
4:     {
5:         public static MessageEntry MandatoryField = new MessageEntry("MandatoryField", "The {0} is mandatory.", "Validation");
6:         public static MessageEntry GreaterThan = new MessageEntry("GreaterThan", "The {0} must be greater than {1}.", "Validation");
7:     }
8:     public static class Confirmation
9:     {
10:         public static MessageEntry ReallyDelete = new MessageEntry("ReallyDelete", "Do you really want to delete the {0}.", "Confirmation");
11:     }
12: }

那么如何通过T4的方式来实现从“数据”(XML)到“代码”的转换呢?在投入到这个稍微复杂的工作之前,我们先来弄个简单的。

二、从Hello World讲起

我们之前一直在讲T4,可能还有人不知道T4到底代表什么。T4是对“Text Template Transformation Toolkit”(4个T)的简称。T4直接包含在VS2008和VS2010中,是一个基于文本文件转换的工具包。T4的核心是一个基于“文本模板”的转换引擎(以下简称T4引擎),我们可以通过它生成一切类型的文本型文件,比如我们常用的代码文件类型包括:C#、VB.NET、T-SQL、XML甚至是配置文件等。

对于需要通过T4来进行代码生成工作的我们来说,需要做的仅仅是根据转换源(Transformation Source),比如数据表、XML等(由于例子简单,HelloWord模板没有输入源)和目标文本(比如最终需要的C#或者T-SQL代码等)定义相应的模板。T4模板作用就相当于进行XML转化过程中使用的XSLT

T4模板的定义非常简单,整个模板的内容包括两种形式:静态形式动态动态。前者就是直接写在模板中作为原样输出的文本,后者是基于某种语言编写代码,T4引擎会动态执行它们。这和我们通过内联的方式编写的ASP.NET页面很相似:HTML是静态的,以C#或者VB.NET代码便写的动态执行的代码通过相应的标签内嵌其中。为了让读者对T4模板有一个直观的认识,我们先来尝试写一个最简单的。假设我们需要通过代码生成的方式生成如下一段简单的C#代码:

1: using System;
2:
3: namespace Artech.CodeGeneration
4: {
5:     class Program
6:     {
7:         static void Main(string[] args)
8:         {
9:             Console.WriteLine("Hello, {0}", "Foo");
10:             Console.WriteLine("Hello, {0}", "Bar");
11:             Console.WriteLine("Hello, {0}", "Baz");
12:         }
13:     }
14: }
现在我们直接通过VS来创建一个T4模板来生成我们期望的C#代码。右击项目文件,选择"Add"|"New Item",在模板列表中选择
"Text Template"。指定文件名后确定,一个后缀名为.
tt的文件会被创建,然后在该文件中编写如下的代码。
1: <#@ template debug="false" hostspecific="false" language="C#" #>
2: <#@ assembly name="System.Core.dll" #>
3: <#@ import namespace="System" #>
4: <#@ output extension=".cs" #>
5: using System;
6:
7: namespace Artech.CodeGeneration
8: {
9:     class Program
10:     {
11:         static void Main(string[] args)
12:         {
13:             <#
14:             foreach(var person in this.InitializePersonList())
15:             {
16:             #>
17:                 Console.WriteLine("Hello, {0}","<#=  person#>");
18:             <#
19:             }
20:             #>
21:         }
22:     }
23: }
24:
25: <#+
26:     public string[] InitializePersonList()
27:     {
28:         return new string[]{
"Foo","Bar","Baz"};
29:     }
30: #>
保存该文件后,一个.cs文件将会作为该TT文件的附属文件被添加(如右图所示的HelloWorld.cs)。上述的这个TT文件虽然简单,却包含了构成一个T4模板的基本元素。在解读该T4模板之前,我们有必要先来了解一个完整的T4模板是如何构成的。

三、T4模板的基本结构

假设我们用“块”(Block)来表示构成T4模板的基本单元,它们基本上可以分成5类:指令块(Directive Block)文本块(Text Block)代码语句块(Statement Block)表达式块(Expression Block)类特性块(Class Feature Block)

1、指令块(Directive Block)

和ASP.NET页面的指令一样,它们出现在文件头,通过<#@…#>表示。其中<#@ template …#>指令是必须的,用于定义模板的基本属性,比如编程语言、基于的文化、是否支持调式等等。比较常用的指令还包括用于程序集引用的<#@ assembly…#>,用于导入命名空间的<#@ import…#>等等。

2、文本块(Text Block)

文本块就是直接原样输出的静态文本,不需要添加任何的标签。在上面的模板文件中,处理定义在<#… #>、<#+… #>和<#=… #>中的文本都属于文本块。比如在指令块结束到第一个“<#”标签之间的内容就是一段静态的文本块。

1: using System;
2:
3: namespace Artech.CodeGeneration
4: {
5:     class Program
6:     {
7:         static void Main(string[] args)
8:         {
9:

3、代码语句块(Statement Block)

代码语句块通过<#Statement#>的形式表示,中间是一段通过相应编程语言编写的程序调用,我们可以通过代码语句快控制文本转化的流程。在上面的代码中,我们通过代码语句块实现对一个数组进行遍历,输出重复的Console.WriteLine(“Hello, {0}”, “Xxx”)语句。

1: <#
2: foreach(var person in this.InitializePersonList())
3: {
4: #>
5:     Console.Write("Hello, {0}","<#=  person#>");
6: <#
7: }
8: #>

4、表达式块(Expression Block)

表达式块以<#=Expression#>的形式表示,通过它之际上动态的解析的字符串表达内嵌到输出的文本中。比如在上面的foreach循环中,每次迭代输出的人名就是通过表达式块的形式定义的(<#= person#>)

5、类特性块(Class Feature Block)

如果文本转化需要一些比较复杂的逻辑,我们需要写在一个单独的辅助方法中,甚至是定义一些单独的类,我们就是将它们定义在类特性块中。类特性块的表现形式为<#+ FeatureCode #>,对于Hello World模板,得到人名列表的InitializePersonList方法就定义在类特性块中。

1: <#+
2:     public string[] InitializePersonList()
3:     {
4:         return new string[]{
"Foo","Bar","Baz"};
5:     }
6: #>

了解T4模板的“五大块”之后,相信读者对定义在HelloWord.tt中的模板体现的文本转化逻辑应该和清楚了吧。

四、通过T4模板实现从“数据到代码”的转变

现在我们来完成我们开篇布置得任务:如何将一个已知结构的表示消息列表的XML转换成C#代码,使得我们可以一强类型的编程方式获取和格式化相应的消息条目。我们的T4模板定义如下

1: <#@ template debug="false" hostspecific="true" language="C#" #>
2: <#@ assembly name="System.Core.dll" #>
3: <#@ assembly name="System.Xml" #>
4: <#@ import namespace="System" #>
5: <#@ import namespace="System.Xml" #>
6: <#@ import namespace="System.Linq" #>
7: <#@ output extension=".cs" #>
8:
9: namespace MessageCodeGenrator
10: {
11:     public static class Messages
12:     {
13:         <#
14:         XmlDocument messageDoc = new XmlDocument();
15:         messageDoc.Load(this.Host.ResolvePath("Messages.xml"));
16:
17:         var messageEntries = messageDoc.GetElementsByTagName("message").Cast
();
18:         var categories = (from element in messageEntries
19:                             select element.Attributes["category"].Value).Distinct();
20:         foreach (var category in categories)
21:         {
22:             #>
23: public  static class <#=  category#>
24:             {
25:                 <#
26:                 foreach (var element in messageDoc.GetElementsByTagName("message").Cast
().Where(element => element.Attributes["category"].Value == category))
27:                 {
28:                     string id           = element.Attributes["id"].Value;
29:                     string value        = element.Attributes["value"].Value;
30:                     string categotry    = element.Attributes["category"].Value;
31:                 #>
32: public static MessageEntry <#= id #> = new MessageEntry("<#= id #>","<#=  value#>","<#=  categotry#>");
33:             <#  } #>
34:             }
35:     <# } #>
36:     }
37: }

模板体现出来的转化流程就是:加载XML文件(Messages.xml),然后获取所有的消息类别,为每个消息类别创建一个内嵌于静态类Messages中的以类别命名的类。然后遍历每个类别下的所有消息条目,定义类型为MessageEntry的静态熟悉。

在这里有一点需要特别指出的是:整个代码生成的输入,即XML文件Messages.xml和模板文件位于相同的目录下,但是我们需要通过Host属性的ResolvePath方法去解析文件的物理路径。对ResolvePath方法的调用,需要模板<#@ template …#>指令中的hostspecific设置为true

1: <#@ template debug="false" hostspecific="true" language="C#" #>

五、T4的文本转化的实现

和我之前采用的代码生成方式(CodeDOM+Custom Tool)一样,对于T4模板的代码生成,VS最终还是通过Custom Tool来完成的。如果你查看TT文件的属性,你会发现Custom Tool会自动设置成:TextTemplatingFileGenerator

当TextTemplatingFileGenerator被触发后(修改后的文件被保存,或者认为执行Custom Tool),会通过T4引擎完成文本的转换和输出工作。具体来讲,T4引擎的文本转化和输出机制可以通过下图来表示。T4引擎首先对模板的静态内容和动态内容进行解析,最终生成一个继承自的类,所有的文本转化逻辑被放入被重写的Transformation方法中。然后动态创建该对象,执行该方法并将最终的类型以附加文件的形式输出来。

 

 

本文转自蒋金楠:

转载于:https://www.cnblogs.com/EasyLive2006/archive/2011/09/25/2190215.html

你可能感兴趣的文章
GCD的同步异步串行并行、NSOperation和NSOperationQueue一级用dispatch_once实现单例
查看>>
团队作业
查看>>
数据持久化时的小bug
查看>>
mysql中key 、primary key 、unique key 与index区别
查看>>
bzoj2257
查看>>
Linux查看文件编码格式及文件编码转换<转>
查看>>
Leetcode: Find Leaves of Binary Tree
查看>>
Vue 模板解释
查看>>
http://www.bootcss.com/
查看>>
20145308 《网络对抗》 注入shellcode+Return-to-libc攻击 学习总结
查看>>
将多张图片和文字合成一张图片
查看>>
自己动手写ORM(01):解析表达式树生成Sql碎片
查看>>
如何使用USBWebserver在本机快速建立网站测试环境
查看>>
百度Ueditor编辑器的Html模式自动替换样式的解决方法
查看>>
变量提升
查看>>
线性表可用顺序表或链表存储的优缺点
查看>>
在现有的mysql主从基础上,搭建mycat实现数据的读写分离
查看>>
[Flex] flex手机项目如何限制横竖屏?只允许横屏?
查看>>
tensorflow的graph和session
查看>>
JavaScript动画打开半透明提示层
查看>>