[转]从数据到代码——基于T4的代码生成方式
本文转自:http://www.cnblogs.com/artech/archive/2010/10/23/1859529.html
在之前写一篇文章《从数据到代码》(上篇、下篇)中,我通过基于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
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: }
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: #>
三、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方法中。然后动态创建该对象,执行该方法并将最终的类型以附加文件的形式输出来。
[转]从数据到代码——基于T4的代码生成方式的更多相关文章
- 基于T4的生成方式
一.什么是T4模板 T4是对“Text Template Transformation Toolkit”(4个T)的简称.是一个基于文本文件转换的工具包.T4的核心是一个基于“文本模板”的转换引擎(以 ...
- 基于数据库的代码自动生成工具,生成JavaBean、生成数据库文档、生成前后端代码等(v6.0.0版)
TableGo v6.0.0 版震撼发布,此次版本更新如下: 1.UI界面大改版,组件大调整,提升界面功能的可扩展性. 2.新增BeautyEye主题,界面更加清新美观,也可以通过配置切换到原生Jav ...
- [转]发布基于T4模板引擎的代码生成器[Kalman Studio]
本文转自:http://www.cnblogs.com/lingyun_k/archive/2010/05/08/1730771.html 自己空闲时间写的一个代码生成器,基于T4模板引擎的,也不仅是 ...
- 07.深入浅出 Spring Boot - 数据访问之Mybatis(附代码下载)
MyBatis 在Spring Boot应用非常广,非常强大的一个半自动的ORM框架. 代码下载:https://github.com/Jackson0714/study-spring-boot.gi ...
- json转换数据后面参数要带ture,代码
强大的PHP已经提供了内置函数:json_encode() 和 json_decode().很容易理解,json_encode()就是将PHP数组转换成Json.相反,json_decode()就是将 ...
- 使用gfortran将数据写成Grads格式的代码示例
使用gfortran将数据写成Grads格式的代码示例: !-----'Fortran4Grads.f90' program Fortran4Grads implicit none integer,p ...
- 基于纯Java代码的Spring容器和Web容器零配置的思考和实现(3) - 使用配置
经过<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(1) - 数据源与事务管理>和<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(2) - ...
- 大数据时代:基于微软案例数据库数据挖掘知识点总结(Microsoft 聚类分析算法)
原文:(原创)大数据时代:基于微软案例数据库数据挖掘知识点总结(Microsoft 聚类分析算法) 本篇文章主要是继续上一篇Microsoft决策树分析算法后,采用另外一种分析算法对目标顾客群体的挖掘 ...
- 用T4消除代码重复,对了,也错了
用T4消除代码重复,对了,也错了 背景 我需要为int.long.float等这些数值类型写一些扩展方法,但是我发现他们不是一个继承体系,我的第一个思维就是需要为每个类型重复写一遍扩展方法,这让我觉得 ...
随机推荐
- [bzoj3238][Ahoi2013]差异_后缀数组_单调栈
差异 bzoj-3238 Ahoi-2013 题目大意:求任意两个后缀之间的$LCP$的和. 注释:$1\le length \le 5\cdot 10^5$. 想法: 两个后缀之间的$LCP$和显然 ...
- UIButton图片文字位置的四种情况
我们在做项目的过程中经常会遇到各定制UIButton 1.左边图片,右边文字 2.左边文字,右边图片 3.上边图片,下边文字 4.上边文字,下边图片 针对这四种情况 使用UIButton的catego ...
- Java中如何获取spring中配置的properties文件内容
有2种方式: 一. 1.通过spring配置properties文件 [java] <bean id="propertyConfigurer" class=&qu ...
- Swift 函数Count,Filter,Map,Reduce
原创Blog,转载请注明出处 blog.csdn.net/hello_hwc 前言:和OC不同,Swift有非常多全局的函数,这些全局函数对简化代码来说非常实用.眼下Swift出到了2.0,只是我这篇 ...
- STM8S---外部中断应用之长按键识别
STM8经常使用中断指令 开总中断 _asm("rim"); 禁止中断 _asm("sim"); 进入停机模式 _asm("halt"); ...
- 关于使用data()获取自定义属性出现undefined的说明
这应该是这个函数的一个bug,没有考虑到驼峰式的写法,当我写成驼峰式,即是有大小写的变量时就会出现没有定义的情况. 今天写个交互,需要用到自定义属性,因为这个自定义属性是当作字段用的,就直接用了字段名 ...
- Django打造大型企业官网(八)
4.16.侧边栏标题和广告位布局完成 templates/news/index.html <div class="sidebar-wrapper"> <div c ...
- 黑马day16 aptana插件的安装
aptana: eclipse或者myeclipse中的javaScript,html,css的代码提示功能非常差...因此我们选择了这个框架. aptana的安装步骤: 1.须要下载aptana的插 ...
- 通俗理解LDA主题模型
通俗理解LDA主题模型 0 前言 印象中,最開始听说"LDA"这个名词,是缘于rickjin在2013年3月写的一个LDA科普系列,叫LDA数学八卦,我当时一直想看来着,记得还打印 ...
- openstack horizon 学习(2) navigation、dashboard、panels
本章的主要内容是如何用horizon的navigation结构添加一个应用的面板. Horizon中提供了两种为应用添加panel的方法,一种是通过Pluggable Settings的方式,另一种是 ...