ASP.NET Core 中的配置

作者:Rick AndersonKirk Larkin

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅此文的 .NET 8 版本

ASP.NET Core 中的应用程序配置是使用一个或多个配置提供程序执行的。 配置提供程序使用各种配置源从键值对读取配置数据:

  • 设置文件,例如 appsettings.json
  • 环境变量
  • Azure Key Vault
  • Azure 应用配置
  • 命令行参数
  • 已安装或已创建的自定义提供程序
  • 目录文件
  • 内存中的 .NET 对象

本文介绍 ASP.NET Core 中的配置。 若要了解如何使用控制台应用中的配置,请参阅 .NET 配置

应用程序和主机配置

ASP.NET Core 应用配置和启动“主机”。 主机负责应用程序启动和生存期管理。 ASP.NET Core 模板创建的 WebApplicationBuilder 包含主机。 虽然可以在主机和应用程序配置提供程序中完成一些配置,但通常,只有主机必需的配置才应在主机配置中完成。

应用程序配置具有最高优先级,下一部分对此进行了详细介绍。 主机配置遵循应用程序配置,本文对此进行了介绍。

默认应用程序配置源

通过 dotnet new 或 Visual Studio 创建的 ASP.NET Core Web 应用会生成以下代码:

var builder = WebApplication.CreateBuilder(args);

WebApplication.CreateBuilder 使用预配置的默认值初始化 WebApplicationBuilder 类的新实例。 经过初始化的 WebApplicationBuilder (builder) 按照以下顺序为应用提供默认配置(从最高优先级到最低优先级):

  1. 使用命令行配置提供程序通过命令行参数提供。
  2. 使用非前缀环境变量配置提供程序通过非前缀环境变量提供。
  3. 应用在 Development 环境中运行时的用户机密
  4. 使用 JSON 配置提供程序通过 appsettings.{Environment}.json 提供。 例如,appsettings.Production.jsonappsettings.Development.json
  5. 使用 JSON 配置提供程序通过 appsettings.json 提供。
  6. 回退到下一部分所述的主机配置。

默认主机配置源

以下列表包含 WebApplicationBuilder 的从最高优先级到最低优先级的默认主机配置源:

  1. 使用命令行配置提供程序通过命令行参数提供
  2. 使用环境变量配置提供程序通过以 DOTNET_ 为前缀的环境变量提供。
  3. 使用环境变量配置提供程序通过以 ASPNETCORE_ 为前缀的环境变量提供。

对于 .NET 通用主机Web 主机,从最高优先级到最低优先级的默认主机配置源为:

  1. 使用环境变量配置提供程序通过以 ASPNETCORE_ 为前缀的环境变量提供。
  2. 使用命令行配置提供程序通过命令行参数提供
  3. 使用环境变量配置提供程序通过以 DOTNET_ 为前缀的环境变量提供。

在主机和应用程序配置中设置配置值时,将使用应用程序配置。

主机变量

初始化主机生成器时,会提前锁定以下变量,并且它们不受应用程序配置的影响:

从应用程序配置而不是主机配置读取所有其他主机设置。

URLS 是许多不是启动设置的常见主机设置之一。 与不在前一个列表中的所有其他主机设置一样,稍后会从应用程序配置中读取 URLS。主机配置是应用程序配置的后备,因此主机配置可用于设置 URLS,但它将被应用程序配置中的任何配置源(如 appsettings.json)覆盖。

有关详细信息,请参阅更改内容根、应用名称和环境通过环境变量或命令行更改内容根、应用名称和环境

本文中的其余部分介绍应用程序配置。

应用程序配置提供程序

以下代码按添加顺序显示了已启用的配置提供程序:

public class Index2Model : PageModel
{
    private IConfigurationRoot ConfigRoot;

    public Index2Model(IConfiguration configRoot)
    {
        ConfigRoot = (IConfigurationRoot)configRoot;
    }

    public ContentResult OnGet()
    {           
        string str = "";
        foreach (var provider in ConfigRoot.Providers.ToList())
        {
            str += provider.ToString() + "\n";
        }

        return Content(str);
    }
}

前面的优先级从高到低的默认配置源列表以它们被添加到模板生成的应用程序中的相反顺序显示提供程序。 例如,JSON 配置提供程序被添加到命令行配置提供程序之前。

后来添加的配置提供程序具有更高的优先级并且会替代之前的密钥设置。 例如,如果 appsettings.json 和环境中都设置了 MyKey,则使用环境值。 通过默认配置提供程序,命令行配置提供程序将替代其他所有提供程序。

若要详细了解 CreateBuilder,请参阅默认生成器设置

appsettings.json

请考虑以下 appsettings.json 文件:

{
  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  },
  "MyKey": "My appsettings.json Value",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

以下来自示例下载的代码显示了上述的一些配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

默认的 JsonConfigurationProvider 会按以下顺序加载配置:

  1. appsettings.json
  2. appsettings.{Environment}.json:例如,appsettings.Production.jsonappsettings.Development.json 文件。 文件的环境版本是根据 IHostingEnvironment.EnvironmentName 加载的。 有关详细信息,请参阅在 ASP.NET Core 中使用多个环境

appsettings.{Environment}.json 值替代 appsettings.json 中的键。 例如,默认情况下:

  • 在开发环境中,appsettings.Development.json 配置会覆盖在 appsettings.json 中找到的值。
  • 在生产环境中,appsettings.Production.json 配置会覆盖在 appsettings.json 中找到的值。 例如,在将应用部署到 Azure 时。

如果必须保证配置值,请参阅 GetValue。 前面的示例只读取字符串,不支持默认值。

使用默认配置时,会通过 reloadOnChange: true 启用 appsettings.jsonappsettings.{Environment}.json 文件。 应用启动后,对 appsettings.jsonappsettings.{Environment}.json 文件所做的更改将由 JSON 配置提供程序读取。

appsettings.json 中的注释

appsettings.jsonappsettings.{Environment}.json 文件中的注释支持使用 JavaScript 或 C# 样式的注释

使用选项模式绑定分层配置数据

读取相关配置值的首选方法是使用选项模式。 例如,若要读取以下配置值,请执行以下操作:

  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

创建以下 PositionOptions 类:

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; } = String.Empty;
    public string Name { get; set; } = String.Empty;
}

选项类:

  • 必须是包含公共无参数构造函数的非抽象类。
  • 类型的所有公共读写属性都已绑定。
  • 字段不是绑定的。 在上面的代码中,Position 未绑定。 由于使用了 Position 字段,因此在将类绑定到配置提供程序时,不需要在应用中对字符串 "Position" 进行硬编码。

下面的代码:

public class Test22Model : PageModel
{
    private readonly IConfiguration Configuration;

    public Test22Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var positionOptions = new PositionOptions();
        Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。

ConfigurationBinder.Get<T> 绑定并返回指定的类型。 使用 ConfigurationBinder.Get<T> 可能比使用 ConfigurationBinder.Bind 更方便。 下面的代码演示如何将 ConfigurationBinder.Get<T>PositionOptions 类配合使用:

public class Test21Model : PageModel
{
    private readonly IConfiguration Configuration;
    public PositionOptions? positionOptions { get; private set; }

    public Test21Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。

使用选项模式时的另一种方法是绑定 Position 部分,并将其添加到依赖关系注入服务容器。 在以下代码中,PositionOptions 已通过 Configure 被添加到了服务容器并已绑定到了配置:

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();

通过使用前面的代码,以下代码将读取位置选项:

public class Test2Model : PageModel
{
    private readonly PositionOptions _options;

    public Test2Model(IOptions<PositionOptions> options)
    {
        _options = options.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Title: {_options.Title} \n" +
                       $"Name: {_options.Name}");
    }
}

在上面的代码中,不会读取在应用启动后对 JSON 配置文件所做的更改。 若要读取在应用启动后的更改,请使用 IOptionsSnapshot

使用默认配置时,会通过 reloadOnChange: true 启用 appsettings.jsonappsettings.{Environment}.json 文件。 应用启动后,对 appsettings.jsonappsettings.{Environment}.json 文件所做的更改将由 JSON 配置提供程序读取。

有关添加其他 JSON 配置文件的信息,请参阅本文档中的 JSON 配置提供程序

合并服务集合

考虑下面的方法,该方法可注册服务并配置选项:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

可以将相关的注册组移动到扩展方法以注册服务。 例如,配置服务会被添加到以下类中:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

剩余的服务会在类似的类中注册。 下面的代码使用新扩展方法来注册服务:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

注意:每个 services.Add{GROUP_NAME} 扩展方法添加并可能配置服务。 例如,AddControllersWithViews 会添加带视图的 MVC 控制器所需的服务,AddRazorPages 会添加 Razor Pages 所需的服务。

安全性和用户机密

配置数据指南:

  • 请勿在配置提供程序代码或纯文本配置文件中存储密码或其他敏感数据。 机密管理器工具可用于存储开发环境中的机密。
  • 不要在开发或测试环境中使用生产机密。
  • 请在项目外部指定机密,避免将其意外提交到源代码存储库。

默认情况下,将在 JSON 配置源后注册用户机密配置源。 因此,用户机密密钥优先于 appsettings.jsonappsettings.{Environment}.json 中的密钥。

有关存储密码或其他敏感数据的详细信息:

Azure Key Vault 安全存储 ASP.NET Core 应用的应用机密。 有关详细信息,请参阅 ASP.NET Core 中的 Azure Key Vault 配置提供程序

非前缀环境变量

非前缀环境变量不是以ASPNETCORE_DOTNET_ 为前缀的环境变量。 例如,launchSettings.json 中的 ASP.NET Core Web 应用程序模板设置集 "ASPNETCORE_ENVIRONMENT": "Development"。 有关 ASPNETCORE_DOTNET_ 环境变量的详细信息,请参阅以下内容:

使用默认配置时,EnvironmentVariablesConfigurationProvider 会在读取 appsettings.jsonappsettings.{Environment}.json用户机密之后,从环境变量键值对中加载配置。 因此,从环境中读取的键值会替代从 appsettings.jsonappsettings.{Environment}.json 和用户机密中读取的值。

所有平台上的环境变量分层键都不支持 : 分隔符。 __(双下划线):

  • 受所有平台支持。 例如,Bash 不支持 : 分隔符,但支持 __
  • 自动替换为 :

以下 set 命令:

  • 在 Windows 上设置上述示例的环境键和值。
  • 在使用示例下载时测试设置。 dotnet run 命令必须在项目目录中运行。
set MyKey="My key from Environment"
set Position__Title=Environment_Editor
set Position__Name=Environment_Rick
dotnet run

前面的环境设置:

  • 仅在进程中设置,这些进程是从设置进程的命令窗口启动的。
  • 不会由通过 Visual Studio 启动的浏览器读取。

以下 setx 命令可用于在 Windows 上设置环境键和值。 与 set 不同,setx 设置是持久的。 /M 在系统环境中设置变量。 如果未使用 /M 开关,则会设置用户环境变量。

setx MyKey "My key from setx Environment" /M
setx Position__Title Environment_Editor /M
setx Position__Name Environment_Rick /M

测试前面的命令是否会替代 appsettings.jsonappsettings.{Environment}.json

  • 使用 Visual Studio:退出并重启 Visual Studio。
  • 使用 CLI:启动新的命令窗口并输入 dotnet run

使用字符串调用 AddEnvironmentVariables 以指定环境变量的前缀:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Configuration.AddEnvironmentVariables(prefix: "MyCustomPrefix_");

var app = builder.Build();

在上述代码中:

前缀会在读取配置键值对时被去除。

以下命令对自定义前缀进行测试:

set MyCustomPrefix_MyKey="My key with MyCustomPrefix_ Environment"
set MyCustomPrefix_Position__Title=Editor_with_customPrefix
set MyCustomPrefix_Position__Name=Environment_Rick_cp
dotnet run

默认配置会加载前缀为 DOTNET_ASPNETCORE_ 的环境变量和命令行参数。 DOTNET_ASPNETCORE_ 前缀会由 ASP.NET Core 用于主机和应用配置,但不用于用户配置。 有关主机和应用配置的详细信息,请参阅 .NET 通用主机

Azure 应用服务上,选择“设置”>“配置”页面上的“新应用程序设置”。 Azure 应用服务应用程序设置:

  • 已静态加密且通过加密的通道进行传输。
  • 已作为环境变量公开。

有关详细信息,请参阅 Azure 应用:使用 Azure 门户替代应用配置

有关 Azure 数据库连接字符串的信息,请参阅连接字符串前缀

环境变量的命名

环境变量名称反映了 appsettings.json 文件的结构。 层次结构中的每个元素由双下划线字符(更可取)或冒号分隔。 当元素结构包含数组时,应将数组索引视为此路径中的附加元素名称。 请考虑以下 appsettings.json 文件及其表示为环境变量的等效值。

appsettings.json

{
    "SmtpServer": "smtp.example.com",
    "Logging": [
        {
            "Name": "ToEmail",
            "Level": "Critical",
            "Args": {
                "FromAddress": "MySystem@example.com",
                "ToAddress": "SRE@example.com"
            }
        },
        {
            "Name": "ToConsole",
            "Level": "Information"
        }
    ]
}

环境变量

setx SmtpServer smtp.example.com
setx Logging__0__Name ToEmail
setx Logging__0__Level Critical
setx Logging__0__Args__FromAddress MySystem@example.com
setx Logging__0__Args__ToAddress SRE@example.com
setx Logging__1__Name ToConsole
setx Logging__1__Level Information

在生成的 launchSettings.json 中设置的环境变量

launchSettings.json 中设置的环境变量将替代在系统环境中设置的变量。 例如,ASP.NET Core Web 模板会生成一个 launchSettings.json 文件,该文件将终结点配置设置为:

"applicationUrl": "https://localhost:5001;http://localhost:5000"

配置 applicationUrl 将设置 ASPNETCORE_URLS 环境变量并重写环境中设置的值。

在 Linux 上转义环境变量

在 Linux 上,必须转义 URL 环境变量的值,使 systemd 可以对其进行分析。 使用 Linux 工具 systemd-escape 生成 http:--localhost:5001

groot@terminus:~$ systemd-escape http://localhost:5001
http:--localhost:5001

显示环境变量

下面的代码显示了应用程序启动时的环境变量和值,这对调试环境设置很有帮助:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

foreach (var c in builder.Configuration.AsEnumerable())
{
    Console.WriteLine(c.Key + " = " + c.Value);
}

命令行

使用默认配置,CommandLineConfigurationProvider 会从以下配置源后的命令行参数键值对中加载配置:

  • appsettings.jsonappsettings.{Environment}.json 文件。
  • 开发环境中的应用机密
  • 环境变量。

默认情况下,在命令行上设置的配置值会替代通过所有其他配置提供程序设置的配置值。

命令行参数

以下命令使用 = 设置键和值:

dotnet run MyKey="Using =" Position:Title=Cmd Position:Name=Cmd_Rick

以下命令使用 / 设置键和值:

dotnet run /MyKey "Using /" /Position:Title=Cmd /Position:Name=Cmd_Rick

以下命令使用 -- 设置键和值:

dotnet run --MyKey "Using --" --Position:Title=Cmd --Position:Name=Cmd_Rick

键值:

  • 必须后跟 =,或者当值后跟一个空格时,键必须具有一个 --/ 的前缀。
  • 如果使用 =,则不是必需的。 例如 MySetting=

在同一命令中,请勿将使用 = 的命令行参数键值对与使用空格的键值对混合使用。

交换映射

交换映射支持键名替换逻辑。 提供针对 AddCommandLine 方法的交换替换字典。

当使用交换映射字典时,会检查字典中是否有与命令行参数提供的键匹配的键。 如果在字典中找到了命令行键,则会传回字典值将键值对设置为应用的配置。 对任何具有单划线 (-) 前缀的命令行键而言,交换映射都是必需的。

交换映射字典键规则:

  • 交换必须以 --- 开头。
  • 交换映射字典不得包含重复键。

若要使用交换映射字典,请将它传递到对 AddCommandLine 的调用中:


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var switchMappings = new Dictionary<string, string>()
         {
             { "-k1", "key1" },
             { "-k2", "key2" },
             { "--alt3", "key3" },
             { "--alt4", "key4" },
             { "--alt5", "key5" },
             { "--alt6", "key6" },
         };

builder.Configuration.AddCommandLine(args, switchMappings);

var app = builder.Build();

运行以下命令可测试键替换:

dotnet run -k1 value1 -k2 value2 --alt3=value2 /alt4=value3 --alt5 value5 /alt6 value6

下面的代码显示了替换后的键的键值:

public class Test3Model : PageModel
{
    private readonly IConfiguration Config;

    public Test3Model(IConfiguration configuration)
    {
        Config = configuration;
    }

    public ContentResult OnGet()
    {
        return Content(
                $"Key1: '{Config["Key1"]}'\n" +
                $"Key2: '{Config["Key2"]}'\n" +
                $"Key3: '{Config["Key3"]}'\n" +
                $"Key4: '{Config["Key4"]}'\n" +
                $"Key5: '{Config["Key5"]}'\n" +
                $"Key6: '{Config["Key6"]}'");
    }
}

对于使用交换映射的应用,调用 CreateDefaultBuilder 不应传递参数。 CreateDefaultBuilder 方法的 AddCommandLine 调用不包括映射的交换,并且无法将交换映射字典传递给 CreateDefaultBuilder。 解决方案不是将参数传递给 CreateDefaultBuilder,而是允许 ConfigurationBuilder 方法的 AddCommandLine 方法处理参数和交换映射字典。

在 Visual Studio 中设置环境和命令行参数

可以在 Visual Studio 中的“启动配置文件”对话框设置环境和命令行参数:

  • 在“解决方案资源管理器”中,右键单击项目,然后选择“属性”。
  • 转到“调试”>“常规”选项卡,然后选择“打开调试启动配置文件 UI”>

分层配置数据

配置 API 在配置键中使用分隔符来展平分层数据,以此来读取分层配置数据。

示例下载包含以下 appsettings.json 文件:

{
  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  },
  "MyKey": "My appsettings.json Value",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

以下来自示例下载的代码显示了一些配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

读取分层配置数据的首选方法是使用选项模式。 有关详细信息,请参阅本文档中的绑定分层配置数据

GetSectionGetChildren 方法可用于隔离各个节和配置数据中某节的子节。 稍后将在 GetSection、GetChildren 和 Exists 中介绍这些方法。

配置键和值

配置键:

  • 不区分大小写。 例如,ConnectionStringconnectionstring 被视为等效键。
  • 如果在多个配置提供程序中设置了某一键和值,则会使用最后添加的提供程序中的值。 有关详细信息,请参阅默认配置
  • 分层键
    • 在配置 API 中,冒号分隔符 (:) 适用于所有平台。
    • 在环境变量中,冒号分隔符可能无法适用于所有平台。 所有平台均支持采用双下划线 __,并且它会自动转换为冒号 :
    • 在 Azure Key Vault 中,分层键使用 -- 作为分隔符。 当机密加载到应用的配置中时,Azure Key Vault 配置提供程序 会自动将 -- 替换为 :
  • ConfigurationBinder 支持使用配置键中的数组索引将数组绑定到对象。 数组绑定将在将数组绑定到类部分中进行介绍。

配置值:

  • 为字符串。
  • NULL 值不能存储在配置中或绑定到对象。

配置提供程序

下表显示了 ASP.NET Core 应用可用的配置提供程序。

提供程序 通过以下对象提供配置
Azure Key Vault 配置提供程序 Azure Key Vault
Azure 应用配置提供程序 Azure 应用程序配置
命令行配置提供程序 命令行参数
自定义配置提供程序 自定义源
环境变量配置提供程序 环境变量
文件配置提供程序 INI、JSON 和 XML 文件
Key-per-file 配置提供程序 目录文件
内存配置提供程序 内存中集合
用户机密 用户配置文件目录中的文件

按照指定的配置提供程序的顺序读取配置源。 代码中的配置提供程序应以特定顺序排列,从而满足应用所需的基础配置源的优先级。

配置提供程序的典型顺序为:

  1. appsettings.json
  2. appsettings.{Environment}.json
  3. 用户机密
  4. 使用环境变量配置提供程序通过环境变量提供。
  5. 使用命令行配置提供程序通过命令行参数提供。

通常的做法是将命令行配置提供程序添加到一系列提供程序的末尾,使命令行参数能够替代由其他提供程序设置的配置。

默认配置中使用了上述提供程序顺序。

连接字符串前缀

对于四个连接字符串环境变量,配置 API 具有特殊的处理规则。 这些连接字符串涉及了为应用环境配置 Azure 连接字符串。 使用默认配置或没有向 AddEnvironmentVariables 应用前缀时,具有表中所示前缀的环境变量将加载到应用中。

连接字符串前缀 提供程序
CUSTOMCONNSTR_ 自定义提供程序
MYSQLCONNSTR_ MySQL
SQLAZURECONNSTR_ Azure SQL 数据库
SQLCONNSTR_ SQL Server

当发现环境变量并使用表中所示的四个前缀中的任何一个加载到配置中时:

  • 通过删除环境变量前缀并添加配置键节 (ConnectionStrings) 来创建配置键。
  • 创建一个新的配置键值对,表示数据库连接提供程序(CUSTOMCONNSTR_ 除外,它没有声明的提供程序)。
环境变量键 转换的配置键 提供程序配置条目
CUSTOMCONNSTR_{KEY} ConnectionStrings:{KEY} 配置条目未创建。
MYSQLCONNSTR_{KEY} ConnectionStrings:{KEY} 键:ConnectionStrings:{KEY}_ProviderName
值:MySql.Data.MySqlClient
SQLAZURECONNSTR_{KEY} ConnectionStrings:{KEY} 键:ConnectionStrings:{KEY}_ProviderName
值:System.Data.SqlClient
SQLCONNSTR_{KEY} ConnectionStrings:{KEY} 键:ConnectionStrings:{KEY}_ProviderName
值:System.Data.SqlClient

文件配置提供程序

FileConfigurationProvider 是从文件系统加载配置的基类。 以下配置提供程序派生自 FileConfigurationProvider

INI 配置提供程序

IniConfigurationProvider 在运行时从 INI 文件键值对加载配置。

以下代码添加多个配置提供程序:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddIniFile("MyIniConfig.ini", optional: true, reloadOnChange: true)
    .AddIniFile($"MyIniConfig.{builder.Environment.EnvironmentName}.ini",
                optional: true, reloadOnChange: true);

builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddCommandLine(args);

builder.Services.AddRazorPages();

var app = builder.Build();

在前面的代码中,MyIniConfig.iniMyIniConfig.{Environment}.ini 文件中的设置会被以下提供程序中的设置替代:

示例下载包含以下 MyIniConfig.ini 文件:

MyKey="MyIniConfig.ini Value"

[Position]
Title="My INI Config title"
Name="My INI Config name"

[Logging:LogLevel]
Default=Information
Microsoft=Warning

以下来自示例下载的代码显示了上述的一些配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

JSON 配置提供程序

JsonConfigurationProvider 从 JSON 文件键值对加载配置。

重载可以指定:

  • 文件是否可选。
  • 如果文件更改,是否重载配置。

考虑下列代码:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddJsonFile("MyConfig.json",
        optional: true,
        reloadOnChange: true);

builder.Services.AddRazorPages();

var app = builder.Build();

前面的代码:

通常,你不会希望自定义 JSON 文件替代在环境变量配置提供程序命令行配置提供程序中设置的值

XML 配置提供程序

XmlConfigurationProvider 在运行时从 XML 文件键值对加载配置。

以下代码添加多个配置提供程序:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddXmlFile("MyXMLFile.xml", optional: true, reloadOnChange: true)
    .AddXmlFile($"MyXMLFile.{builder.Environment.EnvironmentName}.xml",
                optional: true, reloadOnChange: true);

builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddCommandLine(args);

builder.Services.AddRazorPages();

var app = builder.Build();

在前面的代码中,MyXMLFile.xmlMyXMLFile.{Environment}.xml 文件中的设置会被以下提供程序中的设置替代:

示例下载包含以下 MyXMLFile.xml 文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <MyKey>MyXMLFile Value</MyKey>
  <Position>
    <Title>Title from  MyXMLFile</Title>
    <Name>Name from MyXMLFile</Name>
  </Position>
  <Logging>
    <LogLevel>
      <Default>Information</Default>
      <Microsoft>Warning</Microsoft>
    </LogLevel>
  </Logging>
</configuration>

以下来自示例下载的代码显示了上述的一些配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

如果使用 name 属性来区分元素,则使用相同元素名称的重复元素可以正常工作:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <section name="section0">
    <key name="key0">value 00</key>
    <key name="key1">value 01</key>
  </section>
  <section name="section1">
    <key name="key0">value 10</key>
    <key name="key1">value 11</key>
  </section>
</configuration>

以下代码会读取前面的配置文件并显示键和值:

public class IndexModel : PageModel
{
    private readonly IConfiguration Configuration;

    public IndexModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var key00 = "section:section0:key:key0";
        var key01 = "section:section0:key:key1";
        var key10 = "section:section1:key:key0";
        var key11 = "section:section1:key:key1";

        var val00 = Configuration[key00];
        var val01 = Configuration[key01];
        var val10 = Configuration[key10];
        var val11 = Configuration[key11];

        return Content($"{key00} value: {val00} \n" +
                       $"{key01} value: {val01} \n" +
                       $"{key10} value: {val10} \n" +
                       $"{key10} value: {val11} \n"
                       );
    }
}

属性可用于提供值:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <key attribute="value" />
  <section>
    <key attribute="value" />
  </section>
</configuration>

以前的配置文件使用 value 加载以下键:

  • key:attribute
  • section:key:attribute

Key-per-file 配置提供程序

KeyPerFileConfigurationProvider 使用目录的文件作为配置键值对。 该键是文件名。 该值包含文件的内容。 Key-per-file 配置提供程序用于 Docker 托管方案。

若要激活 Key-per-file 配置,请在 ConfigurationBuilder 的实例上调用 AddKeyPerFile 扩展方法。 文件的 directoryPath 必须是绝对路径。

重载允许指定:

  • 配置源的 Action<KeyPerFileConfigurationSource> 委托。
  • 目录是否可选以及目录的路径。

双下划线字符 (__) 用作文件名中的配置键分隔符。 例如,文件名 Logging__LogLevel__System 生成配置键 Logging:LogLevel:System

构建主机时调用 ConfigureAppConfiguration 以指定应用的配置:

.ConfigureAppConfiguration((hostingContext, config) =>
{
    var path = Path.Combine(
        Directory.GetCurrentDirectory(), "path/to/files");
    config.AddKeyPerFile(directoryPath: path, optional: true);
})

内存配置提供程序

MemoryConfigurationProvider 使用内存中集合作为配置键值对。

以下代码将内存集合添加到配置系统中:

var builder = WebApplication.CreateBuilder(args);

var Dict = new Dictionary<string, string>
        {
           {"MyKey", "Dictionary MyKey Value"},
           {"Position:Title", "Dictionary_Title"},
           {"Position:Name", "Dictionary_Name" },
           {"Logging:LogLevel:Default", "Warning"}
        };

builder.Configuration.AddInMemoryCollection(Dict);
builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddCommandLine(args);

builder.Services.AddRazorPages();

var app = builder.Build();

以下来自示例下载的代码显示了上述配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

在前面的代码中,config.AddInMemoryCollection(Dict) 会被添加到默认配置提供程序之后。 有关对配置提供程序进行排序的示例,请参阅 JSON 配置提供程序

有关使用 MemoryConfigurationProvider 的其他示例,请参阅绑定数组

Kestrel 终结点配置

Kestrel 特定的终结点配置将覆盖所有跨服务器终结点配置。 跨服务器终结点配置包括:

请考虑在 ASP.NET Core Web 应用中使用的以下 appsettings.json 文件:

{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://localhost:9999"
      }
    }
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
} 

当在 ASP.NET Core Web 应用中使用前面突出显示的标记,并且应用在命令行上启动,且跨服务器端点配置如下时

dotnet run --urls="https://localhost:7777"

Kestrel 将绑定到专门针对 appsettings.json 文件中的 Kestrel 配置的终结点 (https://localhost:9999),而不是 https://localhost:7777

请考虑将 Kestrel 特定的终结点配置为环境变量:

set Kestrel__Endpoints__Https__Url=https://localhost:8888

在前面的环境变量中,Https 是 Kestrel 特定的终结点的名称。 前面的 appsettings.json 文件还定义了名为 Https 的 Kestrel 特定终结点。 默认情况下,将在 appsettings.{Environment}.json 后读取使用环境变量配置提供程序的环境变量,因此,前面的环境变量用于 Https 终结点。

GetValue

ConfigurationBinder.GetValue 从配置中提取一个具有指定键的值,并将它转换为指定的类型:

public class TestNumModel : PageModel
{
    private readonly IConfiguration Configuration;

    public TestNumModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var number = Configuration.GetValue<int>("NumberKey", 99);
        return Content($"{number}");
    }
}

在前面的代码中,如果在配置中找不到 NumberKey,则使用默认值 99

GetSection、GetChildren 和 Exists

对于下面的示例,请考虑以下 MySubsection.json 文件:

{
  "section0": {
    "key0": "value00",
    "key1": "value01"
  },
  "section1": {
    "key0": "value10",
    "key1": "value11"
  },
  "section2": {
    "subsection0": {
      "key0": "value200",
      "key1": "value201"
    },
    "subsection1": {
      "key0": "value210",
      "key1": "value211"
    }
  }
}

以下代码将 MySubsection.json 添加到配置提供程序:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddJsonFile("MySubsection.json",
                 optional: true,
                 reloadOnChange: true);

builder.Services.AddRazorPages();

var app = builder.Build();

GetSection

IConfiguration.GetSection 会返回具有指定子节键的配置子节。

以下代码将返回 section1 的值:

public class TestSectionModel : PageModel
{
    private readonly IConfiguration Config;

    public TestSectionModel(IConfiguration configuration)
    {
        Config = configuration.GetSection("section1");
    }

    public ContentResult OnGet()
    {
        return Content(
                $"section1:key0: '{Config["key0"]}'\n" +
                $"section1:key1: '{Config["key1"]}'");
    }
}

以下代码将返回 section2:subsection0 的值:

public class TestSection2Model : PageModel
{
    private readonly IConfiguration Config;

    public TestSection2Model(IConfiguration configuration)
    {
        Config = configuration.GetSection("section2:subsection0");
    }

    public ContentResult OnGet()
    {
        return Content(
                $"section2:subsection0:key0 '{Config["key0"]}'\n" +
                $"section2:subsection0:key1:'{Config["key1"]}'");
    }
}

GetSection 永远不会返回 null。 如果找不到匹配的节,则返回空 IConfigurationSection

GetSection 返回匹配的部分时,Value 未填充。 存在该部分时,返回一个 KeyPath 部分。

GetChildren 和 Exists

以下代码将调用 IConfiguration.GetChildren 并返回 section2:subsection0 的值:

public class TestSection4Model : PageModel
{
    private readonly IConfiguration Config;

    public TestSection4Model(IConfiguration configuration)
    {
        Config = configuration;
    }

    public ContentResult OnGet()
    {
        string s = "";
        var selection = Config.GetSection("section2");
        if (!selection.Exists())
        {
            throw new Exception("section2 does not exist.");
        }
        var children = selection.GetChildren();

        foreach (var subSection in children)
        {
            int i = 0;
            var key1 = subSection.Key + ":key" + i++.ToString();
            var key2 = subSection.Key + ":key" + i.ToString();
            s += key1 + " value: " + selection[key1] + "\n";
            s += key2 + " value: " + selection[key2] + "\n";
        }
        return Content(s);
    }
}

前面的代码将调用 ConfigurationExtensions.Exists 以验证该节是否存在:

绑定数组

ConfigurationBinder.Bind 支持使用配置键中的数组索引将数组绑定到对象。 公开数值键段的任何数组格式都能够与 POCO 类数组进行数组绑定。

请考虑示例下载中的 MyArray.json

{
  "array": {
    "entries": {
      "0": "value00",
      "1": "value10",
      "2": "value20",
      "4": "value40",
      "5": "value50"
    }
  }
}

以下代码将 MyArray.json 添加到配置提供程序:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddJsonFile("MyArray.json",
                 optional: true,
                 reloadOnChange: true);

builder.Services.AddRazorPages();

var app = builder.Build();

以下代码将读取配置并显示值:

public class ArrayModel : PageModel
{
    private readonly IConfiguration Config;
    public ArrayExample? _array { get; private set; }

    public ArrayModel(IConfiguration config)
    {
        Config = config;
    }

    public ContentResult OnGet()
    {
       _array = Config.GetSection("array").Get<ArrayExample>();
        if (_array == null)
        {
            throw new ArgumentNullException(nameof(_array));
        }
        string s = String.Empty;

        for (int j = 0; j < _array.Entries.Length; j++)
        {
            s += $"Index: {j}  Value:  {_array.Entries[j]} \n";
        }

        return Content(s);
    }
}
public class ArrayExample
{
    public string[]? Entries { get; set; } 
}

前面的代码会返回以下输出:

Index: 0  Value: value00
Index: 1  Value: value10
Index: 2  Value: value20
Index: 3  Value: value40
Index: 4  Value: value50

在前面的输出中,索引 3 具有值 value40,与 MyArray.json 中的 "4": "value40", 相对应。 绑定的数组索引是连续的,并且未绑定到配置键索引。 配置绑定器不能绑定 NULL 值,也不能在绑定的对象中创建 NULL 条目。

自定义配置提供程序

该示例应用演示了如何使用实体框架 (EF) 创建从数据库读取配置键值对的基本配置提供程序。

提供程序具有以下特征:

  • EF 内存中数据库用于演示目的。 若要使用需要连接字符串的数据库,请实现辅助 ConfigurationBuilder 以从另一个配置提供程序提供连接字符串。
  • 提供程序在启动时将数据库表读入配置。 提供程序不会基于每个键查询数据库。
  • 未实现更改时重载,因此在应用启动后更新数据库对应用的配置没有任何影响。

定义用于在数据库中存储配置值的 EFConfigurationValue 实体。

Models/EFConfigurationValue.cs

public class EFConfigurationValue
{
    public string Id { get; set; } = String.Empty;
    public string Value { get; set; } = String.Empty;
}

添加 EFConfigurationContext 以存储和访问配置的值。

EFConfigurationProvider/EFConfigurationContext.cs

public class EFConfigurationContext : DbContext
{
    public EFConfigurationContext(DbContextOptions<EFConfigurationContext> options) : base(options)
    {
    }

    public DbSet<EFConfigurationValue> Values => Set<EFConfigurationValue>();
}

创建用于实现 IConfigurationSource 的类。

EFConfigurationProvider/EFConfigurationSource.cs

public class EFConfigurationSource : IConfigurationSource
{
    private readonly Action<DbContextOptionsBuilder> _optionsAction;

    public EFConfigurationSource(Action<DbContextOptionsBuilder> optionsAction) => _optionsAction = optionsAction;

    public IConfigurationProvider Build(IConfigurationBuilder builder) => new EFConfigurationProvider(_optionsAction);
}

通过从 ConfigurationProvider 继承来创建自定义配置提供程序。 当数据库为空时,配置提供程序将对其进行初始化。 由于配置密钥不区分大小写,因此用来初始化数据库的字典是用不区分大小写的比较程序 (StringComparer.OrdinalIgnoreCase) 创建的。

EFConfigurationProvider/EFConfigurationProvider.cs

public class EFConfigurationProvider : ConfigurationProvider
{
    public EFConfigurationProvider(Action<DbContextOptionsBuilder> optionsAction)
    {
        OptionsAction = optionsAction;
    }

    Action<DbContextOptionsBuilder> OptionsAction { get; }

    public override void Load()
    {
        var builder = new DbContextOptionsBuilder<EFConfigurationContext>();

        OptionsAction(builder);

        using (var dbContext = new EFConfigurationContext(builder.Options))
        {
            if (dbContext == null || dbContext.Values == null)
            {
                throw new Exception("Null DB context");
            }
            dbContext.Database.EnsureCreated();

            Data = !dbContext.Values.Any()
                ? CreateAndSaveDefaultValues(dbContext)
                : dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
        }
    }

    private static IDictionary<string, string> CreateAndSaveDefaultValues(
        EFConfigurationContext dbContext)
    {
        // Quotes (c)2005 Universal Pictures: Serenity
        // https://www.uphe.com/movies/serenity-2005
        var configValues =
            new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                    { "quote1", "I aim to misbehave." },
                    { "quote2", "I swallowed a bug." },
                    { "quote3", "You can't stop the signal, Mal." }
            };

        if (dbContext == null || dbContext.Values == null)
        {
            throw new Exception("Null DB context");
        }

        dbContext.Values.AddRange(configValues
            .Select(kvp => new EFConfigurationValue
            {
                Id = kvp.Key,
                Value = kvp.Value
            })
            .ToArray());

        dbContext.SaveChanges();

        return configValues;
    }
}

可以使用 AddEFConfiguration 扩展方法将配置源添加到 ConfigurationBuilder

Extensions/EntityFrameworkExtensions.cs

public static class EntityFrameworkExtensions
{
    public static IConfigurationBuilder AddEFConfiguration(
               this IConfigurationBuilder builder,
               Action<DbContextOptionsBuilder> optionsAction)
    {
        return builder.Add(new EFConfigurationSource(optionsAction));
    }
}

下面的代码演示如何在 Program.cs 中使用自定义的 EFConfigurationProvider

//using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddEFConfiguration(
    opt => opt.UseInMemoryDatabase("InMemoryDb"));

var app = builder.Build();

app.Run();

使用依赖关系注入 (DI) 的访问配置

通过解析 IConfiguration 服务,可以使用依赖关系注入 (DI) 将配置注入服务:

public class Service
{
    private readonly IConfiguration _config;

    public Service(IConfiguration config) =>
        _config = config;

    public void DoSomething()
    {
        var configSettingValue = _config["ConfigSetting"];

        // ...
    }
}

有关如何使用 IConfiguration 访问值的信息,请参阅本文中的 GetValue 以及 GetSection、GetChildren 和 Exists

访问 Razor Pages 中的配置

以下代码显示 Razor Pages 中的配置数据:

@page
@model Test5Model
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

在以下代码中,MyOptions 已通过 Configure 被添加到了服务容器并已绑定到了配置:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

以下标记使用 @injectRazor 指令来解析和显示选项值:

@page
@model SampleApp.Pages.Test3Model
@using Microsoft.Extensions.Options
@using SampleApp.Models
@inject IOptions<MyOptions> optionsAccessor


<p><b>Option1:</b> @optionsAccessor.Value.Option1</p>
<p><b>Option2:</b> @optionsAccessor.Value.Option2</p>

访问 MVC 视图文件中的配置

以下代码显示 MVC 视图中的配置数据:

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

访问 Program.cs 中的配置

以下代码访问 Program.cs 文件中的配置。

var builder = WebApplication.CreateBuilder(args);

var key1 = builder.Configuration.GetValue<string>("KeyOne");

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

var key2 = app.Configuration.GetValue<int>("KeyTwo");
var key3 = app.Configuration.GetValue<bool>("KeyThree");

app.Logger.LogInformation("KeyOne: {KeyOne}", key1);
app.Logger.LogInformation("KeyTwo: {KeyTwo}", key2);
app.Logger.LogInformation("KeyThree: {KeyThree}", key3);

app.Run();

在上面的示例的 appsettings.json 中:

{
  ...
  "KeyOne": "Key One Value",
  "KeyTwo": 1999,
  "KeyThree": true
}

使用委托来配置选项

在委托中配置的选项替代在配置提供程序中设置的值。

在以下代码中,向服务容器添加了 IConfigureOptions<TOptions> 服务。 它使用委托来配置 MyOptions 的值:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "Value configured in delegate";
    myOptions.Option2 = 500;
});

var app = builder.Build();

以下代码显示选项值:

public class Test2Model : PageModel
{
    private readonly IOptions<MyOptions> _optionsDelegate;

    public Test2Model(IOptions<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_optionsDelegate.Value.Option1} \n" +
                       $"Option2: {_optionsDelegate.Value.Option2}");
    }
}

在前面的示例中,Option1Option2 的值在 appsettings.json 中指定,然后被配置的委托替代。

主机与应用配置

在配置并启动应用之前,配置并启动主机。 主机负责应用程序启动和生存期管理。 应用和主机均使用本主题中所述的配置提供程序进行配置。 应用的配置中也包含主机配置键值对。 有关在构建主机时如何使用配置提供程序,以及配置源如何影响主机配置的详细信息,请参阅 ASP.NET Core 基础知识概述

默认主机配置

有关使用 Web 主机时默认配置的详细信息,请参阅本主题的 ASP.NET Core 2.2 版本

  • 主机配置通过以下方式提供:
  • 已建立 Web 主机默认配置 (ConfigureWebHostDefaults):
    • Kestrel 用作 Web 服务器,并使用应用的配置提供程序对其进行配置。
    • 添加主机筛选中间件。
    • 如果 ASPNETCORE_FORWARDEDHEADERS_ENABLED 环境变量设置为 true,则添加转发的标头中间件。
    • 启用 IIS 集成。

其他配置

本主题仅与应用配置相关。 运行和托管 ASP.NET Core 应用的其他方面是使用本主题中未包含的配置文件进行配置:

launchSettings.json 中设置的环境变量将替代在系统环境中设置的变量。

若要详细了解如何从旧版 ASP.NET 迁移应用配置,请参阅从 ASP.NET 升级到 ASP.NET Core

从外部程序集添加配置

通过 IHostingStartup 实现,可在启动时从应用 Startup 类之外的外部程序集向应用添加增强功能。 有关详细信息,请参阅在 ASP.NET Core 中使用托管启动程序集

配置绑定源生成器

配置绑定源生成器提供 AOT 和剪裁友好配置。 有关详细信息,请参阅配置绑定源生成器

其他资源

ASP.NET Core 中的应用程序配置是使用一个或多个配置提供程序执行的。 配置提供程序使用各种配置源从键值对读取配置数据:

  • 设置文件,例如 appsettings.json
  • 环境变量
  • Azure Key Vault
  • Azure 应用配置
  • 命令行参数
  • 已安装或已创建的自定义提供程序
  • 目录文件
  • 内存中的 .NET 对象

本文介绍 ASP.NET Core 中的配置。 若要了解如何使用控制台应用中的配置,请参阅 .NET 配置

应用程序和主机配置

ASP.NET Core 应用配置和启动“主机”。 主机负责应用程序启动和生存期管理。 ASP.NET Core 模板创建的 WebApplicationBuilder 包含主机。 虽然可以在主机和应用程序配置提供程序中完成一些配置,但通常,只有主机必需的配置才应在主机配置中完成。

应用程序配置具有最高优先级,下一部分对此进行了详细介绍。 主机配置遵循应用程序配置,本文对此进行了介绍。

默认应用程序配置源

通过 dotnet new 或 Visual Studio 创建的 ASP.NET Core Web 应用会生成以下代码:

var builder = WebApplication.CreateBuilder(args);

WebApplication.CreateBuilder 使用预配置的默认值初始化 WebApplicationBuilder 类的新实例。 经过初始化的 WebApplicationBuilder (builder) 按照以下顺序为应用提供默认配置(从最高优先级到最低优先级):

  1. 使用命令行配置提供程序通过命令行参数提供。
  2. 使用非前缀环境变量配置提供程序通过非前缀环境变量提供。
  3. 应用在 Development 环境中运行时的用户机密
  4. 使用 JSON 配置提供程序通过 appsettings.{Environment}.json 提供。 例如,appsettings.Production.jsonappsettings.Development.json
  5. 使用 JSON 配置提供程序通过 appsettings.json 提供。
  6. 回退到下一部分所述的主机配置。

默认主机配置源

以下列表包含 WebApplicationBuilder 的从最高优先级到最低优先级的默认主机配置源:

  1. 使用命令行配置提供程序通过命令行参数提供
  2. 使用环境变量配置提供程序通过以 DOTNET_ 为前缀的环境变量提供。
  3. 使用环境变量配置提供程序通过以 ASPNETCORE_ 为前缀的环境变量提供。

对于 .NET 通用主机Web 主机,从最高优先级到最低优先级的默认主机配置源为:

  1. 使用环境变量配置提供程序通过以 ASPNETCORE_ 为前缀的环境变量提供。
  2. 使用命令行配置提供程序通过命令行参数提供
  3. 使用环境变量配置提供程序通过以 DOTNET_ 为前缀的环境变量提供。

在主机和应用程序配置中设置配置值时,将使用应用程序配置。

主机变量

初始化主机生成器时,会提前锁定以下变量,并且它们不受应用程序配置的影响:

从应用程序配置而不是主机配置读取所有其他主机设置。

URLS 是许多不是启动设置的常见主机设置之一。 与不在前一个列表中的所有其他主机设置一样,稍后会从应用程序配置中读取 URLS。主机配置是应用程序配置的后备,因此主机配置可用于设置 URLS,但它将被应用程序配置中的任何配置源(如 appsettings.json)覆盖。

有关详细信息,请参阅更改内容根、应用名称和环境通过环境变量或命令行更改内容根、应用名称和环境

本文中的其余部分介绍应用程序配置。

应用程序配置提供程序

以下代码按添加顺序显示了已启用的配置提供程序:

public class Index2Model : PageModel
{
    private IConfigurationRoot ConfigRoot;

    public Index2Model(IConfiguration configRoot)
    {
        ConfigRoot = (IConfigurationRoot)configRoot;
    }

    public ContentResult OnGet()
    {           
        string str = "";
        foreach (var provider in ConfigRoot.Providers.ToList())
        {
            str += provider.ToString() + "\n";
        }

        return Content(str);
    }
}

前面的优先级从高到低的默认配置源列表以它们被添加到模板生成的应用程序中的相反顺序显示提供程序。 例如,JSON 配置提供程序被添加到命令行配置提供程序之前。

后来添加的配置提供程序具有更高的优先级并且会替代之前的密钥设置。 例如,如果 appsettings.json 和环境中都设置了 MyKey,则使用环境值。 通过默认配置提供程序,命令行配置提供程序将替代其他所有提供程序。

若要详细了解 CreateBuilder,请参阅默认生成器设置

appsettings.json

请考虑以下 appsettings.json 文件:

{
  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  },
  "MyKey": "My appsettings.json Value",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

以下来自示例下载的代码显示了上述的一些配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

默认的 JsonConfigurationProvider 会按以下顺序加载配置:

  1. appsettings.json
  2. appsettings.{Environment}.json:例如,appsettings.Production.jsonappsettings.Development.json 文件。 文件的环境版本是根据 IHostingEnvironment.EnvironmentName 加载的。 有关详细信息,请参阅在 ASP.NET Core 中使用多个环境

appsettings.{Environment}.json 值替代 appsettings.json 中的键。 例如,默认情况下:

  • 在开发环境中,appsettings.Development.json 配置会覆盖在 appsettings.json 中找到的值。
  • 在生产环境中,appsettings.Production.json 配置会覆盖在 appsettings.json 中找到的值。 例如,在将应用部署到 Azure 时。

如果必须保证配置值,请参阅 GetValue。 前面的示例只读取字符串,不支持默认值。

使用默认配置时,会通过 reloadOnChange: true 启用 appsettings.jsonappsettings.{Environment}.json 文件。 应用启动后,对 appsettings.jsonappsettings.{Environment}.json 文件所做的更改将由 JSON 配置提供程序读取。

appsettings.json 中的注释

appsettings.jsonappsettings.{Environment}.json 文件中的注释支持使用 JavaScript 或 C# 样式的注释

使用选项模式绑定分层配置数据

读取相关配置值的首选方法是使用选项模式。 例如,若要读取以下配置值,请执行以下操作:

  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

创建以下 PositionOptions 类:

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; } = String.Empty;
    public string Name { get; set; } = String.Empty;
}

选项类:

  • 必须是包含公共无参数构造函数的非抽象类。
  • 类型的所有公共读写属性都已绑定。
  • 字段不是绑定的。 在上面的代码中,Position 未绑定。 由于使用了 Position 字段,因此在将类绑定到配置提供程序时,不需要在应用中对字符串 "Position" 进行硬编码。

下面的代码:

public class Test22Model : PageModel
{
    private readonly IConfiguration Configuration;

    public Test22Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var positionOptions = new PositionOptions();
        Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。

ConfigurationBinder.Get<T> 绑定并返回指定的类型。 使用 ConfigurationBinder.Get<T> 可能比使用 ConfigurationBinder.Bind 更方便。 下面的代码演示如何将 ConfigurationBinder.Get<T>PositionOptions 类配合使用:

public class Test21Model : PageModel
{
    private readonly IConfiguration Configuration;
    public PositionOptions? positionOptions { get; private set; }

    public Test21Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。

使用选项模式时的另一种方法是绑定 Position 部分,并将其添加到依赖关系注入服务容器。 在以下代码中,PositionOptions 已通过 Configure 被添加到了服务容器并已绑定到了配置:

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();

通过使用前面的代码,以下代码将读取位置选项:

public class Test2Model : PageModel
{
    private readonly PositionOptions _options;

    public Test2Model(IOptions<PositionOptions> options)
    {
        _options = options.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Title: {_options.Title} \n" +
                       $"Name: {_options.Name}");
    }
}

在上面的代码中,不会读取在应用启动后对 JSON 配置文件所做的更改。 若要读取在应用启动后的更改,请使用 IOptionsSnapshot

使用默认配置时,会通过 reloadOnChange: true 启用 appsettings.jsonappsettings.{Environment}.json 文件。 应用启动后,对 appsettings.jsonappsettings.{Environment}.json 文件所做的更改将由 JSON 配置提供程序读取。

有关添加其他 JSON 配置文件的信息,请参阅本文档中的 JSON 配置提供程序

合并服务集合

考虑下面的方法,该方法可注册服务并配置选项:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

可以将相关的注册组移动到扩展方法以注册服务。 例如,配置服务会被添加到以下类中:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

剩余的服务会在类似的类中注册。 下面的代码使用新扩展方法来注册服务:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

注意:每个 services.Add{GROUP_NAME} 扩展方法添加并可能配置服务。 例如,AddControllersWithViews 会添加带视图的 MVC 控制器所需的服务,AddRazorPages 会添加 Razor Pages 所需的服务。

安全性和用户机密

配置数据指南:

  • 请勿在配置提供程序代码或纯文本配置文件中存储密码或其他敏感数据。 机密管理器工具可用于存储开发环境中的机密。
  • 不要在开发或测试环境中使用生产机密。
  • 请在项目外部指定机密,避免将其意外提交到源代码存储库。

默认情况下,将在 JSON 配置源后注册用户机密配置源。 因此,用户机密密钥优先于 appsettings.jsonappsettings.{Environment}.json 中的密钥。

有关存储密码或其他敏感数据的详细信息:

Azure Key Vault 安全存储 ASP.NET Core 应用的应用机密。 有关详细信息,请参阅 ASP.NET Core 中的 Azure Key Vault 配置提供程序

非前缀环境变量

非前缀环境变量不是以ASPNETCORE_DOTNET_ 为前缀的环境变量。 例如,launchSettings.json 中的 ASP.NET Core Web 应用程序模板设置集 "ASPNETCORE_ENVIRONMENT": "Development"。 有关 ASPNETCORE_DOTNET_ 环境变量的详细信息,请参阅以下内容:

使用默认配置时,EnvironmentVariablesConfigurationProvider 会在读取 appsettings.jsonappsettings.{Environment}.json用户机密之后,从环境变量键值对中加载配置。 因此,从环境中读取的键值会替代从 appsettings.jsonappsettings.{Environment}.json 和用户机密中读取的值。

所有平台上的环境变量分层键都不支持 : 分隔符。 __(双下划线):

  • 受所有平台支持。 例如,Bash 不支持 : 分隔符,但支持 __
  • 自动替换为 :

以下 set 命令:

  • 在 Windows 上设置上述示例的环境键和值。
  • 在使用示例下载时测试设置。 dotnet run 命令必须在项目目录中运行。
set MyKey="My key from Environment"
set Position__Title=Environment_Editor
set Position__Name=Environment_Rick
dotnet run

前面的环境设置:

  • 仅在进程中设置,这些进程是从设置进程的命令窗口启动的。
  • 不会由通过 Visual Studio 启动的浏览器读取。

以下 setx 命令可用于在 Windows 上设置环境键和值。 与 set 不同,setx 设置是持久的。 /M 在系统环境中设置变量。 如果未使用 /M 开关,则会设置用户环境变量。

setx MyKey "My key from setx Environment" /M
setx Position__Title Environment_Editor /M
setx Position__Name Environment_Rick /M

测试前面的命令是否会替代 appsettings.jsonappsettings.{Environment}.json

  • 使用 Visual Studio:退出并重启 Visual Studio。
  • 使用 CLI:启动新的命令窗口并输入 dotnet run

使用字符串调用 AddEnvironmentVariables 以指定环境变量的前缀:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Configuration.AddEnvironmentVariables(prefix: "MyCustomPrefix_");

var app = builder.Build();

在上述代码中:

前缀会在读取配置键值对时被去除。

以下命令对自定义前缀进行测试:

set MyCustomPrefix_MyKey="My key with MyCustomPrefix_ Environment"
set MyCustomPrefix_Position__Title=Editor_with_customPrefix
set MyCustomPrefix_Position__Name=Environment_Rick_cp
dotnet run

默认配置会加载前缀为 DOTNET_ASPNETCORE_ 的环境变量和命令行参数。 DOTNET_ASPNETCORE_ 前缀会由 ASP.NET Core 用于主机和应用配置,但不用于用户配置。 有关主机和应用配置的详细信息,请参阅 .NET 通用主机

Azure 应用服务上,选择“设置”>“配置”页面上的“新应用程序设置”。 Azure 应用服务应用程序设置:

  • 已静态加密且通过加密的通道进行传输。
  • 已作为环境变量公开。

有关详细信息,请参阅 Azure 应用:使用 Azure 门户替代应用配置

有关 Azure 数据库连接字符串的信息,请参阅连接字符串前缀

环境变量的命名

环境变量名称反映了 appsettings.json 文件的结构。 层次结构中的每个元素由双下划线字符(更可取)或冒号分隔。 当元素结构包含数组时,应将数组索引视为此路径中的附加元素名称。 请考虑以下 appsettings.json 文件及其表示为环境变量的等效值。

appsettings.json

{
    "SmtpServer": "smtp.example.com",
    "Logging": [
        {
            "Name": "ToEmail",
            "Level": "Critical",
            "Args": {
                "FromAddress": "MySystem@example.com",
                "ToAddress": "SRE@example.com"
            }
        },
        {
            "Name": "ToConsole",
            "Level": "Information"
        }
    ]
}

环境变量

setx SmtpServer smtp.example.com
setx Logging__0__Name ToEmail
setx Logging__0__Level Critical
setx Logging__0__Args__FromAddress MySystem@example.com
setx Logging__0__Args__ToAddress SRE@example.com
setx Logging__1__Name ToConsole
setx Logging__1__Level Information

在生成的 launchSettings.json 中设置的环境变量

launchSettings.json 中设置的环境变量将替代在系统环境中设置的变量。 例如,ASP.NET Core Web 模板会生成一个 launchSettings.json 文件,该文件将终结点配置设置为:

"applicationUrl": "https://localhost:5001;http://localhost:5000"

配置 applicationUrl 将设置 ASPNETCORE_URLS 环境变量并重写环境中设置的值。

在 Linux 上转义环境变量

在 Linux 上,必须转义 URL 环境变量的值,使 systemd 可以对其进行分析。 使用 Linux 工具 systemd-escape 生成 http:--localhost:5001

groot@terminus:~$ systemd-escape http://localhost:5001
http:--localhost:5001

显示环境变量

下面的代码显示了应用程序启动时的环境变量和值,这对调试环境设置很有帮助:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

foreach (var c in builder.Configuration.AsEnumerable())
{
    Console.WriteLine(c.Key + " = " + c.Value);
}

命令行

使用默认配置,CommandLineConfigurationProvider 会从以下配置源后的命令行参数键值对中加载配置:

  • appsettings.jsonappsettings.{Environment}.json 文件。
  • 开发环境中的应用机密
  • 环境变量。

默认情况下,在命令行上设置的配置值会替代通过所有其他配置提供程序设置的配置值。

命令行参数

以下命令使用 = 设置键和值:

dotnet run MyKey="Using =" Position:Title=Cmd Position:Name=Cmd_Rick

以下命令使用 / 设置键和值:

dotnet run /MyKey "Using /" /Position:Title=Cmd /Position:Name=Cmd_Rick

以下命令使用 -- 设置键和值:

dotnet run --MyKey "Using --" --Position:Title=Cmd --Position:Name=Cmd_Rick

键值:

  • 必须后跟 =,或者当值后跟一个空格时,键必须具有一个 --/ 的前缀。
  • 如果使用 =,则不是必需的。 例如 MySetting=

在同一命令中,请勿将使用 = 的命令行参数键值对与使用空格的键值对混合使用。

交换映射

交换映射支持键名替换逻辑。 提供针对 AddCommandLine 方法的交换替换字典。

当使用交换映射字典时,会检查字典中是否有与命令行参数提供的键匹配的键。 如果在字典中找到了命令行键,则会传回字典值将键值对设置为应用的配置。 对任何具有单划线 (-) 前缀的命令行键而言,交换映射都是必需的。

交换映射字典键规则:

  • 交换必须以 --- 开头。
  • 交换映射字典不得包含重复键。

若要使用交换映射字典,请将它传递到对 AddCommandLine 的调用中:


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var switchMappings = new Dictionary<string, string>()
         {
             { "-k1", "key1" },
             { "-k2", "key2" },
             { "--alt3", "key3" },
             { "--alt4", "key4" },
             { "--alt5", "key5" },
             { "--alt6", "key6" },
         };

builder.Configuration.AddCommandLine(args, switchMappings);

var app = builder.Build();

运行以下命令可测试键替换:

dotnet run -k1 value1 -k2 value2 --alt3=value2 /alt4=value3 --alt5 value5 /alt6 value6

下面的代码显示了替换后的键的键值:

public class Test3Model : PageModel
{
    private readonly IConfiguration Config;

    public Test3Model(IConfiguration configuration)
    {
        Config = configuration;
    }

    public ContentResult OnGet()
    {
        return Content(
                $"Key1: '{Config["Key1"]}'\n" +
                $"Key2: '{Config["Key2"]}'\n" +
                $"Key3: '{Config["Key3"]}'\n" +
                $"Key4: '{Config["Key4"]}'\n" +
                $"Key5: '{Config["Key5"]}'\n" +
                $"Key6: '{Config["Key6"]}'");
    }
}

对于使用交换映射的应用,调用 CreateDefaultBuilder 不应传递参数。 CreateDefaultBuilder 方法的 AddCommandLine 调用不包括映射的交换,并且无法将交换映射字典传递给 CreateDefaultBuilder。 解决方案不是将参数传递给 CreateDefaultBuilder,而是允许 ConfigurationBuilder 方法的 AddCommandLine 方法处理参数和交换映射字典。

在 Visual Studio 中设置环境和命令行参数

可以在 Visual Studio 中的“启动配置文件”对话框设置环境和命令行参数:

  • 在“解决方案资源管理器”中,右键单击项目,然后选择“属性”。
  • 转到“调试”>“常规”选项卡,然后选择“打开调试启动配置文件 UI”>

分层配置数据

配置 API 在配置键中使用分隔符来展平分层数据,以此来读取分层配置数据。

示例下载包含以下 appsettings.json 文件:

{
  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  },
  "MyKey": "My appsettings.json Value",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

以下来自示例下载的代码显示了一些配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

读取分层配置数据的首选方法是使用选项模式。 有关详细信息,请参阅本文档中的绑定分层配置数据

GetSectionGetChildren 方法可用于隔离各个节和配置数据中某节的子节。 稍后将在 GetSection、GetChildren 和 Exists 中介绍这些方法。

配置键和值

配置键:

  • 不区分大小写。 例如,ConnectionStringconnectionstring 被视为等效键。
  • 如果在多个配置提供程序中设置了某一键和值,则会使用最后添加的提供程序中的值。 有关详细信息,请参阅默认配置
  • 分层键
    • 在配置 API 中,冒号分隔符 (:) 适用于所有平台。
    • 在环境变量中,冒号分隔符可能无法适用于所有平台。 所有平台均支持采用双下划线 __,并且它会自动转换为冒号 :
    • 在 Azure Key Vault 中,分层键使用 -- 作为分隔符。 当机密加载到应用的配置中时,Azure Key Vault 配置提供程序 会自动将 -- 替换为 :
  • ConfigurationBinder 支持使用配置键中的数组索引将数组绑定到对象。 数组绑定将在将数组绑定到类部分中进行介绍。

配置值:

  • 为字符串。
  • NULL 值不能存储在配置中或绑定到对象。

配置提供程序

下表显示了 ASP.NET Core 应用可用的配置提供程序。

提供程序 通过以下对象提供配置
Azure Key Vault 配置提供程序 Azure Key Vault
Azure 应用配置提供程序 Azure 应用程序配置
命令行配置提供程序 命令行参数
自定义配置提供程序 自定义源
环境变量配置提供程序 环境变量
文件配置提供程序 INI、JSON 和 XML 文件
Key-per-file 配置提供程序 目录文件
内存配置提供程序 内存中集合
用户机密 用户配置文件目录中的文件

按照指定的配置提供程序的顺序读取配置源。 代码中的配置提供程序应以特定顺序排列,从而满足应用所需的基础配置源的优先级。

配置提供程序的典型顺序为:

  1. appsettings.json
  2. appsettings.{Environment}.json
  3. 用户机密
  4. 使用环境变量配置提供程序通过环境变量提供。
  5. 使用命令行配置提供程序通过命令行参数提供。

通常的做法是将命令行配置提供程序添加到一系列提供程序的末尾,使命令行参数能够替代由其他提供程序设置的配置。

默认配置中使用了上述提供程序顺序。

连接字符串前缀

对于四个连接字符串环境变量,配置 API 具有特殊的处理规则。 这些连接字符串涉及了为应用环境配置 Azure 连接字符串。 使用默认配置或没有向 AddEnvironmentVariables 应用前缀时,具有表中所示前缀的环境变量将加载到应用中。

连接字符串前缀 提供程序
CUSTOMCONNSTR_ 自定义提供程序
MYSQLCONNSTR_ MySQL
SQLAZURECONNSTR_ Azure SQL 数据库
SQLCONNSTR_ SQL Server

当发现环境变量并使用表中所示的四个前缀中的任何一个加载到配置中时:

  • 通过删除环境变量前缀并添加配置键节 (ConnectionStrings) 来创建配置键。
  • 创建一个新的配置键值对,表示数据库连接提供程序(CUSTOMCONNSTR_ 除外,它没有声明的提供程序)。
环境变量键 转换的配置键 提供程序配置条目
CUSTOMCONNSTR_{KEY} ConnectionStrings:{KEY} 配置条目未创建。
MYSQLCONNSTR_{KEY} ConnectionStrings:{KEY} 键:ConnectionStrings:{KEY}_ProviderName
值:MySql.Data.MySqlClient
SQLAZURECONNSTR_{KEY} ConnectionStrings:{KEY} 键:ConnectionStrings:{KEY}_ProviderName
值:System.Data.SqlClient
SQLCONNSTR_{KEY} ConnectionStrings:{KEY} 键:ConnectionStrings:{KEY}_ProviderName
值:System.Data.SqlClient

文件配置提供程序

FileConfigurationProvider 是从文件系统加载配置的基类。 以下配置提供程序派生自 FileConfigurationProvider

INI 配置提供程序

IniConfigurationProvider 在运行时从 INI 文件键值对加载配置。

以下代码添加多个配置提供程序:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddIniFile("MyIniConfig.ini", optional: true, reloadOnChange: true)
    .AddIniFile($"MyIniConfig.{builder.Environment.EnvironmentName}.ini",
                optional: true, reloadOnChange: true);

builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddCommandLine(args);

builder.Services.AddRazorPages();

var app = builder.Build();

在前面的代码中,MyIniConfig.iniMyIniConfig.{Environment}.ini 文件中的设置会被以下提供程序中的设置替代:

示例下载包含以下 MyIniConfig.ini 文件:

MyKey="MyIniConfig.ini Value"

[Position]
Title="My INI Config title"
Name="My INI Config name"

[Logging:LogLevel]
Default=Information
Microsoft=Warning

以下来自示例下载的代码显示了上述的一些配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

JSON 配置提供程序

JsonConfigurationProvider 从 JSON 文件键值对加载配置。

重载可以指定:

  • 文件是否可选。
  • 如果文件更改,是否重载配置。

考虑下列代码:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddJsonFile("MyConfig.json",
        optional: true,
        reloadOnChange: true);

builder.Services.AddRazorPages();

var app = builder.Build();

前面的代码:

通常,你不会希望自定义 JSON 文件替代在环境变量配置提供程序命令行配置提供程序中设置的值

XML 配置提供程序

XmlConfigurationProvider 在运行时从 XML 文件键值对加载配置。

以下代码添加多个配置提供程序:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddXmlFile("MyXMLFile.xml", optional: true, reloadOnChange: true)
    .AddXmlFile($"MyXMLFile.{builder.Environment.EnvironmentName}.xml",
                optional: true, reloadOnChange: true);

builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddCommandLine(args);

builder.Services.AddRazorPages();

var app = builder.Build();

在前面的代码中,MyXMLFile.xmlMyXMLFile.{Environment}.xml 文件中的设置会被以下提供程序中的设置替代:

示例下载包含以下 MyXMLFile.xml 文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <MyKey>MyXMLFile Value</MyKey>
  <Position>
    <Title>Title from  MyXMLFile</Title>
    <Name>Name from MyXMLFile</Name>
  </Position>
  <Logging>
    <LogLevel>
      <Default>Information</Default>
      <Microsoft>Warning</Microsoft>
    </LogLevel>
  </Logging>
</configuration>

以下来自示例下载的代码显示了上述的一些配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

如果使用 name 属性来区分元素,则使用相同元素名称的重复元素可以正常工作:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <section name="section0">
    <key name="key0">value 00</key>
    <key name="key1">value 01</key>
  </section>
  <section name="section1">
    <key name="key0">value 10</key>
    <key name="key1">value 11</key>
  </section>
</configuration>

以下代码会读取前面的配置文件并显示键和值:

public class IndexModel : PageModel
{
    private readonly IConfiguration Configuration;

    public IndexModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var key00 = "section:section0:key:key0";
        var key01 = "section:section0:key:key1";
        var key10 = "section:section1:key:key0";
        var key11 = "section:section1:key:key1";

        var val00 = Configuration[key00];
        var val01 = Configuration[key01];
        var val10 = Configuration[key10];
        var val11 = Configuration[key11];

        return Content($"{key00} value: {val00} \n" +
                       $"{key01} value: {val01} \n" +
                       $"{key10} value: {val10} \n" +
                       $"{key10} value: {val11} \n"
                       );
    }
}

属性可用于提供值:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <key attribute="value" />
  <section>
    <key attribute="value" />
  </section>
</configuration>

以前的配置文件使用 value 加载以下键:

  • key:attribute
  • section:key:attribute

Key-per-file 配置提供程序

KeyPerFileConfigurationProvider 使用目录的文件作为配置键值对。 该键是文件名。 该值包含文件的内容。 Key-per-file 配置提供程序用于 Docker 托管方案。

若要激活 Key-per-file 配置,请在 ConfigurationBuilder 的实例上调用 AddKeyPerFile 扩展方法。 文件的 directoryPath 必须是绝对路径。

重载允许指定:

  • 配置源的 Action<KeyPerFileConfigurationSource> 委托。
  • 目录是否可选以及目录的路径。

双下划线字符 (__) 用作文件名中的配置键分隔符。 例如,文件名 Logging__LogLevel__System 生成配置键 Logging:LogLevel:System

构建主机时调用 ConfigureAppConfiguration 以指定应用的配置:

.ConfigureAppConfiguration((hostingContext, config) =>
{
    var path = Path.Combine(
        Directory.GetCurrentDirectory(), "path/to/files");
    config.AddKeyPerFile(directoryPath: path, optional: true);
})

内存配置提供程序

MemoryConfigurationProvider 使用内存中集合作为配置键值对。

以下代码将内存集合添加到配置系统中:

var builder = WebApplication.CreateBuilder(args);

var Dict = new Dictionary<string, string>
        {
           {"MyKey", "Dictionary MyKey Value"},
           {"Position:Title", "Dictionary_Title"},
           {"Position:Name", "Dictionary_Name" },
           {"Logging:LogLevel:Default", "Warning"}
        };

builder.Configuration.AddInMemoryCollection(Dict);
builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddCommandLine(args);

builder.Services.AddRazorPages();

var app = builder.Build();

以下来自示例下载的代码显示了上述配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

在前面的代码中,config.AddInMemoryCollection(Dict) 会被添加到默认配置提供程序之后。 有关对配置提供程序进行排序的示例,请参阅 JSON 配置提供程序

有关使用 MemoryConfigurationProvider 的其他示例,请参阅绑定数组

Kestrel 终结点配置

Kestrel 特定的终结点配置将覆盖所有跨服务器终结点配置。 跨服务器终结点配置包括:

请考虑在 ASP.NET Core Web 应用中使用的以下 appsettings.json 文件:

{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://localhost:9999"
      }
    }
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
} 

当在 ASP.NET Core Web 应用中使用前面突出显示的标记,并且应用在命令行上启动,且跨服务器端点配置如下时

dotnet run --urls="https://localhost:7777"

Kestrel 将绑定到专门针对 appsettings.json 文件中的 Kestrel 配置的终结点 (https://localhost:9999),而不是 https://localhost:7777

请考虑将 Kestrel 特定的终结点配置为环境变量:

set Kestrel__Endpoints__Https__Url=https://localhost:8888

在前面的环境变量中,Https 是 Kestrel 特定的终结点的名称。 前面的 appsettings.json 文件还定义了名为 Https 的 Kestrel 特定终结点。 默认情况下,将在 appsettings.{Environment}.json 后读取使用环境变量配置提供程序的环境变量,因此,前面的环境变量用于 Https 终结点。

GetValue

ConfigurationBinder.GetValue 从配置中提取一个具有指定键的值,并将它转换为指定的类型:

public class TestNumModel : PageModel
{
    private readonly IConfiguration Configuration;

    public TestNumModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var number = Configuration.GetValue<int>("NumberKey", 99);
        return Content($"{number}");
    }
}

在前面的代码中,如果在配置中找不到 NumberKey,则使用默认值 99

GetSection、GetChildren 和 Exists

对于下面的示例,请考虑以下 MySubsection.json 文件:

{
  "section0": {
    "key0": "value00",
    "key1": "value01"
  },
  "section1": {
    "key0": "value10",
    "key1": "value11"
  },
  "section2": {
    "subsection0": {
      "key0": "value200",
      "key1": "value201"
    },
    "subsection1": {
      "key0": "value210",
      "key1": "value211"
    }
  }
}

以下代码将 MySubsection.json 添加到配置提供程序:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddJsonFile("MySubsection.json",
                 optional: true,
                 reloadOnChange: true);

builder.Services.AddRazorPages();

var app = builder.Build();

GetSection

IConfiguration.GetSection 会返回具有指定子节键的配置子节。

以下代码将返回 section1 的值:

public class TestSectionModel : PageModel
{
    private readonly IConfiguration Config;

    public TestSectionModel(IConfiguration configuration)
    {
        Config = configuration.GetSection("section1");
    }

    public ContentResult OnGet()
    {
        return Content(
                $"section1:key0: '{Config["key0"]}'\n" +
                $"section1:key1: '{Config["key1"]}'");
    }
}

以下代码将返回 section2:subsection0 的值:

public class TestSection2Model : PageModel
{
    private readonly IConfiguration Config;

    public TestSection2Model(IConfiguration configuration)
    {
        Config = configuration.GetSection("section2:subsection0");
    }

    public ContentResult OnGet()
    {
        return Content(
                $"section2:subsection0:key0 '{Config["key0"]}'\n" +
                $"section2:subsection0:key1:'{Config["key1"]}'");
    }
}

GetSection 永远不会返回 null。 如果找不到匹配的节,则返回空 IConfigurationSection

GetSection 返回匹配的部分时,Value 未填充。 存在该部分时,返回一个 KeyPath 部分。

GetChildren 和 Exists

以下代码将调用 IConfiguration.GetChildren 并返回 section2:subsection0 的值:

public class TestSection4Model : PageModel
{
    private readonly IConfiguration Config;

    public TestSection4Model(IConfiguration configuration)
    {
        Config = configuration;
    }

    public ContentResult OnGet()
    {
        string s = "";
        var selection = Config.GetSection("section2");
        if (!selection.Exists())
        {
            throw new Exception("section2 does not exist.");
        }
        var children = selection.GetChildren();

        foreach (var subSection in children)
        {
            int i = 0;
            var key1 = subSection.Key + ":key" + i++.ToString();
            var key2 = subSection.Key + ":key" + i.ToString();
            s += key1 + " value: " + selection[key1] + "\n";
            s += key2 + " value: " + selection[key2] + "\n";
        }
        return Content(s);
    }
}

前面的代码将调用 ConfigurationExtensions.Exists 以验证该节是否存在:

绑定数组

ConfigurationBinder.Bind 支持使用配置键中的数组索引将数组绑定到对象。 公开数值键段的任何数组格式都能够与 POCO 类数组进行数组绑定。

请考虑示例下载中的 MyArray.json

{
  "array": {
    "entries": {
      "0": "value00",
      "1": "value10",
      "2": "value20",
      "4": "value40",
      "5": "value50"
    }
  }
}

以下代码将 MyArray.json 添加到配置提供程序:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddJsonFile("MyArray.json",
                 optional: true,
                 reloadOnChange: true);

builder.Services.AddRazorPages();

var app = builder.Build();

以下代码将读取配置并显示值:

public class ArrayModel : PageModel
{
    private readonly IConfiguration Config;
    public ArrayExample? _array { get; private set; }

    public ArrayModel(IConfiguration config)
    {
        Config = config;
    }

    public ContentResult OnGet()
    {
       _array = Config.GetSection("array").Get<ArrayExample>();
        if (_array == null)
        {
            throw new ArgumentNullException(nameof(_array));
        }
        string s = String.Empty;

        for (int j = 0; j < _array.Entries.Length; j++)
        {
            s += $"Index: {j}  Value:  {_array.Entries[j]} \n";
        }

        return Content(s);
    }
}
public class ArrayExample
{
    public string[]? Entries { get; set; } 
}

前面的代码会返回以下输出:

Index: 0  Value: value00
Index: 1  Value: value10
Index: 2  Value: value20
Index: 3  Value: value40
Index: 4  Value: value50

在前面的输出中,索引 3 具有值 value40,与 MyArray.json 中的 "4": "value40", 相对应。 绑定的数组索引是连续的,并且未绑定到配置键索引。 配置绑定器不能绑定 NULL 值,也不能在绑定的对象中创建 NULL 条目。

自定义配置提供程序

该示例应用演示了如何使用实体框架 (EF) 创建从数据库读取配置键值对的基本配置提供程序。

提供程序具有以下特征:

  • EF 内存中数据库用于演示目的。 若要使用需要连接字符串的数据库,请实现辅助 ConfigurationBuilder 以从另一个配置提供程序提供连接字符串。
  • 提供程序在启动时将数据库表读入配置。 提供程序不会基于每个键查询数据库。
  • 未实现更改时重载,因此在应用启动后更新数据库对应用的配置没有任何影响。

定义用于在数据库中存储配置值的 EFConfigurationValue 实体。

Models/EFConfigurationValue.cs

public class EFConfigurationValue
{
    public string Id { get; set; } = String.Empty;
    public string Value { get; set; } = String.Empty;
}

添加 EFConfigurationContext 以存储和访问配置的值。

EFConfigurationProvider/EFConfigurationContext.cs

public class EFConfigurationContext : DbContext
{
    public EFConfigurationContext(DbContextOptions<EFConfigurationContext> options) : base(options)
    {
    }

    public DbSet<EFConfigurationValue> Values => Set<EFConfigurationValue>();
}

创建用于实现 IConfigurationSource 的类。

EFConfigurationProvider/EFConfigurationSource.cs

public class EFConfigurationSource : IConfigurationSource
{
    private readonly Action<DbContextOptionsBuilder> _optionsAction;

    public EFConfigurationSource(Action<DbContextOptionsBuilder> optionsAction) => _optionsAction = optionsAction;

    public IConfigurationProvider Build(IConfigurationBuilder builder) => new EFConfigurationProvider(_optionsAction);
}

通过从 ConfigurationProvider 继承来创建自定义配置提供程序。 当数据库为空时,配置提供程序将对其进行初始化。 由于配置密钥不区分大小写,因此用来初始化数据库的字典是用不区分大小写的比较程序 (StringComparer.OrdinalIgnoreCase) 创建的。

EFConfigurationProvider/EFConfigurationProvider.cs

public class EFConfigurationProvider : ConfigurationProvider
{
    public EFConfigurationProvider(Action<DbContextOptionsBuilder> optionsAction)
    {
        OptionsAction = optionsAction;
    }

    Action<DbContextOptionsBuilder> OptionsAction { get; }

    public override void Load()
    {
        var builder = new DbContextOptionsBuilder<EFConfigurationContext>();

        OptionsAction(builder);

        using (var dbContext = new EFConfigurationContext(builder.Options))
        {
            if (dbContext == null || dbContext.Values == null)
            {
                throw new Exception("Null DB context");
            }
            dbContext.Database.EnsureCreated();

            Data = !dbContext.Values.Any()
                ? CreateAndSaveDefaultValues(dbContext)
                : dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
        }
    }

    private static IDictionary<string, string> CreateAndSaveDefaultValues(
        EFConfigurationContext dbContext)
    {
        // Quotes (c)2005 Universal Pictures: Serenity
        // https://www.uphe.com/movies/serenity-2005
        var configValues =
            new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                    { "quote1", "I aim to misbehave." },
                    { "quote2", "I swallowed a bug." },
                    { "quote3", "You can't stop the signal, Mal." }
            };

        if (dbContext == null || dbContext.Values == null)
        {
            throw new Exception("Null DB context");
        }

        dbContext.Values.AddRange(configValues
            .Select(kvp => new EFConfigurationValue
            {
                Id = kvp.Key,
                Value = kvp.Value
            })
            .ToArray());

        dbContext.SaveChanges();

        return configValues;
    }
}

可以使用 AddEFConfiguration 扩展方法将配置源添加到 ConfigurationBuilder

Extensions/EntityFrameworkExtensions.cs

public static class EntityFrameworkExtensions
{
    public static IConfigurationBuilder AddEFConfiguration(
               this IConfigurationBuilder builder,
               Action<DbContextOptionsBuilder> optionsAction)
    {
        return builder.Add(new EFConfigurationSource(optionsAction));
    }
}

下面的代码演示如何在 Program.cs 中使用自定义的 EFConfigurationProvider

//using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddEFConfiguration(
    opt => opt.UseInMemoryDatabase("InMemoryDb"));

var app = builder.Build();

app.Run();

使用依赖关系注入 (DI) 的访问配置

通过解析 IConfiguration 服务,可以使用依赖关系注入 (DI) 将配置注入服务:

public class Service
{
    private readonly IConfiguration _config;

    public Service(IConfiguration config) =>
        _config = config;

    public void DoSomething()
    {
        var configSettingValue = _config["ConfigSetting"];

        // ...
    }
}

有关如何使用 IConfiguration 访问值的信息,请参阅本文中的 GetValue 以及 GetSection、GetChildren 和 Exists

访问 Razor Pages 中的配置

以下代码显示 Razor Pages 中的配置数据:

@page
@model Test5Model
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

在以下代码中,MyOptions 已通过 Configure 被添加到了服务容器并已绑定到了配置:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

以下标记使用 @injectRazor 指令来解析和显示选项值:

@page
@model SampleApp.Pages.Test3Model
@using Microsoft.Extensions.Options
@using SampleApp.Models
@inject IOptions<MyOptions> optionsAccessor


<p><b>Option1:</b> @optionsAccessor.Value.Option1</p>
<p><b>Option2:</b> @optionsAccessor.Value.Option2</p>

访问 MVC 视图文件中的配置

以下代码显示 MVC 视图中的配置数据:

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

访问 Program.cs 中的配置

以下代码访问 Program.cs 文件中的配置。

var builder = WebApplication.CreateBuilder(args);

var key1 = builder.Configuration.GetValue<string>("KeyOne");

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

var key2 = app.Configuration.GetValue<int>("KeyTwo");
var key3 = app.Configuration.GetValue<bool>("KeyThree");

app.Logger.LogInformation("KeyOne: {KeyOne}", key1);
app.Logger.LogInformation("KeyTwo: {KeyTwo}", key2);
app.Logger.LogInformation("KeyThree: {KeyThree}", key3);

app.Run();

在上面的示例的 appsettings.json 中:

{
  ...
  "KeyOne": "Key One Value",
  "KeyTwo": 1999,
  "KeyThree": true
}

使用委托来配置选项

在委托中配置的选项替代在配置提供程序中设置的值。

在以下代码中,向服务容器添加了 IConfigureOptions<TOptions> 服务。 它使用委托来配置 MyOptions 的值:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "Value configured in delegate";
    myOptions.Option2 = 500;
});

var app = builder.Build();

以下代码显示选项值:

public class Test2Model : PageModel
{
    private readonly IOptions<MyOptions> _optionsDelegate;

    public Test2Model(IOptions<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_optionsDelegate.Value.Option1} \n" +
                       $"Option2: {_optionsDelegate.Value.Option2}");
    }
}

在前面的示例中,Option1Option2 的值在 appsettings.json 中指定,然后被配置的委托替代。

主机与应用配置

在配置并启动应用之前,配置并启动主机。 主机负责应用程序启动和生存期管理。 应用和主机均使用本主题中所述的配置提供程序进行配置。 应用的配置中也包含主机配置键值对。 有关在构建主机时如何使用配置提供程序,以及配置源如何影响主机配置的详细信息,请参阅 ASP.NET Core 基础知识概述

默认主机配置

有关使用 Web 主机时默认配置的详细信息,请参阅本主题的 ASP.NET Core 2.2 版本

  • 主机配置通过以下方式提供:
  • 已建立 Web 主机默认配置 (ConfigureWebHostDefaults):
    • Kestrel 用作 Web 服务器,并使用应用的配置提供程序对其进行配置。
    • 添加主机筛选中间件。
    • 如果 ASPNETCORE_FORWARDEDHEADERS_ENABLED 环境变量设置为 true,则添加转发的标头中间件。
    • 启用 IIS 集成。

其他配置

本主题仅与应用配置相关。 运行和托管 ASP.NET Core 应用的其他方面是使用本主题中未包含的配置文件进行配置:

launchSettings.json 中设置的环境变量将替代在系统环境中设置的变量。

若要详细了解如何从旧版 ASP.NET 迁移应用配置,请参阅从 ASP.NET 升级到 ASP.NET Core

从外部程序集添加配置

通过 IHostingStartup 实现,可在启动时从应用 Startup 类之外的外部程序集向应用添加增强功能。 有关详细信息,请参阅在 ASP.NET Core 中使用托管启动程序集

其他资源

ASP.NET Core 中的应用程序配置是使用一个或多个配置提供程序执行的。 配置提供程序使用各种配置源从键值对读取配置数据:

  • 设置文件,例如 appsettings.json
  • 环境变量
  • Azure Key Vault
  • Azure 应用配置
  • 命令行参数
  • 已安装或已创建的自定义提供程序
  • 目录文件
  • 内存中的 .NET 对象

本文介绍 ASP.NET Core 中的配置。 若要了解如何使用控制台应用中的配置,请参阅 .NET 配置

应用程序和主机配置

ASP.NET Core 应用配置和启动“主机”。 主机负责应用程序启动和生存期管理。 ASP.NET Core 模板创建的 WebApplicationBuilder 包含主机。 虽然可以在主机和应用程序配置提供程序中完成一些配置,但通常,只有主机必需的配置才应在主机配置中完成。

应用程序配置具有最高优先级,下一部分对此进行了详细介绍。 主机配置遵循应用程序配置,本文对此进行了介绍。

默认应用程序配置源

通过 dotnet new 或 Visual Studio 创建的 ASP.NET Core Web 应用会生成以下代码:

var builder = WebApplication.CreateBuilder(args);

WebApplication.CreateBuilder 使用预配置的默认值初始化 WebApplicationBuilder 类的新实例。 经过初始化的 WebApplicationBuilder (builder) 按照以下顺序为应用提供默认配置(从最高优先级到最低优先级):

  1. 使用命令行配置提供程序通过命令行参数提供。
  2. 使用非前缀环境变量配置提供程序通过非前缀环境变量提供。
  3. 应用在 Development 环境中运行时的用户机密
  4. 使用 JSON 配置提供程序通过 appsettings.{Environment}.json 提供。 例如,appsettings.Production.jsonappsettings.Development.json
  5. 使用 JSON 配置提供程序通过 appsettings.json 提供。
  6. 回退到下一部分所述的主机配置。

默认主机配置源

以下列表包含从最高优先级到最低优先级的默认主机配置源:

  1. 使用环境变量配置提供程序通过以 ASPNETCORE_ 为前缀的环境变量提供。
  2. 使用命令行配置提供程序通过命令行参数提供
  3. 使用环境变量配置提供程序通过以 DOTNET_ 为前缀的环境变量提供。

在主机和应用程序配置中设置配置值时,将使用应用程序配置。

请参阅此 GitHub 评论中的说明,了解说明在主机配置中以 ASPNETCORE_ 为前缀的环境变量的优先级高于命令行参数的原因。

主机变量

初始化主机生成器时,会提前锁定以下变量,并且它们不受应用程序配置的影响:

从应用程序配置而不是主机配置读取所有其他主机设置。

URLS 是许多不是启动设置的常见主机设置之一。 与不在前一个列表中的所有其他主机设置一样,稍后会从应用程序配置中读取 URLS。主机配置是应用程序配置的后备,因此主机配置可用于设置 URLS,但它将被应用程序配置中的任何配置源(如 appsettings.json)覆盖。

有关详细信息,请参阅更改内容根、应用名称和环境通过环境变量或命令行更改内容根、应用名称和环境

本文中的其余部分介绍应用程序配置。

应用程序配置提供程序

以下代码按添加顺序显示了已启用的配置提供程序:

public class Index2Model : PageModel
{
    private IConfigurationRoot ConfigRoot;

    public Index2Model(IConfiguration configRoot)
    {
        ConfigRoot = (IConfigurationRoot)configRoot;
    }

    public ContentResult OnGet()
    {           
        string str = "";
        foreach (var provider in ConfigRoot.Providers.ToList())
        {
            str += provider.ToString() + "\n";
        }

        return Content(str);
    }
}

前面的优先级从高到低的默认配置源列表以它们被添加到模板生成的应用程序中的相反顺序显示提供程序。 例如,JSON 配置提供程序被添加到命令行配置提供程序之前。

后来添加的配置提供程序具有更高的优先级并且会替代之前的密钥设置。 例如,如果 appsettings.json 和环境中都设置了 MyKey,则使用环境值。 通过默认配置提供程序,命令行配置提供程序将替代其他所有提供程序。

若要详细了解 CreateBuilder,请参阅默认生成器设置

appsettings.json

请考虑以下 appsettings.json 文件:

{
  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  },
  "MyKey": "My appsettings.json Value",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

以下来自示例下载的代码显示了上述的一些配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

默认的 JsonConfigurationProvider 会按以下顺序加载配置:

  1. appsettings.json
  2. appsettings.{Environment}.json:例如,appsettings.Production.jsonappsettings.Development.json 文件。 文件的环境版本是根据 IHostingEnvironment.EnvironmentName 加载的。 有关详细信息,请参阅在 ASP.NET Core 中使用多个环境

appsettings.{Environment}.json 值替代 appsettings.json 中的键。 例如,默认情况下:

  • 在开发环境中,appsettings.Development.json 配置会覆盖在 appsettings.json 中找到的值。
  • 在生产环境中,appsettings.Production.json 配置会覆盖在 appsettings.json 中找到的值。 例如,在将应用部署到 Azure 时。

如果必须保证配置值,请参阅 GetValue。 前面的示例只读取字符串,不支持默认值。

使用默认配置时,会通过 reloadOnChange: true 启用 appsettings.jsonappsettings.{Environment}.json 文件。 应用启动后,对 appsettings.jsonappsettings.{Environment}.json 文件所做的更改将由 JSON 配置提供程序读取。

使用选项模式绑定分层配置数据

读取相关配置值的首选方法是使用选项模式。 例如,若要读取以下配置值,请执行以下操作:

  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

创建以下 PositionOptions 类:

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; } = String.Empty;
    public string Name { get; set; } = String.Empty;
}

选项类:

  • 必须是包含公共无参数构造函数的非抽象类。
  • 类型的所有公共读写属性都已绑定。
  • 字段不是绑定的。 在上面的代码中,Position 未绑定。 由于使用了 Position 字段,因此在将类绑定到配置提供程序时,不需要在应用中对字符串 "Position" 进行硬编码。

下面的代码:

public class Test22Model : PageModel
{
    private readonly IConfiguration Configuration;

    public Test22Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var positionOptions = new PositionOptions();
        Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。

ConfigurationBinder.Get<T> 绑定并返回指定的类型。 使用 ConfigurationBinder.Get<T> 可能比使用 ConfigurationBinder.Bind 更方便。 下面的代码演示如何将 ConfigurationBinder.Get<T>PositionOptions 类配合使用:

public class Test21Model : PageModel
{
    private readonly IConfiguration Configuration;
    public PositionOptions? positionOptions { get; private set; }

    public Test21Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。

使用选项模式时的另一种方法是绑定 Position 部分,并将其添加到依赖关系注入服务容器。 在以下代码中,PositionOptions 已通过 Configure 被添加到了服务容器并已绑定到了配置:

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();

通过使用前面的代码,以下代码将读取位置选项:

public class Test2Model : PageModel
{
    private readonly PositionOptions _options;

    public Test2Model(IOptions<PositionOptions> options)
    {
        _options = options.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Title: {_options.Title} \n" +
                       $"Name: {_options.Name}");
    }
}

在上面的代码中,不会读取在应用启动后对 JSON 配置文件所做的更改。 若要读取在应用启动后的更改,请使用 IOptionsSnapshot

使用默认配置时,会通过 reloadOnChange: true 启用 appsettings.jsonappsettings.{Environment}.json 文件。 应用启动后,对 appsettings.jsonappsettings.{Environment}.json 文件所做的更改将由 JSON 配置提供程序读取。

有关添加其他 JSON 配置文件的信息,请参阅本文档中的 JSON 配置提供程序

合并服务集合

考虑下面的方法,该方法可注册服务并配置选项:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

可以将相关的注册组移动到扩展方法以注册服务。 例如,配置服务会被添加到以下类中:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

剩余的服务会在类似的类中注册。 下面的代码使用新扩展方法来注册服务:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

注意:每个 services.Add{GROUP_NAME} 扩展方法添加并可能配置服务。 例如,AddControllersWithViews 会添加带视图的 MVC 控制器所需的服务,AddRazorPages 会添加 Razor Pages 所需的服务。

安全性和用户机密

配置数据指南:

  • 请勿在配置提供程序代码或纯文本配置文件中存储密码或其他敏感数据。 机密管理器工具可用于存储开发环境中的机密。
  • 不要在开发或测试环境中使用生产机密。
  • 请在项目外部指定机密,避免将其意外提交到源代码存储库。

默认情况下,将在 JSON 配置源后注册用户机密配置源。 因此,用户机密密钥优先于 appsettings.jsonappsettings.{Environment}.json 中的密钥。

有关存储密码或其他敏感数据的详细信息:

Azure Key Vault 安全存储 ASP.NET Core 应用的应用机密。 有关详细信息,请参阅 ASP.NET Core 中的 Azure Key Vault 配置提供程序

非前缀环境变量

非前缀环境变量不是以ASPNETCORE_DOTNET_ 为前缀的环境变量。 例如,launchSettings.json 中的 ASP.NET Core Web 应用程序模板设置集 "ASPNETCORE_ENVIRONMENT": "Development"。 有关 ASPNETCORE_DOTNET_ 环境变量的详细信息,请参阅以下内容:

使用默认配置时,EnvironmentVariablesConfigurationProvider 会在读取 appsettings.jsonappsettings.{Environment}.json用户机密之后,从环境变量键值对中加载配置。 因此,从环境中读取的键值会替代从 appsettings.jsonappsettings.{Environment}.json 和用户机密中读取的值。

所有平台上的环境变量分层键都不支持 : 分隔符。 __(双下划线):

  • 受所有平台支持。 例如,Bash 不支持 : 分隔符,但支持 __
  • 自动替换为 :

以下 set 命令:

  • 在 Windows 上设置上述示例的环境键和值。
  • 在使用示例下载时测试设置。 dotnet run 命令必须在项目目录中运行。
set MyKey="My key from Environment"
set Position__Title=Environment_Editor
set Position__Name=Environment_Rick
dotnet run

前面的环境设置:

  • 仅在进程中设置,这些进程是从设置进程的命令窗口启动的。
  • 不会由通过 Visual Studio 启动的浏览器读取。

以下 setx 命令可用于在 Windows 上设置环境键和值。 与 set 不同,setx 设置是持久的。 /M 在系统环境中设置变量。 如果未使用 /M 开关,则会设置用户环境变量。

setx MyKey "My key from setx Environment" /M
setx Position__Title Environment_Editor /M
setx Position__Name Environment_Rick /M

测试前面的命令是否会替代 appsettings.jsonappsettings.{Environment}.json

  • 使用 Visual Studio:退出并重启 Visual Studio。
  • 使用 CLI:启动新的命令窗口并输入 dotnet run

使用字符串调用 AddEnvironmentVariables 以指定环境变量的前缀:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Configuration.AddEnvironmentVariables(prefix: "MyCustomPrefix_");

var app = builder.Build();

在上述代码中:

前缀会在读取配置键值对时被去除。

以下命令对自定义前缀进行测试:

set MyCustomPrefix_MyKey="My key with MyCustomPrefix_ Environment"
set MyCustomPrefix_Position__Title=Editor_with_customPrefix
set MyCustomPrefix_Position__Name=Environment_Rick_cp
dotnet run

默认配置会加载前缀为 DOTNET_ASPNETCORE_ 的环境变量和命令行参数。 DOTNET_ASPNETCORE_ 前缀会由 ASP.NET Core 用于主机和应用配置,但不用于用户配置。 有关主机和应用配置的详细信息,请参阅 .NET 通用主机

Azure 应用服务上,选择“设置”>“配置”页面上的“新应用程序设置”。 Azure 应用服务应用程序设置:

  • 已静态加密且通过加密的通道进行传输。
  • 已作为环境变量公开。

有关详细信息,请参阅 Azure 应用:使用 Azure 门户替代应用配置

有关 Azure 数据库连接字符串的信息,请参阅连接字符串前缀

环境变量的命名

环境变量名称反映了 appsettings.json 文件的结构。 层次结构中的每个元素由双下划线字符(更可取)或冒号分隔。 当元素结构包含数组时,应将数组索引视为此路径中的附加元素名称。 请考虑以下 appsettings.json 文件及其表示为环境变量的等效值。

appsettings.json

{
    "SmtpServer": "smtp.example.com",
    "Logging": [
        {
            "Name": "ToEmail",
            "Level": "Critical",
            "Args": {
                "FromAddress": "MySystem@example.com",
                "ToAddress": "SRE@example.com"
            }
        },
        {
            "Name": "ToConsole",
            "Level": "Information"
        }
    ]
}

环境变量

setx SmtpServer smtp.example.com
setx Logging__0__Name ToEmail
setx Logging__0__Level Critical
setx Logging__0__Args__FromAddress MySystem@example.com
setx Logging__0__Args__ToAddress SRE@example.com
setx Logging__1__Name ToConsole
setx Logging__1__Level Information

在生成的 launchSettings.json 中设置的环境变量

launchSettings.json 中设置的环境变量将替代在系统环境中设置的变量。 例如,ASP.NET Core Web 模板会生成一个 launchSettings.json 文件,该文件将终结点配置设置为:

"applicationUrl": "https://localhost:5001;http://localhost:5000"

配置 applicationUrl 将设置 ASPNETCORE_URLS 环境变量并重写环境中设置的值。

在 Linux 上转义环境变量

在 Linux 上,必须转义 URL 环境变量的值,使 systemd 可以对其进行分析。 使用 Linux 工具 systemd-escape 生成 http:--localhost:5001

groot@terminus:~$ systemd-escape http://localhost:5001
http:--localhost:5001

显示环境变量

下面的代码显示了应用程序启动时的环境变量和值,这对调试环境设置很有帮助:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

foreach (var c in builder.Configuration.AsEnumerable())
{
    Console.WriteLine(c.Key + " = " + c.Value);
}

命令行

使用默认配置,CommandLineConfigurationProvider 会从以下配置源后的命令行参数键值对中加载配置:

  • appsettings.jsonappsettings.{Environment}.json 文件。
  • 开发环境中的应用机密
  • 环境变量。

默认情况下,在命令行上设置的配置值会替代通过所有其他配置提供程序设置的配置值。

命令行参数

以下命令使用 = 设置键和值:

dotnet run MyKey="Using =" Position:Title=Cmd Position:Name=Cmd_Rick

以下命令使用 / 设置键和值:

dotnet run /MyKey "Using /" /Position:Title=Cmd /Position:Name=Cmd_Rick

以下命令使用 -- 设置键和值:

dotnet run --MyKey "Using --" --Position:Title=Cmd --Position:Name=Cmd_Rick

键值:

  • 必须后跟 =,或者当值后跟一个空格时,键必须具有一个 --/ 的前缀。
  • 如果使用 =,则不是必需的。 例如 MySetting=

在同一命令中,请勿将使用 = 的命令行参数键值对与使用空格的键值对混合使用。

交换映射

交换映射支持键名替换逻辑。 提供针对 AddCommandLine 方法的交换替换字典。

当使用交换映射字典时,会检查字典中是否有与命令行参数提供的键匹配的键。 如果在字典中找到了命令行键,则会传回字典值将键值对设置为应用的配置。 对任何具有单划线 (-) 前缀的命令行键而言,交换映射都是必需的。

交换映射字典键规则:

  • 交换必须以 --- 开头。
  • 交换映射字典不得包含重复键。

若要使用交换映射字典,请将它传递到对 AddCommandLine 的调用中:


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var switchMappings = new Dictionary<string, string>()
         {
             { "-k1", "key1" },
             { "-k2", "key2" },
             { "--alt3", "key3" },
             { "--alt4", "key4" },
             { "--alt5", "key5" },
             { "--alt6", "key6" },
         };

builder.Configuration.AddCommandLine(args, switchMappings);

var app = builder.Build();

运行以下命令可测试键替换:

dotnet run -k1 value1 -k2 value2 --alt3=value2 /alt4=value3 --alt5 value5 /alt6 value6

下面的代码显示了替换后的键的键值:

public class Test3Model : PageModel
{
    private readonly IConfiguration Config;

    public Test3Model(IConfiguration configuration)
    {
        Config = configuration;
    }

    public ContentResult OnGet()
    {
        return Content(
                $"Key1: '{Config["Key1"]}'\n" +
                $"Key2: '{Config["Key2"]}'\n" +
                $"Key3: '{Config["Key3"]}'\n" +
                $"Key4: '{Config["Key4"]}'\n" +
                $"Key5: '{Config["Key5"]}'\n" +
                $"Key6: '{Config["Key6"]}'");
    }
}

对于使用交换映射的应用,调用 CreateDefaultBuilder 不应传递参数。 CreateDefaultBuilder 方法的 AddCommandLine 调用不包括映射的交换,并且无法将交换映射字典传递给 CreateDefaultBuilder。 解决方案不是将参数传递给 CreateDefaultBuilder,而是允许 ConfigurationBuilder 方法的 AddCommandLine 方法处理参数和交换映射字典。

在 Visual Studio 中设置环境和命令行参数

可以在 Visual Studio 中的“启动配置文件”对话框设置环境和命令行参数:

  • 在“解决方案资源管理器”中,右键单击项目,然后选择“属性”。
  • 转到“调试”>“常规”选项卡,然后选择“打开调试启动配置文件 UI”>

分层配置数据

配置 API 在配置键中使用分隔符来展平分层数据,以此来读取分层配置数据。

示例下载包含以下 appsettings.json 文件:

{
  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  },
  "MyKey": "My appsettings.json Value",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

以下来自示例下载的代码显示了一些配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

读取分层配置数据的首选方法是使用选项模式。 有关详细信息,请参阅本文档中的绑定分层配置数据

GetSectionGetChildren 方法可用于隔离各个节和配置数据中某节的子节。 稍后将在 GetSection、GetChildren 和 Exists 中介绍这些方法。

配置键和值

配置键:

  • 不区分大小写。 例如,ConnectionStringconnectionstring 被视为等效键。
  • 如果在多个配置提供程序中设置了某一键和值,则会使用最后添加的提供程序中的值。 有关详细信息,请参阅默认配置
  • 分层键
    • 在配置 API 中,冒号分隔符 (:) 适用于所有平台。
    • 在环境变量中,冒号分隔符可能无法适用于所有平台。 所有平台均支持采用双下划线 __,并且它会自动转换为冒号 :
    • 在 Azure Key Vault 中,分层键使用 -- 作为分隔符。 当机密加载到应用的配置中时,Azure Key Vault 配置提供程序 会自动将 -- 替换为 :
  • ConfigurationBinder 支持使用配置键中的数组索引将数组绑定到对象。 数组绑定将在将数组绑定到类部分中进行介绍。

配置值:

  • 为字符串。
  • NULL 值不能存储在配置中或绑定到对象。

配置提供程序

下表显示了 ASP.NET Core 应用可用的配置提供程序。

提供程序 通过以下对象提供配置
Azure Key Vault 配置提供程序 Azure Key Vault
Azure 应用配置提供程序 Azure 应用程序配置
命令行配置提供程序 命令行参数
自定义配置提供程序 自定义源
环境变量配置提供程序 环境变量
文件配置提供程序 INI、JSON 和 XML 文件
Key-per-file 配置提供程序 目录文件
内存配置提供程序 内存中集合
用户机密 用户配置文件目录中的文件

按照指定的配置提供程序的顺序读取配置源。 代码中的配置提供程序应以特定顺序排列,从而满足应用所需的基础配置源的优先级。

配置提供程序的典型顺序为:

  1. appsettings.json
  2. appsettings.{Environment}.json
  3. 用户机密
  4. 使用环境变量配置提供程序通过环境变量提供。
  5. 使用命令行配置提供程序通过命令行参数提供。

通常的做法是将命令行配置提供程序添加到一系列提供程序的末尾,使命令行参数能够替代由其他提供程序设置的配置。

默认配置中使用了上述提供程序顺序。

连接字符串前缀

对于四个连接字符串环境变量,配置 API 具有特殊的处理规则。 这些连接字符串涉及了为应用环境配置 Azure 连接字符串。 使用默认配置或没有向 AddEnvironmentVariables 应用前缀时,具有表中所示前缀的环境变量将加载到应用中。

连接字符串前缀 提供程序
CUSTOMCONNSTR_ 自定义提供程序
MYSQLCONNSTR_ MySQL
SQLAZURECONNSTR_ Azure SQL 数据库
SQLCONNSTR_ SQL Server

当发现环境变量并使用表中所示的四个前缀中的任何一个加载到配置中时:

  • 通过删除环境变量前缀并添加配置键节 (ConnectionStrings) 来创建配置键。
  • 创建一个新的配置键值对,表示数据库连接提供程序(CUSTOMCONNSTR_ 除外,它没有声明的提供程序)。
环境变量键 转换的配置键 提供程序配置条目
CUSTOMCONNSTR_{KEY} ConnectionStrings:{KEY} 配置条目未创建。
MYSQLCONNSTR_{KEY} ConnectionStrings:{KEY} 键:ConnectionStrings:{KEY}_ProviderName
值:MySql.Data.MySqlClient
SQLAZURECONNSTR_{KEY} ConnectionStrings:{KEY} 键:ConnectionStrings:{KEY}_ProviderName
值:System.Data.SqlClient
SQLCONNSTR_{KEY} ConnectionStrings:{KEY} 键:ConnectionStrings:{KEY}_ProviderName
值:System.Data.SqlClient

文件配置提供程序

FileConfigurationProvider 是从文件系统加载配置的基类。 以下配置提供程序派生自 FileConfigurationProvider

INI 配置提供程序

IniConfigurationProvider 在运行时从 INI 文件键值对加载配置。

以下代码添加多个配置提供程序:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddIniFile("MyIniConfig.ini", optional: true, reloadOnChange: true)
    .AddIniFile($"MyIniConfig.{builder.Environment.EnvironmentName}.ini",
                optional: true, reloadOnChange: true);

builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddCommandLine(args);

builder.Services.AddRazorPages();

var app = builder.Build();

在前面的代码中,MyIniConfig.iniMyIniConfig.{Environment}.ini 文件中的设置会被以下提供程序中的设置替代:

示例下载包含以下 MyIniConfig.ini 文件:

MyKey="MyIniConfig.ini Value"

[Position]
Title="My INI Config title"
Name="My INI Config name"

[Logging:LogLevel]
Default=Information
Microsoft=Warning

以下来自示例下载的代码显示了上述的一些配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

JSON 配置提供程序

JsonConfigurationProvider 从 JSON 文件键值对加载配置。

重载可以指定:

  • 文件是否可选。
  • 如果文件更改,是否重载配置。

考虑下列代码:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddJsonFile("MyConfig.json",
        optional: true,
        reloadOnChange: true);

builder.Services.AddRazorPages();

var app = builder.Build();

前面的代码:

通常,你不会希望自定义 JSON 文件替代在环境变量配置提供程序命令行配置提供程序中设置的值

XML 配置提供程序

XmlConfigurationProvider 在运行时从 XML 文件键值对加载配置。

以下代码添加多个配置提供程序:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddXmlFile("MyXMLFile.xml", optional: true, reloadOnChange: true)
    .AddXmlFile($"MyXMLFile.{builder.Environment.EnvironmentName}.xml",
                optional: true, reloadOnChange: true);

builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddCommandLine(args);

builder.Services.AddRazorPages();

var app = builder.Build();

在前面的代码中,MyXMLFile.xmlMyXMLFile.{Environment}.xml 文件中的设置会被以下提供程序中的设置替代:

示例下载包含以下 MyXMLFile.xml 文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <MyKey>MyXMLFile Value</MyKey>
  <Position>
    <Title>Title from  MyXMLFile</Title>
    <Name>Name from MyXMLFile</Name>
  </Position>
  <Logging>
    <LogLevel>
      <Default>Information</Default>
      <Microsoft>Warning</Microsoft>
    </LogLevel>
  </Logging>
</configuration>

以下来自示例下载的代码显示了上述的一些配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

如果使用 name 属性来区分元素,则使用相同元素名称的重复元素可以正常工作:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <section name="section0">
    <key name="key0">value 00</key>
    <key name="key1">value 01</key>
  </section>
  <section name="section1">
    <key name="key0">value 10</key>
    <key name="key1">value 11</key>
  </section>
</configuration>

以下代码会读取前面的配置文件并显示键和值:

public class IndexModel : PageModel
{
    private readonly IConfiguration Configuration;

    public IndexModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var key00 = "section:section0:key:key0";
        var key01 = "section:section0:key:key1";
        var key10 = "section:section1:key:key0";
        var key11 = "section:section1:key:key1";

        var val00 = Configuration[key00];
        var val01 = Configuration[key01];
        var val10 = Configuration[key10];
        var val11 = Configuration[key11];

        return Content($"{key00} value: {val00} \n" +
                       $"{key01} value: {val01} \n" +
                       $"{key10} value: {val10} \n" +
                       $"{key10} value: {val11} \n"
                       );
    }
}

属性可用于提供值:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <key attribute="value" />
  <section>
    <key attribute="value" />
  </section>
</configuration>

以前的配置文件使用 value 加载以下键:

  • key:attribute
  • section:key:attribute

Key-per-file 配置提供程序

KeyPerFileConfigurationProvider 使用目录的文件作为配置键值对。 该键是文件名。 该值包含文件的内容。 Key-per-file 配置提供程序用于 Docker 托管方案。

若要激活 Key-per-file 配置,请在 ConfigurationBuilder 的实例上调用 AddKeyPerFile 扩展方法。 文件的 directoryPath 必须是绝对路径。

重载允许指定:

  • 配置源的 Action<KeyPerFileConfigurationSource> 委托。
  • 目录是否可选以及目录的路径。

双下划线字符 (__) 用作文件名中的配置键分隔符。 例如,文件名 Logging__LogLevel__System 生成配置键 Logging:LogLevel:System

构建主机时调用 ConfigureAppConfiguration 以指定应用的配置:

.ConfigureAppConfiguration((hostingContext, config) =>
{
    var path = Path.Combine(
        Directory.GetCurrentDirectory(), "path/to/files");
    config.AddKeyPerFile(directoryPath: path, optional: true);
})

内存配置提供程序

MemoryConfigurationProvider 使用内存中集合作为配置键值对。

以下代码将内存集合添加到配置系统中:

var builder = WebApplication.CreateBuilder(args);

var Dict = new Dictionary<string, string>
        {
           {"MyKey", "Dictionary MyKey Value"},
           {"Position:Title", "Dictionary_Title"},
           {"Position:Name", "Dictionary_Name" },
           {"Logging:LogLevel:Default", "Warning"}
        };

builder.Configuration.AddInMemoryCollection(Dict);
builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddCommandLine(args);

builder.Services.AddRazorPages();

var app = builder.Build();

以下来自示例下载的代码显示了上述配置设置:

public class TestModel : PageModel
{
    // requires using Microsoft.Extensions.Configuration;
    private readonly IConfiguration Configuration;

    public TestModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }
}

在前面的代码中,config.AddInMemoryCollection(Dict) 会被添加到默认配置提供程序之后。 有关对配置提供程序进行排序的示例,请参阅 JSON 配置提供程序

有关使用 MemoryConfigurationProvider 的其他示例,请参阅绑定数组

Kestrel 终结点配置

Kestrel 特定的终结点配置将覆盖所有跨服务器终结点配置。 跨服务器终结点配置包括:

请考虑在 ASP.NET Core Web 应用中使用的以下 appsettings.json 文件:

{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://localhost:9999"
      }
    }
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
} 

当在 ASP.NET Core Web 应用中使用前面突出显示的标记,并且应用在命令行上启动,且跨服务器端点配置如下时

dotnet run --urls="https://localhost:7777"

Kestrel 将绑定到专门针对 appsettings.json 文件中的 Kestrel 配置的终结点 (https://localhost:9999),而不是 https://localhost:7777

请考虑将 Kestrel 特定的终结点配置为环境变量:

set Kestrel__Endpoints__Https__Url=https://localhost:8888

在前面的环境变量中,Https 是 Kestrel 特定的终结点的名称。 前面的 appsettings.json 文件还定义了名为 Https 的 Kestrel 特定终结点。 默认情况下,将在 appsettings.{Environment}.json 后读取使用环境变量配置提供程序的环境变量,因此,前面的环境变量用于 Https 终结点。

GetValue

ConfigurationBinder.GetValue 从配置中提取一个具有指定键的值,并将它转换为指定的类型:

public class TestNumModel : PageModel
{
    private readonly IConfiguration Configuration;

    public TestNumModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var number = Configuration.GetValue<int>("NumberKey", 99);
        return Content($"{number}");
    }
}

在前面的代码中,如果在配置中找不到 NumberKey,则使用默认值 99

GetSection、GetChildren 和 Exists

对于下面的示例,请考虑以下 MySubsection.json 文件:

{
  "section0": {
    "key0": "value00",
    "key1": "value01"
  },
  "section1": {
    "key0": "value10",
    "key1": "value11"
  },
  "section2": {
    "subsection0": {
      "key0": "value200",
      "key1": "value201"
    },
    "subsection1": {
      "key0": "value210",
      "key1": "value211"
    }
  }
}

以下代码将 MySubsection.json 添加到配置提供程序:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddJsonFile("MySubsection.json",
                 optional: true,
                 reloadOnChange: true);

builder.Services.AddRazorPages();

var app = builder.Build();

GetSection

IConfiguration.GetSection 会返回具有指定子节键的配置子节。

以下代码将返回 section1 的值:

public class TestSectionModel : PageModel
{
    private readonly IConfiguration Config;

    public TestSectionModel(IConfiguration configuration)
    {
        Config = configuration.GetSection("section1");
    }

    public ContentResult OnGet()
    {
        return Content(
                $"section1:key0: '{Config["key0"]}'\n" +
                $"section1:key1: '{Config["key1"]}'");
    }
}

以下代码将返回 section2:subsection0 的值:

public class TestSection2Model : PageModel
{
    private readonly IConfiguration Config;

    public TestSection2Model(IConfiguration configuration)
    {
        Config = configuration.GetSection("section2:subsection0");
    }

    public ContentResult OnGet()
    {
        return Content(
                $"section2:subsection0:key0 '{Config["key0"]}'\n" +
                $"section2:subsection0:key1:'{Config["key1"]}'");
    }
}

GetSection 永远不会返回 null。 如果找不到匹配的节,则返回空 IConfigurationSection

GetSection 返回匹配的部分时,Value 未填充。 存在该部分时,返回一个 KeyPath 部分。

GetChildren 和 Exists

以下代码将调用 IConfiguration.GetChildren 并返回 section2:subsection0 的值:

public class TestSection4Model : PageModel
{
    private readonly IConfiguration Config;

    public TestSection4Model(IConfiguration configuration)
    {
        Config = configuration;
    }

    public ContentResult OnGet()
    {
        string s = "";
        var selection = Config.GetSection("section2");
        if (!selection.Exists())
        {
            throw new Exception("section2 does not exist.");
        }
        var children = selection.GetChildren();

        foreach (var subSection in children)
        {
            int i = 0;
            var key1 = subSection.Key + ":key" + i++.ToString();
            var key2 = subSection.Key + ":key" + i.ToString();
            s += key1 + " value: " + selection[key1] + "\n";
            s += key2 + " value: " + selection[key2] + "\n";
        }
        return Content(s);
    }
}

前面的代码将调用 ConfigurationExtensions.Exists 以验证该节是否存在:

绑定数组

ConfigurationBinder.Bind 支持使用配置键中的数组索引将数组绑定到对象。 公开数值键段的任何数组格式都能够与 POCO 类数组进行数组绑定。

请考虑示例下载中的 MyArray.json

{
  "array": {
    "entries": {
      "0": "value00",
      "1": "value10",
      "2": "value20",
      "4": "value40",
      "5": "value50"
    }
  }
}

以下代码将 MyArray.json 添加到配置提供程序:

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddJsonFile("MyArray.json",
                 optional: true,
                 reloadOnChange: true);

builder.Services.AddRazorPages();

var app = builder.Build();

以下代码将读取配置并显示值:

public class ArrayModel : PageModel
{
    private readonly IConfiguration Config;
    public ArrayExample? _array { get; private set; }

    public ArrayModel(IConfiguration config)
    {
        Config = config;
    }

    public ContentResult OnGet()
    {
       _array = Config.GetSection("array").Get<ArrayExample>();
        if (_array == null)
        {
            throw new ArgumentNullException(nameof(_array));
        }
        string s = String.Empty;

        for (int j = 0; j < _array.Entries.Length; j++)
        {
            s += $"Index: {j}  Value:  {_array.Entries[j]} \n";
        }

        return Content(s);
    }
}
public class ArrayExample
{
    public string[]? Entries { get; set; } 
}

前面的代码会返回以下输出:

Index: 0  Value: value00
Index: 1  Value: value10
Index: 2  Value: value20
Index: 3  Value: value40
Index: 4  Value: value50

在前面的输出中,索引 3 具有值 value40,与 MyArray.json 中的 "4": "value40", 相对应。 绑定的数组索引是连续的,并且未绑定到配置键索引。 配置绑定器不能绑定 NULL 值,也不能在绑定的对象中创建 NULL 条目。

自定义配置提供程序

该示例应用演示了如何使用实体框架 (EF) 创建从数据库读取配置键值对的基本配置提供程序。

提供程序具有以下特征:

  • EF 内存中数据库用于演示目的。 若要使用需要连接字符串的数据库,请实现辅助 ConfigurationBuilder 以从另一个配置提供程序提供连接字符串。
  • 提供程序在启动时将数据库表读入配置。 提供程序不会基于每个键查询数据库。
  • 未实现更改时重载,因此在应用启动后更新数据库对应用的配置没有任何影响。

定义用于在数据库中存储配置值的 EFConfigurationValue 实体。

Models/EFConfigurationValue.cs

public class EFConfigurationValue
{
    public string Id { get; set; } = String.Empty;
    public string Value { get; set; } = String.Empty;
}

添加 EFConfigurationContext 以存储和访问配置的值。

EFConfigurationProvider/EFConfigurationContext.cs

public class EFConfigurationContext : DbContext
{
    public EFConfigurationContext(DbContextOptions<EFConfigurationContext> options) : base(options)
    {
    }

    public DbSet<EFConfigurationValue> Values => Set<EFConfigurationValue>();
}

创建用于实现 IConfigurationSource 的类。

EFConfigurationProvider/EFConfigurationSource.cs

public class EFConfigurationSource : IConfigurationSource
{
    private readonly Action<DbContextOptionsBuilder> _optionsAction;

    public EFConfigurationSource(Action<DbContextOptionsBuilder> optionsAction) => _optionsAction = optionsAction;

    public IConfigurationProvider Build(IConfigurationBuilder builder) => new EFConfigurationProvider(_optionsAction);
}

通过从 ConfigurationProvider 继承来创建自定义配置提供程序。 当数据库为空时,配置提供程序将对其进行初始化。 由于配置密钥不区分大小写,因此用来初始化数据库的字典是用不区分大小写的比较程序 (StringComparer.OrdinalIgnoreCase) 创建的。

EFConfigurationProvider/EFConfigurationProvider.cs

public class EFConfigurationProvider : ConfigurationProvider
{
    public EFConfigurationProvider(Action<DbContextOptionsBuilder> optionsAction)
    {
        OptionsAction = optionsAction;
    }

    Action<DbContextOptionsBuilder> OptionsAction { get; }

    public override void Load()
    {
        var builder = new DbContextOptionsBuilder<EFConfigurationContext>();

        OptionsAction(builder);

        using (var dbContext = new EFConfigurationContext(builder.Options))
        {
            if (dbContext == null || dbContext.Values == null)
            {
                throw new Exception("Null DB context");
            }
            dbContext.Database.EnsureCreated();

            Data = !dbContext.Values.Any()
                ? CreateAndSaveDefaultValues(dbContext)
                : dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
        }
    }

    private static IDictionary<string, string> CreateAndSaveDefaultValues(
        EFConfigurationContext dbContext)
    {
        // Quotes (c)2005 Universal Pictures: Serenity
        // https://www.uphe.com/movies/serenity-2005
        var configValues =
            new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                    { "quote1", "I aim to misbehave." },
                    { "quote2", "I swallowed a bug." },
                    { "quote3", "You can't stop the signal, Mal." }
            };

        if (dbContext == null || dbContext.Values == null)
        {
            throw new Exception("Null DB context");
        }

        dbContext.Values.AddRange(configValues
            .Select(kvp => new EFConfigurationValue
            {
                Id = kvp.Key,
                Value = kvp.Value
            })
            .ToArray());

        dbContext.SaveChanges();

        return configValues;
    }
}

可以使用 AddEFConfiguration 扩展方法将配置源添加到 ConfigurationBuilder

Extensions/EntityFrameworkExtensions.cs

public static class EntityFrameworkExtensions
{
    public static IConfigurationBuilder AddEFConfiguration(
               this IConfigurationBuilder builder,
               Action<DbContextOptionsBuilder> optionsAction)
    {
        return builder.Add(new EFConfigurationSource(optionsAction));
    }
}

下面的代码演示如何在 Program.cs 中使用自定义的 EFConfigurationProvider

//using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddEFConfiguration(
    opt => opt.UseInMemoryDatabase("InMemoryDb"));

var app = builder.Build();

app.Run();

使用依赖关系注入 (DI) 的访问配置

通过解析 IConfiguration 服务,可以使用依赖关系注入 (DI) 将配置注入服务:

public class Service
{
    private readonly IConfiguration _config;

    public Service(IConfiguration config) =>
        _config = config;

    public void DoSomething()
    {
        var configSettingValue = _config["ConfigSetting"];

        // ...
    }
}

有关如何使用 IConfiguration 访问值的信息,请参阅本文中的 GetValue 以及 GetSection、GetChildren 和 Exists

访问 Razor Pages 中的配置

以下代码显示 Razor Pages 中的配置数据:

@page
@model Test5Model
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

在以下代码中,MyOptions 已通过 Configure 被添加到了服务容器并已绑定到了配置:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

以下标记使用 @injectRazor 指令来解析和显示选项值:

@page
@model SampleApp.Pages.Test3Model
@using Microsoft.Extensions.Options
@using SampleApp.Models
@inject IOptions<MyOptions> optionsAccessor


<p><b>Option1:</b> @optionsAccessor.Value.Option1</p>
<p><b>Option2:</b> @optionsAccessor.Value.Option2</p>

访问 MVC 视图文件中的配置

以下代码显示 MVC 视图中的配置数据:

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

访问 Program.cs 中的配置

以下代码访问 Program.cs 文件中的配置。

var builder = WebApplication.CreateBuilder(args);

var key1 = builder.Configuration.GetValue<string>("KeyOne");

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

var key2 = app.Configuration.GetValue<int>("KeyTwo");
var key3 = app.Configuration.GetValue<bool>("KeyThree");

app.Logger.LogInformation("KeyOne: {KeyOne}", key1);
app.Logger.LogInformation("KeyTwo: {KeyTwo}", key2);
app.Logger.LogInformation("KeyThree: {KeyThree}", key3);

app.Run();

在上面的示例的 appsettings.json 中:

{
  ...
  "KeyOne": "Key One Value",
  "KeyTwo": 1999,
  "KeyThree": true
}

使用委托来配置选项

在委托中配置的选项替代在配置提供程序中设置的值。

在以下代码中,向服务容器添加了 IConfigureOptions<TOptions> 服务。 它使用委托来配置 MyOptions 的值:

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "Value configured in delegate";
    myOptions.Option2 = 500;
});

var app = builder.Build();

以下代码显示选项值:

public class Test2Model : PageModel
{
    private readonly IOptions<MyOptions> _optionsDelegate;

    public Test2Model(IOptions<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_optionsDelegate.Value.Option1} \n" +
                       $"Option2: {_optionsDelegate.Value.Option2}");
    }
}

在前面的示例中,Option1Option2 的值在 appsettings.json 中指定,然后被配置的委托替代。

主机与应用配置

在配置并启动应用之前,配置并启动主机。 主机负责应用程序启动和生存期管理。 应用和主机均使用本主题中所述的配置提供程序进行配置。 应用的配置中也包含主机配置键值对。 有关在构建主机时如何使用配置提供程序,以及配置源如何影响主机配置的详细信息,请参阅 ASP.NET Core 基础知识概述

默认主机配置

有关使用 Web 主机时默认配置的详细信息,请参阅本主题的 ASP.NET Core 2.2 版本

  • 主机配置通过以下方式提供:
  • 已建立 Web 主机默认配置 (ConfigureWebHostDefaults):
    • Kestrel 用作 Web 服务器,并使用应用的配置提供程序对其进行配置。
    • 添加主机筛选中间件。
    • 如果 ASPNETCORE_FORWARDEDHEADERS_ENABLED 环境变量设置为 true,则添加转发的标头中间件。
    • 启用 IIS 集成。

其他配置

本主题仅与应用配置相关。 运行和托管 ASP.NET Core 应用的其他方面是使用本主题中未包含的配置文件进行配置:

launchSettings.json 中设置的环境变量将替代在系统环境中设置的变量。

若要详细了解如何从旧版 ASP.NET 迁移应用配置,请参阅从 ASP.NET 升级到 ASP.NET Core

从外部程序集添加配置

通过 IHostingStartup 实现,可在启动时从应用 Startup 类之外的外部程序集向应用添加增强功能。 有关详细信息,请参阅在 ASP.NET Core 中使用托管启动程序集

其他资源

Kestrel 终结点配置

Kestrel 特定的终结点配置将覆盖所有跨服务器终结点配置。 跨服务器终结点配置包括:

请考虑在 ASP.NET Core Web 应用中使用的以下 appsettings.json 文件:

{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://localhost:9999"
      }
    }
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
} 

当在 ASP.NET Core Web 应用中使用前面突出显示的标记,并且应用在命令行上启动,且跨服务器端点配置如下时

dotnet run --urls="https://localhost:7777"

Kestrel 将绑定到专门针对 appsettings.json 文件中的 Kestrel 配置的终结点 (https://localhost:9999),而不是 https://localhost:7777

请考虑将 Kestrel 特定的终结点配置为环境变量:

set Kestrel__Endpoints__Https__Url=https://localhost:8888

在前面的环境变量中,Https 是 Kestrel 特定的终结点的名称。 前面的 appsettings.json 文件还定义了名为 Https 的 Kestrel 特定终结点。 默认情况下,将在 appsettings.{Environment}.json 后读取使用环境变量配置提供程序的环境变量,因此,前面的环境变量用于 Https 终结点。

GetValue

ConfigurationBinder.GetValue 从配置中提取一个具有指定键的值,并将它转换为指定的类型。 此方法是 IConfiguration 的扩展方法:

public class TestNumModel : PageModel
{
    private readonly IConfiguration Configuration;

    public TestNumModel(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var number = Configuration.GetValue<int>("NumberKey", 99);
        return Content($"{number}");
    }
}

在前面的代码中,如果在配置中找不到 NumberKey,则使用默认值 99

GetSection、GetChildren 和 Exists

对于下面的示例,请考虑以下 MySubsection.json 文件:

{
  "section0": {
    "key0": "value00",
    "key1": "value01"
  },
  "section1": {
    "key0": "value10",
    "key1": "value11"
  },
  "section2": {
    "subsection0": {
      "key0": "value200",
      "key1": "value201"
    },
    "subsection1": {
      "key0": "value210",
      "key1": "value211"
    }
  }
}

以下代码将 MySubsection.json 添加到配置提供程序:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddJsonFile("MySubsection.json", 
                    optional: true, 
                    reloadOnChange: true);
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

GetSection

IConfiguration.GetSection 会返回具有指定子节键的配置子节。

以下代码将返回 section1 的值:

public class TestSectionModel : PageModel
{
    private readonly IConfiguration Config;

    public TestSectionModel(IConfiguration configuration)
    {
        Config = configuration.GetSection("section1");
    }

    public ContentResult OnGet()
    {
        return Content(
                $"section1:key0: '{Config["key0"]}'\n" +
                $"section1:key1: '{Config["key1"]}'");
    }
}

以下代码将返回 section2:subsection0 的值:

public class TestSection2Model : PageModel
{
    private readonly IConfiguration Config;

    public TestSection2Model(IConfiguration configuration)
    {
        Config = configuration.GetSection("section2:subsection0");
    }

    public ContentResult OnGet()
    {
        return Content(
                $"section2:subsection0:key0 '{Config["key0"]}'\n" +
                $"section2:subsection0:key1:'{Config["key1"]}'");
    }
}

GetSection 永远不会返回 null。 如果找不到匹配的节,则返回空 IConfigurationSection

GetSection 返回匹配的部分时,Value 未填充。 存在该部分时,返回一个 KeyPath 部分。

GetChildren 和 Exists

以下代码将调用 IConfiguration.GetChildren 并返回 section2:subsection0 的值:

public class TestSection4Model : PageModel
{
    private readonly IConfiguration Config;

    public TestSection4Model(IConfiguration configuration)
    {
        Config = configuration;
    }

    public ContentResult OnGet()
    {
        string s = null;
        var selection = Config.GetSection("section2");
        if (!selection.Exists())
        {
            throw new System.Exception("section2 does not exist.");
        }
        var children = selection.GetChildren();

        foreach (var subSection in children)
        {
            int i = 0;
            var key1 = subSection.Key + ":key" + i++.ToString();
            var key2 = subSection.Key + ":key" + i.ToString();
            s += key1 + " value: " + selection[key1] + "\n";
            s += key2 + " value: " + selection[key2] + "\n";
        }
        return Content(s);
    }
}

前面的代码将调用 ConfigurationExtensions.Exists 以验证该节是否存在:

绑定数组

ConfigurationBinder.Bind 支持使用配置键中的数组索引将数组绑定到对象。 公开数值键段的任何数组格式都能够与 POCO 类数组进行数组绑定。

请考虑示例下载中的 MyArray.json

{
  "array": {
    "entries": {
      "0": "value00",
      "1": "value10",
      "2": "value20",
      "4": "value40",
      "5": "value50"
    }
  }
}

以下代码将 MyArray.json 添加到配置提供程序:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddJsonFile("MyArray.json", 
                    optional: true, 
                    reloadOnChange: true);
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

以下代码将读取配置并显示值:

public class ArrayModel : PageModel
{
    private readonly IConfiguration Config;
    public ArrayExample _array { get; private set; }

    public ArrayModel(IConfiguration config)
    {
        Config = config;
    }

    public ContentResult OnGet()
    {
        _array = Config.GetSection("array").Get<ArrayExample>();
        string s = null;

        for (int j = 0; j < _array.Entries.Length; j++)
        {
            s += $"Index: {j}  Value:  {_array.Entries[j]} \n";
        }

        return Content(s);
    }
}

前面的代码会返回以下输出:

Index: 0  Value: value00
Index: 1  Value: value10
Index: 2  Value: value20
Index: 3  Value: value40
Index: 4  Value: value50

在前面的输出中,索引 3 具有值 value40,与 MyArray.json 中的 "4": "value40", 相对应。 绑定的数组索引是连续的,并且未绑定到配置键索引。 配置绑定器不能绑定 NULL 值,也不能在绑定的对象中创建 NULL 条目

以下代码将通过 AddInMemoryCollection 扩展方法加载 array:entries 配置:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var arrayDict = new Dictionary<string, string>
        {
            {"array:entries:0", "value0"},
            {"array:entries:1", "value1"},
            {"array:entries:2", "value2"},
            //              3   Skipped
            {"array:entries:4", "value4"},
            {"array:entries:5", "value5"}
        };

        return Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddInMemoryCollection(arrayDict);
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
    }
}

以下代码将读取 arrayDictDictionary 中的配置并显示值:

public class ArrayModel : PageModel
{
    private readonly IConfiguration Config;
    public ArrayExample _array { get; private set; }

    public ArrayModel(IConfiguration config)
    {
        Config = config;
    }

    public ContentResult OnGet()
    {
        _array = Config.GetSection("array").Get<ArrayExample>();
        string s = null;

        for (int j = 0; j < _array.Entries.Length; j++)
        {
            s += $"Index: {j}  Value:  {_array.Entries[j]} \n";
        }

        return Content(s);
    }
}

前面的代码会返回以下输出:

Index: 0  Value: value0
Index: 1  Value: value1
Index: 2  Value: value2
Index: 3  Value: value4
Index: 4  Value: value5

绑定对象中的索引 #3 保留 array:4 配置键的配置数据及其值 value4。 当绑定包含数组的配置数据时,配置键中的数组索引用于在创建对象时迭代配置数据。 无法在配置数据中保留 null 值,并且当配置键中的数组跳过一个或多个索引时,不会在绑定对象中创建 null 值条目。

可以在由任何读取索引 #3 键/值对的配置提供程序绑定到 ArrayExample 实例之前提供索引 #3 的缺失配置项。 请考虑示例下载中的以下 Value3.json 文件:

{
  "array:entries:3": "value3"
}

以下代码包含 Value3.jsonarrayDictDictionary 的配置:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var arrayDict = new Dictionary<string, string>
        {
            {"array:entries:0", "value0"},
            {"array:entries:1", "value1"},
            {"array:entries:2", "value2"},
            //              3   Skipped
            {"array:entries:4", "value4"},
            {"array:entries:5", "value5"}
        };

        return Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.AddInMemoryCollection(arrayDict);
                config.AddJsonFile("Value3.json",
                                    optional: false, reloadOnChange: false);
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
    }
}

以下代码将读取上述配置并显示值:

public class ArrayModel : PageModel
{
    private readonly IConfiguration Config;
    public ArrayExample _array { get; private set; }

    public ArrayModel(IConfiguration config)
    {
        Config = config;
    }

    public ContentResult OnGet()
    {
        _array = Config.GetSection("array").Get<ArrayExample>();
        string s = null;

        for (int j = 0; j < _array.Entries.Length; j++)
        {
            s += $"Index: {j}  Value:  {_array.Entries[j]} \n";
        }

        return Content(s);
    }
}

前面的代码会返回以下输出:

Index: 0  Value: value0
Index: 1  Value: value1
Index: 2  Value: value2
Index: 3  Value: value3
Index: 4  Value: value4
Index: 5  Value: value5

不需要自定义配置提供程序实现数组绑定。

自定义配置提供程序

该示例应用演示了如何使用实体框架 (EF) 创建从数据库读取配置键值对的基本配置提供程序。

提供程序具有以下特征:

  • EF 内存中数据库用于演示目的。 若要使用需要连接字符串的数据库,请实现辅助 ConfigurationBuilder 以从另一个配置提供程序提供连接字符串。
  • 提供程序在启动时将数据库表读入配置。 提供程序不会基于每个键查询数据库。
  • 未实现更改时重载,因此在应用启动后更新数据库对应用的配置没有任何影响。

定义用于在数据库中存储配置值的 EFConfigurationValue 实体。

Models/EFConfigurationValue.cs

public class EFConfigurationValue
{
    public string Id { get; set; }
    public string Value { get; set; }
}

添加 EFConfigurationContext 以存储和访问配置的值。

EFConfigurationProvider/EFConfigurationContext.cs

// using Microsoft.EntityFrameworkCore;

public class EFConfigurationContext : DbContext
{
    public EFConfigurationContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<EFConfigurationValue> Values { get; set; }
}

创建用于实现 IConfigurationSource 的类。

EFConfigurationProvider/EFConfigurationSource.cs

// using Microsoft.EntityFrameworkCore;
// using Microsoft.Extensions.Configuration;

public class EFConfigurationSource : IConfigurationSource
{
    private readonly Action<DbContextOptionsBuilder> _optionsAction;

    public EFConfigurationSource(Action<DbContextOptionsBuilder> optionsAction)
    {
        _optionsAction = optionsAction;
    }

    public IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        return new EFConfigurationProvider(_optionsAction);
    }
}

通过从 ConfigurationProvider 继承来创建自定义配置提供程序。 当数据库为空时,配置提供程序将对其进行初始化。 由于配置密钥不区分大小写,因此用来初始化数据库的字典是用不区分大小写的比较程序 (StringComparer.OrdinalIgnoreCase) 创建的。

EFConfigurationProvider/EFConfigurationProvider.cs

// using Microsoft.EntityFrameworkCore;
// using Microsoft.Extensions.Configuration;

public class EFConfigurationProvider : ConfigurationProvider
{
    public EFConfigurationProvider(Action<DbContextOptionsBuilder> optionsAction)
    {
        OptionsAction = optionsAction;
    }

    Action<DbContextOptionsBuilder> OptionsAction { get; }

    public override void Load()
    {
        var builder = new DbContextOptionsBuilder<EFConfigurationContext>();

        OptionsAction(builder);

        using (var dbContext = new EFConfigurationContext(builder.Options))
        {
            dbContext.Database.EnsureCreated();

            Data = !dbContext.Values.Any()
                ? CreateAndSaveDefaultValues(dbContext)
                : dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
        }
    }

    private static IDictionary<string, string> CreateAndSaveDefaultValues(
        EFConfigurationContext dbContext)
    {
        // Quotes (c)2005 Universal Pictures: Serenity
        // https://www.uphe.com/movies/serenity-2005
        var configValues = 
            new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
            {
                { "quote1", "I aim to misbehave." },
                { "quote2", "I swallowed a bug." },
                { "quote3", "You can't stop the signal, Mal." }
            };

        dbContext.Values.AddRange(configValues
            .Select(kvp => new EFConfigurationValue 
                {
                    Id = kvp.Key,
                    Value = kvp.Value
                })
            .ToArray());

        dbContext.SaveChanges();

        return configValues;
    }
}

可以使用 AddEFConfiguration 扩展方法将配置源添加到 ConfigurationBuilder

Extensions/EntityFrameworkExtensions.cs

// using Microsoft.EntityFrameworkCore;
// using Microsoft.Extensions.Configuration;

public static class EntityFrameworkExtensions
{
    public static IConfigurationBuilder AddEFConfiguration(
        this IConfigurationBuilder builder, 
        Action<DbContextOptionsBuilder> optionsAction)
    {
        return builder.Add(new EFConfigurationSource(optionsAction));
    }
}

下面的代码演示如何在 Program.cs 中使用自定义的 EFConfigurationProvider

// using Microsoft.EntityFrameworkCore;

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            config.AddEFConfiguration(
                options => options.UseInMemoryDatabase("InMemoryDb"));
        })

访问 Startup 中的配置

以下代码显示 Startup 方法中的配置数据:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
        Console.WriteLine($"MyKey : {Configuration["MyKey"]}");
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        Console.WriteLine($"Position:Title : {Configuration["Position:Title"]}");

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

有关使用启动便捷方法访问配置的示例,请参阅应用启动:便捷方法

访问 Razor Pages 中的配置

以下代码显示 Razor Pages 中的配置数据:

@page
@model Test5Model
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

在以下代码中,MyOptions 已通过 Configure 被添加到了服务容器并已绑定到了配置:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));

    services.AddRazorPages();
}

以下标记使用 @injectRazor 指令来解析和显示选项值:

@page
@model SampleApp.Pages.Test3Model
@using Microsoft.Extensions.Options
@inject IOptions<MyOptions> optionsAccessor


<p><b>Option1:</b> @optionsAccessor.Value.Option1</p>
<p><b>Option2:</b> @optionsAccessor.Value.Option2</p>

访问 MVC 视图文件中的配置

以下代码显示 MVC 视图中的配置数据:

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

使用委托来配置选项

在委托中配置的选项替代在配置提供程序中设置的值。

示例应用中的示例 2 展示了如何使用委托来配置选项。

在以下代码中,向服务容器添加了 IConfigureOptions<TOptions> 服务。 它使用委托来配置 MyOptions 的值:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(myOptions =>
    {
        myOptions.Option1 = "Value configured in delegate";
        myOptions.Option2 = 500;
    });

    services.AddRazorPages();
}

以下代码显示选项值:

public class Test2Model : PageModel
{
    private readonly IOptions<MyOptions> _optionsDelegate;

    public Test2Model(IOptions<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_optionsDelegate.Value.Option1} \n" +
                       $"Option2: {_optionsDelegate.Value.Option2}");
    }
}

在前面的示例中,Option1Option2 的值在 appsettings.json 中指定,然后被配置的委托替代。

主机与应用配置

在配置并启动应用之前,配置并启动主机。 主机负责应用程序启动和生存期管理。 应用和主机均使用本主题中所述的配置提供程序进行配置。 应用的配置中也包含主机配置键值对。 有关在构建主机时如何使用配置提供程序,以及配置源如何影响主机配置的详细信息,请参阅 ASP.NET Core 基础知识概述

默认主机配置

有关使用 Web 主机时默认配置的详细信息,请参阅本主题的 ASP.NET Core 2.2 版本

  • 主机配置通过以下方式提供:
    • 使用环境变量配置提供程序通过前缀为 DOTNET_的环境变量(例如,DOTNET_ENVIRONMENT)提供。 在配置键值对加载后,前缀 (DOTNET_) 会遭去除。
    • 使用命令行配置提供程序通过命令行参数提供。
  • 已建立 Web 主机默认配置 (ConfigureWebHostDefaults):
    • Kestrel 用作 Web 服务器,并使用应用的配置提供程序对其进行配置。
    • 添加主机筛选中间件。
    • 如果 ASPNETCORE_FORWARDEDHEADERS_ENABLED 环境变量设置为 true,则添加转发的标头中间件。
    • 启用 IIS 集成。

其他配置

本主题仅与应用配置相关。 运行和托管 ASP.NET Core 应用的其他方面是使用本主题中未包含的配置文件进行配置:

launchSettings.json 中设置的环境变量将替代在系统环境中设置的变量。

若要详细了解如何从旧版 ASP.NET 迁移应用配置,请参阅从 ASP.NET 升级到 ASP.NET Core

从外部程序集添加配置

通过 IHostingStartup 实现,可在启动时从应用 Startup 类之外的外部程序集向应用添加增强功能。 有关详细信息,请参阅在 ASP.NET Core 中使用托管启动程序集

其他资源