zoukankan      html  css  js  c++  java
  • 从数据到代码基于T4的VS代码自动生成方式

    目录
    一、我们的目标是:从XML文件到C#代码
    二、从Hello World讲起
    三、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:             #>
      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<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

    image

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

    T4 Template Transformation Process

  • 相关阅读:
    左偏树
    论在Windows下远程连接Ubuntu
    ZOJ 3711 Give Me Your Hand
    SGU 495. Kids and Prizes
    POJ 2151 Check the difficulty of problems
    CodeForces 148D. Bag of mice
    HDU 3631 Shortest Path
    HDU 1869 六度分离
    HDU 2544 最短路
    HDU 3584 Cube
  • 原文地址:https://www.cnblogs.com/egojit/p/2325266.html
Copyright © 2011-2022 走看看