Как использовать конфигурацию с ValidateDataAnnotations - PullRequest
2 голосов
/ 06 марта 2019

Я прочитал документацию 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 ]

2 голосов
/ 06 марта 2019

Вы можете попробовать самостоятельно проверить класс при запуске, прежде чем добавить его в набор служб.

Запуск

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 голос
/ 08 марта 2019

До сих пор нет ответа относительно того, как работают 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 будет работать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...