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

在 Visual Studio 中进行构建时进行配置自定义

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (10投票s)

2008年6月22日

CPOL

5分钟阅读

viewsIcon

70522

downloadIcon

342

在编译时修改具有特定于机器或生成的配置文件。

引言

本文介绍了一种解决方案,用于解决多个开发人员和环境的配置文件管理的长期问题。核心是一个命令行工具,该工具将基本(默认)配置文件与一个截断的文件(差异或“diff”文件)合并。此 diff 文件仅包含需要添加或更改的元素。

问题

你懂的。两名、十名或二十名开发人员拥有 Web 和/或应用程序配置文件的可写本地副本。QA、暂存和生产服务器也有自己的副本。必须将更改传播到所有版本,其中一些版本可能不在源控件中。各种变通方法被实施——有时,在同一个项目上实施不止一种。

电子邮件满天飞。开发人员和测试人员正在浪费时间,追踪假性错误,因为有人没有更新配置文件。

解决方案

基于机器名或生成配置,将基本配置文件与“差异”文件合并。采用此技术,无需本地维护文件;所有文件都可以签入源控件,并且每个人的文件结构都相同。

这是示例中的 app.config,它演示了合并可以处理的一些不同场景

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="ApplicationConnString"
      connectionString="Data Source=OtherBox; ... ;User ID=not_sa;Password=abc"
      providerName="System.Data.SqlClient" />
  </connectionStrings>
  <appSettings>
    <add key="GeneralSettingOne" value="ValueOne" />
    <add key="GeneralSettingTwo" value="ValueTwo" />
  </appSettings>
  <system.web>
    <httpHandlers>
      <add verb="*" path="*.example" type="ExampleHandler" />
      <add verb="GET,HEAD" path="*.specific" type="SpecificHandler"/>
    </httpHandlers>
  </system.web>
    ...
  Pages and pages of other stuff
    ...
</configuration>

现在,假设一个开发人员想自定义此文件的多个方面。她需要修改连接字符串,修改一个 appSetting,并添加一个新的 appSetting。她还将添加一个全新的诊断部分,用于跟踪。diff 文件 app.devbox1.config 可能看起来像这样

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="ApplicationConnString"
      connectionString="Data Source=MyBox; ... ;User ID=not_sa;Password=abc"
      providerName="System.Data.SqlClient" />
  </connectionStrings>
  <appSettings>
    <add key="GeneralSettingTwo" value="MyGeneralSettingValue" />
    <add key="MySetting" value="MyValue" />
  </appSettings>
  <system.diagnostics>
    <sources>
      ...
    </sources>
    <sharedListeners>
      ...
    </sharedListeners>
  </system.diagnostics>
</configuration>

此文件仅包含要添加或更改的元素,而不是将保持不变的其他内容的页面。在生成后事件期间,diff 文件会被合并到源文件中并放置在输出文件夹中。

diff 文件中具有与基本文件中的 namekey 属性匹配的元素会覆盖基本文件值。因此,ApplicationConnStringGeneralSettingTwo 会被覆盖。

如果 namekey 是新的,则会将该元素添加到基本文件中,因此会添加 MySetting

如果基本文件树中没有对应的元素,则会添加 diff 文件中的元素及其子元素。因此,整个 <system.diagnostics> 部分将被添加到目标文件中。

工作原理

postbuild 命令指定了基本、diff 和目标配置文件。它调用 ConfigMerge.bat,后者又调用 ConfigMerge.exe 实用程序。命令行看起来像这样

"$(SolutionDir)ConfigMerge.bat" "$(ProjectDir)" 
   app.config "$(OutDir)$(TargetFileName).config" 
   app.%COMPUTERNAME%.config "app.$(ConfigurationName).config"

批处理文件和实用程序文件可以安全地用于包含空格的路径;这就是命令行中所有引号的原因。让我们检查一下每个元素

  • $(SolutionDir)ConfigMerge.bat - 批处理文件路径。放置在解决方案根目录中,可用于多个项目。
  • $(ProjectDir) - 项目根目录。用于构造文件路径并查找 ConfigMerge.exe 文件,该文件假定在批处理中位于解决方案根目录。
  • app.config - 基本配置文件名(对于 Web 项目,请更改为 web.config)。
  • $(OutDir)$(TargetFileName).config - 目标文件名。如果它与基本文件相同,将被覆盖。
  • app.%COMPUTERNAME%.config - 要查找的第一个 diff 文件;在这种情况下,文件名为执行生成的计算机名称。对于将在本地运行的开发人员生成很有用。
  • app.$(ConfigurationName).config - 可选的第二个要查找的文件;在这种情况下,文件名为生成配置名称。对于将在生成服务器上运行的生成很有用。

批处理文件没有什么特别之处;它会检查参数并调用合并实用程序。

IF EXIST "%_diff%" GOTO DOMERGE

:: Optional second file to do a diff against, if it exists
IF NOT [%~5]==[] (
    SET _diff=%_path%%~5
    IF EXIST "%_diff%" GOTO DOMERGE
)

如果未找到任何 diff 文件,它只会将基本文件复制到目标文件。

:DOMERGE
:: Modify the config with the difference file
"%_path%..\ConfigMerge.exe" "%_config%" "%_diff%" "%_target%"
:: If ConfigMerge.exe reports an exception, pass this up to visual studio
IF ERRORLEVEL 1 EXIT 1

在此过程中,Visual Studio 的输出窗口会显示有关合并进度和正在执行的操作的消息。ConfigMerge.exe 中的异常将导致生成失败。

Compile complete -- 0 errors, 0 warnings
Test Application -> C:\Source\ConfigMerge\Test Application\bin\Debug\Tes...
"C:\Source\ConfigMerge\ConfigMerge.bat" "C:\Source\ConfigMerge\Test Appl...
ConfigMerge: Modifying [C:\Source\ConfigMerge\Test Application\app.confi...
ConfigMerge: Wrote target file [C:\Source\ConfigMerge\Test Application\b...
========== Build: 2 succeeded or up-to-date, 0 failed, 0 skipped =========

ConfigMerge.exe 控制台应用程序也相当简单。由于它依赖于特定的属性,因此不适合通用的 XML 合并,但对于 .NET 配置文件来说效果很好。

ConfigMerge.exe 递归处理 diff XML 文档中的节点

static void ProcessNode(XmlNode baseNode, XmlNode diffNode)
{
    // Check each of the children to see if they match
    foreach (XmlNode diffNodeChild in diffNode.ChildNodes)
    {
        . . . 
        
        // Look for corresonding nodes in the base document.

        // If the child node has a name, key, or other recognized set of 
        // attributes, we'll look for a corresponding node based on that
        bool namedPath = false;
        string path = GetComparisonPath(diffNodeChild, out namedPath);

        XmlNodeList children = baseNode.SelectNodes(path);

        // Does the base document have corresponding nodes?

        . . . 

        if (children.Count == 1)
        {
            // Replace the node if it is recognized (update with new
            // information) or if it is an "endpoint" node.
            // For endpoints, it is assumed that it wouldn't be in the diff
            // file if it wasn't different somehow, even if there is no 
            // name attribute.
            if (namedPath || !diffNodeChild.HasChildNodes)
            {
                XmlNode newNode = baseNode.OwnerDocument.ImportNode(
                    diffNodeChild, true);
                baseNode.ReplaceChild(newNode, children[0]);
            }
            else
            {
                // Node is not named, and has children; process the 
                // children looking for differences
                ProcessNode(children[0], diffNodeChild);
            }
        }
        else
        {
            // No corresponding node; stick this whole node in there
            XmlNode newNode = baseNode.OwnerDocument.ImportNode(
                diffNodeChild, true);
            baseNode.AppendChild(newNode);
        }
    }
}

匹配是基于一组识别的参数完成的。目前,这包括以下内容

static string[] UniqueAttributes = new string[] {
    "name", 
    "key", 
    "verb,path" };  // No space after comma!

verbpath 用于 HttpHandler。如果需要其他一组属性,可以将其添加到此数组中。

集成到您的项目中

这部分很简单。只要将 ConfigMerge 批处理和控制台应用程序放在解决方案根目录中,您就可以在不修改的情况下使用批处理文件和 postbuild 命令行示例。将文件放到解决方案的根目录中,将它们添加到项目中(它们会在解决方案资源管理器下的“解决方案项”中显示),然后将 postbuild 命令行复制到您的项目中。

现在,将您的本地 app.config 复制到 app.mycomputername.config,从其中剪切所有未修改的部分,然后获取最新版本。生成。您就完成了。其他团队成员可以复制现有的 diff 文件,重命名它,并为他们的本地机器进行修改。

对于使用 web.config 文件的 ASP.NET 或 WCF 应用程序,存在一个小的复杂性,即配置文件不会传输到输出目录。

合并 web.config 文件有两种选择

  1. 创建一个具有不同名称的模板文件,并将合并的目标设置为 web.config。如果未检测到 diff 文件,批处理文件会复制基本文件,因此 web.config 总是会被创建。

    "$(SolutionDir)ConfigMerge.bat" "$(ProjectDir)" 
      web.template.config web.config web.%COMPUTERNAME%.config 
      "web.$(ConfigurationName).config"
  2. 在 postbuild 命令行中将基本和目标都设置为 web.config,这将导致合并覆盖现有的 web.config。下次从源控件获取时它可能会被覆盖,但会由生成重新创建。

    "$(SolutionDir)ConfigMerge.bat" "$(ProjectDir)" 
      web.config web.config web.%COMPUTERNAME%.config "web.$(ConfigurationName).config"

示例项目

可下载的 Visual Studio 2005 解决方案包括带有源代码的 ConfigMerge 项目,以及 ConfigMerge.bat 文件。它还包括一个带有配置文件的示例应用程序项目,已为此项目配置了 postbuild。

要查看效果,请将 app.devbox1.config 重命名为您的计算机名称,或重命名为 app.debug.config(当然,用于调试生成)。

该项目应能毫无问题地升级到 Visual Studio 2008。

历史

  • 2008.06.22 - 首次发布。
© . All rights reserved.