我已经养成了将我的配置存储在 Azure 中的 AzureAppConfig 和/或 AzureKeyVault 下的习惯。它为我提供了一个中心位置来管理我的开发、登台/测试、生产设置,并且不需要我通过操作 appsettings 文件或将它们存储在某种部署存储库中来使我的部署复杂化。它实际上只在应用程序启动时从天蓝色读取(我不需要在我的应用程序运行时刷新它们)。话虽如此,这让本地开发故事变得有点有趣,因为我个人希望操作顺序为appsettings.json、appsettings.{environment}.json、AzureAppConfig、KeyVault,最后是secrets.json。这样一来,无论如何,我都可以使用本地机密文件覆盖 azure 中的设置(即使我所覆盖的设置在技术上不是机密)。
我基本上最终在 program.cs 中编写了一些自定义代码来处理从 Azure 加载配置源,然后查找具有 Path 或 "secrets.json" 的 JsonConfigurationSource,然后将其撞到我的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();
值得注意的是,仅当您尝试从 DI 请求类似 MailOptions 之类的内容时才会运行此验证(因此不在启动时)
出于这个原因,我还创建了您自己的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)
}
});
}