How to Use Configuration with ValidateDataAnnotations

后端 未结 2 466
悲&欢浪女
悲&欢浪女 2021-01-21 05:01

I\'ve read the Microsoft documentation of fundamentals for Options and Configuration, but still can\'t find the right way to extract configuration into an object while validatin

相关标签:
2条回答
  • 2021-01-21 05:41

    You can try validating the class yourself in start up before adding it to service collection.

    Startup

    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);
    

    That way you are not coupled to IOptions and you also allow your code to fail early and you can explicitly inject the dependency where needed.

    You could package the validation up into your own extension method like

    public static T GetValid<T>(this IConfiguration configuration) {
        var obj = configuration.Get<T>();    
        //validate
         Validator.ValidateObject(obj, new ValidationContext(obj), true);    
        return obj;
    }
    

    for calls like

    EmailConfig emailSection = Configuration.GetSection("Email").GetValid<EmailConfig>();
    services.AddSingleton(emailSection);
    

    Internally, ValidateDataAnnotations is basically doing the same thing.

    /// <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

    0 讨论(0)
  • 2021-01-21 05:41

    There is still no answer as to how ValidateDataAnnotations work, but based on Nkosi's answer, I wrote this class extension to easily run the validation on-demand. Because it's an extension on Object, I put it into a sub-namespace to only enable it when needed.

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

    Then in Startup it's quite straightforward

    EmailConfig EmailSection = Configuration.GetSection("Email").Get<EmailConfig>().ValidateAndThrow();
    services.AddSingleton<EmailConfig>(EmailSection);
    

    Works like a charm; actually works like I'd expect ValidateDataAnnotations to work.

    0 讨论(0)
提交回复
热议问题