65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.59/5 (18投票s)

2008 年 4 月 11 日

CPOL

5分钟阅读

viewsIcon

59029

downloadIcon

976

如何扩展 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 并让 StartInfoFileName 指向 xsd.exexsd.exe 有一些参数

  • /c: 创建一个类(而不是一个 DataSet),
  • /l: 语言(VB 表示 Visual Basic,CS 表示 C# 等),
  • /n: 命名空间
  • 文件名
  • /out: 要将生成的源代码写入的目录。

通常,Language 参数的值与基类 GetDefaultExtension 方法的返回值相同。但是,我注意到,如果我在 J# 中创建一个新类,该文件将具有 .vjs 扩展名,而语言属性应为 "jsl"。GetLanguage 方法负责此映射。

如果我们有一个 Books 集合,其中包含 Book 元素,每个 Book 都有一个 Author 和一个 Title,并且如果我们省略了命名空间参数,那么 BooksBook 对象将在默认命名空间中创建,这在处理许多对象时会变得混乱。所以,我们最好提供一个。如果 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 文件生成源代码!

用户手册

这一切都很好,但我该如何使用它?很简单 :-)

  1. 向您的项目添加一个新的 XML 架构。
  2. addnewitem.png

  3. 编写架构。
  4. schema.png

    <?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>
  5. 设置要使用的自定义工具。
  6. properties.png

    customtool.png

  7. 运行自定义工具(或保存您的架构)。
  8. runcustomtool.png

瞧:您的强类型 Books

result.png

结论

首先,我希望您发现这个工具有用 :-)。我也希望它能激励您编写自己的自定义工具,并希望您能写一篇关于它的文章。

历史

  • 2008-04-11: 初始文章。
  • 2008-05-05: 更改了 GAC 的说明。
© . All rights reserved.