我已经阅读了Options和Configuration的Microsoft基础文档,但是仍然找不到在验证数据注释时将配置提取到对象中的正确方法。
我在Startup.ConfigureServices
中尝试过的一种方法
services.AddOptions<EmailConfig>().Bind(Configuration.GetSection("Email")).ValidateDataAnnotations();
此“应该”允许通过将其添加到类构造函数中来访问配置:(IOptions<EmailConfig> emailConfig)
但是它不起作用。
另一种方法是将(IConfiguration configuration)
添加到构造函数中,但这不允许我调用ValidateDataAnnotations
。
configuration.GetSection("Email").Get<EmailConfig>();
第一个问题:绑定和验证配置的责任是属于Startup类还是属于使用它的类?如果它被多个类使用,我会说它属于Startup;并且该类可以在具有不同配置布局的另一个项目中使用。
第二个问题:绑定和验证配置以便可以从类中访问它的正确语法是什么?
第三个问题:如果我要通过Startup中的数据批注进行验证,则使用配置的类仅假设该配置有效,并且我不进行任何重新验证?
答案 0 :(得分:2)
您可以在将其添加到服务集合之前尝试在启动时亲自验证该类。
启动
var settings = Configuration.GetSection("Email").Get<EmailConfig>();
//validate
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(settings, serviceProvider: null, items: null);
if (!Validator.TryValidateObject(settings, validationContext, validationResults,
validateAllProperties: true)) {
//...Fail early
//will have the validation results in the list
}
services.AddSingleton(settings);
这样,您就不会与IOptions
耦合,并且还允许代码尽早失败,并且可以在需要时显式注入依赖项。
您可以将验证打包到您自己的扩展方法中,例如
public static T GetValid<T>(this IConfiguration configuration) {
var obj = configuration.Get<T>();
//validate
Validator.ValidateObject(obj, new ValidationContext(obj), true);
return obj;
}
用于类似
的通话EmailConfig emailSection = Configuration.GetSection("Email").GetValid<EmailConfig>();
services.AddSingleton(emailSection);
在内部,ValidateDataAnnotations
基本上是在做同样的事情。
/// <summary>
/// Validates a specific named options instance (or all when name is null).
/// </summary>
/// <param name="name">The name of the options instance being validated.</param>
/// <param name="options">The options instance.</param>
/// <returns>The <see cref="ValidateOptionsResult"/> result.</returns>
public ValidateOptionsResult Validate(string name, TOptions options)
{
// Null name is used to configure all named options.
if (Name == null || name == Name)
{
var validationResults = new List<ValidationResult>();
if (Validator.TryValidateObject(options,
new ValidationContext(options, serviceProvider: null, items: null),
validationResults,
validateAllProperties: true))
{
return ValidateOptionsResult.Success;
}
return ValidateOptionsResult.Fail(String.Join(Environment.NewLine,
validationResults.Select(r => "DataAnnotation validation failed for members " +
String.Join(", ", r.MemberNames) +
" with the error '" + r.ErrorMessage + "'.")));
}
// Ignored if not validating this instance.
return ValidateOptionsResult.Skip;
}
答案 1 :(得分:1)
关于ValidateDataAnnotations是如何工作的,目前尚无答案,但是基于Nkosi的答案,我编写了此类扩展名以轻松按需运行验证。因为它是对Object的扩展,所以我将其放在子命名空间中,以便仅在需要时启用它。
namespace Websites.Business.Validation {
/// <summary>
/// Provides methods to validate objects based on DataAnnotations.
/// </summary>
public static class ValidationExtensions {
/// <summary>
/// Validates an object based on its DataAnnotations and throws an exception if the object is not valid.
/// </summary>
/// <param name="obj">The object to validate.</param>
public static T ValidateAndThrow<T>(this T obj) {
Validator.ValidateObject(obj, new ValidationContext(obj), true);
return obj;
}
/// <summary>
/// Validates an object based on its DataAnnotations and returns a list of validation errors.
/// </summary>
/// <param name="obj">The object to validate.</param>
/// <returns>A list of validation errors.</returns>
public static ICollection<ValidationResult> Validate<T>(this T obj) {
var Results = new List<ValidationResult>();
var Context = new ValidationContext(obj);
if (!Validator.TryValidateObject(obj, Context, Results, true))
return Results;
return null;
}
}
}
然后在Statup中非常简单
EmailConfig EmailSection = Configuration.GetSection("Email").Get<EmailConfig>().ValidateAndThrow();
services.AddSingleton<EmailConfig>(EmailSection);
像魅力一样运作;实际上就像我期望ValidateDataAnnotations一样工作。