任何数据类型的源代码生成器






4.59/5 (18投票s)
如何扩展 Visual Studio 以便为任何数据类型生成代码。
引言
您是否有时需要一个用于复杂数据类型的类?我需要,但我觉得自己输入它太麻烦了。您知道 .NET Framework 有一个工具可以从 XSD 文件生成类吗?它有,但需要命令行,而且每次更改 XML 架构时,都必须重新运行它。您知道 Visual Studio 可以处理自定义工具吗?它有,但如何创建它是相当神秘的,至少对我来说是这样。
本文将介绍如何创建自定义工具、安装它以及使用它。该示例可以从 XSD 文件生成类,您可以将其直接集成到 Visual Studio 2005 和 2008(已测试)中。
背景
Visual Studio 在创建数据集时内部使用 xsd.exe。但是,它也能够从其他 XSD 文件生成强类型类,我将在本文中进行演示。
要创建自定义工具,您需要创建一个继承自 Microsoft.VisualStudio.Design.BaseCodeGeneratorWithSite
的类,但出于某种原因,该类是内部的,因此无法继承。 Gert Servranckx 创建了一个名为 Microsoft.CustomTool.BaseCodeGeneratorWithSite
的类,可以从中继承。
使用代码
项目
首先,我创建了一个名为“xsd2class”的新类库项目。因为自定义工具是一个 COM 对象,所以我勾选了 COM 可见(在属性 > 应用程序 > 程序集信息中)。复制项目的 GUID,因为稍后在定义自定义工具的类时需要它。
这个特定的 COM 对象实际上是一个 Visual Studio 应该能够找到的 .NET 程序集,因此它需要位于 GAC 中。为了做到这一点,它需要被签名,所以我创建了一个新的 key.snk 并用它签名了程序集。如果程序集被自动添加到 GAC,可以节省一些工作,所以我设置了一个带有命令行参数的生成后事件
"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm" /codebase "$(TargetPath)"
自定义工具类骨架
如前所述,我们需要一个 COM 可见且继承自 Microsoft.CustomTool.BaseCodeGeneratorWithSite
的类,所以让我们创建一个。让我们先添加对程序集“Microsoft.VisualStudio.BaseCodeGeneratorWithSite.dll”的引用。然后,添加一个新类,如下所示。(Guid
特性的值与您从程序集信息中复制的 GUID 相同。)
namespace Xsd2Class
{
[ComVisible(true)]
[Guid("0e439c89-b1be-489d-a0f4-c2d191db6f9b")]
public class Xsd2Class : Microsoft.CustomTool.BaseCodeGeneratorWithSite
{
}
}
Microsoft.CustomTool.BaseCodeGeneratorWithSite.GenerateCode
方法是抽象的,所以我们必须重写它
protected override byte[] GenerateCode(string inputFileName, string inputFileContent)
{
return new byte[] { };
}
为了让 Visual Studio 识别自定义工具,它需要在注册表中注册(还能去哪里)
- 对于 Visual Studio 2005,是 "HKLM\SOFTWARE\Microsoft\VisualStudio\8.0\Generators",
- 对于 Visual Studio 2008,是 "HKLM\SOFTWARE\Microsoft\VisualStudio\9.0\Generators"。
Generators 键有以 GUID 命名的子键。它们的意思是
- {164B10B9-B200-11D0-8C61-00A0C91E29D5}: Visual Basic
- {E6FDF8B0-F3D1-11D4-8576-0002A516ECE8}: J#
- {FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}: C#
- 其他:不知道。
我们必须为所有三种语言注册我们的自定义工具。所以,在每个键中,我们再创建一个名为“Xsd2Class”的键,其值为
- (默认值): 字符串: Xsd2Class (这是我们的自定义工具的名称)
- CLSID: 字符串: {0e439c89-b1be-489d-a0f4-c2d191db6f9b} (您从项目程序集信息中复制的 GUID) 和
- GeneratesDesignTimeSource: DWORD: 1
对于 Visual Studio 2005,reg 文件将是
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\
Generators\{164B10B9-B200-11D0-8C61-00A0C91E29D5}\Xsd2Class]
@="Xsd2Class"
"CLSID"="{0e439c89-b1be-489d-a0f4-c2d191db6f9b}"
"GeneratesDesignTimeSource"=dword:00000001
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\
Generators\{E6FDF8B0-F3D1-11D4-8576-0002A516ECE8}\Xsd2Class]
@="Xsd2Class"
"CLSID"="{0e439c89-b1be-489d-a0f4-c2d191db6f9b}"
"GeneratesDesignTimeSource"=dword:00000001
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\
Generators\{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}\Xsd2Class]
@="Xsd2Class"
"CLSID"="{0e439c89-b1be-489d-a0f4-c2d191db6f9b}"
"GeneratesDesignTimeSource"=dword:00000001
对于 Visual Studio 2008,只需将 "8.0" 替换为 "9.0" 即可。
现在,我们有了一个工作的自定义工具,它什么也不做!
做些事情
Xsd.exe
如前所述,xsd.exe 是一个命令行工具。所以,让我们为它创建一个包装器。
private void GenerateCodeFile(string inputFileName)
{
FileInfo FI = new FileInfo(inputFileName);
Process proc = new Process();
proc.StartInfo.FileName = @"C:\Program Files\" +
@"Microsoft Visual Studio 8\SDK\v2.0\Bin\xsd.exe";
string Namespace = FI.Name.Substring(0, FI.Name.Length - FI.Extension.Length);
string Language = GetLanguage();
proc.StartInfo.Arguments =
String.Format(@"/c /l:{0} /n:{1} ""{2}"" /out:""{3}""",
Language, Namespace, inputFileName, FI.DirectoryName);
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.RedirectStandardError = true;
proc.Start();
proc.WaitForExit();
if (proc.ExitCode != 0)
{
using (StreamReader SR = proc.StandardError)
{
string Errors = SR.ReadToEnd();
if (!(string.IsNullOrEmpty(Errors)))
{
throw new Exception(Errors);
}
}
}
}
我们创建一个新的 Process
并让 StartInfo
的 FileName
指向 xsd.exe。xsd.exe 有一些参数
- /c: 创建一个类(而不是一个
DataSet
), - /l: 语言(VB 表示 Visual Basic,CS 表示 C# 等),
- /n: 命名空间
- 文件名
- /out: 要将生成的源代码写入的目录。
通常,Language 参数的值与基类 GetDefaultExtension
方法的返回值相同。但是,我注意到,如果我在 J# 中创建一个新类,该文件将具有 .vjs 扩展名,而语言属性应为 "jsl"。GetLanguage
方法负责此映射。
如果我们有一个 Books
集合,其中包含 Book
元素,每个 Book
都有一个 Author
和一个 Title
,并且如果我们省略了命名空间参数,那么 Books
和 Book
对象将在默认命名空间中创建,这在处理许多对象时会变得混乱。所以,我们最好提供一个。如果 XSD 文件名为 Books.xsd,我认为 "Books" 是一个不错的命名空间。
文件名是 GenerateCode
方法的 inputFilename
参数,输出目录是 inputFile
的目录。
因为我们不想每次都看到命令行窗口出现,所以我们将 UseShellExecute
设置为 false
,将 CreateNoWindow
设置为 true
。
因为我们想知道是否出了什么问题,所以我们重定向了标准错误。
返回结果
GenerateCode
方法需要结果作为 byte
数组,所以我们只需读取 XSD 的输出并将其作为 byte
数组返回
private byte[] GetGeneratedFileContent(string inputFileName)
{
string DefaultExtension = GetDefaultExtension();
string outputFilename = inputFileName.Replace(".xsd", DefaultExtension);
FileInfo FI = new FileInfo(outputFilename);
using (StreamReader FS = FI.OpenText())
{
string Contents = FS.ReadToEnd();
return System.Text.Encoding.ASCII.GetBytes(Contents);
}
}
整合
现在,把它们组合起来…
protected override byte[] GenerateCode(string inputFileName,
string inputFileContent)
{
GenerateCodeFile(inputFileName);
byte[] GeneratedFileContent =
GetGeneratedFileContent(inputFileName);
return GeneratedFileContent;
}
…我们有了一个工作的自定义工具,可以从 XSD 文件生成源代码!
用户手册
这一切都很好,但我该如何使用它?很简单 :-)
- 向您的项目添加一个新的 XML 架构。
- 编写架构。
- 设置要使用的自定义工具。
- 运行自定义工具(或保存您的架构)。
<?xml version="1.0" encoding="utf-8"?>
<xs:schema
id="Books"
targetNamespace="http://tempuri.org/Books.xsd"
elementFormDefault="http://tempuri.org/Books.xsd"
xmlns:mstns="http://tempuri.org/Books.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Books" type="Books">
<xs:annotation>
<xs:documentation>
Collection of Books.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:complexType name="Books">
<xs:sequence>
<xs:element name="Book" type="Book">
<xs:annotation>
<xs:documentation>
A book with a title and an author.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Book">
<xs:sequence>
<xs:element name="Title" type="xs:string">
<xs:annotation>
<xs:documentation>
The Book's main title.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="Author" type="xs:string">
<xs:annotation>
<xs:documentation>
The name of the main writer.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>
瞧:您的强类型 Books
类
结论
首先,我希望您发现这个工具有用 :-)。我也希望它能激励您编写自己的自定义工具,并希望您能写一篇关于它的文章。
历史
- 2008-04-11: 初始文章。
- 2008-05-05: 更改了 GAC 的说明。