ASP.NET 的配置生成器

作者 :斯蒂芬·莫洛伊里克·安德森

配置生成器为 ASP.NET 应用提供新式和敏捷的机制,用于从外部源获取配置值。

配置生成器:

  • 可在 .NET Framework 4.7.1 及更高版本中使用。
  • 提供用于读取配置值的灵活机制。
  • 在应用进入容器和云重点环境时,解决应用的某些基本需求。
  • 可以利用先前无法使用的源(例如 Azure Key Vault 和环境变量)在 .NET 配置系统中改进配置数据的保护。

键/值配置生成器

配置生成器可以处理的常见方案是为遵循键/值模式的配置节提供基本键/值替换机制。 ConfigurationBuilders 的 .NET Framework 概念不限于特定的配置部分或模式。 但是,在Microsoft.Configuration.ConfigurationBuildersgithubNuGet)中,许多配置生成器在键/值模式下工作。

键/值配置生成器的设置

以下设置适用于所有键/值配置生成器。Microsoft.Configuration.ConfigurationBuilders

模式

配置生成器使用键/值信息的外部源来填充配置系统的所选键/值元素。 具体而言,<appSettings/><connectionStrings/> 部分从配置生成器处接受特殊处理。 构建者以三种模式工作:

  • Strict - 默认模式。 在此模式下,配置生成器仅对以已知键/以值为中心的配置部分进行操作。 Strict 模式枚举节中的每个键。 如果在外部源中找到匹配的键:

    • 配置生成器将生成的配置部分中的值替换为外部源中的值。
  • Greedy - 此模式与模式密切相关 Strict 。 而不是仅限于原始配置中已存在的密钥:

    • 配置生成器会将外部源中的所有键/值对添加到生成的配置部分。
  • Expand - 在分析到配置节对象之前,先对原始 XML 进行操作。 可以将其视为字符串中标记的扩展。 与 ${token} 模式匹配的任何 XML 原始字符串部分都是候选的令牌扩展。 如果在外部源中找不到相应的值,则不会更改令牌。 在此模式下,生成器不仅限于 <appSettings/><connectionStrings/> 部分。

以下来自web.config的标记在模式下启用EnvironmentConfigBuilder

<configuration>

  <configSections>
    <section name="configBuilders" 
             type="System.Configuration.ConfigurationBuildersSection, 
             System.Configuration, Version=4.0.0.0, Culture=neutral, 
             PublicKeyToken=b03f5f7f11d50a3a"
             restartOnExternalChanges="false" requirePermission="false" />
  </configSections>

  <configBuilders>
    <builders>
      <add name="MyEnvironment"
           type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, 
           Microsoft.Configuration.ConfigurationBuilders.Environment, 
           Version=1.0.0.0, Culture=neutral" />
    </builders>
  </configBuilders>

  <appSettings configBuilders="MyEnvironment">
    <add key="ServiceID" value="ServiceID value from web.config" />
    <add key="ServiceKey" value="ServiceKey value from web.config" />
  </appSettings>

  <connectionStrings configBuilders="MyEnvironment">
    <add name="default" connectionString="Data Source=web.config/mydb.db" />
  </connectionStrings>

以下代码读取前面 web.config 文件中所示的 <appSettings/><connectionStrings/>

using System;
using System.Configuration;
using System.Web.UI;

namespace MyConfigBuilders
{
    public partial class About : Page
    {
        public string ServiceID { get; set; }
        public string ServiceKey { get; set; }
        public string ConString { get; set; }

        protected void Page_Load(object sender, EventArgs e)
        {
            ServiceID = ConfigurationManager.AppSettings["ServiceID"];
            ServiceKey = ConfigurationManager.AppSettings["ServiceKey"];
            ConString = ConfigurationManager.ConnectionStrings["default"]
                                            ?.ConnectionString;
        }
    }
}

前面的代码将属性值设置为:

  • 如果环境变量中未设置键,则使用 web.config 文件中的值。
  • 环境变量的值(如果已设置)。

例如, ServiceID 将包含:

  • 如果未设置环境变量 ServiceID ,则为“web.config 中的 ServiceID 值”。
  • 环境变量的值 ServiceID (如果已设置)。

下图显示了在环境编辑器中从上文中的 web.config 文件设置的 <appSettings/> 键/值:

屏幕截图显示了环境变量编辑器,其中突出显示了 ServiceID 和 ServiceKey 变量。

注意:可能需要退出并重启 Visual Studio 才能查看环境变量中的更改。

前缀处理

键前缀可以简化设置键,因为:

  • .NET Framework 配置复杂且嵌套。
  • 外部键/值源通常是基本和平面的。 例如,环境变量不是嵌套的。

使用以下任一方法通过环境变量将<appSettings/><connectionStrings/>注入到配置中:

  • EnvironmentConfigBuilder 默认 Strict 模式下,配置文件中具有相应的密钥名称。 前面的代码和标记采用此方法。 使用此方法时,您不能在<appSettings/><connectionStrings/>中有同名的键。
  • Greedy 模式中使用两个以不同前缀的 EnvironmentConfigBuilderstripPrefix。 使用此方法,应用可以读取 <appSettings/><connectionStrings/> 无需更新配置文件。 下一部分 stripPrefix 演示如何执行此操作。
  • Greedy模式中使用两个EnvironmentConfigBuilder,并为每个EnvironmentConfigBuilder指定不同的前缀。 使用此方法时,不能有重复的键名称,因为密钥名称必须因前缀而异。 例如:
<configuration>

  <configSections>
    <section name="configBuilders"
             type="System.Configuration.ConfigurationBuildersSection, 
             System.Configuration, Version=4.0.0.0, Culture=neutral, 
             PublicKeyToken=b03f5f7f11d50a3a"
             restartOnExternalChanges="false" requirePermission="false" />
  </configSections>

  <configBuilders>
    <builders>
      <add name="AS_Environment" mode="Greedy" prefix="AppSetting_"
           type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, 
           Microsoft.Configuration.ConfigurationBuilders.Environment" />
      <add name="CS_Environment" mode="Greedy" prefix="ConnStr_"
           type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, 
           Microsoft.Configuration.ConfigurationBuilders.Environment" />
    </builders>
  </configBuilders>

  <appSettings configBuilders="AS_Environment">
    <add key="AppSetting_ServiceID" value="ServiceID value from web.config" />
    <add key="AppSetting_default" value="AppSetting_default value from web.config" />
  </appSettings>

  <connectionStrings configBuilders="CS_Environment">
    <add name="ConnStr_default" connectionString="Data Source=web.config/mydb.db" />
  </connectionStrings>

使用前面的标记,可以使用同一平面键/值源来填充两个不同的部分的配置。

下图显示了在环境编辑器中,从先前的web.config文件中设置的<appSettings/><connectionStrings/>键/值对:

屏幕截图显示了环境变量编辑器,其中突出显示了AppSetting_default、AppSetting_ServiceID和ConnStr_default变量。

以下代码读取前面web.config文件中包含的键/值<appSettings/><connectionStrings/>

public partial class Contact : Page
{
    public string ServiceID { get; set; }
    public string AppSetting_default { get; set; }
    public string ConString { get; set; }

    protected void Page_Load(object sender, EventArgs e)
    {
        ServiceID = ConfigurationManager.AppSettings["AppSetting_ServiceID"];
        AppSetting_default = ConfigurationManager.AppSettings["AppSetting_default"];
        ConString = ConfigurationManager.ConnectionStrings["ConnStr_default"]
                                     ?.ConnectionString;
    }
}

前面的代码将属性值设置为:

  • 如果环境变量中未设置键,则使用 web.config 文件中的值。
  • 环境变量的值(如果已设置)。

例如,使用以前的 web.config 文件、上一环境编辑器映像中的键/值以及上一个代码,将设置以下值:

密钥
AppSetting_ServiceID 从环境变量中提取AppSetting_ServiceID
AppSetting_default 来自 env 的 AppSetting_default 值
ConnStr_default 从 env 获取 ConnStr_default 默认值

stripPrefix

stripPrefix:布尔值,默认值为 false.

前面的 XML 标记将应用设置与连接字符串分开,但要求 web.config 文件中的所有键都使用指定的前缀。 例如,必须将前缀 AppSetting 添加到 ServiceID 密钥(“AppSetting_ServiceID”)。 使用 stripPrefix时,该前缀不会在 web.config 文件中使用。 配置生成器源中需要前缀(例如,在环境中)。我们预计大多数开发人员都将使用 stripPrefix

应用程序通常去除前缀。 以下 web.config 将去除前缀:

<configuration>

  <configSections>
    <section name="configBuilders"
             type="System.Configuration.ConfigurationBuildersSection, 
             System.Configuration, Version=4.0.0.0, Culture=neutral, 
             PublicKeyToken=b03f5f7f11d50a3a"
             restartOnExternalChanges="false" requirePermission="false" />
  </configSections>

  <configBuilders>
    <builders>
      <add name="AS_Environment" mode="Greedy" prefix="AppSetting_" 
           stripPrefix="true"
           type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, 
           Microsoft.Configuration.ConfigurationBuilders.Environment, 
           Version=1.0.0.0, Culture=neutral" />
      <add name="CS_Environment" mode="Greedy" prefix="ConnStr_" 
           stripPrefix="true"
            type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder, 
           Microsoft.Configuration.ConfigurationBuilders.Environment, 
           Version=1.0.0.0, Culture=neutral" />
    </builders>
  </configBuilders>

  <appSettings configBuilders="AS_Environment">
    <add key="ServiceID" value="ServiceID value from web.config" />
    <add key="default" value="AppSetting_default value from web.config" />
  </appSettings>

  <connectionStrings configBuilders="CS_Environment">
    <add name="default" connectionString="Data Source=web.config/mydb.db" />
  </connectionStrings>

在前面的 web.config 文件中, default 密钥同时位于 <appSettings/><connectionStrings/>中。

下图显示了环境编辑器中的前一个web.config文件里<appSettings/><connectionStrings/>键/值:

屏幕截图显示了环境变量编辑器,其中突出显示了AppSetting_default、AppSetting_ServiceID和ConnStr_default变量。

以下代码读取前面 web.config 文件中包含的 <appSettings/><connectionStrings/> 键/值:

public partial class About2 : Page
{
    public string ServiceID { get; set; }
    public string AppSetting_default { get; set; }
    public string ConString { get; set; }

    protected void Page_Load(object sender, EventArgs e)
    {
        ServiceID = ConfigurationManager.AppSettings["ServiceID"];
        AppSetting_default = ConfigurationManager.AppSettings["default"];
        ConString = ConfigurationManager.ConnectionStrings["default"]
                                        ?.ConnectionString;
    }
}

前面的代码将属性值设置为:

  • 如果环境变量中未设置键,则使用 web.config 文件中的值。
  • 环境变量的值(如果已设置)。

例如,使用之前的web.config文件、之前环境编辑器映像中的键/值和之前的代码,将设置以下值:

密钥
ServiceID 从环境变量中提取的 AppSetting_ServiceID
默认 AppSetting_default 值来自环境变量 env
默认 从环境中获取 ConnStr_default 值

tokenPattern

tokenPattern:字符串,默认值为 @"\$\{(\w+)\}"

Expand生成器的行为在原始 XML 中搜索类似于${token}的标记。 使用默认正则表达式 @"\$\{(\w+)\}"完成搜索。 匹配 \w 的字符集比 XML 更严格,并且许多配置源都允许。 在令牌名称中需要字符数超过@"\$\{(\w+)\}"时使用tokenPattern

tokenPattern:字符串:

  • 允许开发人员更改用于令牌匹配的正则表达式。
  • 没有验证,以确保它是一个格式正确的非危险正则表达式。
  • 它必须包含捕获组。 正则表达式必须完全匹配整个标记。 第一个捕获必须是要在配置源中查找的令牌名称。

Microsoft.Configuration.ConfigurationBuilders 中的配置生成器

EnvironmentConfigBuilder

<add name="Environment"
    [mode|prefix|stripPrefix|tokenPattern] 
    type="Microsoft.Configuration.ConfigurationBuilders.EnvironmentConfigBuilder,
    Microsoft.Configuration.ConfigurationBuilders.Environment" />

EnvironmentConfigBuilder

  • 最简单的配置生成器。
  • 从环境中读取值。
  • 没有任何其他配置选项。
  • 属性值 name 是任意的。

注意: 在 Windows 容器环境中,运行时设置的变量仅注入到 EntryPoint 进程环境中。 作为服务或非 EntryPoint 进程运行的应用不会获取这些变量,除非通过容器中的机制将其注入。 对于基于IIS/ASP.NET的容器,当前版本的ServiceMonitor.exe仅在DefaultAppPool中处理此问题。 其他基于 Windows 的容器变体可能需要为非 EntryPoint 进程开发自己的注入机制。

UserSecrets配置构建器

警告

切勿在源代码中存储密码、敏感连接字符串或其他敏感数据。 不应将生产机密用于开发或测试。

<add name="UserSecrets"
    [mode|prefix|stripPrefix|tokenPattern]
    (userSecretsId="{secret string, typically a GUID}" | userSecretsFile="~\secrets.file")
    [optional="true"]
    type="Microsoft.Configuration.ConfigurationBuilders.UserSecretsConfigBuilder,
    Microsoft.Configuration.ConfigurationBuilders.UserSecrets" />

在前面的 XML 中, userSecretsFile 路径可以使用 ~/~\。 例如,路径可以编写为 userSecretsFile="~/secrets.file。 有关详细信息, 请参阅 ConfigurationBuilders Utils 类。

此配置生成器提供的功能类似于 ASP.NET 核心机密管理器

UserSecretsConfigBuilder 可用于 .NET Framework 项目,但必须指定机密文件。 或者,可以在项目文件中定义 UserSecretsId 属性,并在正确的位置创建原始机密文件进行读取。 若要使外部依赖项远离项目,机密文件的格式为 XML。 XML 格式是实现详细信息,不应依赖该格式。 如果需要与 .NET Core 项目共享 secrets.json 文件,请考虑使用 SimpleJsonConfigBuilderSimpleJsonConfigBuilder格式也应被视为 .NET Core 的一个可能更改的实现细节。

UserSecretsConfigBuilder 的配置属性:

  • userSecretsId - 这是用于标识 XML 机密文件的首选方法。 它的工作方式类似于 .NET Core,它使用 UserSecretsId 项目属性来存储此标识符。 字符串必须是唯一的,它不需要是 GUID。 使用此属性,您将在已知的本地位置 (%APPDATA%\Microsoft\UserSecrets\<UserSecrets Id>\secrets.xml) 查找与此标识符关联的机密文件 UserSecretsConfigBuilder
  • userSecretsFile - 指定包含机密的文件的可选属性。 ~该字符可用于开始引用应用程序根目录。 此属性或 userSecretsId 属性是必需的。 如果同时指定了两者, userSecretsFile 则优先。
  • optional:布尔值,默认值 true - 如果找不到机密文件,则阻止异常。
  • 属性值 name 是任意的。

机密文件的格式如下:

<?xml version="1.0" encoding="utf-8" ?>
<root>
  <secrets ver="1.0">
    <secret name="secret key name" value="secret value" />
  </secrets>
</root>

AzureKeyVaultConfigBuilder

<add name="AzureKeyVault"
    [mode|prefix|stripPrefix|tokenPattern]
    (vaultName="MyVaultName" |
     uri="https:/MyVaultName.vault.azure.net")
    [version="secrets version"]
    [preloadSecretNames="true"]
    type="Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder,
    Microsoft.Configuration.ConfigurationBuilders.Azure" />

AzureKeyVaultConfigBuilder 读取存储在 Azure 密钥库中的值。

vaultName 是必需的(保管库的名称或保管库的 URI)。 其他属性允许控制要连接到哪个保管库,但仅在应用程序未在使用 Microsoft.Azure.Services.AppAuthentication的环境中运行时才是必需的。 Azure 服务身份验证库可用于尽可能自动从执行环境中选取连接信息。 可以通过提供连接字符串来手动替代自动获取的连接信息。

  • vaultName - 当未提供uri时此项为必需。 指定 Azure 订阅中用于读取密钥/值对的保管库名称。
  • uri - 与具有指定uri值的其他密钥库提供程序进行连接。 如果未指定,Azure (vaultName) 是保管库提供程序。
  • version- Azure 密钥库为机密提供版本控制功能。 如果 version 已指定,则生成器仅检索与此版本匹配的机密。
  • preloadSecretNames - 默认情况下,此生成器在初始化密钥保管库时查询 密钥保管库中的所有 密钥名称。 若要防止读取所有键值,请将此属性设置为 false。 将此设置设置为 false 逐个读取机密。 如果保管库允许“获取”访问,但不允许“列出”访问,则一次读取机密非常有用。 注意: 使用 Greedy 模式时, preloadSecretNames 必须是 true (默认值)。

KeyPerFileConfigBuilder

<add name="KeyPerFile"
    [mode|prefix|stripPrefix|tokenPattern]
    (directoryPath="PathToSourceDirectory")
    [ignorePrefix="ignore."]
    [keyDelimiter=":"]
    [optional="false"]
    type="Microsoft.Configuration.ConfigurationBuilders.KeyPerFileConfigBuilder,
    Microsoft.Configuration.ConfigurationBuilders.KeyPerFile" />

KeyPerFileConfigBuilder 是一个基本配置生成器,它使用目录的文件作为值的源。 文件的名称是键,内容是值。 在协调的容器环境中运行时,此配置生成器非常有用。 像 Docker Swarm 和 Kubernetes 这样的系统以每个文件一个密钥的方式为其编排的 Windows 容器提供 secrets

属性详细信息:

  • directoryPath - 必需。 指定要查找值的路径。 默认情况下,适用于 Windows 的 Docker 机密存储在 C:\ProgramData\Docker\secrets 目录中。
  • ignorePrefix - 排除以此前缀开头的文件。 默认值为“忽略”。
  • keyDelimiter - 默认值为 null. 如果指定,配置生成器将遍历目录的多个级别,并使用此分隔符构建密钥名称。 如果值为此值 null,则配置生成器仅查看目录的顶层。
  • optional - 默认值为 false. 指定如果源目录不存在,配置生成器是否应导致错误。

SimpleJsonConfigBuilder

警告

切勿在源代码中存储密码、敏感连接字符串或其他敏感数据。 不应将生产机密用于开发或测试。

<add name="SimpleJson"
    [mode|prefix|stripPrefix|tokenPattern]
    jsonFile="~\config.json"
    [optional="true"]
    [jsonMode="(Flat|Sectional)"]
    type="Microsoft.Configuration.ConfigurationBuilders.SimpleJsonConfigBuilder,
    Microsoft.Configuration.ConfigurationBuilders.Json" />

.NET Core 项目经常使用 JSON 文件进行配置。 SimpleJsonConfigBuilder 生成器允许在 .NET Framework 中使用 .NET Core JSON 文件。 此配置生成器提供从平面键/值源到 .NET Framework 配置的特定键/值区域的基本映射。 此配置生成器不提供分层配置。 JSON 支持文件类似于字典,而不是复杂的分层对象。 可以使用多层分层文件。 此提供程序flatten通过在每个级别追加属性名称,并使用:作为分隔符来实现深度。

属性详细信息:

  • jsonFile - 必需。 指定要从中读取的 JSON 文件。 ~该字符可用于在开始处引用应用根。

  • optional - 布尔值,默认值为 true. 如果找不到 JSON 文件,则防止引发异常。

  • jsonMode - [Flat|Sectional]Flat 是默认值。 如果 jsonModeFlat,JSON 文件是单个平面键/值源。 EnvironmentConfigBuilderAzureKeyVaultConfigBuilder也是单一的平面键/值源。 SimpleJsonConfigBuilderSectional模式下配置时:

    • JSON 文件在概念上只在顶层被分为多个字典。
    • 每个字典仅应用于与其附带的顶级属性名称匹配的配置节。 例如:
    {
        "appSettings" : {
            "setting1" : "value1",
            "setting2" : "value2",
            "complex" : {
                "setting1" : "complex:value1",
                "setting2" : "complex:value2",
            }
        }
    }

配置生成器顺序

请参阅 aspnet/MicrosoftConfigurationBuilders GitHub 存储库中的 ConfigurationBuilders 执行顺序

实现自定义键/值配置生成器

如果配置生成器不满足你的需求,则可以编写自定义生成器。 KeyValueConfigBuilder基类处理替换模式和大多数前缀问题。 实现项目只需要:

using Microsoft.Configuration.ConfigurationBuilders;
using System.Collections.Generic;

public class MyCustomConfigBuilder : KeyValueConfigBuilder
{
    public override string GetValue(string key)
    {
        // Key lookup should be case-insensitive, because most key/value collections in 
        // .NET Framework config sections are case-insensitive.
        return "Value for given key, or null.";
    }

    public override ICollection<KeyValuePair<string, string>> GetAllValues(string prefix)
    {
        // Populate the return collection.
        return new Dictionary<string, string>() { { "one", "1" }, { "two", "2" } };
    }
}

KeyValueConfigBuilder 类提供键/值配置生成器的大部分工作和一致行为。

其他资源