T4(Text Template Transformation Toolkit)在 Visual Studio 中,“T4 文本模板”是由一些文本块和控制逻辑组成的混合模板,它可以生成文本文件。 在 Visual C# 或 Visual Basic 中,控制逻辑编写为程序代码的片段。生成的文件可以是任何类型的文本,例如网页、资源文件或任何语言的程序源代码。
T4 文本模板有两种类型:
若要创建运行时模板,请向您的项目中添加“已预处理的文本模板”文件。
另外,您还可以添加纯文本文件并将其“自定义工具”属性设置为“TextTemplatingFilePreprocessor”。
2、设计时模板
在 Visual Studio 中执行设计时 T4 文本模板,以便定义应用程序的部分源代码和其他资源。
通常,您可以使用读取单个输入文件或数据库中的数据的多个模板,并生成一些 .cs、.vb 或其他源文件。
每个模板都生成一个文件。 在 Visual Studio 或 MSBuild 内执行它们。
若要创建设计时模板,请向您的项目中添加“文本模板”文件。 另外,您还可以添加纯文本文件并将其“自定义工具”属性设置为“TextTemplatingFileGenerator”。
二、T4例子--HelloWorld
最简单的HelloWorld,通过此例子,可以看到T4的基本结构
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".cs" #> <#@ import namespace="System" #> <#@ import namespace="System.Collections.Generic" #> using System; namespace Test { public class HelloWorld { public static void Main(string[] args) { <# List<Person> people = GetPersonList(); foreach(Person p in people) { #> Console.WriteLine("Hello {0},Welcome to T4 World!","<#= p.Name #>"); <#} #> } } } <#+ //类 public class Person { ///名称 public string Name{ get; set; } public Person(string name) { this.Name = name; } } //初始化众人 public List<Person> GetPersonList() { List<Person> people = new List<Person>(); Person p1 = new Person("Tom"); Person p2 = new Person("Jim"); Person p3 = new Person("Lucy"); people.Add(p1); people.Add(p2); people.Add(p3); return people; } #>
生成的代码:
using System; namespace Test { public class HelloWorld { public static void Main(string[] args) { Console.WriteLine("Hello {0},Welcome to T4 World!", "Tom"); Console.WriteLine("Hello {0},Welcome to T4 World!", "Jim"); Console.WriteLine("Hello {0},Welcome to T4 World!", "Lucy"); } } }
代码块的总体分类,就是两种:文本、程序脚本。
我感觉这样分类就够了,跟我们用c#写代码生成原理差不多。也是又C#代码和文本组成的。
文本:就是需要生成的文本
程序脚本:内部执行,最终生成想要的文本。T4中<# #>中的部分都属于程序脚本内容。
为了细分语法,方便大家更好的理解,用“块”(Block)来表示构成T4模板的基本单元,它们基本上可以分成5类:指令块(Directive Block)、文本块(Text Block)、代码语句块(Statement Block)、表达式块(Expression Block)和类特性块(Class Feature Block)。
1、指令块(Directive Block)
和ASP.NET页面的指令一样,它们出现在文件头,通过<#@…#>表示。其中<#@ template …#>指令是必须的,用于定义模板的基本属性,比如编程语言、基于的文化、是否支持调式等等。比较常用的指令还包括用于程序集引用的<#@ assembly…#>,用于导入命名空间的<#@ import…#>等等。
指令通常是模板文件或包含的文件中的第一个元素。不应将它们放置在代码块 <#...#> 内,也不应放置在类功能块 <#+...#> 之后。
T4 模板指令
<#@ template [language="VB"] [hostspecific="true"] [debug="true"] [inherits="templateBaseClass"] [culture="code"] [compilerOptions="options"] #>
T4 参数指令
<#@ parameter type="Full.TypeName"name="ParameterName"#>
T4 输出指令
<#@ output extension=".fileNameExtension"[encoding="encoding"] #>
T4 程序集指令
<#@ assembly name="[assembly strong name|assembly file name]"#>
T4 导入指令
<#@ import namespace="namespace"#>
T4 包含指令
<#@ include file="filePath"#>
2、文本块(Text Block)
文本块就是直接原样输出的静态文本,不需要添加任何的标签。
3、代码语句块(Statement Block)
代码语句块通过<#Statement#>的形式表示,中间是一段通过相应编程语言编写的程序调用,我们可以通过代码语句快控制文本转化的流程。在上面的代码中,我们通过代码语句块实现对一个数组进行遍历,输出重复的Console.WriteLine("Hello {0},Welcome to T4 World!","<#= p.Name #>");语句。
4、表达式块(Expression Block)
表达式块以<#=Expression#>的形式表示,通过它之际上动态的解析的字符串表达内嵌到输出的文本中。比如在上面的foreach循环中,每次迭代输出的人名就是通过表达式块的形式定义的(<#= p.Name #>)
5、类特性块(Class Feature Block)
如果文本转化需要一些比较复杂的逻辑,我们需要写在一个单独的辅助方法中,甚至是定义一些单独的类,我们就是将它们定义在类特性块中。类特性块的表现形式为<#+ FeatureCode #>,对于Hello World模板,得到人名列表的GetPersonList方法就定义在类特性块中。
一、具体步骤
1、新建项目名为:T4Sample,添加新项,选择“文本模板”即可创建设计时模板
模板属性,请确认:
自定义工具 = |
TextTemplatingFileGenerator |
生成操作 = |
无 |
打开模板,默认只有两行语句
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".txt" #>
2、修改模板,依然以HelloWorld为例
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".cs" #> <#@ import namespace="System" #> <#@ import namespace="System.Collections.Generic" #> using System; namespace Test { public class HelloWorld { public static void Main(string[] args) { <# List<Person> people = GetPersonList(); foreach(Person p in people) { #> Console.WriteLine("Hello {0},Welcome to T4 World!","<#= p.Name #>"); <#} #> } } } <#+ //类 public class Person { ///名称 public string Name{ get; set; } public Person(string name) { this.Name = name; } } //初始化众人 public List<Person> GetPersonList() { List<Person> people = new List<Person>(); Person p1 = new Person("Tom"); Person p2 = new Person("Jim"); Person p3 = new Person("Lucy"); people.Add(p1); people.Add(p2); people.Add(p3); return people; } #>
3、生成代码,可通过以下方式
生成代码如下:
using System; namespace Test { public class HelloWorld { public static void Main(string[] args) { Console.WriteLine("Hello {0},Welcome to T4 World!", "Tom"); Console.WriteLine("Hello {0},Welcome to T4 World!", "Jim"); Console.WriteLine("Hello {0},Welcome to T4 World!", "Lucy"); } } }
二、模板指令
首先了解最重要的指令块,在以后复杂的模板编写中,将会经常用到。
其实T4模板就是C#代码(当然还有VB)与文本的组合,一些逻辑代码就跟写C#代码一样。下一章将给出具体如何写T4模板。
C#代码与T4不同,就是引用、调试、输出等方面,而T4中最重要的指令Template ,就包含这些不同。
所以这里特别重点强调下指令Template,方便在以后复杂的T4模板编写中,能够得心应手。
使用 Template 指令
<#@ template [language="VB"] [hostspecific="true"] [debug="true"] [inherits="templateBaseClass"] [culture="code"] [compilerOptions="options"] #>
template 指令有多个特性,通过这些特性可以指定转换的不同方面。 所有特性都是可选的。
1、compilerOptions 特性
示例:
compilerOptions="optimize+"
有效值:
任何有效的编译器选项。
在模板已经转换为 Visual C# 或 Visual Basic 并且生成的代码已编译时会应用这些选项。
2、culture 特性
示例:
culture="de-CH"
有效值:
"",不变的区域性,它是默认值。
表示为 xx-XX 形式字符串的区域性。 例如:en-US、ja-JP、de-CH、de-DE。
Culture 特性指定将表达式块转换为文本时要使用的区域性。
3、debug 特性
示例:
debug="true"
有效值:
true, false. 默认值为 false。
debug 特性指定是否启用调试。 如果是 true,则中间代码文件将包含使调试器能够识别模板中中断或异常发生位置的信息。 对于
设计时模板,中间代码文件将写入您的 %TEMP% 目录。
若要在模板执行的特定点启动调试器,请插入对 Launch 的调用。 若要在后续的点处中断执行,请插入对 Break 的调用。
<#@ template debug="true" language="C#" #> <#@ output extension=".txt" #> Output something. <# // Break here: System.Diagnostics.Debugger.Launch(); #> Output more. <# // Break here also: System.Diagnostics.Debugger.Break(); #> Output more.
4、hostspecific 特性
有效值:
true, false. 默认值为 false。
如果将此特性的值设置为 true,则会将名为 Host 的属性添加到由文本模板生成的类中。 该属性是对转换引擎的宿主的引用,并声
明为 Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost。 如果已经定义了自定义宿主,则可以将其转换为自
定义宿主类型。
因为此属性的类型取决于宿主的类型,所以仅当编写只适用于特定宿主的文本模板时才有用。
当 hostspecific 为 true,而且正在使用 Visual Studio 时,可以将 this.Host 强制转换为 IServiceProvider,以访问 Visual
Studio 功能。 还可以使用 Host.ResolvePath(filename) 来获得项目中文件的绝对路径。
5、language 特性
示例:
language="VB"
有效值:
C#(默认值)
VB
(值 VBv3.5 和 C#v3.5 在此版本中已过时,但是会解释为 VB 和 C#。)
language 特性指定要用于语句和表达式块中的源代码的语言(Visual Basic 或 Visual C#)。 从中生成输出的中间代码文件将使
用此语言。 此语言与您的模板生成的语言无关,它可以是任何类型的文本。
6、inherits 特性
可以指定模板的程序代码可以继承自另一个类,这个类也可以从文本模板生成。
如何快速高效的写出高质量的T4模板呢?
一、总结:先验证C#代码,然后转T4模板!
通过实践,总结如下:先验证C#代码,然后转T4模板!
因为T4模板难以调试,以后会就调试,专门拿一张来介绍。T4模板编写的实质就是脚本代码和文本。脚本代码通常就是C#和VB.Net。主要逻辑都在脚本代码中。而C#代码调试要简单方便的多。所以,在编写T4模板的时候,我们完全,可以先写C#代码,然后再修改为T4模板。需要修改的地方,只是将C#放在<##>标记中,一些引用需要修改,而模板还有个好处,就是不用定义类型,直接添加默认函数或者属性
本文将结合实际例子,来进行说明。
二、实例:代码生成器
该实例,就是我们通常见过的,模型代码生成器,根据数据模型生成代码框架,如模型层、数据访问层等。
本实例先根据数据表,生存模型层代码。这是一个简单实例,如果有功夫,您可以根据这个原理写出自己的代码生成框架。
三、具体步骤:
1、预置数据
--创建库 CREATE DATABASE TestDB --创建表 CREATE TABLE PersonInfo ( ID INT PRIMARY KEY NOT NULL, NAME nvarchar(50) NOT NULL, Company NVARCHAR(50) NULL, Job NVARCHAR(20) NULL )
2、写C#代码,并验证通过。可用Console程序验证
模型管理器:主要实现,数据访问、获取表结构
using System; using System.Data.SqlClient; using System.Data; namespace T4_3FiledGenerator { public class ModelManager { /// <summary> /// 数据库连接字符串 /// </summary> private const string CONNECTION_STRING= "server=localhost;database=testDB;uid=sa;pwd=ufsoft;"; /// <summary> /// 用户信息表名 /// </summary> private const string PERSONINFO_TABLE_NAME = "PersonInfo"; /// <summary> /// 根据表名查询表结构信息 /// </summary> private const string SELECT_SCHEMA_BY_TABLE_NAME = @"SELECT TABLE_NAME AS TableName , ORDINAL_POSITION AS ColumnID , COLUMN_NAME AS ColumnName , DATA_TYPE AS DataType , CASE WHEN IS_NULLABLE = 'NO' THEN 'false' ELSE 'true' END AS IsNullable FROM INFORMATION_SCHEMA.COLUMNS AS A LEFT JOIN sysobjects AS B ON A.TABLE_NAME = B.name WHERE A.TABLE_NAME = '{0}'"; /// <summary> /// 获得数据连接 /// </summary> /// <returns></returns> private SqlConnection GetConnection() { return new SqlConnection(CONNECTION_STRING); } /// <summary> /// 释放连接 /// </summary> /// <param name="con"></param> private void ReleaseConnection(SqlConnection con) { if (con != null) { if (con.State == ConnectionState.Open) { con.Close(); } } } /// <summary> /// /// </summary> /// <param name="tableName"></param> public DataTable GetTableSchema(string tableName) { DataTable dt; using (SqlConnection con = GetConnection()) { con.Open(); SqlCommand cmd = con.CreateCommand(); cmd.CommandText = string.Format(SELECT_SCHEMA_BY_TABLE_NAME,tableName); cmd.CommandType = CommandType.Text; SqlDataAdapter adapter = new SqlDataAdapter(cmd); DataSet ds = new DataSet(); adapter.Fill(ds); dt = ds.Tables[0]; } return dt; } /// <summary> /// /// </summary> public void Generate() { DataTable table = GetTableSchema(PERSONINFO_TABLE_NAME); if (table != null && table.Rows.Count > 0) { foreach (DataRow row in table.Rows) { Console.WriteLine("public class {0}", row["TableName"]); Console.WriteLine("public {0} {1}", TransFromSqlType(row["DataType"].ToString()), row["ColumnName"]); } } } /// <summary> /// SQL /// </summary> /// <param name="type"></param> /// <returns></returns> public string TransFromSqlType(string type) { if (string.IsNullOrEmpty(type)) { return string.Empty; } if (string.Equals(type, "int", StringComparison.OrdinalIgnoreCase)) { return "int"; } else if (string.Equals(type, "nvarchar", StringComparison.OrdinalIgnoreCase)) { return "string"; } return "string"; } } }
测试程序:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace T4_3FiledGenerator { class Program { static void Main(string[] args) { ModelManager manager = new ModelManager(); manager.Generate(); } } }
3、C#代码转换成T4
3.1 添加引用,在C#中一般都是直接用using,在这里,需要改为import方式,如:<#@ import namespace="System.Data" #>
如果有些dll,需要添加引用,则需要assembly,如:<#@ assembly name="System.Data" #>。
对于程序集的加载,通常有两种情况,
(1)如果C#自带的,则直接用dll的名称即可,
(2)如果实自定义DLL文件,需要添加全路径,这是最简单的方法,还有其他一些方法,这里暂且不讨论。
3.2 将写成的C#代码,放在<#+ #>标签中,一般放置在T4模板最后
3.3 调用C#的方法,初始化变量
3.4 编写T4文本模板
3.5 对模板中的一些变量,调用2.3初始化的变量进行赋值
具体的T4代码如下:
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".cs" #> <#@ assembly name="System.Data" #> <#@ assembly name="System.Xml" #> <#@ import namespace="System" #> <#@ import namespace="System.Xml" #> <#@ import namespace="System.Data" #> <#@ import namespace="System.Data.SqlClient" #> <# ModelManager manager = new ModelManager(); string tableName = "PersonInfo"; DataTable table= manager.GetTableSchema(tableName); #> using System; namespace Model { public class <#= tableName #> { <# foreach(DataRow row in table.Rows) { #> public <#= manager.TransFromSqlType(row["DataType"].ToString())#> <#=row["ColumnName"]#>{ get; set; } <#} #> } } <#+ public class ModelManager { /// <summary> /// 数据库连接字符串 /// </summary> private const string CONNECTION_STRING= "server=localhost;database=testDB;uid=sa;pwd=ufsoft;"; /// <summary> /// 用户信息表名 /// </summary> private const string PERSONINFO_TABLE_NAME = "PersonInfo"; /// <summary> /// 根据表名查询表结构信息 /// </summary> private const string SELECT_SCHEMA_BY_TABLE_NAME = @"SELECT TABLE_NAME AS TableName , ORDINAL_POSITION AS ColumnID , COLUMN_NAME AS ColumnName , DATA_TYPE AS DataType , CASE WHEN IS_NULLABLE = 'NO' THEN 'false' ELSE 'true' END AS IsNullable FROM INFORMATION_SCHEMA.COLUMNS AS A LEFT JOIN sysobjects AS B ON A.TABLE_NAME = B.name WHERE A.TABLE_NAME = '{0}'"; /// <summary> /// 获得数据连接 /// </summary> /// <returns></returns> private SqlConnection GetConnection() { return new SqlConnection(CONNECTION_STRING); } /// <summary> /// 释放连接 /// </summary> /// <param name="con"></param> private void ReleaseConnection(SqlConnection con) { if (con != null) { if (con.State == ConnectionState.Open) { con.Close(); } } } /// <summary> /// /// </summary> /// <param name="tableName"></param> public DataTable GetTableSchema(string tableName) { DataTable dt; using (SqlConnection con = GetConnection()) { con.Open(); SqlCommand cmd = con.CreateCommand(); cmd.CommandText = string.Format(SELECT_SCHEMA_BY_TABLE_NAME,tableName); cmd.CommandType = CommandType.Text; SqlDataAdapter adapter = new SqlDataAdapter(cmd); DataSet ds = new DataSet(); adapter.Fill(ds); dt = ds.Tables[0]; } return dt; } /// <summary> /// /// </summary> public void Generate() { DataTable table = GetTableSchema(PERSONINFO_TABLE_NAME); if (table != null && table.Rows.Count > 0) { foreach (DataRow row in table.Rows) { Console.WriteLine("public class {0}", row["TableName"]); Console.WriteLine("public {0} {1}", TransFromSqlType(row["DataType"].ToString()), row["ColumnName"]); } } } /// <summary> /// SQL /// </summary> /// <param name="type"></param> /// <returns></returns> public string TransFromSqlType(string type) { if (string.IsNullOrEmpty(type)) { return string.Empty; } if (string.Equals(type, "int", StringComparison.OrdinalIgnoreCase)) { return "int"; } else if (string.Equals(type, "nvarchar", StringComparison.OrdinalIgnoreCase)) { return "string"; } return "string"; } } #>
4、生成代码并调试修改。
其实转过来时,出现错误的几率已经很少,比较难调的就是某些组件的引用。
5、结果,T4模板生成的代码
using System; namespace Model { public class PersonInfo { public int ID { get; set; } public string NAME { get; set; } public string Company { get; set; } public string Job { get; set; } } }
使用 Visual Studio 预处理过的文本模板,可以在运行时在应用程序中生成文本字符串。 执行应用程序的计算机不必具有 Visual Studio。 预处理过的模板有时称为“运行时文本模板”。每个模板都包含将显示在生成的字符串中的文本和程序代码的片段。 程序片段为字符串的可变部分提供值,还控制条件部分和重复部分。
创建运行时文本模板
一、具体步骤:
1、在解决方案资源管理器中,右击项目,指向“添加”,再单击“新建项”。
2、在“添加新项”对话框中,选择“预处理文本模板”。 (在 Visual Basic 中的“常用项\常规”下查看。)
3、键入模板文件的名称,如:MyWebPage
4、单击“添加”。
将创建一个扩展名为 .tt 的新文件。 该文件的“自定义工具”属性设置为 TextTemplatingFilePreprocessor。与之前讲到的设计时模板不同。
设计时模板: TextTemplatingFileGenerator
运行时模板:TextTemplatingFilePreprocessor
模板转换:
当然设计时模板可以转为运行时模板,只要将该文件的“自定义工具”属性设置为 TextTemplatingFilePreprocessor即可。
但是运行时模板很多情况下不能转为设计时模板。后面的内容会帮我们揭开缘由。
二、实例--显示课程网页
在新建模板的同时,生成了模板的部分类。
一个是以模板名+code结尾:MyWebPageCode.cs,在这里我们可以声明模板需要的变量、方法。
这里我们需要设置课程集合。
public partial class MyWebPage { private List<string> items; public List<string> Items { get { return items; } set { items = value; } } public MyWebPage(List<string> data) { this.items = data; } }
另一个请展开 .tt 文件节点,此附属文件包含一个分部类,该类包含一个名为 TransformText() 的方法。这个方法中的内容会根据模板内容的变化而变化,其实就是模板代码转为C#代码。此方法可以从应用程序中调用。
修改模板:
<#@ template language="C#" #> <html> <body> <h1>计算机课程</h2> <table> <# foreach(string item in Items) { #> <tr><td>Course name : <#= item #> </td></tr> <# } #> </table> </body> </html>
查看附属文件的部分类,方法:TransformText()
public virtual string TransformText() { this.GenerationEnvironment = null; this.Write("<html>\r\n<body>\r\n<h1>计算机课程</h2>\r\n<table>\r\n "); #line 6 "D:\Code\C#\UFIDA.U8.UAP.EntityGenerator\PreTemplateTest\MyWebPage.tt" foreach(string item in Items) { #line default #line hidden this.Write(" <tr><td>Course name : "); #line 8 "D:\Code\C#\UFIDA.U8.UAP.EntityGenerator\PreTemplateTest\MyWebPage.tt" this.Write(this.ToStringHelper.ToStringWithCulture(item)); #line default #line hidden this.Write(" </td></tr>\r\n "); #line 9 "D:\Code\C#\UFIDA.U8.UAP.EntityGenerator\PreTemplateTest\MyWebPage.tt" } #line default #line hidden this.Write(" </table>\r\n </body>\r\n </html>"); return this.GenerationEnvironment.ToString(); }
程序调用,对模板变量赋值,并调用模板,输出文件。
static void Main(string[] args) { List<string> items = new List<string>(); items.Add("计算机基础"); items.Add("数据库"); items.Add("计算机网络"); MyWebPage page = new MyWebPage(items); String pageContent = page.TransformText(); //如果有汉字,需要设置编码格式 System.IO.File.WriteAllText("outputPage.html", pageContent,Encoding.UTF8); }
运行程序,生成模板:
生成的HTML
<html><body> <h1>计算机课程</h2> <table> <tr><td>Course name : 计算机基础 </td></tr> <tr><td>Course name : 数据库 </td></tr> <tr><td>Course name : 计算机网络 </td></tr> </table> </body></html>
浏览器打开效果:
在运行时生成文本,若要在特定命名空间中放置已生成的类,请设置文本模板文件的“自定义工具命名空间”属性。
三、基本原理
由上述实例,我们已经基本了解了运行时模板的基本步骤,现在总结一下运行时模板的基本原理。
我们通常也通过C#输出文件,可能也要按照一定的格式来生成。其实这里的运行时模板,就是这样一个工具,设定好模板后,然后VS自动转为C#代码,在部分类中TransformText()方法,我们可以找到踪迹。
运行时模板,简单的说就是可视化的C#代码生成器。
运行时模板,提供了可视化功能,将夹杂在C#代码中的模板代码拿出来,让我们更加直观的看到模板的真实样子。方便进行修改和维护。
运行时模板就好比C#代码,比起设计时模板,方便跟踪调试。
四、源码下载
点击下载
原文http://www.cnblogs.com/yank/archive/2012/02/28/2352507.html