首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

Asp .Net Core 系列:基于 T4 模板生成代码

编程知识
2024年07月16日 22:20

目录

简介

T4模板,即Text Template Transformation Toolkit,是微软官方在Visual Studio中引入的一种代码生成引擎。自Visual Studio 2008开始,T4模板就被广泛应用于生成各种类型的文本文件,包括网页、资源文件以及各种编程语言的源代码等。

T4模板是一种由文本块和控制逻辑组成的混合模板,它可以根据预设的规则和输入数据生成目标文本文件。

官网:https://learn.microsoft.com/zh-cn/visualstudio/modeling/code-generation-and-t4-text-templates?view=vs-2022

组成部分

T4模板主要由以下几部分组成:

  1. 指令块:向文本模板化引擎提供关于如何生成转换代码和输出文件的一般指令。常见的指令包括<#@ template #><#@ parameter #><#@ assembly #><#@ import #><#@ include #><#@ output #>等。

    • 模板指令<#@ template #>):定义模板的基本属性,如使用的编程语言、是否开启调试模式等。
    • 参数指令<#@ parameter #>):声明模板代码中从外部上下文传入的值初始化的属性。
    • 程序集指令<#@ assembly #>):引用外部程序集,以便在模板中使用其中的类型和方法。
    • 导入指令<#@ import #>):允许在模板中引用其他命名空间中的类型,类似于C#中的using指令或Visual Basic中的Imports指令。
    • 包含指令<#@ include #>):在模板中包含另一个文件的内容,通常用于共享常用的代码片段或模板设置。
    • 输出指令<#@ output #>):定义输出文件的扩展名和编码方式。
  2. 文本块:直接复制到输出文件的内容,不会进行任何处理或转换。

  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 #>

分类

  1. 设计时模板(文本模版)

    在 Visual Studio 中执行设计时 T4 文本模板,以便定义应用程序的部分源代码和其他资源。通常,您可以使用读取单个输入文件或数据库中的数据的多个模板,并生成一些 .cs、.vb 或其他源文件。每个模板都生成一个文件。 在 Visual Studio 或 MSBuild 内执行它们。若要创建设计时模板,请向您的项目中添加“文本模板”文件。 另外,您还可以添加纯文本文件并将其“自定义工具”属性设置为“TextTemplatingFileGenerator”。

  2. 运行时模板(预处理模板)

    可在应用程序中执行运行时 T4 文本模板(“预处理过的”模板)以便生成文本字符串(通常作为其输出的一部分)。若要创建运行时模板,请向您的项目中添加“已预处理的文本模板”文件。另外,您还可以添加纯文本文件并将其“自定义工具”属性设置为“TextTemplatingFilePreprocessor”。

Visual Studio 中使用T4模板

1.创建T4模板文件

  1. 新建文件:在Visual Studio中,你可以通过右键点击项目,选择“添加” -> “新建项...”,然后在搜索框中输入“T4”或“Text Template”来找到T4模板文件模板(通常称为“文本模板”)。选择它并命名你的模板文件(例如:MyTemplate.tt)。

image

  1. 编辑模板:双击新创建的.tt文件以在Visual Studio中打开它。此时,你可以看到模板的初始内容,包括一些基本的指令和控制块。

2. 编写T4模板

在T4模板中,你可以使用C#或VB.NET代码(取决于你的项目设置)来编写控制逻辑,并使用特定的语法来定义输出文本的格式。

  • 指令块:如前所述,使用指令块来定义模板的行为和引入必要的资源。
  • 控制块:使用<# ... #>来包围代码块,这些代码块在模板转换时执行。
  • 表达式块:使用<#= ... #>来输出表达式的值到生成的文本中。
  • 类特征块:使用<#+ ... #>来定义辅助方法、属性或类,这些方法可以在模板的其他部分中被调用。
<#@ template debug="false" hostspecific="false" language="C#" #>  
<#@ output extension=".cs" #>
using System;  
  
namespace MyNamespace  
{  
    public class MyClass  
    {  
        public string MyProperty { get; set; }  
  
        public void MyMethod()  
        {  
            Console.WriteLine("Hello from T4 Template!");  
        }  
    }  
}

3. 转换模板

  • 自动转换:在Visual Studio中,通常当你保存T4模板文件时,Visual Studio会自动执行模板转换并生成输出文件。
  • 手动转换:你也可以通过右键点击模板文件并选择“运行自定义工具”来手动触发模板的转换。

中心控制Manager

上面T4模板的简单内容。可以生成模板,但是只能保存在t4模板的目录下方,无法进行更多操作。假如是项目集,还需要手动赋值粘贴很麻烦,基于Manage类进行块控制和保存文件到指定位置

<#@ assembly name="System.Core"#>
<#@ assembly name="EnvDTE"#>
<#@ import namespace="System.Collections.Generic"#>
<#@ import namespace="System.IO"#>
<#@ import namespace="System.Text"#>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating"#>
<#@ output extension=".cs" #>
<#+
class Manager
{
    public struct Block {
        public int Start, Length;
		public String Name,OutputPath;
    }

    public List<Block> blocks = new List<Block>();
    public Block currentBlock;
    public Block footerBlock = new Block();
    public Block headerBlock = new Block();
    public ITextTemplatingEngineHost host;
    public ManagementStrategy strategy;
    public StringBuilder template;
    public Manager(ITextTemplatingEngineHost host, StringBuilder template, bool commonHeader) {
        this.host = host;
        this.template = template;
        strategy = ManagementStrategy.Create(host);
    }
    public void StartBlock(String name,String outputPath) {
        currentBlock = new Block { Name = name, Start = template.Length ,OutputPath=outputPath};
    }

    public void StartFooter() {
        footerBlock.Start = template.Length;
    }

    public void EndFooter() {
        footerBlock.Length = template.Length - footerBlock.Start;
    }

    public void StartHeader() {
        headerBlock.Start = template.Length;
    }

    public void EndHeader() {
        headerBlock.Length = template.Length - headerBlock.Start;
    }    

    public void EndBlock() {
        currentBlock.Length = template.Length - currentBlock.Start;
        blocks.Add(currentBlock);
    }
    public void Process(bool split) {
        String header = template.ToString(headerBlock.Start, headerBlock.Length);
        String footer = template.ToString(footerBlock.Start, footerBlock.Length);
        blocks.Reverse();
        foreach(Block block in blocks) {
            String fileName = Path.Combine(block.OutputPath, block.Name);
            if (split) {
                String content = header + template.ToString(block.Start, block.Length) + footer;
                strategy.CreateFile(fileName, content);
                template.Remove(block.Start, block.Length);
            } else {
                strategy.DeleteFile(fileName);
            }
        }
    }
}
class ManagementStrategy
{
    internal static ManagementStrategy Create(ITextTemplatingEngineHost host) {
        return (host is IServiceProvider) ? new VSManagementStrategy(host) : new ManagementStrategy(host);
    }

    internal ManagementStrategy(ITextTemplatingEngineHost host) { }

    internal virtual void CreateFile(String fileName, String content) {
        File.WriteAllText(fileName, content);
    }

    internal virtual void DeleteFile(String fileName) {
        if (File.Exists(fileName))
            File.Delete(fileName);
    }
}

class VSManagementStrategy : ManagementStrategy
{
    private EnvDTE.ProjectItem templateProjectItem;

    internal VSManagementStrategy(ITextTemplatingEngineHost host) : base(host) {
        IServiceProvider hostServiceProvider = (IServiceProvider)host;
        if (hostServiceProvider == null)
            throw new ArgumentNullException("Could not obtain hostServiceProvider");

        EnvDTE.DTE 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);
    }
    internal override void CreateFile(String fileName, String content) {
        base.CreateFile(fileName, content);
        //((EventHandler)delegate { templateProjectItem.ProjectItems.AddFromFile(fileName); }).BeginInvoke(null, null, null, null);
    }
    internal override void DeleteFile(String fileName) {
        ((EventHandler)delegate { FindAndDeleteFile(fileName); }).BeginInvoke(null, null, null, null);
    }
    private void FindAndDeleteFile(String fileName) {
        foreach(EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems) {
            if (projectItem.get_FileNames(0) == fileName) {
                projectItem.Delete();
                return;
            }
        }
    }
}#>

每一个文件就要进行一次block的开关,即manager.StartBlock(文件名)manager.EndBlock(),在文件都结束后,执行manager.Process(true),进行文件的写操作。

注意:Manager类实现了文件块的开关和保存位置的设定。
这里需要设置template指令 :hostspecific=“true”

如果提示错误:T4 模板 错误 当前上下文中不存在名称“Host” ,请按照设置hostspecific=“true”

根据 MySQL 数据库生成实体

MySqlHelper.tt

<#@ assembly name="C:\Users\xxxx\.nuget\packages\mysql.data\9.0.0\lib\net48\MySql.Data.dll" #>
<#@ assembly name="System.Core.dll" #>
<#@ assembly name="System.Data.dll" #>
<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="MySql.Data.MySqlClient" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>

<#+  
    public class EntityHelper
    {
        public static List<Entity> GetEntities(string connectionString, List<string> databases)
        {
            var list = new List<Entity>();
            var conn = new MySqlConnection(connectionString);
            try
            {
                conn.Open();
                var dbs = string.Join("','", databases.ToArray());
                var cmd = string.Format(@"SELECT `information_schema`.`COLUMNS`.`TABLE_SCHEMA`
                                                    ,`information_schema`.`COLUMNS`.`TABLE_NAME`
                                                    ,`information_schema`.`COLUMNS`.`COLUMN_NAME`
                                                    ,`information_schema`.`COLUMNS`.`DATA_TYPE`
                                                    ,`information_schema`.`COLUMNS`.`COLUMN_COMMENT`
                                                FROM `information_schema`.`COLUMNS`
                                                WHERE `information_schema`.`COLUMNS`.`TABLE_SCHEMA` IN ('{0}') ", dbs);
                using (var reader = MySqlHelper.ExecuteReader(conn, cmd))
                {
                    while (reader.Read())
                    {
                        var db = reader["TABLE_SCHEMA"].ToString();
                        var table = reader["TABLE_NAME"].ToString();
                        var column = reader["COLUMN_NAME"].ToString();
                        var type = reader["DATA_TYPE"].ToString();
                        var comment = reader["COLUMN_COMMENT"].ToString();
                        var entity = list.FirstOrDefault(x => x.EntityName == table);
                        if (entity == null)
                        {
                            entity = new Entity(table);
                            entity.Fields.Add(new Field
                            {
                                Name = column,
                                Type = GetCLRType(type),
                                Comment = comment
                            });

                            list.Add(entity);
                        }
                        else
                        {
                            entity.Fields.Add(new Field
                            {
                                Name = column,
                                Type = GetCLRType(type),
                                Comment = comment
                            });
                        }
                    }
                }
            }
            finally
            {
                conn.Close();
            }

            return list;
        }

        public static string GetCLRType(string dbType)
        {
            switch (dbType)
            {
                case "tinyint":
                case "smallint":
                case "mediumint":
                case "int":
                case "integer":
                    return "int";
                case "double":
                    return "double";
                case "float":
                    return "float";
                case "decimal":
                    return "decimal";
                case "numeric":
                case "real":
                    return "decimal";
                case "bit":
                    return "bool";
                case "date":
                case "time":
                case "year":
                case "datetime":
                case "timestamp":
                    return "DateTime";
                case "tinyblob":
                case "blob":
                case "mediumblob":
                case "longblog":
                case "binary":
                case "varbinary":
                    return "byte[]";
                case "char":
                case "varchar":
                case "tinytext":
                case "text":
                case "mediumtext":
                case "longtext":
                    return "string";
                case "point":
                case "linestring":
                case "polygon":
                case "geometry":
                case "multipoint":
                case "multilinestring":
                case "multipolygon":
                case "geometrycollection":
                case "enum":
                case "set":
                default:
                    return dbType;
            }
        }
    }

    public class Entity
    {
        public Entity()
        {
            this.Fields = new List<Field>();
        }

        public Entity(string name)
            : this()
        {
            this.EntityName = name;
        }

        public string EntityName { get; set; }
        public List<Field> Fields { get; set; }
        public string PascalEntityName
        {
            get
            {
                return CommonConver.ToPascalCase(this.EntityName);
            }
        }
        public string CamelEntityName
        {
            get
            {
                return CommonConver.ToCamelCase(this.EntityName);
            }
        }
    }

    public class Field
    {
        public string Name { get; set; }
        public string Type { get; set; }
        public string Comment { get; set; }
    }
    public class CommonConver
    {
        public static string ToPascalCase(string tableName)
        {
            string upperTableName = tableName.Substring(0, 1).ToUpper() + tableName.Substring(1, tableName.Length - 1);
            return upperTableName;
        }
        public static string ToCamelCase(string tableName)
        {
            string lowerTableName = tableName.Substring(0, 1).ToLower() + tableName.Substring(1, tableName.Length - 1);
            return lowerTableName;
        }
    }

    class config
    {

        public static readonly string ConnectionString = "Database=test;Data Source=127.0.0.1;User Id=root;Password=123456;pooling=false;CharSet=utf8;port=3306";
        public static readonly string ModelNameSpace = "App.Entities";
    }
#>

AutoCreateModel.tt

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Data.Common.dll" #>
<#@ assembly name="System.Core.dll" #>
<#@ assembly name="System.Data.dll" #>
<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ include file="$(ProjectDir)Manage.tt"  #>
<#@ include file="$(ProjectDir)MySqlHelper.tt"  #>
<#@ output extension=".cs" #>
<# var manager = new Manager(Host, GenerationEnvironment, true); #>
<# 
    
      var OutputPath1 ="D:\\test"; //设置文件存储逇位置
      var entities =EntityHelper.GetEntities(config.ConnectionString,new List<string> { "test"});
      foreach(Entity entity in entities)
     {
	 manager.StartBlock(entity.EntityName+".cs",OutputPath1);
#>
using System;

namespace <#=config.ModelNameSpace#>
{
    /// <summary>
    /// <#= entity.EntityName #> Entity Model
    /// </summary>   

    public class <#= entity.EntityName #>
    {
<#
        for(int i = 0; i < entity.Fields.Count; i++)
        {
            if(i ==0)
            {
#>      
        /// <summary>
        /// <#= entity.Fields[i].Comment #>
        /// </summary>
        public <#= entity.Fields[i].Type #> <#= entity.Fields[i].Name #> { get; set; }
<#
            }
            else
            {
#>   
        /// <summary>
        /// <#= entity.Fields[i].Comment #>
        /// </summary>

        public <#= entity.Fields[i].Type #> <#= entity.Fields[i].Name #> { get; set; }
<#            
            }
        }
#>
    }
}
<#       
        manager.EndBlock();
    }
    manager.Process(true);
#>

介绍几个常用的$(variableName) 变量:

  • $(SolutionDir):当前项目所在解决方案目录
  • $(ProjectDir):当前项目所在目录
  • $(TargetPath):当前项目编译输出文件绝对路径
  • $(TargetDir):当前项目编译输出目录,即web项目的Bin目录,控制台、类库项目bin目录下的debug或release目录(取决于当前的编译模式)

举个例子:比如我们在D盘根目录建立了一个控制台项目TestConsole,解决方案目录为D:\LzrabbitRabbit,项目目录为
D:\LzrabbitRabbit\TestConsole,那么此时在Debug编译模式下

  • $(SolutionDir)的值为D:\LzrabbitRabbit
  • $(ProjectDir)的值为D:\LzrabbitRabbit\TestConsole
  • $(TargetPath)值为D:\LzrabbitRabbit\TestConsole\bin\Debug\TestConsole.exe
  • $(TargetDir)值为D:\LzrabbitRabbit\TestConsole\bin\Debug\
From:https://www.cnblogs.com/vic-tory/p/18306326
本文地址: http://shuzixingkong.net/article/110
0评论
提交 加载更多评论
其他文章 yearrecord——一个类似痕迹墙的React数据展示组件
介绍一下自己做的一个类似于力扣个人主页提交记录和GitHub主页贡献记录的React组件。 下图分别是力扣个人主页提交记录和GitHub个人主页的贡献记录,像这样类似痕迹墙的形式可以比较直观且高效得展示一段时间内得数据记录。 然而要从0实现这个功能还是有一些麻烦得,并且该功能可用的场景也比较多,于是
yearrecord——一个类似痕迹墙的React数据展示组件 yearrecord——一个类似痕迹墙的React数据展示组件 yearrecord——一个类似痕迹墙的React数据展示组件
MViT:性能杠杠的多尺度ViT | ICCV 2021
论文提出了多尺度视觉Transformer模型MViT,将多尺度层级特征的基本概念与Transformer模型联系起来,在逐层扩展特征复杂度同时降低特征的分辨率。在视频识别和图像分类的任务中,MViT均优于单尺度的ViT。 来源:晓飞的算法工程笔记 公众号 论文: Multiscale Vision
MViT:性能杠杠的多尺度ViT | ICCV 2021 MViT:性能杠杠的多尺度ViT | ICCV 2021 MViT:性能杠杠的多尺度ViT | ICCV 2021
咬文嚼图式的介绍二叉树、B树/B-树
网上的很多博客都是只有文字说明,比较抽象,所以笔者决定自己画一些图来解释二叉树,二叉搜索树,B树/B-树。
咬文嚼图式的介绍二叉树、B树/B-树 咬文嚼图式的介绍二叉树、B树/B-树 咬文嚼图式的介绍二叉树、B树/B-树
manim边学边做--Matrix
在代数问题中,矩阵是必不可少的工具,manim中提供了一套展示矩阵(Matrix)的模块,专门用于在动画中显示矩阵格式的数据。关于矩阵的类主要有4个: Matrix:通用的矩阵 IntegerMatrix:元素是整数的矩阵 DecimalMatrix:元素包含小数的矩阵 MobjectMatrix:
manim边学边做--Matrix manim边学边做--Matrix manim边学边做--Matrix
从基础到高级应用,详解用Python实现容器化和微服务架构
本文分享自华为云社区《Python微服务与容器化实践详解【从基础到高级应用】》,作者: 柠檬味拥抱。 Python中的容器化和微服务架构实践 在现代软件开发中,容器化和微服务架构已经成为主流。容器化技术使得应用程序可以在任何环境中一致运行,而微服务架构通过将应用拆分成多个独立的服务,从而提升了系统的
可视化—gojs 超多超实用经验分享(三)
目录32.go.Palette 一排放两个33.go.Palette 基本用法34.创建自己指向自己的连线35.设置不同的 groupTemplate 和 linkTemplate36.监听在图形对象 GraphObject 上的右键单击37.定义节点/连线/canvas 背景上的右键菜单38.从节
ComfyUI进阶:Comfyroll插件 (一)
ComfyUI进阶:Comfyroll插件 (一)前言:学习ComfyUI是一场持久战,而Comfyroll Studio 是一款功能强大的自定义节点集合,专为 ComfyUI 用户打造,旨在提供更加丰富和专业的图像生成与编辑工具。借助这些节点,用户可以在静态图像的精细调整和动态动画的复杂构建方面进
ComfyUI进阶:Comfyroll插件 (一) ComfyUI进阶:Comfyroll插件 (一) ComfyUI进阶:Comfyroll插件 (一)
基于MindSpore实现BERT对话情绪识别
本文分享自华为云社区《【昇思25天学习打卡营打卡指南-第二十四天】基于 MindSpore 实现 BERT 对话情绪识别》,作者:JeffDing。 模型简介 BERT全称是来自变换器的双向编码器表征量(Bidirectional Encoder Representations from Trans