使用T4模板生成设计时代码
使用设计时 T4 文本模板,你可以在 Visual Studio 项目中生成程序代码和其他文件。 通常,你编写一些模板,以便它们根据来自模型的数据来改变所生成的代码。 模型是包含有关应用程序要求的关键信息的文件或数据库。
1.创建设计时 T4 文本模板
在 Visual Studio 中创建设计时 T4 模板
- 创建一个 Visual Studio 项目或打开一个现有项目。
例如,在“文件”菜单上,选择“新”、“项目”。
- 向你的项目添加文本模板文件,并给予其带有扩展名 .tt 的名称。
若要执行此操作,在“解决方案资源管理器”中,在你的项目的快捷菜单上,选择“添加”、“新建项”。 在“添加新项”对话框的中间窗格选择“文本模板”。
请注意,该文件的“自定义工具”属性为“TextTemplatingFileGenerator”。
- 打开该文件。 该文件中已包含下列指令:
4. <#@ template hostspecific="false" language="C#" #>
5. <#@ output extension=".txt" #>
如果已将模板添加到 Visual Basic 项目,则语言特性将为“VB”。
- 在文件末尾添加一些文本。 例如:
7. Hello, world!
- 保存该文件。
你可能会看到一个“安全警告”消息框,要求确认要运行该模板。 单击“确定”。
- 在“解决方案资源管理器”中,展开模板文件节点,你将找到带有扩展名 .txt 的文件。 该文件包含从该模板生成的文本。
生成可变文本
通过文本模板,可以使用程序代码更改已生成文件的内容。
使用程序代码生成文本
1. 更改 .tt 文件的内容:
<#@ template hostspecific="false" language="C#" #>
<#@ output extension=".txt" #><#int top = 10;for (int i = 0; i<=top; i++)
{ #> The square of <#= i #> is <#= i*i #><# } #>
2. 保存 .tt 文件,然后重新检查已生成的 .txt 文件。 该文件列出数字 0 到 10 的平方。
请注意,语句括在 <#...#> 内,单个表达式括在 <#=...#> 内。 如果在 Visual Basic 中编写生成代码,则 template 指令应包含 language="VB"。 默认为 "C#"。
调试设计时 T4 文本模板
创建文本模板:
· 将 debug="true" 插入 template 指令。 例如:
<#@ template debug="true" hostspecific="false" language="C#" #>
· 在模板中使用为普通代码设置断点的相同方式设置断点。
· 在“解决方案资源管理器”中,从文本模板文件的快捷菜单选择“调试 T4 模板”。
该模板将运行并在断点处停止。 你可以以常用方式检查变量并逐步执行代码。
结构化文本模板
作为一种良好做法,我们往往将模板代码分成两部分:
· 配置或数据收集部分,它在变量中设置值,但不包含文本块。 在上一个示例中,此部分是 properties 的初始化。
此部分有时称为“模型”部分,因为它会构造一个存储内模型,并且通常读取模型文件。
· 文本生成部分(示例中的 foreach(...){...}),它使用变量的值。
虽然这不是必要的分离,但是通过这种方式可以降低包括文本的部分的复杂性,从而更便于读取模板。
读取文件或其他源
若要访问模型文件或数据库,模板代码可以使用诸如 System.XML 之类的程序集。 若要获取对这些程序集的访问权限,必须插入如下指令:
<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.IO" #>
assembly 指令使指定的程序集可供模板代码使用,方式与 Visual Studio 项目中的“引用”部分相同。 你无需包括对 System.dll 的引用,它是自动引用的。 import 指令允许你使用类型而不使用其完全限定名,方式与普通程序文件中的 using 指令相同。
例如,导入 System.IO 之后,可以编写:
C#
<# var properties = File.ReadLines("C:\\propertyList.txt");#>
...
<# foreach (string propertyName in properties) { #>
...
通过相对路径名打开文件
若要从相对于文本模板的位置加载文件,可以使用 this.Host.ResolvePath()。 若要使用 this.Host,你必须在 hostspecific="true" 中设置template:
<#@ template debug="false" hostspecific="true" language="C#" #>
然后你可以进行编写,例如:
C#
<# string fileName = this.Host.ResolvePath("filename.txt");
string [] properties = File.ReadLines(filename);#>
...
<# foreach (string propertyName in properties { #>
...
还可以使用 this.Host.TemplateFile,它标识当前模板文件的名称。
this.Host 的类型(在 VB 中是 Me.Host)是 Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost。
从 Visual Studio 获取数据
若要使用 Visual Studio 中提供的服务,请设置 hostSpecific 特性并加载 EnvDTE 程序集。 然后,你可以使用 IServiceProvider.GetCOMService() 访问 DTE 和其他服务。 例如:
scr
<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".txt" #>
<#@ assembly name="EnvDTE" #>
<#
IServiceProvider serviceProvider = (IServiceProvider)this.Host;
EnvDTE.DTE dte = (EnvDTE.DTE) serviceProvider.GetCOMService(typeof(EnvDTE.DTE));
#>
Number of projects in this VS solution: <#= dte.Solution.Projects.Count #>
自动重新生成代码
通常,Visual Studio 解决方案中的多个文件都使用一个输入模型生成。 每个文件从其自己的模板生成,但这些模板全都引用同一个模型。
如果源模型发生更改,则应重新运行该解决方案中的所有模板。 若要手动执行此操作,请选择“生成”菜单上的“转换所有模板”。
如果已安装 Visual Studio 可视化和建模 SDK,则可以在每次执行生成时自动转换所有模板。 为此,可在文本编辑器中编辑项目文件(.csproj 或 .vbproj),然后在文件末尾附近(其他任何 <import> 语句之后)添加以下行:
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v11.0\TextTemplating\Microsoft.TextTemplating.targets" />
<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
<!-- Other properties can be inserted here -->
</PropertyGroup>
错误报告
若要在 Visual Studio 错误窗口中放置错误消息和警告消息,可以使用以下方法:
Error("An error message");Warning("A warning message"); 将现有文件转换为模板
模板的一个非常有用的特性是:它们看起来与其生成的文件(加上一些插入的程序代码)非常相似。 这暗示了创建模板的一种有用方法。 首先,创建一个普通的文件(如 Visual C# 文件)作为原型,然后逐步引入可更改所生成文件的生成代码。
将现有文件转换为设计时模板
1. 对于你的 Visual Studio 项目,添加要生成的类型的文件,例如 .cs、.vb 或 .resx 文件。
2. 测试新文件以确保其工作。
3. 在解决方案资源管理器中,将文件扩展名更改为 .tt。
4. 验证 .tt 文件的以下属性:
| 自定义工具 = | TextTemplatingFileGenerator |
| 生成操作 = | 无 |
5. 在文件开头插入以下行:
6. <#@ template debug="false" hostspecific="false" language="C#" #>7. <#@ output extension=".cs" #> 如果要以 Visual Basic 编写模板的生成代码,请将 language 特性设置为 "VB",而不是 "C#"。
将 extension 特性设置为要生成的文件类型的文件扩展名,例如 .cs、.resx 或 .xml。
8. 保存该文件。
将使用指定扩展名创建一个附属文件。 该文件对于相应文件类型具有正确的属性。 例如,.cs 文件的“生成操作”属性将为“编译”。
验证生成的文件是否包含与原始文件相同的内容。
9. 确定要更改的文件部分。 例如,一个仅在特定条件下显示的部分、一个重复的部分或特定值会有所变化的部分。 插入生成代码。 保存该文件,然后验证附属文件是否正确生成。 重复此步骤。
2. 演练:使用文本模板生成代码
通过代码生成,可以生成在源模型更改时能够轻松更改的强类型程序代码。
用于读取 XML 的指定类型化代码
|
|
| |
<?xml version="1.0" encoding="utf-8" ?>
<catalog>
<artist id ="Mike%20Nash" name="Mike Nash Quartet">
<song id ="MikeNashJazzBeforeTeatime">Jazz Before Teatime</song>
<song id ="MikeNashJazzAfterBreakfast">Jazz After Breakfast</song>
</artist>
<artist id ="Euan%20Garden" name="Euan Garden">
<song id ="GardenScottishCountry">Scottish Country Garden</song>
</artist>
</catalog>
Catalog catalog = new Catalog(xmlDocument);
foreach (Artist artist in catalog.Artist)
{Console.WriteLine(artist.name);
foreach (Song song in artist.Song)
{ Console.WriteLine(" " + song.Text);}
}
XmlNode catalog = xmlDocument.SelectSingleNode("catalog");foreach (XmlNode artist in catalog.SelectNodes("artist")){Console.WriteLine(artist.Attributes["name"].Value);
foreach (XmlNode song in artist.SelectNodes("song")) { Console.WriteLine(" " + song.InnerText);}
}
设置项目
创建项目
将原型 XML 文件添加到项目
添加 XML 文件
添加测试代码文件
using System;
namespace MyProject
{class CodeGeneratorTest
{public void TestMethod()
{Catalog catalog = new Catalog(@"..\..\exampleXml.xml");
foreach (Artist artist in catalog.Artist)
{Console.WriteLine(artist.name);
foreach (Song song in artist.Song)
{ Console.WriteLine(" " + song.Text);} } } } }
添加文本模板文件
将文本模板文件添加到项目
|
|
| |
6. <#@ template debug="false" hostspecific="true" language="C#" #>
7. <#@ output extension=".cs" #>
开发文本模板
确定要生成的代码的原型
class Catalog {} class Artist {}class Song {} 从模型 XML 文件生成应用程序代码
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml"#>
<#@ import namespace="System.Xml" #>
<#
XmlDocument doc = new XmlDocument();
// Replace this file path with yours:
doc.Load(@"C:\MySolution\MyProject\exampleXml.xml");
foreach (XmlNode node in doc.SelectNodes("//*")) {#>
public partial class <#= node.Name #> {}<#
}
#>
读取模型文件,然后生成代码
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml"#>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Collections.Generic" #>
<#
// Read the model file
XmlDocument doc = new XmlDocument();
doc.Load(@"C:\MySolution\MyProject\exampleXml.xml");
Dictionary <string, string> nodeTypes =
new Dictionary<string, string>();
foreach (XmlNode node in doc.SelectNodes("//*")) {nodeTypes[node.Name] = "";
}
// Generate the code
foreach (string nodeName in nodeTypes.Keys)
{#>
public partial class <#= nodeName #> {}<#
}
#>
添加辅助方法
// Generate the code
foreach (string nodeName in nodeTypes.Keys)
{#>
public partial class <#= UpperInitial(nodeName) #> {}<#
}
#>
<#+
private string UpperInitial(string name)
{ return name[0].ToString().ToUpperInvariant() + name.Substring(1); }#>
public partial class Catalog {}public partial class Artist {}public partial class Song {} 访问 Visual Studio API
<#@ template debug="false" hostspecific="true" language="C#" #>
...
<#@ assembly name="EnvDTE" #>
...
EnvDTE.DTE dte = (EnvDTE.DTE) ((IServiceProvider) this.Host)
.GetService(typeof(EnvDTE.DTE));
// Open the prototype document.
XmlDocument doc = new XmlDocument();
doc.Load(System.IO.Path.Combine(dte.ActiveDocument.Path, "exampleXml.xml"));
完成文本模板
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Collections.Generic" #>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
namespace MyProject
{<#
// Map node name --> child name --> child node type
Dictionary<string, Dictionary<string, XmlNodeType>> nodeTypes = new Dictionary<string, Dictionary<string, XmlNodeType>>();
// The Visual Studio host, to get the local file path.
EnvDTE.DTE dte = (EnvDTE.DTE) ((IServiceProvider) this.Host)
.GetService(typeof(EnvDTE.DTE));
// Open the prototype document.
XmlDocument doc = new XmlDocument();
doc.Load(System.IO.Path.Combine(dte.ActiveDocument.Path, "exampleXml.xml"));
// Inspect all the nodes in the document.
// The example might contain many nodes of the same type,
// so make a dictionary of node types and their children.
foreach (XmlNode node in doc.SelectNodes("//*")) {Dictionary<string, XmlNodeType> subs = null;
if (!nodeTypes.TryGetValue(node.Name, out subs))
{subs = new Dictionary<string, XmlNodeType>();
nodeTypes.Add(node.Name, subs);
}
foreach (XmlNode child in node.ChildNodes)
{subs[child.Name] = child.NodeType;
}
foreach (XmlNode child in node.Attributes)
{subs[child.Name] = child.NodeType;
}
}
// Generate a class for each node type.
foreach (string className in nodeTypes.Keys)
{// Capitalize the first character of the name.
#>
partial class <#= UpperInitial(className) #>
{private XmlNode thisNode;
public <#= UpperInitial(className) #>(XmlNode node)
{ thisNode = node; }<#
// Generate a property for each child.
foreach (string childName in nodeTypes[className].Keys)
{// Allow for different types of child.
switch (nodeTypes[className][childName])
{// Child nodes:
case XmlNodeType.Element:
#>
public IEnumerable<<#=UpperInitial(childName)#>><#=UpperInitial(childName) #>
{ get
{ foreach (XmlNode node in
thisNode.SelectNodes("<#=childName#>")) yield return new <#=UpperInitial(childName)#>(node);
} }
<#
break;
// Child attributes:
case XmlNodeType.Attribute:
#>
public string <#=childName #>
{ get { return thisNode.Attributes["<#=childName#>"].Value; } }<#
break;
// Plain text:
case XmlNodeType.Text:
#>
public string Text { get { return thisNode.InnerText; } }<#
break;
} // switch
} // foreach class child
// End of the generated class:
#>
}
<#
} // foreach class
// Add a constructor for the root class
// that accepts an XML filename.
string rootClassName = doc.SelectSingleNode("*").Name;#>
partial class <#= UpperInitial(rootClassName) #>
{public <#= UpperInitial(rootClassName) #>(string fileName)
{XmlDocument doc = new XmlDocument();
doc.Load(fileName);
thisNode = doc.SelectSingleNode("<#=rootClassName#>");}
}
}
<#+
private string UpperInitial(string name)
{return name[0].ToString().ToUpperInvariant() + name.Substring(1);
}
#>
运行测试程序
using System;
namespace MyProject
{ class Program { static void Main(string[] args) { new CodeGeneratorTest().TestMethod();// Allow user to see the output:
Console.ReadLine();
} } }
编写和更新应用程序
结束语

浙公网安备 33010602011771号