更新:我想补充一点,我不再使用 Azure 应用程序配置,仅使用 KeyVault。我很失望的是,定价允许每个订阅 1 个免费 AppConfig,然后似乎对第一个之后的每个订阅每天收取超过 1.20 美元的 CDN。对我来说,这意味着每月要额外支付 36 美元来购买每天只使用一次的东西(假设我让网络应用程序在晚上关闭)。价格点根本没有任何意义。我会尽快使用多个 KeyVault,因为它们的成本看起来纯粹基于使用情况,并且每数千笔交易的成本微薄。我不明白 AppConfig 的预期用例是什么来证明其价格点的合理性。
我已经养成了将配置存储在 Azure 中的 AzureAppConfig 和/或 AzureKeyVault 下的习惯。它为我提供了一个集中位置来管理我的开发、登台/测试、生产设置,并且不需要我通过操作 appsettings 文件或将它们存储在某种部署存储库中来使部署复杂化。它实际上只在应用程序启动时从 azure 读取(我不需要在应用程序运行时刷新它们)。话虽如此,这使得本地开发故事变得有点有趣,因为我个人希望操作顺序是appsettings.json
, appsettings.{environment}.json
, AzureAppConfig
, KeyVault
,那么最后secrets.json
。这样,无论如何,我都可以使用本地机密文件覆盖 azure 中的设置(即使我覆盖的设置在技术上不是秘密)。
我基本上最终编写了一些自定义代码program.cs
处理从 Azure 加载配置源,然后完成查找JsonConfigurationSource
那个有一个Path
of "secrets.json"
,然后将其作为我的最后一项IConfigurationBuilder.Sources
.
对我来说,我的文件的使用方式如下
-
appsettings.json
- 需要为任何环境设置的通用设置,并且将likely永远不会因环境而改变。appsettings.{environment}.json
- 大部分只是空的 JSON 文件,基本上只是命名AzureAppConfig
& AzuerKeyVault
要连接的资源名称
-
AzureAppConfig
- 基本上对于生产、登台/测试或本地开发之间的任何不同,AND 不是敏感信息。 API 端点地址、IP 地址、各种 URL、错误日志信息等等。
-
AzureKeyVault
- 任何敏感的事情。用户名、密码、外部 API 密钥(身份验证、许可证密钥、连接字符串等)。
问题是,即使你设置了appsettings.json
,这并不意味着你不能用appsettings.{enviroment}.json
或者别的地方。我经常将设置放在根设置文件中,其值为NULL
,只是为了提醒我这是应用程序中使用的设置。所以一个更好的问题可能是,您是否希望能够运行您的应用程序(没有错误),除了基础之外什么都没有appsettings.json
and secrets.json
?或者内容来自appsettings.{enviroment}.json
总是需要成功启动?
根据您的问题要查看的另一件事是对您的配置的验证。以后的版本Microsoft.Extensions.Options
提供各种方法来验证您的选项,以便您可以尝试捕获某些内容为空/未定义的实例。我通常用数据注释属性来装饰 POCO Options 类,然后使用ValidateDataAnnotations()
验证它们的设置是否正确。
例如
services.AddOptions<MailOptions>().Bind(configuration.GetSection("MailSettings")).ValidateDataAnnotations();
值得注意的是,仅当您尝试请求类似以下内容时,此验证才会运行MailOptions
我使用上面的例子,来自 DI(所以不是在启动时)
为此,我也创建了你自己的IStartupFilter
在应用程序启动时抢先向服务提供商请求一个或多个选项类,以便在应用程序开始接受请求之前强制运行相同的验证。
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;
}
}
startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IStartupFilter>(x =>
new EagerOptionsValidationStartupFilter(x)
{
EagerValidateTypes = {
typeof(MailOptions),
typeof(OtherOptions),
typeof(MoreImportantOptions)
}
});
}