har*_*don 14 .net .net-core asp.net-core
在 .Net Core 中寻找构建 appsettings.json 文件的明智方法。
是否应该将基本的“appsettings.json”文件配置为在开发环境中运行,然后基于环境的覆盖(例如 appsettings.production.json)覆盖生产的特定密钥?
或者 appsettings.json 是否应该只包含在所有环境中共享的配置,然后使用特定的 appsettings.development/staging.json 文件为这些环境显式设置密钥?
我担心的是 - 假设应用程序部署到实时服务器,但存储在环境变量(例如覆盖连接字符串)中的密钥丢失或拼写错误等。在这种情况下,应用程序将回退到基础 appsettings.json连接字符串,这对于实时环境来说是不正确的数据库。像这样的场景听起来非常灾难性,特别是因为这很容易被忽视?
所以真正的问题归结为 - 基本 appsettings.json 文件的内容是否应该是默认的“开发”值(例如开发数据库、沙箱 API),并被生产数据覆盖,反之亦然?
小智 7
有多种方法可以塑造您的设置(这就是 .NET Core 的美妙之处)。我通常的做法如下:
appsetting.json (template)
appsettings.development.json (dev with no secrets)
Run Code Online (Sandbox Code Playgroud)
实际上我没有在 appsettings.json 中放置任何设置。我将其用作部署期间必须(可以)设置的设置的模板映射。
// appsettings.json
{
"ConnectionStrings": {
"dbConnection": "************************"
},
"environment": "************************",
"Logging": {
"LogLevel": {
"Default": "************************"
}
},
}
Run Code Online (Sandbox Code Playgroud)
这样,如果我错过任何设置,稍后就会发现它被遗忘了。我不必担心意外使用“滑过”层次结构的设置。因此,如果您查看其他 json,它们是完整的并且没有隐藏设置。
// appsettings.Development.json
{
"ConnectionStrings": {
"dbConnection": "data source=localhost"
},
"environment": "local",
"Logging": {
"LogLevel": {
"Default": "Verbose"
}
}
}
Run Code Online (Sandbox Code Playgroud)
对于小型应用程序来说,共享设置似乎是个好主意。如果您的应用程序变得更加复杂,它实际上会带来更多问题。
这里有一些原则发挥作用:
首先,任何损坏/丢失的项目都应该出错,而不是在某些情况下默默地工作。这很有价值,因为它可以在开发早期发现问题。仅将跨环境不变的值放入基本文件中,或者在未覆盖(例如在测试时)时显示缺失值。这使您能够将负面测试用例写入已知值,这有助于发现更复杂配置中的错误。
其次,任何额外部署的内容都会增加风险,因此不要部署额外的内容。将每个环境的适当值放入特定于环境的文件中,仅此而已。这些值应覆盖基本文件,使您无需手动干预即可部署和运行。使用开箱即用的配置加载器加载(仅)当前环境的正确文件。
第三,有一种方法可以覆盖环境中的值而不需要重新部署任何文件。这里的值取决于您的环境和情况,例如安全事件。因此,环境变量应该覆盖前面的两个源。
如果您使用集中式配置源,是否可以允许部署的文件覆盖它?这是一个开发-安全-操作/策略问题。您的答案将决定集中式配置应位于列表中的位置。你把它放得越低,你的开发人员就越有可能需要在本地运行一个实例。
可能还有其他考虑因素或对您的项目有意义的附加层。重要的是对你所做的选择有一个“原因”,并能够在你的背景下逻辑地解释和证明它们的合理性。
我认为这个答案很无聊;这取决于。但我最喜欢的方法是这样的:
appsetting.json (base settings)
appsettings.development.json (dev with no secrets)
appsettings.production.json (production with no secrets)
Run Code Online (Sandbox Code Playgroud)
Appsettings 中的秘密值仅存在于基本设置中,而其他值则写入相应的 appsettings.[env].json。因此,示例数据库连接键仅存在于本地数据库的基本设置中。替换它是环境的工作
数据库连接和日志记录示例
appsettings.json
{
"ConnectionStrings": {
“dbConnection: “data source=localhost” <—— only here
},
“environment”: “local”,
"Logging": {
"LogLevel": {
"Default": “Verbose”
}
},
}
Run Code Online (Sandbox Code Playgroud)
appsettings.development.json
{
“environment”: “development”,
"Logging": {
"LogLevel": {
"Default": “Warning”
}
},
}
Run Code Online (Sandbox Code Playgroud)
appsettings.production.json
{
“environment”: “production”,
"Logging": {
"LogLevel": {
"Default": “Information”
}
},
}
Run Code Online (Sandbox Code Playgroud)
我担心的是 - 假设应用程序部署到实时服务器,但存储在环境变量(例如覆盖连接字符串)中的密钥丢失或拼写错误等。在这种情况下,应用程序将回退到基础 appsettings.json连接字符串,这对于实时环境来说是不正确的数据库。像这样的场景听起来非常灾难性,特别是因为这很容易被忽视?
你总是可以这样做。但是一些健全性测试应该可以做到。如果您的基础设施/部署管道允许,请在您 ping 数据库的位置进行简单的健康检查。
我已经养成了将我的配置存储在 Azure 中的 AzureAppConfig 和/或 AzureKeyVault 下的习惯。它为我提供了一个中央位置来管理我的开发、暂存/测试、生产设置,并且不需要我通过操作 appsettings 文件来使我的部署复杂化,或者将它们存储在某种部署存储库中。它实际上只在应用程序启动时从 azure 读取(我不需要在我的应用程序运行时能够刷新它们)。话虽如此,这让本地开发故事变得有点有趣,因为我个人希望操作的顺序是appsettings.json, appsettings.{environment}.json, AzureAppConfig, KeyVault,然后 finally secrets.json。这样,无论如何,我都可以使用本地机密文件覆盖 azure 中的设置(即使我覆盖的设置不是“
我基本上最终编写了一些自定义代码program.cs来处理从 Azure 加载配置源,然后完成查找JsonConfigurationSource具有Pathof 的"secrets.json",然后将其作为我的IConfigurationBuilder.Sources.
对我来说,我的文件按如下方式使用
appsettings.json- 需要为任何环境设置的通用设置,并且可能永远不会因环境而改变。
appsettings.{environment}.json-大多只是带空的JSON文件基本上只是命名AzureAppConfig和AzuerKeyVault连接到资源名称AzureAppConfig- 基本上对于生产、分期/测试或本地开发之间的任何不同,并且不是敏感信息。API 端点地址、IP 地址、各种 URL、错误日志信息等等。AzureKeyVault- 任何敏感的东西。用户名、密码、外部 API 的密钥(身份验证、许可证密钥、连接字符串等)。问题是,即使您将设置放入 中appsettings.json,也不意味着您不能用appsettings.{enviroment}.json或其他地方覆盖它。我经常在根设置文件中放置一个值为 的设置NULL,只是为了提醒我这是应用程序中使用的设置。所以一个更好的问题可能是,您是否希望能够运行您的应用程序(如没有错误),除了基础appsettings.json和secrets.json? 或者appsettings.{enviroment}.json总是需要内容才能成功启动?
根据您的问题要查看的另一件事是验证您的配置。更高版本Microsoft.Extensions.Options提供了各种方法来验证您的选项,以便您可以尝试捕获某些内容为空/未定义的实例。我通常用数据注释属性装饰我的 POCO Options 类,然后ValidateDataAnnotations()用来验证它们是否正确设置。
例如
services.AddOptions<MailOptions>().Bind(configuration.GetSection("MailSettings")).ValidateDataAnnotations();
Run Code Online (Sandbox Code Playgroud)
值得注意的是,此验证仅在您尝试MailOptions从 DI请求类似于上面示例中使用的内容时运行(因此不在启动时)出于这个原因,我还创建了您自己的IStartupFilter以抢先请求一个或多个我的 Options 类在应用程序启动时从服务提供商处获取,以便在应用程序甚至开始接受请求之前强制运行相同的验证。
public class EagerOptionsValidationStartupFilter : IStartupFilter
{
public readonly ICollection<Type> EagerValidateTypes = new List<Type>();
private readonly IServiceProvider serviceProvider;
public EagerOptionsValidationStartupFilter(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
foreach (var eagerType in EagerValidateTypes)
{
dynamic test = serviceProvider.GetService(typeof(IOptions<>).MakeGenericType(eagerType));
_ = test.Value;
}
return next;
}
}
Run Code Online (Sandbox Code Playgroud)
startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IStartupFilter>(x =>
new EagerOptionsValidationStartupFilter(x)
{
EagerValidateTypes = {
typeof(MailOptions),
typeof(OtherOptions),
typeof(MoreImportantOptions)
}
});
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6913 次 |
| 最近记录: |