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

自定义资源设计器生成的代码

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.83/5 (6投票s)

2008年12月19日

CPOL

8分钟阅读

viewsIcon

48600

downloadIcon

1017

自定义资源设计器生成的代码。

引言

Visual Studio 自带 ResX 设计器,可帮助您在项目中定义资源。在后台,它会生成代码以以编程方式访问这些资源。自 VS 2003 以来,它几乎没有变化,能够完成工作,并且每个人都知道如何使用它。

然而,有一个缺点。您无法自定义生成的代码。与几乎所有其他设计器一样,它能正常工作,但您可能想要一些不同的东西。撰写本文实际上受到了 Code Project 上一篇精彩文章的启发:《扩展强类型资源生成器》,作者是 Dmytro Kryvko。正如文章引起的兴趣和评论所证明的那样,对不同事物的需求确实存在。

我将向您展示一种不同且更简单的方法,如何在几分钟内自定义和丰富 ResX 生成的代码。与上述解决方案不同,您将能够在同一台机器上同时运行特定于解决方案的自定义,并且在应用自己的自定义时,能够实现更快的自定义/测试/使用周期。您也不需要学习自定义工具如何工作来创建自己的自定义。自定义对标准的 ResX 设计器完全透明,您仍然可以使用它来定义资源

简要说明

根据所需生成的代码与 ResX 设计器生成的代码的差异程度,我们可以将问题分为三种场景。每种场景将在下面更详细地讨论,并将基于一个名为 Reegenerator 的免费 Visual Studio 工具来实现。

使用此工具,您可以在解决方案中定义一个单独的项目,其中包含不属于构建过程的代码生成器。然后,您将资源文件 (.resx) 附加到该项目中定义的代码生成类。当您使用 ResX 设计器并保存文件时,代码生成将自动调用,以便创建依赖于 resx 文件的 .Designer.cs 文件。

Using the Code

从上面的链接下载一个 zip 文件。您需要安装 Visual Studio 2008 和 Reegenerator。解压文件,并将解决方案文件从解压的文件夹加载到 Visual Studio 中。该解决方案包含一个名为 CustomizeDesigners 的项目,其中包含代码生成器,以及一个名为 Business 的项目,其中包含 resx 文件和资源文件。在 Business 和 RenderersLibrary 项目中,都有三个 Scenario 文件夹,其中包含 resx 文件和代码生成器,如下面每种场景所述。除此之外,还有一个 ResXFileCodeGeneratorEx 文件夹,它模拟了 Dmytro 的自定义工具所做的工作。

要触发代码生成,您需要编辑并保存 resx 文件,或者右键单击它并选择“运行自定义工具”。要调试,只需在 RenderersLibrary 项目中的代码生成器代码处设置断点,然后启动调试会话。该会话将调用 Visual Studio 的第二个实例。在第二个实例中加载相同的解决方案,然后通过保存 resx 文件来触发代码生成。生成的代码将作为项目项(通常是“.Designer.cs”)保存,并依赖于 resx 文件。您需要单击 resx 项目项左侧的加号才能展开其子项。

要查看 resx 项目项附加到哪个项,请右键单击它并选择“附加渲染器...”("Attach Rendererers...")。

下图显示了您的工作环境将是什么样子

场景 1 - 小改动

在这种情况下,我们最有可能希望简单地调用 ResX 默认代码生成器,将生成的代码作为字符串获取,以我们想要的方式对其进行操作,然后将修改后的代码发送回 Visual Studio。

Business 项目中 Scenario1 文件夹下的 resx 文件链接到 CustomizeDesigners\Scenario1\SmallChangesToDefaultResX 类。实现所需修改的代码非常简单

    public class SmallChangesToDefaultResX : Generators.CodeRenderer
    {
        /// <summary>
        /// Default custom tool name for resx files.
        /// </summary>
        public const string CustomToolName = "ResXFileCodeGenerator";

        public override Generators.RenderResults Render()
        {
            // execute the default custom tool.
            byte[] resultsAsBytes = base.RunOtherCustomTool(CustomToolName);
            // get Microsoft's generated code as string.
            string results = System.Text.Encoding.Default.GetString(resultsAsBytes);
            // transform the string.
            string modifiedResults = AddSomething(results);
            // return the string to be saved into the .Designer.cs file.
            return new Generators.RenderResults(modifiedResults);
        }

        private string AddSomething(string results)
        {
            results = results.Replace("internal class", "internal partial class");
        
            results = string.Format(@"// -------------------------------------------
// Automatically generated with Kodeo's Reegenerator
// Generation date: {0}
// Generated by: {1}
// -------------------------------------------------------
{2}",
                       System.DateTime.Now.ToString("yyyy-MM-dd hh:mm"),
                       System.Security.Principal.WindowsIdentity.GetCurrent().Name,
                       results);
            return results;
        }    

如上面的代码所示,我们调用标准的代码生成器来检索 Microsoft 设计器生成的代码。一旦您拥有了它,就可以通过向其中添加代码,或者使用字符串操作例程或正则表达式替换其中的某些部分来对其进行操作。上面的代码将生成的 Resources 类转换为部分类并添加了一些注释。

要进行测试,请在 Business 项目中编辑 Business\Scenario1\Resources.resx,保存它,展开其子项,然后打开 Business\Scenario1\Resources.Designer.cs。您可以看到文件中添加了 Microsoft 代码的额外代码。

要进行调试,请在 Render 方法中设置断点,启动调试会话,在第二个 Visual Studio 实例中加载相同的解决方案,并执行与测试时相同的步骤。

场景 2 - 重大添加

在此场景中,您对 Microsoft 生成的内容感到满意,但您需要额外的代码。例如,可以在 Microsoft 生成的 Resources 类中添加一个方法。

实现此目的的最简单方法是将 resx 文件链接到两个代码生成器。第一个调用 Microsoft 的代码生成器,您可能希望像场景 1 一样稍微修改结果。第二个生成额外的代码。

为了正确构建 resx 文件的代码生成器,您需要能够将其反序列化为可序列化的类实例。这可以通过在代码生成器项目中拥有一个模式来实现,该模式用于从模式生成可序列化的类。第二个代码生成器将 resx 文件反序列化为此类,然后使用它来生成所需的内容。

resx 文件的模式存储在 resx 文件中,如果您使用 XML 编辑器编辑它们,可以看到它。该模式在 CustomizeDesigners 项目中复制为 ResourcesSchema.xsd。它链接到 CustomizeDesigners\XsdRenderers.rgt 代码生成器,该生成器调用与 xsd.exe 相同的机制来从模式创建可序列化的类。展开 ResourcesSchema.xsd 的子项目项以查看可序列化的类。

Business\Scenario2\Resources.resx 链接到两个代码生成器(右键单击它并选择“附加渲染器...”("Attach Rendererers...") 进行检查),在您保存它时会创建/更新两个 *.cs 文件。Business\Scenario2\Resources.Designer.cs 文件由 CustomizeDesigners\Scenario2\SmallChangesToDefaultResX.cs 生成,与场景 1 完全相同。

Business\Scenario2\Resources.Hello.cs 文件由 CustomizeDesigners\Scenario2\AddHelloMethodToResourcesClass.rgt 生成。这是一个代码生成器模板文件,看起来非常像一个 aspx 页面。它还有一个代码隐藏文件 CustomizeDesigners\Scenario2\AddHelloMethodToResourcesClass.rgt.cs,其中包含手动编写的代码。CustomizeDesigners\Scenario2\AddHelloMethodToResourcesClass.Designer.cs 是模板的代码表示。与代码隐藏文件一起,它构成一个类,将在 resx 文件保存时调用。

CustomizeDesigners\Scenario2\AddHelloMethodToResourcesClass.rgt 代码生成器模板看起来像

<%@ Template Language="C#" ClassName="AddHelloMethodToResourcesClass" %>
<%@ Import Namespace="System" %>
 
// -------------------------------------------------------
// Automatically generated with Kodeo's Reegenerator
// Generator: Scenario2.AddHelloMethodToResourcesClass
// Generation date: <%= System.DateTime.Now.ToString("yyyy-MM-dd hh:mm") %>
// Generated by: <%= System.Security.Principal.WindowsIdentity.GetCurrent().Name %>
// -------------------------------------------------------

namespace <%= base.ProjectItem.CodeNamespace %>
{
	partial class Resources 
	{
<% RenderHello(); %>	
	}
}

<%@ Method Name="RenderHello" %>
		public string Hello()
		{
			return "Hello from the <%= this.NumberOfResources %> 
						resources defined";
		}
<%/ Method %>   

与 aspx 不同,您可以定义渲染方法,这些方法可以帮助您将代码生成工作分解为更易于管理的单元。

Scenario2\AddHelloMethodToResourcesClass.rgt.cs 模板代码隐藏看起来像

    public partial class AddHelloMethodToResourcesClass
    {
        /// <summary>
        /// The root element as deserialized from the resources file.
        /// </summary>
        root _root;

        public override void PreRender()
        {
            base.PreRender();
            this._root = root.FromFileWithNoValidation(base.ProjectItem.FullPath);
        }

        private int NumberOfResources
        {
            get
            {
                int count = 0;
                if (this._root.Items != null)
                {
                    foreach (object o in this._root.Items)
                    {
                        rootData rd = o as rootData;
                        if (rd != null)
                            count++;
                    }
                }
                return count;
            }
        }   

在代码生成发生之前,resx 文件会被反序列化到 _root 属性中。根类是从 CustomizeDesigners\ResourcesSchema.xsd 生成的可序列化类。

要进行测试,请编辑 Business\Scenario2\Resources.resx,保存它,展开其子项,并检查两个子 CS 文件的内容。

要进行调试,请在 PreRender 方法中设置断点,启动调试会话,在第二个 Visual Studio 实例中加载相同的解决方案,并执行与测试时相同的步骤。

场景 3 - 完全重写

当您对设计器生成的代码完全不满意,或者您只是想将设计器用于其他目的时,就会出现这种情况。Dmytro 的自定义工具就是一个很好的例子。

CustomizeDesigners\Scenario3\ResourcesRenderer.rgt 在复制 Microsoft 的代码生成器方面做得很好。它的目的是作为一个示例,提供入门所需的熟悉度和结构,以便利用已经完成的设计器来构建高度自定义的代码生成器。

Business\Scenario3\Resources.resx 链接到 CustomizeDesigners\Scenario3\ResourcesRenderer.rgt,您可以按照前几个场景的相同方式进行测试和调试。

复制 Dmytro 的 ResXFileCodeGeneratorEx

这是一个场景 3 的真实示例。它以更少的精力模仿了 Dmytro 的自定义工具的输出。它更容易修改和测试。您可以在同一台机器上,甚至在同一个解决方案中拥有不同类型的资源代码生成。

CustomizeDesigners\ResXFileCodeGeneratorEx\ResXFileCodeGeneratorEx.rgt 实现了代码生成,并且基本上是 CustomizeDesigners\Scenario3\ResourcesRenderer.rgt 的修改版本。

Business\ResXFileCodeGeneratorEx\Resources.resx 链接到 CustomizeDesigners\ResXFileCodeGeneratorEx\ResXFileCodeGeneratorEx.rgt,您可以按照前几个场景的相同方式进行测试和调试。

最后说明

现在您可以使用 ResX 设计器实现无限的可能性。以下是一些示例

  • 您可以构建自己的 ResX 自定义,这些自定义在您使用设计器时会自动创建。
  • 只要设计器能足够好地完成您要实现的目标,您就可以将其用于与资源完全不同且无关的其他目的。

最后但并非最不重要的是,非常感谢 Dmytro。

© . All rights reserved.