ASP.NET Core配置

原文:Configuration
作者:Steve SmithDaniel Roth
翻译:刘怡(AlexLEWIS)
校对:孟帅洋(书缘)

ASP.NET Core 支持多种配置选项。应用程序配置数据内建支持读取 JSON、XML 和 INI 格式的配置文件和环境变量。你也可以编写自己的自定义配置提供程序

章节:

访问或下载样例代码

获取和设置配置

ASP.NET Core 配置系统针对以前的 ASP.NET 版本(依赖于 System.Configuration 和 XML 配置文件(如 Web.config))进行了重新架构。新的配置模型提供了精简高效的通过检索多样化提供程序的获取基于键/值对配置的能力。应用程序和框架可以通过新的选择模式访问配置。

在 ASP.NET 应用程序中,建议你在应用程序的 Startup 类中只实例化一个 Configuration 实例。然后使用选择模式来访问各自的设置。

简单来说,Configuration 类只是一个提供了读写名/值对能力的 Providers 集合。你至少需要配置一个提供程序,使得 Configuration 能正常工作。下例演示了如何测试把 Configuration 作为一个键/值对存储来处理。

var builder = new ConfigurationBuilder();
builder.AddInMemoryCollection();
var config = builder.Build();
config["somekey"] = "somevalue";

// do some other work

var setting = config["somekey"]; // also returns "somevalue"

注意
你必须至少设置一个配置提供程序。

一般不会把配置值存储在一个有层次的结构中,尤其是使用外部文件(如 JSON、XML、INI)时。在此情况下,可以使用以 : 符号分隔(从层次结构的根开始)的键来取回配置值。以下面的 appsettings.json 文件为例:

{"ConnectionStrings":{"DefaultConnection":"Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApplication1-26e8893e-d7c0-4fc6-8aab-29b59971d622;Trusted_Connection=True;MultipleActiveResultSets=true"},"Logging":{"IncludeScopes":false,"LogLevel":{"Default":"Debug","System":"Information","Microsoft":"Information"}}}

应用程序使用 configuration 配置正确的连接字符串。可以通过键 Data:DefaultConnection:ConnectionString 来访问 ConnectionString 的设置。

应用程序所需要的设置和指定配置的机制(configuration 便是一例)都可通过使用选择模式解耦。创建自己的配置类(可以是几个不同的类,分别对应不同的配置组),而后通过选项服务注入到应用程序中。然后你就可以通过配置或其它你所选择的机制来设置了。

注意
你可将 Configuration 实例设计为一个服务,但这会导致不必要地把应用程序和配置系统与指定配置键耦合在一起。相反可通过选择模式来避免这一问题。

使用内建提供程序

配置框架已内建支持 JSON、XML 和 INI 配置文件,内存配置(直接通过代码设置值),从环境变量和命令行参数中拉取配置。开发者并不受限于必须使用单个配置提供程序。事实上可以把多个配置提供程序组合在一起,就像是用从当前存在的另一个配置提供程序中获取配置值覆盖默认配置一样。

扩展方法支持为配置添加额外的配置文件提供程序。这些方法能被独立的或链式的(如 fluent API)调用在 ConfigurationBuilder 实例之上,如下所示:

// work with with a builder using multiple calls
var builder = new ConfigurationBuilder();
builder.SetBasePath(Directory.GetCurrentDirectory());
builder.AddJsonFile("appsettings.json");
var connectionStringConfig = builder.Build();

// chain calls together as a fluent API
var config = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json")
    .AddEntityFrameworkConfig(options =>
        options.UseSqlServer(connectionStringConfig.GetConnectionString("DefaultConnection"))
    )
    .Build();

指定配置提供程序的顺序非常重要,这将影响它们的设置被应用的优先级(如果存在于多个位置)。上例中,如果相同的配置同时存在于 appsettings.json 和环境变量,则环境变量的设置将被最终使用。最后指定的配置提供程序将“获胜”(如果该设置存在于至少两处位置)。ASP.NET 团队建议最后指定环境变量,如此一来本地环境可以覆盖任何部署在配置文件中的设置。

注意
如果通过环境变量重写嵌套键,请把变量中键名的 : 替换为 __ (两个下划线)。

这对于指定环境的配置文件非常有用,这能通过以下代码来实现:

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); //手动高亮

    if (env.IsDevelopment())
    {
        // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
        builder.AddUserSecrets();
    }

    builder.AddEnvironmentVariables();
    Configuration = builder.Build();
}

IHostingEnvironment 服务用于获取当前环境。在 Development 环境中,上例高亮行代码将寻找名为 appsettings.Development.json 的配置文件,并用其中的值覆盖当前存在的其它值。更多请参见 environments

警告
谨记,严禁把密码或其他敏感数据保存在代码或纯文本配置文件中,严谨在开发环境或测试环境中使用生产环境的机密数据(这些机密数据应当在项目树的外部被指定,这样就不会意外提交到仓库内)。移步了解更多 environments 和管理 Safe storage of app secrets during development

影响 Configuration 优先级顺序的一个因素是指定可被重写的默认值。在本控制台应用程序中,默认的 username 设置由 MemoryConfigurationProvider 指定,但如果命令行参数中有个 username 参数被传递给应用程序,它将被覆盖。在输出中可以看到程序的每一个步骤中有多少个配置提供程序在进行配置工作。

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;

namespace ConfigConsole
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var builder = new ConfigurationBuilder();
            Console.WriteLine("Initial Config Sources: " + builder.Sources.Count());

            builder.AddInMemoryCollection(new Dictionary<string, string>
            {
                { "username", "Guest" }
            });

            Console.WriteLine("Added Memory Source. Sources: " + builder.Sources.Count());

            builder.AddCommandLine(args); //手动高亮
            Console.WriteLine("Added Command Line Source. Sources: " + builder.Sources.Count());

            var config = builder.Build(); //手动高亮
            string username = config["username"];

            Console.WriteLine($"Hello, {username}!");
        }
    }
}

当运行时,程序将显示默认值,除非使用命令行参数重写之。

使用选项和配置对象

通过使用选择模式(options pattern)你可将任何类或 POCO(Plain Old CLR Object)转换为设置类。推荐把你创建的配置根据应用程序的功能分解为多个配置对象,从而实现 ISP(Interface Segregation Principle,接口隔离原则,类只依赖于它们自己使用的配置设置)和 SoC(Separation of Concerns,关注分离,设置与应用程序相互隔离,减少彼此之间的干扰和影响)。

一个简单的 MyOptions 类如下所示:

public class MyOptions
{
    public string Option1 { get; set; }
    public int Option2 { get; set; }
}

通过 IOptions<TOptions> ,配置选项将被注入到应用程序中。比方说,如 controller 使用 IOptions<TOptions> 来访问需要在 Index 视图中渲染的配置:

public class HomeController : Controller
{
    private readonly IOptions<MyOptions> _optionsAccessor;      //手动高亮

    public HomeController(IOptions<MyOptions> optionsAccessor)  //手动高亮
    {
        _optionsAccessor = optionsAccessor;
    }                                                           //手动高亮

    // GET: /<controller>/
    public IActionResult Index() => View(_optionsAccessor.Value);
}

建议
更多请浏览 Dependency Injection

为设置 IOptions<TOption> 服务,你需在启动期间在 ConfigureServices 方法内调用 AddOptions() 扩展方法。

public void ConfigureServices(IServiceCollection services)
{
    // Setup options with DI
    services.AddOptions();//手动高亮

Index 视图将显示配置选项:

配置选项使用 Configure<TOption> 扩展方法。你可以通过委托或绑定配置选项的方式来进行配置:

public void ConfigureServices(IServiceCollection services)
{
    // Setup options with DI
    services.AddOptions();                          //手动高亮

    // Configure MyOptions using config by installing Microsoft.Extensions.Options.ConfigurationExtensions
    services.Configure<MyOptions>(Configuration);

    // Configure MyOptions using code
    services.Configure<MyOptions>(myOptions =>      //手动高亮
    {
        myOptions.Option1 = "value1_from_action";   //手动高亮
    });                                             //手动高亮

    // Configure MySubOptions using a sub-section of the appsettings.json file
    services.Configure<MySubOptions>(Configuration.GetSection("subsection"));//手动高亮

    // Add framework services.
    services.AddMvc();
}

当你通过绑定选项来配置选项类型的每一个属性,实际上是绑定到每一个配置键(比如 property:subproperty:...)。比方说,MyOptions.Option1 属性绑定到键 Option1,那么就会从 appsettings.json 中读取 option1 属性。注意,配置键是大小写不敏感的。

通过调用 Configure<TOption> 将一个 IConfigureOptions<TOption> 服务加入服务容器,是为了之后应用程序或框架能通过 IOptions<TOption> 服务来获取配置选项。若是想从其他途径(比如之前从数据库)获取配置,你可使用 ConfigureOptions<TOptions> 扩展方法直接指定经过定制的 IConfigureOptions<TOption> 服务。

同一个选项类型可以有多个 IConfigureOptions<TOption> 服务,届时将按顺序应用。在上中,Option1 和 Option2 都在 appsettings.json 中指定,但 Option1 的值最后被配置委托所覆盖。

编写自定义提供程序

除使用内建的配置提供程序,你也可自行定制。为此,你只需从 ConfigurationProvider 继承并使用来自你配置提供程序的配置来填充 Data 属性即可。

你或许希望将应用程序的配置保存在数据库中,然后通过 EntityFramework(EF)来访问它们。保存这些配置值有很多办法可以选择,比方说一张简易表格,一列表示配置名、另一列表示配置的值。在本例中,我将创建一个简易的配置提供程序,通过 EF 从数据库中读取名值对(name-value pair)。

在开始之前我们先定义一个简单的 ConfigurationValue 实体模型用来表示存储在数据库中的配置值。

public class ConfigurationContext : DbContext
{
    public ConfigurationContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<ConfigurationValue> Values { get; set; } //手动高亮
}

然后需要一个 ConfigurationContext 用来通过 EF 存储和访问配置值

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
    public class EntityFrameworkConfigurationSource : IConfigurationSource      //手动高亮
    {
        private readonly Action<DbContextOptionsBuilder> _optionsAction;

        public EntityFrameworkConfigurationSource(Action<DbContextOptionsBuilder> optionsAction)
        {
            _optionsAction = optionsAction;
        }

        public IConfigurationProvider Build(IConfigurationBuilder builder)      //手动高亮
        {                                                                       //手动高亮
            return new EntityFrameworkConfigurationProvider(_optionsAction);    //手动高亮
        }                                                                       //手动高亮
    }
}

接着,通过继承 ConfigurationProvider 创建一个定制的配置提供程序。配置数据由重写的 Load 方法(该方法将从配置数据库中读取所有配置数据)所提供。由于这是演示,所以配置提供程序需要自行初始化数据库(如果该数据库尚未创建并初始化)

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
    public class EntityFrameworkConfigurationProvider : ConfigurationProvider   //手动高亮
    {
        public EntityFrameworkConfigurationProvider(Action<DbContextOptionsBuilder> optionsAction)
        {
            OptionsAction = optionsAction;
        }

        Action<DbContextOptionsBuilder> OptionsAction { get; }

        public override void Load()                                             //手动高亮
        {                                                                       //手动高亮
            var builder = new DbContextOptionsBuilder<ConfigurationContext>();  //手动高亮
            OptionsAction(builder);                                             //手动高亮

            using (var dbContext = new ConfigurationContext(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(
            ConfigurationContext dbContext)
        {
            var configValues = new Dictionary<string, string>
                {
                    { "key1", "value_from_ef_1" },                              //手动高亮
                    { "key2", "value_from_ef_2" }                               //手动高亮
                };
            dbContext.Values.AddRange(configValues
                .Select(kvp => new ConfigurationValue { Id = kvp.Key, Value = kvp.Value })
                .ToArray());
            dbContext.SaveChanges();
            return configValues;
        }
    }
}

按惯例,我们同样可以添加一个 AddEntityFramework 扩展方法来增加配置提供程序:

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
    public static class EntityFrameworkExtensions
    {
        public static IConfigurationBuilder AddEntityFrameworkConfig(                   //手动高亮
            this IConfigurationBuilder builder, Action<DbContextOptionsBuilder> setup)
        {
            return builder.Add(new EntityFrameworkConfigurationSource(setup));
        }
    }
}

在下例中将演示如何在应用程序中使用此定制的 ConfigurationProvider。创建一个新的 [ConfigurationBuilder][ConfigurationBuilder1] 来设置配置提供程序。指定数据提供程序和连接字符串后,添加 EntityFrameworkConfigurationProvider 配置提供程序。那你如何配置连接字符串呢?当然也是使用配置了!添加 appsettings.json 作为配置提供者来引导建立 EntityFrameworkConfigurationProvider 。通过重用相同的 ConfigurationBuilder,在数据库中指定的任何配置都将覆盖 appsettings.json 中所指定的对应设置:

using System;
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace CustomConfigurationProvider
{
    public static class Program
    {
        public static void Main()
        {
            // work with with a builder using multiple calls
            var builder = new ConfigurationBuilder();
            builder.SetBasePath(Directory.GetCurrentDirectory());
            builder.AddJsonFile("appsettings.json");
            var connectionStringConfig = builder.Build();

            // chain calls together as a fluent API
            var config = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")                //手动高亮
                .AddEntityFrameworkConfig(options =>            //手动高亮
                    options.UseSqlServer(connectionStringConfig.GetConnectionString("DefaultConnection"))                 //手动高亮
                )   //手动高亮
                .Build();

            Console.WriteLine("key1={0}", config["key1"]);
            Console.WriteLine("key2={0}", config["key2"]);
            Console.WriteLine("key3={0}", config["key3"]);
        }
    }
}

运行程序,看到所配置的值。

总结

ASP.NET Core 提供了非常灵活的配置模型,支持多种配置文件类型、命令行、内存和环境变量。它能与配置模型无缝协作,因此你可为你的应用程序或框架注入强类型配置。你也可以创建自己定制的配置提供程序,用于协同或取代内置提供程序,保证了最大程序的灵活性。