一起谈.NET技术,从数据到代码—基于T4的代码生成方式

  在之前写一篇文章《从数据到代码》(上篇下篇)中,我通过基于CodeDOM+Custom Tool的代码生成方式实现了将一个XML表示的消息列表转换成了相应的C#代码,从而达到了强类型编程的目的。实际上,我们最常用的代码生成当时不是CodeDOM,而是T4,这是一个更为强大,并且适用范围更广的代码生成技术。今天,我将相同的例子通过T4的方式再实现一次,希望为那些对T4不了解的读者带来一些启示。同时这篇文章将作为后续文章的引子,在此之后,我将通过两篇文章通过具体实例的形式讲述如果在项目将T4为我所用,以达到提高开发效率和保证质量的目的。[这里有T4相关的资料][文中的例子可以从这里下载]。

  一、我们的目标是:从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: <?xml version="1.0" encoding="utf-8" ?>
   2: <messages> 
   3:   <message id="MandatoryField" value="The {0} is mandatory."  category="Validation"/> 
   4:   <message id="GreaterThan" value="The {0} must be greater than {1}."  category="Validation"/> 
   5:   <message id="ReallyDelete" value="Do you really want to delete the {0}."  category="Confirmation"/>  
   6: </messages>

  在上面的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: #>Console.WriteLine("Hello, {0}","<#= person#>");
17: <# } #>
18: }
19: }
20: }
21:
22: <#+
23: public string[] InitializePersonList()
24: {
25: return new string[]{"Foo","Bar","Baz"};
26: }
27: #>

保存该文件后,一个.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<XmlElement>();
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<XmlElement>().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引擎首先对模板的静态内容和动态内容进行解析,最终生成一个继承自Microsoft.VisualStudio.TextTemplating.TextTransformation的类,所有的文本转化逻辑被放入被重写的Transformation方法中。然后动态创建该对象,执行该方法并将最终的类型以附加文件的形式输出来。

时间: 2024-09-17 17:06:23

一起谈.NET技术,从数据到代码—基于T4的代码生成方式的相关文章

从数据到代码——基于T4的代码生成方式

在之前写一篇文章<从数据到代码>(上篇.下篇)中,我通过基于CodeDOM+Custom Tool的代码生成方式实现了将一个XML表示的消息列表转换成了相应的C#代码,从而达到了强类型编程的目的.实际上,我们最常用的代码生成当时不是CodeDOM,而是T4,这是一个更为强大,并且适用范围更广的代码生成技术.今天,我将相同的例子通过T4的方式再实现一次,希望为那些对T4不了解的读者带来一些启示.同时这篇文章将作为后续文章的引子,在此之后,我将通过两篇文章通过具体实例的形式讲述如果在项目将T4为我

一起谈.NET技术,发布NGuestBook(一个基于.NET平台的分层架构留言本小系统)

发布NGuestBook的动机说明      大约在半年前,我在博客上发表了一个系列文章:<基于.NET平台的分层架构实战>.当时在讲解过程中用到了一个叫NGuestBook的案例,在那以后,有很多朋友留言或发E-mail希望能得到NGuestBook的完整源代码,以便对照文章研究学习.但是,在当时NGuestBook只是我虚拟的一个案例,并没有成型的系统和完整的源代码.       但是后来一直有很多朋友询问这个事情,所以我觉得,将那个NGuestBook做出来还是很有必要的,所以,我花了两

一起谈.NET技术,疯狂的想法——基于.NET的软件超市平台构想与5年实现之路

在2005年的时候,我曾经基于.NET 2003开发了一个小的组件,这个组件的目的是为了解决模块化开发和模块复用的问题.我将该组件命名为Common Form Framework,它的目的是允许每一个开发人员独立的开发自己的模块且可以直接专注于业务模块,然后通过配置可以快速将所有开发人员开发的业务逻辑窗体集成到这个组件中. 该组件的思路如下图所示.该组件提供了一个如"2"标识的空的窗体,每一个开发人员通过编写一个如"1"的XML配置文件即可将一个模块的功能附加到空窗

一起谈.NET技术,创建代码生成器可以很简单:如何通过T4模板生成代码?[下篇]

在<上篇>中我们通过T4模板为我们指定的数据表成功生成了我们需要的用于添加.修改和删除操作的存储过程.但是这是一种基于单个文件的解决方案,即我们必须为每一个生成的存储过程建立一个模板.如果我们提供一种基于多文件的代码生成方式,将会为编程人员带来极大的便利.借助于T4 ToolBox这个开源工具箱,多文件的SQL Generator的实现变得异常简单. 一.多文件代码生成器会带来多大的便利? 我们先来直观的感受一下较之<上篇>提供的单一文件的代码生成器,基于多文件的代码生成解决方案会

一起谈.NET技术,解决T4模板的程序集引用的五种方案

在众多.NET应用下的代码生成方案中,比如CodeDOM,BuildProvider, 我觉得T4是最好的一种.关于T4的基本概念和模板结果,可以参考我的文章<基于T4的代码生成方式>.如果要了解T4具体的应用,则可以参考我的文章<创建代码生成器可以很简单:如何通过T4模板生成代码?>(上篇)(下篇).如果你编写T4模板,你不得不面对一个问题--如何引用一个程序集?VS 2010采用了与VS2008不同的程序集引用的解析机制.本篇文章为你介绍在VS2010下5种不同的程序集引用的方

一起谈.NET技术,走向ASP.NET架构设计——第二章:设计/ 测试/代码

再次申明一下:本系列不是讲述TDD的,只是用TDD来建立设计的思想.即便是用DDD,有时候还是结合TDD一起使用的. 开发方式比较 我们用下面的一段分析来引出今天的内容: 想想我们平时是如何在写代码:拿来需求,分析功能,编写功能代码.这样的方式,没有问题,大家也一直沿用很多年了.为了后面描述方便,我们称这种方式为传统流程. TDD的怎么做的: 拿来需求,分析功能,写功能测试代码,编写功能代码.其实两个过程差不多的,真的差不多的. 首先来分析下两种开发流程.个人认为:因为TDD多了一个角色转换的过

一起谈.NET技术,ASP.NET前台代码绑定后台变量方法总结

经常会碰到在前台代码中要使用(或绑定)后台代码中变量值的问题.一般有<%= str%>和<%# str %>两种方式,这里简单总结一下.如有错误或异议之处,敬请各位指教. 一方面,这里所讲的前台即通常的.aspx文件,后台指的是与aspx相关联的CodeBehind,文件后缀名为.aspx.cs:另一方面,这里的绑定是指用户发出访问某一页面指令后,服务器端在执行过程中就已经将前台代码进行了赋值,而后生成html格式回传客户端显示,而并非已经显示到客户端后,然后通过其他方法(如aja

一起谈.NET技术,使用VS2010代码分析功能增强ASP.NET应用程序安全

任何从事ASP.NET开发的人都不得不承认,在其职业生涯中曾经遇到过应用程序安全问题,开发人员常常被迫尽快交付代码,平台的复杂性和各种配置选项让应用程序的安全总达不到预期,此外,调试和生产环境的配置要求可能会不同,因此,一个常见的问题是将调试配置引入到生产环境,从而造成各种问题. 经过多年的发展,ASP.NET平台已经成熟,MSDN和社区博客也产生了大量优秀的文档,但确定使用哪些功能或配置往往是件麻烦事,即使开发人员有着良好的安全意识和安全技能,错误总是在所难免,程序中的漏洞仍然很多.同行代码审

一起谈.NET技术,改善代码设计 —— 简化条件表达式(Simplifying Conditional Expressions)

系列博客       1. 改善代码设计 -- 优化函数的构成(Composing Methods)       2. 改善代码设计 -- 优化物件之间的特性(Moving Features Between Objects)       3. 改善代码设计 -- 组织好你的数据(Composing Data)       4. 改善代码设计 -- 简化条件表达式(Simplifying Conditional Expressions)       5. 改善代码设计 -- 简化函数调用(Maki