T4模板学习心得

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了T4模板学习心得相关的知识,希望对你有一定的参考价值。


title: T4模板学习心得
tags: T4,学习心得,
---

    近来工作比较轻松,因此可以有时间学习一些感兴趣的是内容,一开始目标是研究dapper,简单用了一下之后,感觉是介于写sql和EF这种大型ORM框架之间的,然后就准备开始写一个demo,就发现一个个手写类好麻烦,就想到了T4模板,之前用过一些代码生成器,似乎都是基于这个实现的,好吧,那就先学习一下T4模板。

T4模板是什么

    它是一个工具,可以帮我们生成一些固定格式的文件。举个例子
    
using System;
using System.Collections.Generic;

namespace testNameSpace
{
    public partial class testClass1
    {
        public testClass1()
        {
        }
        public string name { get; set; }

        public int age { get; set; }

        public bool sex { get; set; }
    }
}

实体类基本都是这个结构,包括最顶上的引用,然后是命名空间,类名,构造函数,属性名,其实可以看到最上面的引用基本上是固定的,命名空间、类名、参数名和参数类型是变化的,但是结构是一致的,在DB first 的时候,我们是可以取到表名、参数名和参数类型的,那么我们就可以直接从数据库中读取到表结构然后生成实体类了!

基础语法

    [T4的官方说明文档][1]
    里面写的比较详细,这里就不重复了,按照我练习的思路来吧。

第一步生成固定文件

<#@ output extension=".cs" #>
using System;
using System.Collections.Generic;

namespace testNameSpace
{
    public partial class testClass1
    {
        public testClass1()
        {
        }
        public string name { get; set; }

        public int age { get; set; }

        public bool sex { get; set; }
    }
}

这个代码其实用到的语法就只有第一行,表示输出的文件的后缀名是.cs,然后内容就是后面的这些内容作为文本写到文件中,保存之后再路径下面就有一个类文件,内容完全同上面一致。

第二步动态生成文件

虽然已经生成了文件,但是完全没用,如果这种完全写好的,何必再重复一次呢?所以我们需要动态生成。

<#@ output extension=".cs" #>
<#@import namespace="System"#>
<#@import namespace="System.Collections.Generic"#>
<#@ template language="C#" hostspecific="True"#>  
using System;
using System.Collections.Generic;
<#  var model = GetModel(); #>

namespace <#= model.NameSpace #>
{
    public partial class <#= model.ClassName #>
    {
        public <#= model.ClassName #>()
        {
        }

        <# 
            foreach(var key in model.KeyDic.Keys)
            {
         #>
         public <#= model.KeyDic[key]#>  <#= key #>  {get;set;}

         <# } #>

    }
}
<#+ 
 private ModelClass GetModel()
{
    var model = new ModelClass()
    {
        NameSpace = "testNameSpace",
        ClassName ="testClass1",
        KeyDic = new Dictionary<string,string>()
        {
            {"name","string"},
            {"age","int"},
            {"sex","bool"},
        }
    };
    return model;
}


private class ModelClass
{
    public string NameSpace;
    public string ClassName;
    public Dictionary<string,string> KeyDic;
}
 #>

这段代码的结果和上面是一致的,但是我们是动态的对吧,现在是通过GetModel()里面实例化的,那么从数据库里面取结构也没什么区别。还是回来再说一下语法吧

<#@ template language="C#" hostspecific="True"#>  

这行代码是标记该目标用的语言是C#,默认就是这个,当然也可以用VB。

<#@import namespace="System"#>
<#@import namespace="System.Collections.Generic"#>

引用嘛,一看就知道,这个模板中的代码块需要引用。
<# #>中的就是代码块, 简单的C#代码, <#= #>这个表达式控制块,就是把结果输出到文本中,这个代码也是非常简单的,现在单个类搞定了,但是我们的数据库一般都不是单表啊,所以我们也是需要多个,多个的代码其实也是没有太多区别,无非就是GetModel 改成GetList而已

<#@ output extension=".cs" #>
<#@import namespace="System"#>
<#@import namespace="System.Collections.Generic"#>
<#@ template language="C#" hostspecific="True"#>  
using System;
using System.Collections.Generic;

<# 
   var list = GetList();
                foreach(var model in list)
            {
                

 #>

namespace <#= model.NameSpace #>
{
    public partial class <#= model.ClassName #>
    {
        public <#= model.ClassName #>()
        {
        }

        <# 
            foreach(var key in model.KeyDic.Keys)
            {
         #>
         public <#= model.KeyDic[key]#>  <#= key #>  {get;set;}

         <# } #>
    }
}

         <# } #>
        
<#+
private List<ModelClass> GetList()
{
    var list = new List<ModelClass>();
    var model = new ModelClass()
    {
        NameSpace = "testNameSpace",
        ClassName ="testClass1",
        KeyDic = new Dictionary<string,string>()
        {
            {"name","string"},
            {"age","int"},
            {"sex","bool"},
        }
    };

    list.Add(model);
     model = new ModelClass()
    {
        NameSpace = "testNameSpace",
        ClassName ="testClass2",
        KeyDic = new Dictionary<string,string>()
        {
            {"grade","string"},
            {"num","int"},
            {"count","bool"},
        }
    };

        list.Add(model);

    return list;
}

private class ModelClass
{
    public string NameSpace;
    public string ClassName;
    public Dictionary<string,string> KeyDic;
}

#>

只是多了一个list循环而已,跟上面的区别不大生成的结果如下


using System;
using System.Collections.Generic;


namespace testNameSpace
{
    public partial class testClass1
    {
        public testClass1()
        {
        }

        public string name { get; set; }

        public int age { get; set; }

        public bool sex { get; set; }

    }
}


namespace testNameSpace
{
    public partial class testClass2
    {
        public testClass2()
        {
        }
        public string grade { get; set; }

        public int num { get; set; }

        public bool count { get; set; }
    }
}

第三步生成多个文件

    虽然刚刚已经生成了多个类,但是并不完全是我们想要的结果,我们不可能把所有的类都写到一个文件里面,我们需要的结果是每个类都是一个单独的文件,但是T4在多个文件上的支持并不好,查了很多资料,几乎所有都是用的一个大牛的写的类,来完成这个功能
    
<#@ assembly name="System.Core"#>
<#@ assembly name="System.Data.Linq"#>
<#@ assembly name="EnvDTE"#>
<#@ assembly name="System.Xml"#>
<#@ assembly name="System.Xml.Linq"#>
<#@ import namespace="System"#>
<#@ import namespace="System.CodeDom"#>
<#@ import namespace="System.CodeDom.Compiler"#>
<#@ import namespace="System.Collections.Generic"#>
<#@ import namespace="System.Data.Linq"#>
<#@ import namespace="System.Data.Linq.Mapping"#>
<#@ import namespace="System.IO"#>
<#@ import namespace="System.Linq"#>
<#@ import namespace="System.Reflection"#>
<#@ import namespace="System.Text"#>
<#@ import namespace="System.Xml.Linq"#>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating"#>
<#+
 
// Manager class records the various blocks so it can split them up
class Manager {
    private class Block {
        public String Name;
        public int Start, Length;
    }
 
    private Block currentBlock;
    private List<Block> files = new List<Block>();
    private Block footer = new Block();
    private Block header = new Block();
    private ITextTemplatingEngineHost host;
    private StringBuilder template;
    protected List<String> generatedFileNames = new List<String>();
 
    public static Manager Create(ITextTemplatingEngineHost host, StringBuilder template) {
        return (host is IServiceProvider) ? new VSManager(host, template) : new Manager(host, template);
    }
 
    public void StartNewFile(String name) {
        if (name == null)
            throw new ArgumentNullException("name");
        CurrentBlock = new Block { Name = name };
    }
 
    public void StartFooter() {
        CurrentBlock = footer;
    }
 
    public void StartHeader() {
        CurrentBlock = header;
    }
 
    public void EndBlock() {
        if (CurrentBlock == null)
            return;
        CurrentBlock.Length = template.Length - CurrentBlock.Start;
        if (CurrentBlock != header && CurrentBlock != footer)
            files.Add(CurrentBlock);
        currentBlock = null;
    }
 
    public virtual void Process(bool split) {
        if (split) {
            EndBlock();
            String headerText = template.ToString(header.Start, header.Length);
            String footerText = template.ToString(footer.Start, footer.Length);
            String outputPath = Path.GetDirectoryName(host.TemplateFile);
            files.Reverse();
            foreach(Block block in files) {
                String fileName = Path.Combine(outputPath, block.Name);
                String content = headerText + template.ToString(block.Start, block.Length) + footerText;
                generatedFileNames.Add(fileName);
                CreateFile(fileName, content);
                template.Remove(block.Start, block.Length);
            }
        }
    }
 
    protected virtual void CreateFile(String fileName, String content) {
        if (IsFileContentDifferent(fileName, content))
            File.WriteAllText(fileName, content);
    }
 
    public virtual String GetCustomToolNamespace(String fileName) {
        return null;
    }
 
    public virtual String DefaultProjectNamespace {
        get { return null; }
    }
 
    protected bool IsFileContentDifferent(String fileName, String newContent) {
        return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent);
    }
 
    private Manager(ITextTemplatingEngineHost host, StringBuilder template) {
        this.host = host;
        this.template = template;
    }
 
    private Block CurrentBlock {
        get { return currentBlock; }
        set {
            if (CurrentBlock != null)
                EndBlock();
            if (value != null)
                value.Start = template.Length;
            currentBlock = value;
        }
    }
 
    private class VSManager: Manager {
        private EnvDTE.ProjectItem templateProjectItem;
        private EnvDTE.DTE dte;
        private Action<String> checkOutAction;
        private Action<IEnumerable<String>> projectSyncAction;
 
        public override String DefaultProjectNamespace {
            get {
                return templateProjectItem.ContainingProject.Properties.Item("DefaultNamespace").Value.ToString();
            }
        }
 
        public override String GetCustomToolNamespace(string fileName) {
            return dte.Solution.FindProjectItem(fileName).Properties.Item("CustomToolNamespace").Value.ToString();
        }
 
        public override void Process(bool split) {
            if (templateProjectItem.ProjectItems == null)
                return;
            base.Process(split);
            projectSyncAction.EndInvoke(projectSyncAction.BeginInvoke(generatedFileNames, null, null));
        }
 
        protected override void CreateFile(String fileName, String content) {
            if (IsFileContentDifferent(fileName, content)) {
                CheckoutFileIfRequired(fileName);
                File.WriteAllText(fileName, content);
            }
        }
 
        internal VSManager(ITextTemplatingEngineHost host, StringBuilder template)
            : base(host, template) {
            var hostServiceProvider = (IServiceProvider) host;
            if (hostServiceProvider == null)
                throw new ArgumentNullException("Could not obtain IServiceProvider");
            dte = (EnvDTE.DTE) hostServiceProvider.GetService(typeof(EnvDTE.DTE));
            if (dte == null)
                throw new ArgumentNullException("Could not obtain DTE from host");
            templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile);
            checkOutAction = (String fileName) => dte.SourceControl.CheckOutItem(fileName);
            projectSyncAction = (IEnumerable<String> keepFileNames) => ProjectSync(templateProjectItem, keepFileNames);
        }
 
        private static void ProjectSync(EnvDTE.ProjectItem templateProjectItem, IEnumerable<String> keepFileNames) {
            var keepFileNameSet = new HashSet<String>(keepFileNames);
            var projectFiles = new Dictionary<String, EnvDTE.ProjectItem>();
            var originalFilePrefix = Path.GetFileNameWithoutExtension(templateProjectItem.get_FileNames(0)) + ".";
            foreach(EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems)
                projectFiles.Add(projectItem.get_FileNames(0), projectItem);
 
            // Remove unused items from the project
            foreach(var pair in projectFiles)
                if (!keepFileNames.Contains(pair.Key) && !(Path.GetFileNameWithoutExtension(pair.Key) + ".").StartsWith(originalFilePrefix))
                    pair.Value.Delete();
 
            // Add missing files to the project
            foreach(String fileName in keepFileNameSet)
                if (!projectFiles.ContainsKey(fileName))
                    templateProjectItem.ProjectItems.AddFromFile(fileName);
        }
 
        private void CheckoutFileIfRequired(String fileName) {
            var sc = dte.SourceControl;
            if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName))
                checkOutAction.EndInvoke(checkOutAction.BeginInvoke(fileName, null, null));
        }
    }
} #>

新建一个manager.tt来保存这个类,然后把我们刚刚生成多个类的模板修改一下

<#@ output extension=".cs" #>
<#@import namespace="System"#>
<#@import namespace="System.Collections.Generic"#>
<#@ template language="C#" hostspecific="True"#>  
<#@include file="Manager.tt"#>  
<# var manager = Manager.Create(Host, GenerationEnvironment); #>  
<# 
   var list = GetList();
                foreach(var model in list)
            {
                manager.StartNewFile(model.ClassName + ".cs");

 #>
using System;
using System.Collections.Generic;

namespace <#= model.NameSpace #>
{
    public partial class <#= model.ClassName #>
    {
        public <#= model.ClassName #>()
        {
        }

        <# 
            foreach(var key in model.KeyDic.Keys)
            {
         #>
         public <#= model.KeyDic[key]#>  <#= key #>  {get;set;}

         <# } #>
    }
}
<# manager.EndBlock(); #>  
         <# } #>
         <# manager.Process(true); #>  
<#+
private List<ModelClass> GetList()
{
    var list = new List<ModelClass>();
    var model = new ModelClass()
    {
        NameSpace = "testNameSpace",
        ClassName ="testClass1",
        KeyDic = new Dictionary<string,string>()
        {
            {"name","string"},
            {"age","int"},
            {"sex","bool"},
        }
    };

    list.Add(model);
     model = new ModelClass()
    {
        NameSpace = "testNameSpace",
        ClassName ="testClass2",
        KeyDic = new Dictionary<string,string>()
        {
            {"grade","string"},
            {"num","int"},
            {"count","bool"},
        }
    };

        list.Add(model);

    return list;
}

private class ModelClass
{
    public string NameSpace;
    public string ClassName;
    public Dictionary<string,string> KeyDic;
}
#>

相比之前多了一个语法<#@include file="Manager.tt"#> 即引用别的模板文件,然后功能上也就比之前多了manager类的一下方法,manager.StartNewFile(model.ClassName + ".cs");,开始一个新的文件,<# manager.Process(true); #> 这一行代码才是分文件,一定要加上。

总结

    写到这里就结束了,因为在后面无非就是从数据库读表结构然后生成实体类,这样的代码有太多,而且跟这个也没有太大关系了,至于更多的功能性的也都可以做,单其实也都只是在这个基础上做加法,参考当年做三层的动软的代码生成器 bll dal model 层都可以自动生成,原理都是一样的。
    虽然我学习是以写实体类作为方向,但是T4模板的运用方向其实是很广的,并不仅仅是生成cs文件,txt、html、xml等等,都是可以的,具体的运用不同,方法却是大同小异。作为VS提供一个工具,在适用的场景下用一用,真的能省不少事。

以上是关于T4模板学习心得的主要内容,如果未能解决你的问题,请参考以下文章

T4模板之菜菜鸟篇

T4模板:T4模板之基础篇

T4模板之基础篇

T4模板之基础篇

T4 模板

使用 t4 模板生成动态代码