Asp .Net Core 系列:基于 T4 模板生成代码
简介
T4模板,即Text Template Transformation Toolkit
,是微软官方在Visual Studio中引入的一种代码生成引擎。自Visual Studio 2008开始,T4模板就被广泛应用于生成各种类型的文本文件,包括网页、资源文件以及各种编程语言的源代码等。
T4模板是一种由文本块和控制逻辑组成的混合模板,它可以根据预设的规则和输入数据生成目标文本文件。
组成部分
T4模板主要由以下几部分组成:
指令块:向文本模板化引擎提供关于如何生成转换代码和输出文件的一般指令。常见的指令包括
<#@ template #>
、<#@ parameter #>
、<#@ assembly #>
、<#@ import #>
、<#@ include #>
和<#@ output #>
等。- 模板指令(
<#@ template #>
):定义模板的基本属性,如使用的编程语言、是否开启调试模式等。 - 参数指令(
<#@ parameter #>
):声明模板代码中从外部上下文传入的值初始化的属性。 - 程序集指令(
<#@ assembly #>
):引用外部程序集,以便在模板中使用其中的类型和方法。 - 导入指令(
<#@ import #>
):允许在模板中引用其他命名空间中的类型,类似于C#中的using
指令或Visual Basic中的Imports
指令。 - 包含指令(
<#@ include #>
):在模板中包含另一个文件的内容,通常用于共享常用的代码片段或模板设置。 - 输出指令(
<#@ output #>
):定义输出文件的扩展名和编码方式。
- 模板指令(
文本块:直接复制到输出文件的内容,不会进行任何处理或转换。
代码语句块(Statement Block)
代码语句块通过
<#Statement#>
的形式表示,中间是一段通过相应编程语言编写的程序调用,我们可以通过代码语句快控制文本转化的流程。在上面的代码中,我们通过代码语句块实现对一个数组进行遍历,输出重复的Console.WriteLine("Hello {0},Welcome to T4 World!","<#= p.Name #>");
语句。表达式块(Expression Block)
表达式块以<#=Expression#>的形式表示,通过它之际上动态的解析的字符串表达内嵌到输出的文本中。比如在上面的foreach循环中,每次迭代输出的人名就是通过表达式块的形式定义的(
<#= p.Name #>
)类特性块(Class Feature Block)
如果文本转化需要一些比较复杂的逻辑,我们需要写在一个单独的辅助方法中,甚至是定义一些单独的类,我们就是将它们定义在类特性块中。类特性块的表现形式为
<#+ FeatureCode #>
分类
设计时模板(文本模版)
在 Visual Studio 中执行设计时 T4 文本模板,以便定义应用程序的部分源代码和其他资源。通常,您可以使用读取单个输入文件或数据库中的数据的多个模板,并生成一些 .cs、.vb 或其他源文件。每个模板都生成一个文件。 在 Visual Studio 或 MSBuild 内执行它们。若要创建设计时模板,请向您的项目中添加“文本模板”文件。 另外,您还可以添加纯文本文件并将其“自定义工具”属性设置为“TextTemplatingFileGenerator”。
运行时模板(预处理模板)
可在应用程序中执行运行时 T4 文本模板(“预处理过的”模板)以便生成文本字符串(通常作为其输出的一部分)。若要创建运行时模板,请向您的项目中添加“已预处理的文本模板”文件。另外,您还可以添加纯文本文件并将其“自定义工具”属性设置为“TextTemplatingFilePreprocessor”。
Visual Studio 中使用T4模板
创建T4模板文件
- 新建文件:在Visual Studio中,你可以通过右键点击项目,选择“添加” -> “新建项...”,然后在搜索框中输入“T4”或“Text Template”来找到T4模板文件模板(通常称为“文本模板”)。选择它并命名你的模板文件(例如:
MyTemplate.tt
)。
- 编辑模板:双击新创建的
.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\
Asp .Net Core 系列:基于 T4 模板生成代码的更多相关文章
- 使用T4模板生成代码的学习
之前做项目使用的都是Db First,直接在项目中添加Entity Framework,使用T4模板(T4模板引擎之基础入门)生成DAL BLL层等(T4模板是一个同事给的,也没有仔细研究,代码如下: ...
- T4模板生成代码。 数据实体层与数据仓储层。备注
文件生成模板:TempleteManager.ttinclude <#@ assembly name="System.Core" #><#@ assembly n ...
- C# 通过T4自动生成代码
通过T4模板生成代码,运行时实现 关键代码段:Host using Microsoft.VisualStudio.TextTemplating; using System; using System. ...
- 利用T4模板生成ASP.NET Core控制器的构造函数和参数
前言 在ASP.NET Core中引入了DI,并且通过构造函数注入参数,控制器中会大量使用DI注入各种的配置参数,如果配置注入的参数比较多,而且各个控制器需要的配置参数都基本一样的话,那么不断重复的复 ...
- 技术的正宗与野路子 c#, AOP动态代理实现动态权限控制(一) 探索基于.NET下实现一句话木马之asmx篇 asp.net core 系列 9 环境(Development、Staging 、Production)
黄衫女子的武功似乎与周芷若乃是一路,飘忽灵动,变幻无方,但举手抬足之间却是正而不邪,如说周芷若形似鬼魅,那黄衫女子便是态拟神仙. 这段描写出自<倚天屠龙记>第三十八回. “九阴神抓”本是& ...
- 基于Asp.Net Core,利用ZXing来生成二维码的一般流程
本文主要介绍如何在.net环境下,基于Asp.Net Core,利用ZXing来生成二维码的一般操作.对二维码工作原理了解,详情见:https://blog.csdn.net/weixin_36191 ...
- MVC实用架构设计(三)——EF-Code First(3):使用T4模板生成相似代码
前言 经过前面EF的<第一篇>与<第二篇>,我们的数据层功能已经较为完善了,但有不少代码相似度较高,比如负责实体映射的 EntityConfiguration,负责仓储操作的I ...
- asp.net core系列 40 Web 应用MVC 介绍与详细示例
一. MVC介绍 MVC架构模式有助于实现关注点分离.视图和控制器均依赖于模型. 但是,模型既不依赖于视图,也不依赖于控制器. 这是分离的一个关键优势. 这种分离允许模型独立于可视化展示进行构建和测试 ...
- asp.net core系列 36 WebAPI 搭建详细示例
一.概述 HTTP不仅仅用于提供网页.HTTP也是构建公开服务和数据的API强大平台.HTTP简单灵活且无处不在.几乎任何你能想到的平台都有一个HTTP库,因此HTTP服务可以覆盖广泛的客户端,包括浏 ...
- [转]MVC实用架构设计(三)——EF-Code First(3):使用T4模板生成相似代码
本文转自:http://www.cnblogs.com/guomingfeng/p/mvc-ef-t4.html 〇.目录 一.前言 二.工具准备 三.T4代码生成预热 (一) 单文件生成:Hello ...
随机推荐
- 大数据之Hadoop集群中Yarn常用命令
Yarn状态的查询,除了可以在hadoop103:8088页面查看以外,还可以通过命令操作.常见的命令操作如下所示. 需求:执行WordCount案例,并通过Yarn命令查看任务运行情况.原文:sw- ...
- golang nsq 同一个topic有多个channel,同时项目又互相引用时出现的问题
p.p1 { margin: 0; font: 12px ".PingFang SC" } span.s1 { font: 12px "Helvetica Neue&qu ...
- java中SimpleDateFormat解析日期格式的问题
在日常写代码的过程中,我们经常要处理各种格式的日期,常见的日期格式有:"20240601","2024-06-01","2024-6-1". ...
- kubernetes的搭建(一)
集群的搭建 集群的类型 kubunetes的集群类型大致上分为两类: 一主多从和多主多从. 一主多从: 一台master节点和多台node节点,搭建简单,但是有单机故障的风险,适用于测试环境 多主多从 ...
- openstack nova 报错
openstack compute service list The server is currently unavailable. Please try again at a later time ...
- centos7下利用qemu搭建arm模拟器
1 目的 在centos7下使用qemu搭建arm模拟器. 参考博客: centos下利用qemu搭建arm模拟器 - 寒水司天 - 博客园 (cnblogs.com) 从零使用qemu模拟器搭建ar ...
- Easysearch 内核完善之 OOM 内存溢出优化案例一则
最近某客户在使用 Easysearch 做聚合时,报出 OOM 导致掉节点的问题,当时直接让客户试着调整 indices.breaker.request.limit ,但是不起作用,于是又看了下 Ea ...
- The bean ‘xxx‘ could not be injected as a ‘xxx‘because it is a JDK dynamic proxy that implements错误解决
1.解决方法:使用@Autowired 2.@autowired和@resource注解的区别区别:1.@Autowired注解由Spring提供,只按照byType注入:@resource注解由J2 ...
- AlertManager解析:构建高效告警系统
本文深入探讨了AlertManager的技术细节和实际应用,从基本概念.核心组件.工作流程,到与Prometheus的集成和实战案例,旨在为专业人士提供一个全面的AlertManager技术和应用指南 ...
- 一款.NET开源、功能强大、跨平台的绘图库 - OxyPlot
前言 今天大姚给大家分享一款.NET开源(MIT License).免费.跨平台.功能强大的绘图库,支持多平台使用(包括:WPF.UWP.WinForm.Silverlight.Xamarin.iOS ...