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

在所有环境中(开发、测试、生产)使用单一 web.config 文件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (14投票s)

2007年2月15日

CPOL

8分钟阅读

viewsIcon

188115

downloadIcon

1170

您是否遇到过这样的问题:每次将网站发布到新环境时,都需要针对每个环境自定义 web.config 中的设置?本文介绍了一种解决方案,您只需设置一次 web.config,然后让代码完成其余工作。

Configuration at work

引言

您是否遇到过这样的问题:每次将网站发布到新环境时,都需要针对每个环境自定义 web.config 中的设置?例如,从开发服务器迁移到测试服务器时,需要更改连接字符串。或者从测试迁移到生产时,需要更改 SMTP 主机。本文介绍了一种解决方案,您只需设置一次 web.config,然后让代码来决定它所处哪个环境。

背景

我发现在部署网站到不同环境时,我通常不会复制 web.config 文件。我担心会忘记该环境的一些自定义设置。有时这会导致问题,因为 web.config 文件中已进行的更改是新的。我决定需要一个解决方案,可以为每个环境设置 web.config 文件,然后您就无需担心其余问题。您只需发布单个 web.config 文件,然后让系统处理每个环境的自定义设置。

问题

我发现在生产和测试服务器上,我从未覆盖现有的 web.config 文件。每当在开发环境中修改 web.config 文件时,我都需要手动修改这些文件,以便为测试和生产环境提供正确的值。我真的不想再这样做了。

解决方案

通过 ASP.NET 2.0,我们在处理 web.config 文件方面获得了许多新功能。因此,我在 web.config 中创建了一个自定义节,用于定义哪些服务器属于哪些环境。接下来,我创建了另一个自定义节来定义每个环境。最后,我使用了一些自定义类来访问我们的配置设置。访问此自定义类(EnvironmentSettings)一次即可为当前环境修复配置文件。我使用了 static(VB 中为 Shared)构造函数,以便代码只访问一次配置文件。然后,您可以使用静态 EnvironmentSettings 类来访问您的配置设置。

一些细节

这是 web.config 文件需要的第一部分:

<configSections>
 <section name="environmentConfiguration" 
     type="ConfigIt.KeyValueConfigurationSection, ConfigIt"/>
 <section name="dev" type="ConfigIt.KeyValueConfigurationSection, ConfigIt"/>
 <section name="test" type="ConfigIt.KeyValueConfigurationSection, ConfigIt"/>
 <section name="prod" type="ConfigIt.KeyValueConfigurationSection, ConfigIt"/>
</configSections>

configSections 会直接放入您的 web.config 文件中。它定义了我们将要添加的自定义节。

接下来,我们有一个自定义节来定义我们的服务器:

<environmentConfiguration>
 <elements>
  <add key="devpcname" value="dev" />
  <add key="testpcname" value="test" />
  <add key="prodpcname" value="prod" />
 </elements>
</environmentConfiguration>

请注意,“environmentConfiguration”是在上面的 configSections 中定义的。您可以在此处放置您的 PC 和服务器名称。由于代码默认假设找不到此处定义的机器名称,因此您实际上不必在此处放置开发 PC/服务器名称。重要的是要有您的测试和生产服务器名称。

接下来,我们有 devtestprod 自定义节:

<dev>
 <elements>
   <add key="SystemEmailAddress" value="administrator@d.com" />
   <add key="SMTPServer" value="devSMTP" />
 </elements>
 <connectionStrings>
   <add name="YourDatabase" 
     connectionString="Database=YourDatabase;Server=devsqlpcname;uid=DevUser;pwd=DevUser;"
     providerName="System.Data.SqlClient" />
  </connectionStrings>
</dev>
<test>
 <elements>
   <add key="SystemEmailAddress" value="administrator@t.com" />
   <add key="SMTPServer" value="testSMTP" />
 </elements>
 <connectionStrings>
   <add name="YourDatabase" 
    connectionString="Database=YourDatabase;Server=testsqlpcname;uid=TestUser;pwd=TestUser;"
    providerName="System.Data.SqlClient" />
 </connectionStrings>
</test>
<prod>
 <elements>
   <add key="SystemEmailAddress" value="administrator@p.com" />
   <add key="SMTPServer" value="prodSMTP" />
 </elements>
 <connectionStrings>
   <add name="YourDatabase" 
    connectionString="Database=YourDatabase;Server=prodsqlpcname;uid=ProdUser;pwd=ProdUser;"
    providerName="System.Data.SqlClient" />
 </connectionStrings>
</prod>

因此,您不必使用 appSetting,而是可以在正确环境的 elements 部分中添加一个新的键值对。请注意,每个环境都定义了一个 connectionStrings 部分。

接下来,让我们看一下 web.config 文件中正常的 connectionStrings 部分:

<connectionStrings configSource="ConnectionStrings.config" />

那里除了 configSource 什么都没有。configSource 告诉此部分,有一个外部文件包含 connectionStrings 部分。注意:外部配置文件仍然必须有一个 .config 扩展名。Web 浏览器无法访问这些扩展名。这是 ConnectionStrings.config 文件:

<?xml version="1.0" encoding="utf-8"?>
<connectionStrings>
   <clear />
   <add name="YourDatabase" connectionString=""
      providerName="System.Data.SqlClient" />
</connectionStrings>

请注意,connectionString 属性是空的。它将被上面定义的正确环境部分的正确值覆盖。

好了,到目前为止,您可能会问这一切都是关于什么的?为什么我们需要一个外部文件?嗯,我告诉你,我第一次尝试这个的时候,我没有使用外部文件。但是有一个问题。web.config 文件会被更新,但 WebConfigurationManager.ConnectionStrings 即使文件已更改也不会重新加载。现在我有理由怀疑,那是因为 IIS 工作进程实际上在更改配置文件,但我并不确定。因此,在使用外部配置文件时,您可以设置一个名为“RestartOnExternalChanges”的属性。当设置为 true 时,WebConfigurationManager.ConnectionStrings 确实会得到更新。

好了,您可能仍然想知道为什么您会关心这个变量?您不能只使用 EnvironmentSettings.ConnectionStrings 吗?答案是可以,但是……如果您当前使用的是 roleManager,在 providers 中,有一个名为 connectionStringName 的属性。它使用 WebConfigurationManager.ConnectionStrings,而不是您的自定义节。对于 profilemembership 也是如此。因此,如果您使用其中任何一个,WebConfigurationManager 获得正确的连接字符串至关重要。另外,如果您使用 SqlDataSource,您将在 HTML 中将 connectionString 属性设置为“<%$ ConnectionStrings:NorthwindConnectionString %>”;您没有使用自定义节 ConnectionStrings。所以情况也是一样的。

SMTP(邮件服务器)名称与此情况相同,如果您使用 ASP.NET 2.0 登录控件。所有邮件功能都假定您已使用正确的结构设置了 web.config 文件,其中包括 SMTP 邮件服务器名称(主机)。

那么它是如何工作的?我已经讨论了带有自定义节的 web.config 文件,现在让我们继续讨论代码。我创建了一个单独的类库项目,以便可以轻松地将此功能添加到任何 Web 项目中。在类库内部,有两个类。

第一个是 EnvironmentSettings 类。该类的静态构造函数会查看自定义节,决定代码运行在哪个计算机上,获取正确的自定义环境,然后将连接字符串和 SMTP 的自定义设置与 web.config 当前拥有的内容进行比较。如果它们不匹配,自定义设置将覆盖配置文件中的当前设置并保存文件。由于这是一个 static(VB 中为 Shared)构造函数,它应该每个应用程序域只发生一次。在下载的示例中,您会看到我添加了一个 Global.asax 文件,其中有一个 application_Start 事件。在该事件中,我访问了静态类,以便调用构造函数。

类库中的另一个类是 KeyValueConfigurationSection。该类定义了我们添加到 web.config 文件中的自定义节。它也是我们指向 web.config 的 configSections 部分的类。这是 C# 中的静态构造函数(我也提供了 VB.NET 源代码下载):

//Constructor

static EnvironmentSettings()
{
  KeyValueConfigurationSection section = null;

  // Get the current configuration file.

  Configuration config = 
  System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~",null,null,
         System.Environment.MachineName,
         System.Environment.MachineName + @"\WebConfigUser","WebConfigPassword");

  //Next Get which environment we are in based
  //off what the machine name this code is running on

  section = config.GetSection("environmentConfiguration") 
                                   as KeyValueConfigurationSection;
  string machineName = System.Environment.MachineName.ToLower();
  if (section.Elements[machineName] != null &&
      section.Elements[machineName].Value != null &&
      section.Elements[machineName].Value != string.Empty)
     {
       _environment = section.Elements[machineName].Value;
     }
  else
     { //Default to dev

       _environment = "dev";
     }

  _elements = ((KeyValueConfigurationSection)config.GetSection(_environment)).Elements;
  _connectionStrings = 
    ((KeyValueConfigurationSection)config.GetSection(_environment)).ConnectionStrings;

  //NOTE we only want to save the config file if something changed...

  Boolean somethingChanged = false;

  //Update the email stuff if environment specific value is different

  _systemNet = (System.Net.Configuration.NetSectionGroup)
                     config.GetSectionGroup("system.net");
  if (_systemNet.MailSettings.Smtp.Network.Host != null && 
      _elements["SMTPServer"] != null &&
      _systemNet.MailSettings.Smtp.Network.Host != 
                 _elements["SMTPServer"].Value)
    {
      _systemNet.MailSettings.Smtp.Network.Host = _elements["SMTPServer"].Value;
      somethingChanged = true;
    }

  //Update the connection strings if the environment specific value is different

  foreach (ConnectionStringSettings css in _connectionStrings)
  {
    Boolean foundIt = false;
                
    foreach (ConnectionStringSettings css2 in config.ConnectionStrings.ConnectionStrings)
    {
      if (css.Name.Trim() == css2.Name.Trim())
        {
          foundIt = true;
          if (css2.ConnectionString != css.ConnectionString)
            {                          
              css2.ConnectionString = css.ConnectionString;
              config.ConnectionStrings.SectionInformation.RestartOnExternalChanges = true;
                            
              somethingChanged = true;
            }
        }
    } //foreach css2

    if (!foundIt)
      {
        config.ConnectionStrings.ConnectionStrings.Add(css);
        config.ConnectionStrings.SectionInformation.RestartOnExternalChanges = true;
        somethingChanged = true;
      }

  } //foreach css

  if (somethingChanged)
    {                         
      config.Save(ConfigurationSaveMode.Modified);
    }
}

最后一步是网站如何更新 web.config 文件。答案是您需要在运行此网站的计算机上创建一个本地用户。然后授予该用户读写权限,以便它可以更新 web.config 文件。注意:您可以只授予 ASPNET 用户权限,但我**不**建议这样做,因为它有点不安全。接下来,您需要将 IIS_WPG 工作进程组添加到该文件夹的读写权限。

以下是我 C# 示例 page_load 事件中访问配置属性的一些示例:

string databaseName = "YourDatabase";
//Getting config items from the EnvironmentSettings static var...

lbEnvior.Text = "You are running in the " + 
                EnvironmentSettings.Environment + " Environment";
lbEnvirSQL.Text = EnvironmentSettings.ConnectionStrings[databaseName].ConnectionString;
lbEnvirSMTP.Text = EnvironmentSettings.SystemNet.MailSettings.Smtp.Network.Host;

//Getting config items from WebConfigurationManager

lbWebConfigManSQL.Text = 
  System.Web.Configuration.WebConfigurationManager.
  ConnectionStrings[databaseName].ConnectionString;
        
//Getting config items from the config file directly

System.Configuration.Configuration config = 
System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
lbWebConfigDirectSQL.Text = 
  config.ConnectionStrings.ConnectionStrings[databaseName].ConnectionString;
System.Net.Configuration.NetSectionGroup _systemNet = 
(System.Net.Configuration.NetSectionGroup)config.GetSectionGroup("system.net");
lbWebConfigDirectSMTP.Text = _systemNet.MailSettings.Smtp.Network.Host; 

//Getting Config items from ConfigurationManager NOTE this is not suggested in a Web site.

lbConfigManSQL.Text = ConfigurationManager.ConnectionStrings[databaseName].ConnectionString;

步骤

注意:您需要管理员权限才能执行其中一些步骤。

  1. 在要运行网站的计算机上创建一个本地用户(我选择了 WebConfigUser)。右键单击“我的电脑”。单击“管理”。单击“本地用户和组”上的加号。右键单击“用户”文件夹。单击“新建用户”。输入用户名、全名和密码。然后,取消选中“用户下次登录时必须更改密码”,并选中“密码永不过期”。单击“创建”。
  2. 转到 web.config 所在的目录。右键单击文件夹,选择“属性”。选择“安全”选项卡。单击“添加”按钮。您可能需要单击“位置”按钮,以便您的位置是您的计算机,而不是域。单击“确定”。选中“修改”复选框。接下来,添加 IIS_WPG 组。确保选中“修改”复选框。单击“确定”。
  3. EnvironmentSettings 类的构造函数中,有一个对 OpenWebConfiguration 的调用。在此调用内部,您需要输入刚才创建的本地用户的用户名和密码。
  4. web.config 中,添加一个 configSections。您可以直接从下载的示例文件中复制。
  5. web.config 中,添加 environmentConfiguration 自定义节。这是您列出服务器的地方。
  6. web.config 中,添加 devtestprod 或您想如何称呼您的环境的自定义节。确保每个环境节都有一个 ConnectionStrings 节。
  7. Web.config 中,将现有的 ConnectionStrings 节指向一个外部文件。用正确的代码创建外部文件。SMTP 节也一样。下载的示例代码在这里应该有效。您只需要更改连接名称和连接字符串以匹配您的系统。
  8. 将类库项目(ConfigItC# 或 ConfigItVB)添加到您的 Web 项目解决方案中。在 Web 项目中添加对它的引用。
  9. 添加一个 global.asax 文件并添加一个 application_start 方法。您可以使用下载的示例文件。

精简版

如果您真的不认为自己会使用 WebConfigurationManager.ConnectionStrings 或 SMTP 相关的东西,您可以随时修改 EnvironmentSettings 类。删除 OpenWebconfiguration 以及所有比较和保存配置文件相关的代码。

结论

我希望您觉得这个跨环境管理 web.config 文件的方法有帮助。弄清楚这一点有点麻烦,但现在它终于奏效了,它确实为我省去了很多麻烦。我想感谢 Steve Rowe 在设置自定义配置设置的一些细节上提供的帮助。

© . All rights reserved.