Core2有一个钩子,用于验证从appsettings.json
读取的选项:
services.PostConfigure<MyConfig>(options => {
// do some validation
// maybe throw exception if appsettings.json has invalid data
});
此验证代码在首次使用MyConfig
时以及之后的每次触发时触发。所以我遇到多个运行时错误。
但是在启动过程中运行验证更为明智-如果配置验证失败,我希望应用程序立即失败。 docs imply that是它的工作方式,但事实并非如此。
所以我做对了吗?如果是这样,并且这是设计使然,那么如何更改正在执行的操作,使其按我想要的方式工作?
(另外,PostConfigure
和PostConfigureAll
之间有什么区别?在这种情况下没有区别,所以什么时候应该使用其中一个?)
答案 0 :(得分:5)
在启动过程中没有运行配置验证的真正方法。正如您已经注意到的,后配置操作就像普通的配置操作一样在请求选项对象时懒惰地运行。这完全是设计使然,并具有许多重要功能,例如在运行时重新加载配置,或者选项缓存无效。
后配置动作通常用于的不是验证“如果有什么问题,则抛出异常” ,而是“如果有什么问题,退回到合理的默认设置并使其正常工作。” 。
例如,身份验证堆栈中有一个后配置步骤,可确保始终为远程身份验证处理程序设置SignInScheme
:
options.SignInScheme = options.SignInScheme ?? _authOptions.DefaultSignInScheme ?? _authOptions.DefaultScheme;
如您所见,这不会失败,而只会提供多个后备。
从这个意义上讲,记住选项和配置实际上是两个独立的部分也很重要。只是配置是配置选项的常用来源。因此,有人可能会争辩说,验证配置是否正确实际上不是选项的工作。
因此,在配置选项之前,实际检查启动中的配置可能更有意义。像这样:
var myOptionsConfiguration = Configuration.GetSection("MyOptions");
if (string.IsNullOrEmpty(myOptionsConfiguration["Url"]))
throw new Exception("MyOptions:Url is a required configuration");
services.Configure<MyOptions>(myOptionsConfiguration);
当然,这很容易变得非常多余,并且可能会迫使您手动绑定/解析许多属性。它还将忽略选项模式支持的配置链(即,配置具有多个源/动作的单个选项对象)。
因此,您在这里可以做的是保留配置后的操作以进行验证,并通过实际请求options对象简单地在启动过程中触发验证。例如,您可以简单地将IOptions<MyOptions>
作为对Startup.Configure
方法的依赖项:
public void Configure(IApplicationBuilder app, IOptions<MyOptions> myOptions)
{
// all configuration and post configuration actions automatically run
// …
}
如果您有多个这些选项,甚至可以将其移动到单独的类型:
public class OptionsValidator
{
public OptionsValidator(IOptions<MyOptions> myOptions, IOptions<OtherOptions> otherOptions)
{ }
}
那时,您还可以将逻辑从后期配置操作移至该OptionsValidator
。因此,您可以在应用程序启动时明确触发验证:
public void Configure(IApplicationBuilder app, OptionsValidator optionsValidator)
{
optionsValidator.Validate();
// …
}
如您所见,对此没有一个答案。您应该考虑自己的要求,并了解最适合您的情况的方案。当然,整个验证只对某些配置有意义。特别是,在运行时会更改配置的情况下,您会遇到困难(您可以可以使用自定义选项监视器来执行此操作,但这可能不值得麻烦)。但是由于大多数自己的应用程序通常只使用缓存的IOptions<T>
,因此您可能不需要它。
对于PostConfigure
和PostConfigureAll
,它们都注册了IPostConfigure<TOptions>
。区别仅在于前者仅匹配单个 named 选项(默认情况下为unnamed选项-如果您不关心选项名称),而PostConfigureAll
将针对所有名称运行
命名选项例如用于身份验证堆栈,其中每种身份验证方法均通过其方案名称来标识。因此,您可以例如添加多个OAuth处理程序,并使用PostConfigure("oauth-a", …)
配置一个,并使用PostConfigure("oauth-b", …)
配置另一个,或者使用PostConfigureAll(…)
同时配置它们。
答案 1 :(得分:3)
在ASP.NET Core 2.2项目中,我可以按照以下步骤进行 渴望 验证。
给出一个类似这样的Options类:
public class CredCycleOptions
{
[Range(1753, int.MaxValue, ErrorMessage = "Please enter a valid integer Number.")]
public int VerifiedMinYear { get; set; }
[Range(1753, int.MaxValue, ErrorMessage = "Please enter a valid integer Number.")]
public int SignedMinYear { get; set; }
[Range(1753, int.MaxValue, ErrorMessage = "Please enter a valid integer Number.")]
public int SentMinYear { get; set; }
[Range(1753, int.MaxValue, ErrorMessage = "Please enter a valid integer Number.")]
public int ConfirmedMinYear { get; set; }
}
在Startup.cs
中,将以下行添加到ConfigureServices
方法中:
services.AddOptions();
// This will validate Eagerly...
services.ConfigureAndValidate<CredCycleOptions>("CredCycle", Configuration);
ConfigureAndValidate
是here的扩展方法。
public static class OptionsExtensions
{
private static void ValidateByDataAnnotation(object instance, string sectionName)
{
var validationResults = new List<ValidationResult>();
var context = new ValidationContext(instance);
var valid = Validator.TryValidateObject(instance, context, validationResults);
if (valid)
return;
var msg = string.Join("\n", validationResults.Select(r => r.ErrorMessage));
throw new Exception($"Invalid configuration for section '{sectionName}':\n{msg}");
}
public static OptionsBuilder<TOptions> ValidateByDataAnnotation<TOptions>(
this OptionsBuilder<TOptions> builder,
string sectionName)
where TOptions : class
{
return builder.PostConfigure(x => ValidateByDataAnnotation(x, sectionName));
}
public static IServiceCollection ConfigureAndValidate<TOptions>(
this IServiceCollection services,
string sectionName,
IConfiguration configuration)
where TOptions : class
{
var section = configuration.GetSection(sectionName);
services
.AddOptions<TOptions>()
.Bind(section)
.ValidateByDataAnnotation(sectionName)
.ValidateEagerly();
return services;
}
public static OptionsBuilder<TOptions> ValidateEagerly<TOptions>(this OptionsBuilder<TOptions> optionsBuilder) where TOptions : class
{
optionsBuilder.Services.AddTransient<IStartupFilter, StartupOptionsValidation<TOptions>>();
return optionsBuilder;
}
}
我在ValidateEargerly
内部使用了ConfigureAndValidate
扩展方法。它利用了here中的另一个类:
public class StartupOptionsValidation<T> : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return builder =>
{
var options = builder.ApplicationServices.GetService(typeof(IOptions<>).MakeGenericType(typeof(T)));
if (options != null)
{
// Retrieve the value to trigger validation
var optionsValue = ((IOptions<object>)options).Value;
}
next(builder);
};
}
}
这使我们能够在CredCycleOptions
上添加数据注释,并在应用程序启动时立即获得不错的错误反馈。
如果选项丢失或值有误,我们不希望用户在运行时捕获这些错误。那将是一个糟糕的经历。
答案 2 :(得分:2)
此NuGet package提供了一种// Component A
function App() {
const apiURL = "https://services9.arcgis.com/M4Su6cnNT6vqupbh/arcgis/rest/services/COVID19_Data/FeatureServer/0/query?where=1%3D1&outFields=*&outSR=4326&f=json";
const apiData = fetch(apiURL)
.catch(err=>console.log(err))
.then(data=>console.log(data));
return (
<div>
<h1>Hello</h1>
<DataVisualize data={apiData}/>
</div>
);
}
// Component B
function DataVisualize(props){
return <div>{props.data}</div>;
}
扩展方法,可在启动时使用ConfigureAndValidate<TOptions>
验证选项。
它基于Microsoft.Extensions.Options.DataAnnotations。但是与Microsoft的软件包不同,它甚至可以验证嵌套属性。它与.NET Core 3.1和.NET 5兼容。
Documentation & source code (GitHub)
安德鲁·洛克(Andrew Lock)解释options validation with IStartupFilter
。
答案 3 :(得分:1)
下面是一种通用的ConfigureAndValidate
方法,可以立即进行验证并“快速失败”。
总结步骤:
serviceCollection.Configure
进行选择serviceCollection.BuildServiceProvider().CreateScope()
scope.ServiceProvider.GetRequiredService<IOptions<T>>
获取选项实例(请记住使用.Value
)Validator.TryValidateObject
public static class ConfigExtensions
{
public static void ConfigureAndValidate<T>(this IServiceCollection serviceCollection, Action<T> configureOptions) where T : class, new()
{
// Inspired by https://blog.bredvid.no/validating-configuration-in-asp-net-core-e9825bd15f10
serviceCollection.Configure(configureOptions);
using (var scope = serviceCollection.BuildServiceProvider().CreateScope())
{
var options = scope.ServiceProvider.GetRequiredService<IOptions<T>>();
var optionsValue = options.Value;
var configErrors = ValidationErrors(optionsValue).ToArray();
if (!configErrors.Any())
{
return;
}
var aggregatedErrors = string.Join(",", configErrors);
var count = configErrors.Length;
var configType = typeof(T).FullName;
throw new ApplicationException($"{configType} configuration has {count} error(s): {aggregatedErrors}");
}
}
private static IEnumerable<string> ValidationErrors(object obj)
{
var context = new ValidationContext(obj, serviceProvider: null, items: null);
var results = new List<ValidationResult>();
Validator.TryValidateObject(obj, context, results, true);
foreach (var validationResult in results)
{
yield return validationResult.ErrorMessage;
}
}
}
答案 4 :(得分:1)
使用 IStartupFilter
和 IValidateOptions
可以轻松进行验证。
您可以将您的 ASP.NET Core 项目代码放在下面。
public static class OptionsBuilderExtensions
{
public static OptionsBuilder<TOptions> ValidateOnStartupTime<TOptions>(this OptionsBuilder<TOptions> builder)
where TOptions : class
{
builder.Services.AddTransient<IStartupFilter, OptionsValidateFilter<TOptions>>();
return builder;
}
public class OptionsValidateFilter<TOptions> : IStartupFilter where TOptions : class
{
private readonly IOptions<TOptions> _options;
public OptionsValidateFilter(IOptions<TOptions> options)
{
_options = options;
}
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
_ = _options.Value; // Trigger for validating options.
return next;
}
}
}
然后将 ValidateOnStartup
方法链接到 OptionsBuilder<TOptions>
上。
services.AddOptions<SampleOption>()
.Bind(Configuration)
.ValidateDataAnnotations()
.ValidateOnStartupTime();
如果您想为选项类创建自定义验证器。在文章下方结帐。