如何使用带有ValidateDataAnnotations的配置

时间:2019-03-06 14:16:16

标签: c# asp.net-mvc asp.net-core

我已经阅读了OptionsConfiguration的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中的数据批注进行验证,则使用配置的类仅假设该配置有效,并且我不进行任何重新验证?

2 个答案:

答案 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;
}

Source Code

答案 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一样工作。