四、分离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这个内置的对象获取宿主上下文环境。使用此对象即可获取到程序传递过来的参数。依据预先准备好的参数即可动态生成实体类,具体程序实现细节请参照源码。实现效果如下:

程序源码

时间: 2024-10-10 14:43:04

四、分离T4引擎的相关文章

一、初识T4引擎

对于代码生成器我们并不陌生,在日常编码中这也是用的比较多的工具之一.一般代码生成器主要功能是生成公共或基础代码来减少编码人员的工作量,而一款优秀的代码生成器除了生产代码以外,同时兼具生成项目架构和基础模块的能力,让开发人员把关注的核心放在业务逻辑上,提高编码效率减轻工作量. 现在市面上使用最多的代码生成技术就是模板生成技术,在这里推荐一款比较优秀的模板式生成引擎T4(Text Template Transformation Toolkit),因为VisualStudio本身也是用的它.作为一款十

关系型数据库(四),引擎MyISAM和InnoDB

目录 1.MyISAM和InnoDB关于锁方面的区别是什么 2.MYSQL的两个常用存储引擎 3.MyISAM应用场景 4.InnoDB适合场景 四.引擎MyISAM和InnoDB 1.MyISAM和InnoDB关于锁方面的区别是什么 MyISAM默认用的是表级锁,不支持行级锁 InnoDB默认用的是行级锁,也支持表级锁 2.MYSQL的两个常用存储引擎 有两个常用存储引擎:MyISAM与InnoDB(MySQL默认的) MyISAM与InnoDB的区别: (1)事务处理方面: MyISAM强调

MySQL学习笔记(四):存储引擎的选择

一:几种常用存储引擎汇总表 二:如何选择 ? MyISAM:默认的MySQL插件式存储引擎.如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性.并发性要求不是很高,那么选择这个存储引擎是非常适合的.MyISAM是在Web.数据仓储和其他应用环境下最常使用的存储引擎之一. ? InnoDB:用于事务处理应用程序,支持外键.如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询以外,还包括很多的更新.删除操作,那么InnoDB存储引擎应

jsp基础学习(四)----jsp引擎工作原理

JSP引擎的工作原理 当一个JSP页面第一次被访问的时候,JSP引擎将执行以下步骤:      (1)将JSP页面翻译成一个Servlet,就是一个Java文件,同时也是一个完整的Java程序.(相当于是c语言程序的预处理,补充完整所有的代码)      (2)JSP引擎调用Java编译器对这个Servlet进行编译,得到可执行文件class.(和C语言程序的编译是一样的) (3)JSP引擎调用java虚拟机来解释执行class文件,生成向客户端发送的应答,然后发送给客户端.(jsp是调用jav

【Razor语法规则小手册....】

在经过长达半年的Windows开发后,Razor的一些语法有些生疏了.搜集些,再熟悉下.呵呵,甚是怀念以前做web 项目的时候,基于动软代码生成器自定义T4模板,后来vs2010后开始支持T4模板. 又是一顿.tt文件.最后动软也升级了T4模板.然后就呵呵呵了.但是vs2010出现   Razor引擎!!又是一套模板引擎.............更新迭代速度越来越快...... ASP.NET mvc 1-2 的View引擎就是基于T4模板引擎,标记为 :<##> (动软代码生成器确实是T4引

T4模板

T4,即4个T开头的英文字母组合:Text Template Transformation Toolkit. T4文本模板,即一种自定义规则的代码生成器.根据业务模型可生成任何形式的文本文件或供程序调用的字符串.(模型以适合于应用程序域的形式包含信息,并且可以在应用程序的生存期更改) VS本身只提供一套基于T4引擎的代码生成的执行环境,由下面程序集构成: Microsoft.VisualStudio.TextTemplating.10.0.dll Microsoft.VisualStudio.T

解决T4模板的程序集引用的五种方案

在众多.NET应用下的代码生成方案中,比如CodeDOM,BuildProvider, 我觉得T4是最好的一种.关于T4的基本概念和模板结果,可以参考我的文章<基于T4的代码生成方式>.如果要了解T4具体的应用,则可以参考我的文章<创建代码生成器可以很简单:如何通过T4模板生成代码?>(上篇)(下篇).如果你编写T4模板,你不得不面对一个问题——如何引用一个程序集?VS 2010采用了与VS2008不同的程序集引用的解析机制.本篇文章为你介绍在VS2010下5种不同的程序集引用的方

吴涛作品介绍-易语言和VOLCANO 3D游戏引擎

易语言 易语言是一个自主开发,适合国情,不同层次不同专业的人员易学易用的汉语编程语言.易语言降低了广大电脑用户编程的门槛,可以通过使用本语言极其快速地进入Windows程序编写的大门. VOLCANO 3D 游戏引擎 VOLCANO是一款MMORPG(大型多人在线角色扮演)3D网络游戏的开发引擎,用作支持用户快速并简单地开发具有真实游戏环境和丰富游戏玩点的游戏. 易语言 易语言是一个自主开发,适合国情,不同层次不同专业的人员易学易用的汉语编程语言.易语言降低了广大电脑用户编程的门槛,尤其是根本不

你必须懂的 T4 模板:深入浅出

示例代码:示例代码__你必须懂的T4模板:浅入深出.rar (一)什么是T4模板? T4,即4个T开头的英文字母组合:Text Template Transformation Toolkit. T4文本模板,即一种自定义规则的代码生成器.根据业务模型可生成任何形式的文本文件或供程序调用的字符串.(模型以适合于应用程序域的形式包含信息,并且可以在应用程序的生存期更改) VS本身只提供一套基于T4引擎的代码生成的执行环境,由下面程序集构成: Microsoft.VisualStudio.TextTe