使用更简单的方法从 XML 文件在 Visual Studio 中生成代码





5.00/5 (13投票s)
在这篇文章中,我们将探讨如何在 Visual Studio 中,从一个简单的 XML 模型生成代码,适用于许多场景。
代码生成应该很简单,我希望在 Visual Studio 中使用简单的 XML 文件作为模型来实现。所以,这里是我为极简主义者/极简场景创建的一个用于 XML 驱动代码生成的快速封装器。
源代码
- 对于第一个示例,请安装 Install-Package AmazedSaint.ElasticObject
- 下载 CanML ASP.NET MVC 压缩示例
介绍超简单的 XML 驱动代码生成
我们开始吧——这基于我的 ElasticObject 实现,使用文本模板 (TT/T4) 通过简单的 XML 文件作为模型来生成代码。它非常简单,以至于不支持完整的 XML 模式 ;) - 但我暂时可以接受。所以,步骤很简单,这要感谢 Nuget。
步骤 1 - 获取 ElasticObject Nuget 包
安装 AmazedSaint.ElasticObject,在 包管理器控制台 中运行以下命令
PM> Install-Package AmazedSaint.ElasticObject
或者您可以通过 Nuget 包管理器安装,这取决于您。
步骤 2 – 享受
这将添加对 ElasticObject 库的引用,并在解决方案资源管理器中添加一个 Xml 模型文件和一个 T4 模板(见图)。Xml 文件可以作为您的模型,TT 文件包含一些代码,用于包装模型以生成您需要的任何内容(源代码、视图、脚手架等)。您可以修改 xml 和 TT 文件。此外,还有一个名为 *ReadMe-ElasticObject.cs.txt* 的文件,您可以将其重命名为 CS 文件,并查看/运行测试以了解 ElasticObject 的用法。
Xml 模型文件和 TT 生成器中有什么?
示例模型文件包含一些示例 xml,它可以是任何内容(实际上,是 xml 的一个合理子集;))
<model>
<class name="MyClass1">
<property name="MyProperty1" type="string"/>
<property name="MyProperty2" type="string"/>
</class>
<class name="MyClass2">
<property name="MyProperty1" type="string"/>
<property name="MyProperty2" type="string"/>
</class>
</model>
正如您所看到的,模型可以是任何内容,只要在一个有效的根元素中即可。示例只是模拟了一些类和其中的属性。现在,让我们快速看一下 Nuget 包添加的示例 TT 文件。不要害怕,它是一些简单的 T4 语法 - 在这里复习您的 T4 技能,Oleg 提供了所有您开始使用 T4 语法(以及更多)所需的内容。
我有没有告诉过您可以使用 Visual T4 Editor 免费版来减少使用 T4 模板时的痛苦?今天就从 VS 扩展库获取它吧,否则您最终会给创建 Visual Studio T4 编辑器的人发送仇恨邮件。
在下面的代码中,您可以看到我们正在从 XML 创建一个弹性对象,然后迭代元素以生成代码——如果您不熟悉 ElasticObject,请参阅我的这篇文章——它只是我为松散包装 XML 等内容而创建的一个包装器动态对象。此外,您还可以观看这个 LIDNUG 演示文稿,其中 Shey 在他的动态演讲中演示 ElasticObject。
无论如何,这是您将在我们的 Nuget 包添加的 tt 文件中找到的 T4 代码。
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ Assembly Name="System.Xml.dll" #>
<#@ Assembly Name="System.Xml.Linq.dll" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ Assembly Name="Microsoft.CSharp.dll" #>
<#@ Assembly name="$(SolutionDir)packages\AmazedSaint.ElasticObject.1.0.0\lib\net40\AmazedSaint.Elastic.dll" #>
<#@ Import Namespace="System.Xml" #>
<#@ Import Namespace="System.Xml.Linq" #>
<#@ Import Namespace="AmazedSaint.Elastic" #>
<#@ Import Namespace="AmazedSaint.Elastic.Lib" #>
<#@ output extension=".cs" #>
<#
var model=FromFile("ElasticDemoModel.xml");
foreach(var c in model["class"]) //get all classes
WriteClass(c);
#>
<#+
//Create an elastic object
private dynamic FromFile(string file)
{
var path= Host.ResolvePath(file);
return XDocument.Load(path).Root.ToElastic();
}
//Write a class
private void WriteClass(dynamic c)
{
#>
//Class generated
class <#= c.name #>
{
<#+
foreach(var p in c["property"])
WriteProperty(p);
#>
}
<#+
}
//Write a Property
private void WriteProperty(dynamic p)
{
#>
public <#= p.type #> <#= p.name #> {get;set;}
<#+
}
#>
您猜对了,生成的文件在这里。
//Class generated
class MyClass1
{
public string MyProperty1 {get;set;}
public string MyProperty2 {get;set;}
}
//Class generated
class MyClass2
{
public string MyProperty1 {get;set;}
public string MyProperty2 {get;set;}
}
请注意,这需要 C# 动态支持,因此目标是 .NET 4.0 运行时——因此,如果您想在没有动态支持的其他平台中进行代码生成,请添加一个 .NET 4.0 项目,将您的模型和 TT/T4 放在那里,并将生成的文件添加/链接到其他项目。此示例仅展示了从模型生成 C# 代码,但您可以想象您可以做多少事情;)
一个更具生产力的例子 - CanML
在此示例中,我们将探讨如何创建一种简单的基于 XML 的标记语言来定义 HTML5 Canvas 的形状。我们将通过从 XML 定义生成 JavaScript 代码来实现这一点,本文旨在指导您如何利用脚手架和元编程来简化许多场景。如果您没有很多孩子要照顾,请修改此示例,创建您自己的功能齐全的 CanML 版本(使用该名称),然后告诉我
回到正题——我们将使用我的 Elastic Object Nuget 包和 T4 模板进行代码生成,正如我在上一篇文章中解释的 [使用 AmazedSaint.ElasticObject Nuget 包进行代码生成],所以阅读那篇文章肯定会有帮助。我还建议您快速阅读这篇 MDN 中的基本 Canvas 教程
我们打算实现什么?
您将能够使用 XML 声明您的画布形状,例如,看看我如何声明一个心形和一个三角形切片。在心形中,我使用绝对坐标,并且我为三角形切片使用变量,这样您就可以在任何地方绘制它。
<?xml version="1.0" encoding="utf-8" ?>
<shapes>
<!--Normal Heart Shape, Fixed Coords-->
<shape name="shapeHeart" type="fill" params="fillColor">
<fillStyle value="fillColor"/>
<to params="75,40"/>
<bcurve params="75,37,70,25,50,25"/>
<bcurve params="20,25,20,62.5,20,62.5"/>
<bcurve params="20,80,40,102,75,120"/>
<bcurve params="110,102,130,80,130,62.5"/>
<bcurve params="130,62.5,130,25,100,25"/>
<bcurve params="85,25,75,37,75,40"/>
</shape>
<!--Triangle Slice with variables-->
<shape name="shapeTriangleSlice" type="fill" params="fillColor,firstX,firstY,delta">
<fillStyle value="fillColor"/>
<to params="firstX,firstY"/>
<line params="firstX,firstY+delta"/>
<line params="firstX+delta,firstY"/>
</shape>
<!--Draw both-->
<shape name="allShapes">
<shape name="shapeHeart" params="'yellow'"/>
<shape name="shapeTriangleSlice" params="'red',200,200,100"/>
<shape name="shapeTriangleSlice" params="'red',200,200,-100"/>
</shape>
</shapes>
它将被渲染到 HTML5 画布中,如下所示。这个技巧是,我们正在使用我上面解释的技术,从上述标记生成所需的 JavaScript 代码
生成的 JavaScript 怎么样?
如果您有兴趣查看从上述标记生成的 JavaScript,请参阅下面。如果您仔细查看生成的代码以及我们上面的 xml 模型,您会发现我们遵循了一些简单的约定来从上述标记生成代码。
- 每个 <shape/> 定义都封装为一个方法,带有一个画布参数(如果存在 param 元素,则带有其他参数)
- Shape 的 type 属性
- 我们有一些快捷符号(例如 to -> moveTo 和 bcurve -> bezierCurveTo)
- 如果元素有 param 属性,它将渲染为一个方法(例如 moveTo)
- 如果一个元素有一个 value 属性,它将被渲染为一个属性(例如
//Method generated
var shapeHeart = function (canvas, fillColor)
{
if (canvas.getContext){
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.fillStyle=fillColor;
ctx.moveTo(75,40);
ctx.bezierCurveTo(75,37,70,25,50,25);
ctx.bezierCurveTo(20,25,20,62.5,20,62.5);
ctx.bezierCurveTo(20,80,40,102,75,120);
ctx.bezierCurveTo(110,102,130,80,130,62.5);
ctx.bezierCurveTo(130,62.5,130,25,100,25);
ctx.bezierCurveTo(85,25,75,37,75,40);
ctx.fill();
}
}
//Method generated
var shapeTriangleSlice = function (canvas, fillColor,firstX,firstY,delta)
{
if (canvas.getContext){
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.fillStyle=fillColor;
ctx.moveTo(firstX,firstY);
ctx.lineTo(firstX,firstY+delta);
ctx.lineTo(firstX+delta,firstY);
ctx.fill();
}
}
//Method generated
var allShapes = function (canvas)
{
if (canvas.getContext){
var ctx = canvas.getContext('2d');
ctx.beginPath();
shapeHeart(canvas,'yellow');
shapeTriangleSlice(canvas,'red',200,200,100);
shapeTriangleSlice(canvas,'red',200,200,-100);
}
}
请注意,所有方法都带有一个 canvas 输入参数。现在,您可以通过在 HTML 页面中链接到生成的 CanvasShapes.js 脚本文件来调用这些方法——请参阅我的 index.cshtml ASP.NET MVC Razor 视图,其中我调用了 allShapes 方法。只需获取实际的 canvas 元素,并将其传递给 draw,如下所示。@{
ViewBag.Title = "Home Page";
}
@section headerSection{
<script src="@Url.Content("~/CanvasShapes/CanvasShapes.js")" type="text/javascript"></script>
<style type="text/css">
canvas { border: 1px solid black; margin-left:auto; margin-right:auto;}
</style>
}
<canvas id="myCanvas" width=800 height=480></canvas>
<script>
var canvas = document.getElementById("myCanvas");
allShapes(canvas);
</script>
告诉我关于从 XML 实际生成 JS 的情况?
实际生成非常简单——事实上,这只是一个使用 Elastic Object Nuget 包 进行代码生成的示例用例。
要手动尝试此操作,
- 在 Visual Studio 中创建一个新的 ASP.NET MVC 项目。
- 按照我上一篇文章中的说明安装 AmazedSaint.ElasticObject 包——这还将为您的项目添加一个 Xml 文件和一个 TT(文本模板文件)
- 将 Xml 文件重命名为 CanvasModel.xml – 并将上述 XML 代码复制到其中
- 将 TT 文件重命名为 CanvasShapes.tt,并将下面的 T4 代码添加到 TT 文件中
这是我们用于生成 JS 代码的简单 T4 代码。哎呀,实际的转换代码甚至不到 25 行,但这足以传达这个想法,对吧?您可以进一步充实它,添加一些对渐变、过渡等的支持,使其功能齐全。请随身携带 MDN Canvas 文档。
<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ Assembly Name="System.Xml.dll" #>
<#@ Assembly Name="System.Xml.Linq.dll" #>
<#@ Assembly Name="System.Core.dll" #>
<#@ Assembly Name="Microsoft.CSharp.dll" #>
<#@ Assembly name="$(SolutionDir)packages\AmazedSaint.ElasticObject.1.2.0\lib\net40\AmazedSaint.Elastic.dll" #>
<#@ Import Namespace="System.Xml" #>
<#@ Import Namespace="System.Xml.Linq" #>
<#@ Import Namespace="AmazedSaint.Elastic" #>
<#@ Import Namespace="System.Collections.Generic" #>
<#@ Import Namespace="System.Collections" #>
<#@ Import Namespace="AmazedSaint.Elastic.Lib" #>
<#@ output extension=".js" #>
<#
var model=FromFile("CanvasModel.xml");
foreach(var c in model["shape"]) //get all classes
WriteShapeMethod(c);
#>
<#+
//Create an elastic object
private dynamic FromFile(string file)
{
var path= Host.ResolvePath(file);
return XDocument.Load(path).Root.ToElastic();
}
//Write a shape method
private void WriteShapeMethod(dynamic c)
{
#>
//Method generated
var <#= c.name #> = function (<#=c.HasAttribute("params")
?"canvas, " + c.@params :"canvas" #>)
{
if (canvas.getContext){
var ctx = canvas.getContext('2d');
ctx.beginPath();
<#+
var tokens = new System.Collections.Generic.Dictionary<string, string>()
{
{"bcurve","bezierCurveTo"},
{"qcurve","quadraticCurveTo"},
{"line","lineTo"},
{"to","moveTo"},
{"arcTo","arcTo"}
};
foreach (var member in c[null])
{
string mname = tokens.ContainsKey(member.InternalName)
? tokens[member.InternalName] : member.InternalName;
if (member.HasAttribute("params"))
{
if (member.InternalName=="shape")
WriteLine("\t" + member.name + "(canvas," + member.@params + ");");
else
WriteLine("\tctx." + mname + "(" + member.@params + ");");
}
else if (member.HasAttribute("value"))
WriteLine("\tctx." + mname + "=" + member.@value + ";");
else
WriteLine("\tctx." + mname + "();");
}
if (c.HasAttribute("type"))
WriteLine("\tctx." + c.type + "();");
#>
}
}
<#+
}
#>
好的,像往常一样,祝您编程愉快。享受这些简单的元编码小点心。如果您对更多“动态”感兴趣,请参阅我以前的一些文章
您喜欢 .NET 和 C# 吗?不要错过我的博客 http://www.amazedsaint.com