四、分离T4引擎
在前几篇文章中,我使用大量的篇幅来介绍T4在VisualStudio中如何使用。虽然在一定程度上可以提高我们的工作效率,但并没有实质上的改变。不过从另一方面来说,我们确实了解到了T4的强大。如何让这个强大的工具为我们所用呢?本章将讲解如何在自己的程序中使用T4。在原来的解决方案中新建一个窗体项目T4Generator。T4引擎被封装在了:
Microsoft.VisualStudio.TextTemplating.10.0.dll
Microsoft.VisualStudio.TextTemplating.Interfaces.10.0.dll
这两个dll文件中,具体位置在C:\Windows\Microsoft.NET\assembly\GAC_MSIL这个路径下找到和Microsoft.VisualStudio.TextTemplating相关的文件夹即可。这里需要注意的文件末尾的10.0是版本号。可以根据自己的VS版本选择相应的版本号,当然哪一个版本都无所谓。

我这里有10.0和12.0两个版本,为了协调我使用了10.0的版本。因为12.0版本对应的Interfaces文件版本号是11.0看着觉得变扭。将上述两个文件添加引用到自己的项目中。
TT模板的执行需要一个宿主环境,Microsoft.VisualStudio.TextTemplating.10.0.dll提供了模板运行的环境也即引擎。TT模板和宿主环境之间怎样进行衔接?比如传递参数,这就需要一个下上文环境。Microsoft.VisualStudio.TextTemplating.Interfaces.10.0.dll接口则定义了上下文环境。我们需要做的就是实现该接口。用F12跟踪源码可见该接口定义如下:

内容挺多,但是没任何注解,这也是VS类库的最让人心碎的地方。怎么实现该接口?如果不知道具体方法的含义估计要实现该接口近乎是沮丧的。好在MSDN上资料比较齐全,在MSDN中查看该接口时提供了一个自定义模板宿主的案例。这里说明下:我理解的宿主就是指引擎本身,这个接口我理解成上下文环境。如果仅仅通过字面意思理解,可能和我说的不一样,这仅仅是理解不同,实质不冲突,我也是为了方便讲解。
MSDN案例地址:https://msdn.microsoft.com/en-us/library/bb126579.aspx
依据MSDN的案例,基本已经了解该接口实现的细节了。故此可以依葫芦画瓢打造自己的上下文环境了。在实现该接口之前还需要了解有关参数传递的方式。因为是自定义程序,提取表结构和响应用户操作全是由程序完成,模板如何接受程序传递的参数?

如图所示,主程序可以直接通过参数传递方式传递给宿主,在模版中可以获取宿主上下文对象,从而间接拿到主程序传递的参数。
这里继续使用上一次的需求做一个实体生成器。首先打开主窗体,界面布局如下:

然后需要创建两个类文件用于封装需要传递给模板的数据。代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace T4Generator
{
/// <summary>
/// 表结构信息
/// </summary>
[Serializable]
public class SchemaInfo
{
/// <summary>
/// 列描述信息
/// </summary>
public string ColumnDesc { get; set; } /// <summary>
/// 列数据类型
/// </summary>
public string ColumnType { get; set; } /// <summary>
/// 列名称
/// </summary>
public string ColumnName { get; set; }
}
}
该类用于描述表结构信息用的。在上一篇的讲解中表结构使用DataSet封装,由于DataSet需要涉及到的命名空间比较多,在模板里操作不是很方便,这里就直接改用自定义类来封装数据了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace T4Generator
{
/// <summary>
/// 参数对象
/// </summary>
[Serializable]
public class HostParam
{
/// <summary>
/// 数据表名称
/// </summary>
public string TableName { get; set; } /// <summary>
/// 命名空间
/// </summary>
public string NameSpace { get; set; } /// <summary>
/// 数据表列集合
/// </summary>
public List<SchemaInfo> ColumnList { get; set; }
}
}
此类用于封装一些额外数据,以便在模版中调用。需要注意的这两个作为封装数据的类一定要声明可序列化,否则执行时会出错。只要涉及在宿主环境或模板中使用的类都必须可序列化。
接下来最重要的工作就是实现接口,具体代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.CodeDom.Compiler;
using Microsoft.VisualStudio.TextTemplating; namespace T4Generator
{
/// <summary>
/// 模版宿主
/// </summary>
[Serializable]
public class TemplateHost : ITextTemplatingEngineHost
{
#region 字段
private CompilerErrorCollection _ErrorCollection;
private Encoding _fileEncodingValue = Encoding.UTF8;
private string _fileExtensionValue = ".cs";
private string _namespace = "T4Generator";
internal string _templateFileValue;
#endregion #region 属性
/// <summary>
/// 编译错误对象集合
/// </summary>
public CompilerErrorCollection ErrorCollection
{
get { return this._ErrorCollection; }
} /// <summary>
/// 文件编码方式
/// </summary>
public Encoding FileEncoding
{
get { return this._fileEncodingValue; }
} /// <summary>
/// 文件扩展名
/// </summary>
public string FileExtension
{
get { return this._fileExtensionValue; }
} /// <summary>
/// 宿主所在命名空间
/// </summary>
public string NameSpace
{
get { return this._namespace; }
set { this._namespace = value; }
} /// <summary>
/// 模版需调用的其他程序集引用
/// </summary>
public IList<string> StandardAssemblyReferences
{
get
{
return new string[] {
typeof(Uri).Assembly.Location,
typeof(HostParam).Assembly.Location,
typeof(SchemaInfo).Assembly.Location,
typeof(TemplateHost).Assembly.Location
};
}
} /// <summary>
/// 模版调用标准程序集引用
/// </summary>
public IList<string> StandardImports
{
get
{
return new string[] {
"System",
"System.Text",
"System.Collections.Generic",
"T4Generator"
};
}
} /// <summary>
/// 模版文件
/// </summary>
public string TemplateFile
{
get { return this._templateFileValue; }
set { this._templateFileValue = value; }
} /// <summary>
/// 自定义参数对象用于向模板传参
/// </summary>
public HostParam Param { get; set; }
#endregion #region 方法
public object GetHostOption(string optionName)
{
string str;
return (((str = optionName) != null) && (str == "CacheAssemblies"));
} public bool LoadIncludeText(string requestFileName, out string content, out string location)
{
content = string.Empty;
location = string.Empty;
if (File.Exists(requestFileName))
{
content = File.ReadAllText(requestFileName);
return true;
}
return false;
} public void LogErrors(CompilerErrorCollection errors)
{
this._ErrorCollection = errors;
} public AppDomain ProvideTemplatingAppDomain(string content)
{
return AppDomain.CreateDomain("Generation App Domain");
} public string ResolveAssemblyReference(string assemblyReference)
{
if (File.Exists(assemblyReference))
{
return assemblyReference;
}
string path = Path.Combine(Path.GetDirectoryName(this.TemplateFile), assemblyReference);
if (File.Exists(path))
{
return path;
}
return "";
} public Type ResolveDirectiveProcessor(string processorName)
{
string.Compare(processorName, "XYZ", StringComparison.OrdinalIgnoreCase);
throw new Exception("没有找到指令处理器");
} public string ResolveParameterValue(string directiveId, string processorName, string parameterName)
{
if (directiveId == null)
{
throw new ArgumentNullException("the directiveId cannot be null");
}
if (processorName == null)
{
throw new ArgumentNullException("the processorName cannot be null");
}
if (parameterName == null)
{
throw new ArgumentNullException("the parameterName cannot be null");
}
return string.Empty;
} public string ResolvePath(string fileName)
{
if (fileName == null)
{
throw new ArgumentNullException("the file name cannot be null");
}
if (!File.Exists(fileName))
{
string path = Path.Combine(Path.GetDirectoryName(this.TemplateFile), fileName);
if (File.Exists(path))
{
return path;
}
}
return fileName;
} public void SetFileExtension(string extension)
{
this._fileExtensionValue = extension;
} public void SetOutputEncoding(Encoding encoding, bool fromOutputDirective)
{
this._fileEncodingValue = encoding;
}
#endregion
}
}
该接口实现基本可以参照MSDN给的方案即可,如果在模板中需要调用程序中定义的类,那么需要把该类位于程序集的位置注入到宿主环境
public IList<string> StandardAssemblyReferences
{
get
{
return new string[] {
typeof(Uri).Assembly.Location,
typeof(HostParam).Assembly.Location,
typeof(SchemaInfo).Assembly.Location,
typeof(TemplateHost).Assembly.Location
};
}
}
该属性的实现就是把程序中自定义的3个类:HostParam、SchemaInfo、TemplateHost本身所在程序集位置注入到宿主环境中。
public IList<string> StandardImports
{
get
{
return new string[] {
"System",
"System.Text",
"System.Collections.Generic",
"T4Generator"
};
}
}
这里就是把模板需要用的命名空间注入到宿主环境中。依据前面所述,模板中是可以拿到这个上下文对象的,故此我们把需要传递的参数也可以一并定义在该类中。
public HostParam Param { get; set; }
所以这里定义了一个属性用于接受程序传递的参数。
接下来只要在生成按钮的事件下调用即可,代码如下:
//定义引擎对象
private Engine engine; //Microsoft.VisualStudio.TextTemplating命名空间下 public FrmMain()
{
InitializeComponent();
this.engine = new Engine();
} private void btnGenerate_Click(object sender, EventArgs e)
{
string connString = string.Format("Data Source={0};Database={1};uid={2};pwd={3}", txtServer.Text, txtDB.Text, txtUser.Text, txtPwd.Text); //创建参数对象
HostParam param = new HostParam();
param.TableName = this.txtTableName.Text;
param.NameSpace = this.txtNameSpace.Text;
param.ColumnList = DBHelper.GetSchema(connString, param.TableName); //模板文件
string templateFile = "Entity.txt";
string content = File.ReadAllText(templateFile); //创建宿主
TemplateHost host = new TemplateHost
{
TemplateFile = templateFile,
Param = param
}; //生成代码
this.txtCode.Text = engine.ProcessTemplate(content, host);
}
最后需要说明就是,在自定义程序中模板文件只要是文本文件即可,这里直接用了Entity.txt来作为模板文件。文件内容如下:
<#@ template language="c#" HostSpecific="True" #>
<#@ output extension= ".cs" #>
<#
//获取宿主对象
TemplateHost host = Host as TemplateHost;
#>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace <#=host.Param.NameSpace #>
{
public class <#= host.Param.TableName #>Entity
{
<#
foreach(SchemaInfo info in host.Param.ColumnList)
{
#>
/// <summary>
/// <#= info.ColumnDesc #>
/// </summary>
public <#= info.ColumnType #> <#= info.ColumnName #> { get; set; } <# } #>
}
}
相比之前的TT模板简化了很多。当然功能是一样的。
<#@ template language="c#" HostSpecific="True" #>首先使用了@ template 指令指明模板宿主已变更。
其次可以直接使用Host这个内置的对象获取宿主上下文环境。使用此对象即可获取到程序传递过来的参数。依据预先准备好的参数即可动态生成实体类,具体程序实现细节请参照源码。实现效果如下:

四、分离T4引擎的更多相关文章
- 【关于微软的上一代模板引擎 T4引擎】
导语:国内有名的动软代码生成器用的就是T4引擎......可以自己下载下来用用,批量生成固定模式的代码文件,十分有用........... 示例代码:示例代码__你必须懂的T4模板:浅入深出.rar ...
- 使用Photon引擎进行unity网络游戏开发(四)——Photon引擎实现网络游戏逻辑
使用Photon引擎进行unity网络游戏开发(四)--Photon引擎实现网络游戏逻辑 Photon PUN Unity 网络游戏开发 网络游戏逻辑处理与MasterClient 网络游戏逻辑处理: ...
- 一、初识T4引擎
对于代码生成器我们并不陌生,在日常编码中这也是用的比较多的工具之一.一般代码生成器主要功能是生成公共或基础代码来减少编码人员的工作量,而一款优秀的代码生成器除了生产代码以外,同时兼具生成项目架构和基础 ...
- Spring Boot (四)模板引擎Thymeleaf集成
一.Thymeleaf介绍 Thymeleaf是一种Java XML / XHTML / HTML5模板引擎,可以在Web和非Web环境中使用.它更适合在基于MVC的Web应用程序的视图层提供XHTM ...
- mysql中四种存储引擎的区别和选择
前言 数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建.查询.更新和删除数据.不同的存储引擎提供不同的存储机制.索引技巧.锁定水平等功能,使用不同的存储引擎,还可以 ...
- 关系型数据库(四),引擎MyISAM和InnoDB
目录 1.MyISAM和InnoDB关于锁方面的区别是什么 2.MYSQL的两个常用存储引擎 3.MyISAM应用场景 4.InnoDB适合场景 四.引擎MyISAM和InnoDB 1.MyISAM和 ...
- 【MySQL】MySQL(四)存储引擎、索引、锁、集群
MySQL存储引擎 MySQL体系结构 体系结构的概念 任何一套系统当中,每个部件都能起到一定的作用! MySQL的体系结构 体系结构详解 客户端连接 支持接口:支持的客户端连接,例如C.Java.P ...
- MySQL学习笔记(四):存储引擎的选择
一:几种常用存储引擎汇总表 二:如何选择 一句话:除非需要InnoDB 不具备的特性,并且没有其他办法替代,否则都应该优先考虑InnoDB:或者,不需要InnoDB的特性,并且其他的引擎更加合适当前情 ...
- hadoop与spark的处理技巧(四)推荐引擎处理技巧
经常一起购买的商品 scala> var file=sc.textFile("/user/ghj/togeterBought") file: org.apache.spark ...
随机推荐
- Java JPA 查询实体部分字段
前言 相信大家在用Java JPA作为ORM的时候都会有这种困惑,就是某个表T我仅仅希望取到其中的A.B.C三个字段,可是jpa是通过Entity Class映射的方式组合查询结果的. 那么如何通过使 ...
- [转]Aggregate tasks i Sharepoint 2013
from http://sharepoint247.com/mysite/aggregate-tasks-i-sharepoint-2013/ Aggregate tasks i Sharepoint ...
- android 如何进入某个具体的应用管理页面
http://stackoverflow.com/questions/4421527/start-android-application-info-screen/4772481#4772481 pri ...
- Strust2的json插件
以下这段摘自网上: Json是一种轻量级的数据交换格式,JSon插件提供了一种名为json的ActionResultType .一旦为Action指定了该结果处理类型,JSON插件就会自动将Actio ...
- java学习之函数
讲完了语句结构还有运算符.变量,下面我们来了解下函数. 那么什么是函数,函数的定义是怎样的呢? 函数的定义: 函数是指在类当中定义的一段有特殊功能的代码段,同时函数在类中也被成为方法. class F ...
- Microsoft Office 2013 激活方法
Microsoft Office 2013 激活方法 Microsoft Office 2013是微软的新一代Office办公软件,全面采用Metro界面,包括Word.PowerPoint.Ex ...
- 南桥先生谈《OUTLIERS》
借来一套语音版的 Outliers 听完了.这本书里有很多故事,可是希望借此找到成功的奥秘恐怕很难,作者做的是一描述而不是预见.听了半天,只听出了六个字: “天时地利人和”. 比如比尔·盖茨,他之所以 ...
- 浅谈JavaScript函数
JavaScript作为一种基于对象(非严格面向对象)的语言,函数在JS中的地位非同一般:用函数声明类和对象.甚至函数本身也是对象. 一.函数的三种声明方式辨析. 1.function命令 funct ...
- codevs3945 完美拓印
3945 完美拓印 codevs月赛 第一场 时间限制: 1 s 空间限制: 256000 KB 题目等级 : 黄金 Gold 题目描述 Description 小Q获得了一个神奇的印章,这个印章宽n ...
- 敏捷开发 and 敏捷测试
名词解释 agile: 敏捷的:灵活:敏捷开发. scrum: 扭打,混打:并列争球:参加并列争球. sprint: 冲刺,全速跑. backlog: 积压的工作:积压待办的事务. retrospe ...