加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > asp.Net > 正文

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

发布时间:2020-12-16 09:07:08 所属栏目:asp.Net 来源:网络整理
导读:在之前写一篇文章《从数据到代码》(上篇、下篇)中,我通过基于 CodeDOM+Custom Tool 的代码生成方式实现了将一个XML表示的消息列表转换成了相应的C#代码,从而达到了 强类型编程 的目的。实际上,我们最常用的代码生成当时不是CodeDOM,而是 T4 ,这是一个

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

目录
一、我们的目标是:从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
   3:     string Id { get; private set; }
   5:     string Category { get;    6:? 
   8:     {
  10:         this.Value      = value;
  12:     }
  14:     {
  16:     }
  
   2: <messages> 
   4:   ="GreaterThan" ="The {0} must be greater than {1}."  /> 
   6: </>

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

class Validation
   5:         static MessageEntry MandatoryField = new MessageEntry("MandatoryField",1)">"The {0} is mandatory.",1)">"Validation");
   7:     }
   9:     {
  11:     }
using System;
   3: namespace Artech.CodeGeneration
class Program
   7:         void Main(string[] args)
   9:             Console.WriteLine("Hello,{0}",1)">"Foo");
  11:             Console.WriteLine("Baz");
  13:     }
   1: <#@ template debug="false" hostspecific="false" language="C#" #>
   3: <#@ import namespace="System" #>
   5:    7:    8: {
  12:         {    
  14:             foreach(var person in this.InitializePersonList()) 
  16:             #>
  18:             <#
  20:             #>
  22:     }
  24:? 
  26:     string[] InitializePersonList()
  28:         new string[]{"Foo",1)">"Bar",1)">"Baz"};
  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)

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

8: {
   1: <#
   4: #>
   7: } 
   1: <#+ 
   4:            5:     }
"true" language=   3: <#@ assembly name="System.Xml" #>
   5: <#@ import    6: <#@ import "System.Linq" #>
   8:? 
  10: {
  13:         <# 
  15:         messageDoc.Load(this.Host.ResolvePath("Messages.xml"));
  17:         var messageEntries = messageDoc.GetElementsByTagName("message").Cast<XmlElement>();  
  19:                             select element.Attributes["category"].Value).Distinct();
  21:         {
  23: public  class <#=  category#>
  25:                 <#
  27:                 {                      
  29:                     value        = element.Attributes["value"].Value;  
  31:                 #>
  33:             <#  } #>
  35:     <# } #>
  37: }

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

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