应用配置
ASP.NET Core内置集成了Microsoft.Extensions.Configuration
配置框架,我们能够很容易的实现读取配置文件、分环境配置等功能。
默认配置提供者
ASP.NET Core内置的配置框架中默认集成了一组配置提供者,实际上我们也可以修改甚至自定义ASP.NET Core的配置提供者,但实际开发中一般不需要这么做,我们在默认的配置提供者对应的地方编写配置即可,默认的配置提供者和其加载顺序如下。
- 加载现有的
IConfiguration
- 加载项目根目录的
appsettings.json
- 加载项目根目录的
appsettings.[环境名].json
- 加载用户机密配置
- 加载环境变量中的配置
- 加载命令行中的配置
按照加载顺序,后加载的会合并和覆盖先加载的配置内容。实际开发时,我们大部分的配置一般都写在appsettings.json
和对应环境的appsettings.[环境名].json
配置文件中,而一些关键的字段如密钥等则通常使用用户机密配置、环境变量或命令行参数指定。规划项目的配置时,另一个注意点是不要将配置过于分散到多个配置来源中,否则很容易由于忘记某个配置在哪而出现诡异的问题。
读取应用配置
假设appsettings.json
内有如下这样的一段配置。
{
"BlogTitle": "Gacfox's Blog",
"PostConfig": {
"AllowPost": false
}
}
在Program.cs
中,我们可以通过WebApplication
对象的Configuration
属性来读取配置,它是一个IConfiguration
类型的对象,我们可以调用其中的各种方法来加载配置。
string blogTitle = app.Configuration.GetValue<string>("BlogTitle");
bool allowPost = app.Configuration.GetValue<bool>("PostConfig:AllowPost");
这里我们使用了GetValue()
方法来读取配置。除此之外,我们也可以将JSON配置绑定到一个对象上,下面是一个例子。
public class PostConfig
{
public bool AllowPost { get; set; }
}
PostConfig postConfig = app.Configuration.GetSection("PostConfig").Get<PostConfig>();
这里我们使用了GetSection()
方法来读取配置,然后使用IConfigurationSection
的Get()
方法来获取绑定的对象。
在Controller等由IoC容器托管的类中,我们可以通过构造函数注入IConfiguration
对象然后读取配置,下面是一个例子。
using DemoWebAPI.Model;
using Microsoft.AspNetCore.Mvc;
namespace DemoWebAPI.Controllers;
[ApiController]
[Route("api/[controller]")]
public class DemoController : ControllerBase
{
private readonly IConfiguration _configuration;
public DemoController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpGet("[action]")]
public ActionResult<ApiResult> DemoAction()
{
string blogTitle = _configuration.GetValue<string>("BlogTitle");
return ApiResult.Success(blogTitle);
}
}
代码中,我们通过构造函数注入了IConfiguration
对象,在DemoAction()
方法中我们通过GetValue()
方法读取配置值。除了上面介绍的GetValue()
和GetSection()
,IConfiguration
还有很多其它的方法用来读取配置,具体可以参考相关文档。
appsettings.json 应用配置文件
在Visual Studio中创建一个工程后,通常默认会生成appsettings.json
和appsettings.Development.json
两个配置文件,它们就是ASP.NET Core中默认约定加载的配置文件,默认生成的配置例子如下。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
文件名中,Development
是指用于开发环境的配置,如果我们有开发、测试、生产等不同环境,可以增加对应的appsettings.[环境名].json
配置文件。这个环境名是可以任意自定义的,但推荐优先使用Development
、Staging
、Production
这三个值,app.Environment
对象提供了类似app.Environment.IsDevelopment()
的预制方法来支持这三个值的判断。
在程序启动时,程序会读取ASPNETCORE_ENVIRONMENT
环境变量,例如将该环境变量指定为Development
,程序启动时就会将appsettings.Development.json
和appsettings.json
合并作为应用配置。我们可以通过app.Environment.EnvironmentName
读取当前环境,也可以用app.Environment.IsDevelopment()
等方法判断当前环境。
在Visual Studio中,我们可以在调试的绿色三角按钮下拉选择调试属性对话框,在这里可以配置调试时的环境变量。至于生产环境,我们通常还是需要修改系统配置来设置环境变量。
注:ASPNETCORE_ENVIRONMENT
如果没有指定任何值,默认是值Production
。
secrets.json 开发环境用户机密配置
用户机密配置是一种用于开发环境的配置文件,ASP.NET Core中默认实现了用户机密配置的配置提供者,其中的配置可以和其它配置项合并加载。在Visual Studio中,我们在项目上点击右键,选择管理用户机密
即可创建机密配置,这会自动创建一个secrets.json
文件,注意这个文件并不在项目目录中,而是在类似C:\Users\gacfo\AppData\Roaming\Microsoft\UserSecrets\f760f4cb-98b5-4baa-a86e-b50178e2543a
的地方,我们查看项目的.csproj.user
工程描述文件,可以看到类似下面这样的一条配置,它和机密配置路径中的一长串内容对应。
<UserSecretsId>f760f4cb-98b5-4baa-a86e-b50178e2543a</UserSecretsId>
注:.csproj.user
是用户的工程描述文件,它和传统的.csproj
合并描述一个工程,但.csproj.user
包含当前主机的个性化本地配置,这个文件不应该被提交到版本控制系统中。
如果使用dotnet
命令行工具,我们可以执行以下命令创建该机密配置。
dotnet user-secrets init
secrets.json
文件的使用方法和appsettings.json
是完全一致的,在Visual Studio中应用启动时,这两个配置源会按照配置加载顺序合并和覆盖。
实际上,这个用户机密配置有点鸡肋,它仅适用于开发环境,生产环境发布后是无法附上secrets.json
的。这个功能的意义在于分离开发环境的代码库和数据库密码等机密信息,避免程序员误将数据库密码等有意或无意的提交到公开代码仓库上。使用用户机密配置还有一个缺点,就是它破坏了工程开发环境搭建的简便性,由于部分配置被从appsettings.json
抽离了,团队内每个人都要重新编写secrets.json
,否则程序就会缺少配置难以运行。
另一个值得注意的是secrets.json
文件并非加密的文件,它仍是明文存储的。ASP.NET Core默认并没有提供配置加密功能,这个功能需要我们自行实现。
环境变量配置和命令行参数配置
环境变量配置和命令行参数配置通常用于配置少量但关键或内容敏感的信息,例如数据库密码或是配置的解密密钥等。为了解释如何使用环境变量配置和命令行参数配置,我们还是使用之前的例子,以下配置是等效的。
例子appsettings.json
配置。
{
"PostConfig": {
"AllowPost": false
}
}
等效环境变量配置如下。
等效命令行参数配置如下。
多级的配置在环境变量配置和命令行参数配置中使用冒号分隔的形式指定,不过在Linux环境下由于冒号是环境变量的分隔符,因此我们需要使用双下划线代替,例如PostConfig__AllowPost
。
规划配置的最佳实践
实际开发中,无论是Java的SpringBoot还是ASP.NET Core,为了在开发的便捷性和安全性方便取得平衡,配置的规划其实有一套较为成熟的最佳实践。以ASP.NET Core工程为例,我们应该尽量遵循以下规则。
不要过度分散配置文件:合理的按模块划分配置是可接受的,但千万不要过度划分。我曾经历过一个配置规划极为混乱的老式Spring工程,这个工程不仅每个代码模块都有自己的配置,底层依赖库还有一套分散的十余个配置文件,更让人血压升高的是还有很多配置胡乱存储在数据库、配置中心和环境变量中,每次调整配置都会花费大量时间Debug,过度分散配置绝对是个糟糕的坏主意。一个比较推荐的配置划分是配置文件(分环境) - 配置中心 - 环境变量或启动参数
,配置文件存储应用的基础配置,例如数据库连接串等;配置中心存储业务逻辑相关配置;环境变量或启动参数存储极少量的敏感信息,如加密配置的解密密钥。
配置文件和配置中心的职责界限:一般来说,配置文件存储那些不太会变更的配置,如数据库的连接字符串、Redis服务器地址、某接口的路径等通常不是需要运行时更改的配置,而那些需要相对频繁调整的配置我们应将其分离到配置中心,这些通常也都是业务相关的配置,例如电子商城系统中开启某个活动的开关。
敏感配置加密:敏感配置加密是很多企业级项目开发的基本要求,一个最佳实践是我们将敏感信息如数据库密码加密后写在appsettings.json
配置文件里,而密钥由上线实施人员掌握,密钥通过环境变量或启动命令行参数加载到生产环境应用中。配置加密是防源代码泄露的手段,即使源码泄露,由于别人不知道解密密钥,攻击者也无法得知明文的数据库密码。
单配置文件是个坏主意:一些项目的配置流程是这样的,它只有一个appsettings.json
配置文件,开发环境程序员修改配置进行开发,生产环境实施人员再修改配置部署到生产,这种配置流程虽然看似简单,但实际上非常麻烦且易出错,现代企业级项目中CI/CD自动化持续集成和部署是非常基本的基础设施,需要大量手动操作的部署流程不适用于现代化的项目开发。当然,如果是特别简单的个人项目,这样配置也无可厚非,我们根据实际情况选择即可。