FluentValidation с использованием валидатора на неверной модели представления - PullRequest
0 голосов
/ 16 мая 2019

Я впервые использую FluentValidation. У меня была базовая проверка работоспособности, но потом я понял, что мне нужно будет выполнить поиск в базе данных для более сложной проверки. Это потребовало выполнения Dependency Injection, чтобы я мог использовать службу базы данных, и это привело меня к моему текущему состоянию: застрял. Я не могу заставить это работать.

Чтобы упростить ситуацию, я притворюсь, что мое приложение имеет дело со спортивными лигами и командами, потому что я считаю, что это более простая ментальная модель, чем контракты, счета, источники финансирования, поставщики и субподрядчики. : -)

Итак, предположим, у меня есть модель для спортивной лиги. Внутри этой модели просмотра есть коллекция моделей для команд, которые находятся в этой лиге.

У меня есть экран для редактирования лиги. На этом же экране можно изменить некоторые сведения о командах, входящих в эту лигу.

LeagueViewModel

Модель просмотра для лиги содержит Список моделей просмотра для команд.

[FluentValidation.Attributes.Validator(typeof(LeagueValidator))]
public class LeagueViewModel
{
    public string LeagueName { get; set; }
    public DateTime SeasonBeginDate { get; set; }
    public DateTime SeasonEndDate { get; set; }

    public List<TeamViewModel> TeamViewModels { get; set; }
}

Я создал валидатор для LeagueViewModel. К сожалению, когда я редактирую лигу и нажимаю кнопку отправки, я получаю это сообщение об ошибке:

InvalidCastException: невозможно привести объект типа «TeamViewModel» к типу «LeagueViewModel». at FluentValidation.ValidationContext.ToGenericT

По-видимому, он пытается проверить TeamViewModel с помощью LeagueValidator.

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

Validator

public class LeagueValidator : AbstractValidator<LeagueViewModel>
{
    private readonly ILeagueService _leagueService;

    public LeagueValidator(ILeagueService leagueService)
    {
        _leagueService = leagueService;

        RuleFor(x => x.SeasonEndDate)
            .NotNull()
            .GreaterThan(x => x.SeasonBeginDate)
            .WithMessage("Season End Date must be later than Season Begin Date.");
    }
}

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

Обратите внимание, что в LeagueValidator нет правил проверки для каких-либо полей в Списке TeamViewModels.

Фабрика Валидаторов Лиги

public class LeagueValidatorFactory : ValidatorFactoryBase
{
    private readonly Container _container;

    public LeagueValidatorFactory(Container container)
    {
        _container = container;
    }

    public override IValidator CreateInstance(Type validatorType)
    {
        return _container.GetInstance<LeagueValidator>();
    }
}

Инжектор зависимостей

Мы используем SimpleInjector для DI. Как часть этой существующей установки, он вызывает метод для регистрации сервисов. В этом методе я добавил вызов к этому:

private static void RegisterValidators(Container container)
{
    DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

    var leagueValidatorProvider =
        new FluentValidationModelValidatorProvider(new LeagueValidatorFactory(container));
    leagueValidatorProvider.AddImplicitRequiredValidator = false;
    ModelValidatorProviders.Providers.Add(leagueValidatorProvider);

    container.Register<LeagueValidator>();
}

Вопросы

  1. Как мне заставить это работать должным образом?
  2. Почему он пытается использовать LeagueValidator для проверки TeamViewModel?
  3. Нужно ли иметь отдельную фабрику валидаторов и валидаторов для каждой модели представления?
  4. Даже те, у кого нет правил проверки?
  5. Как мне сказать, какой валидатор использовать для какой модели представления?

Я полагаю, что я неправильно понимаю что-то базовое.

Редактировать

Ответ Стивена ниже указал мне правильное направление! После внесения предложенных им изменений я столкнулся с другой ошибкой. Как только я это исправлю, это работает! Вот изменения, которые я сделал, чтобы приведенный выше код работал правильно.

LeagueViewModel

Я удалил эту строку, так как она не нужна.

[FluentValidation.Attributes.Validator(typeof(LeagueValidator))]

LeagueValidatorFactory

Я переименовал его в «ValidatorFactory», потому что оказалось, что будет только одна фабрика валидаторов, независимо от того, сколько валидаторов я создаю. Затем я изменил метод CreateInstance следующим образом:

    public override IValidator CreateInstance(Type validatorType)
    {
        if (_container.GetRegistration(validatorType) == null)
        {
            return null;
        }

        return (IValidator)_container.GetInstance(validatorType);
    }

Это больше не указывает явно тип валидатора, который нужно получить (именно поэтому потребуется только одна фабрика). Чтобы определить, доступен ли валидатор для данного типа, он вызывает GetRegistration, возвращая ноль, если ничего не найдено.

Это было важно! Для каждой модели представления будет пытаться найти валидатор. Без этой нулевой проверки генерируется исключение InvalidCastException.

Зависимый инжектор

Следуя предложению Стивена, я заменил контейнер. Зарегистрируйте строку следующим образом:

container.Register(typeof(IValidator<>), new[] { typeof(SimpleInjectorInitializer).Assembly });

Это исключает необходимость явного перечисления каждого валидатора при каждом добавлении нового.

И теперь все это работает! Большое спасибо за вашу помощь, Стивен!

1 Ответ

1 голос
/ 16 мая 2019

Я незнаком с FluentValidation, но, похоже, ваш LeagueValidatorFactory запрашивает неверный тип из контейнера, учитывая, что он дополняется типом для проверки.

Из-за этого я ожидаю, что ваша фабрика проверки будет выглядеть примерно так:

public class LeagueValidatorFactory : ValidatorFactoryBase
{
    private readonly Container _container;

    public LeagueValidatorFactory(Container container) =>
        _container = container;

    public override IValidator CreateInstance(Type validatorType) =>
        (IValidator)_container.GetInstance(validatorType);
}

Что я могу видеть из исходного кода FluentValidator, так это то, что validatorType является версией общего типа типа IValidator<T>, причем T является действительным проверяемым типом. Это означает, что вам придется регистрировать валидаторы по их интерфейсу IValidator<T>. Например:

container.Register<IValidator<LeagueViewModel>, LeagueValidator>();

Эта конфигурация в виде кода (или явный-регистр) модель, в которой вы регистрируете каждый валидатор явно, используя строку кода, может нормально работать, если у вас всего несколько валидаторов, но обычно это приводит к Корень композиции , который необходимо часто обновлять.

Поэтому лучше использовать Авторегистрация , где вы регистрируете все реализации IValidator<T>, используя отражение. К счастью, вам не нужно реализовывать это самостоятельно; У простого инжектора есть спина:

var validatorAssemblies = new[] { typeof(LeagueValidator).Assembly };
container.Register(typeof(IValidator<>), validatorAssemblies);

Это гарантирует, что вам никогда не придется менять корень композиции, когда вы только что добавили новый валидатор (в этой конкретной сборке).

При такой настройке я не вижу причин, по которым вы должны пометить модель вида на FluentValidation.Attributes.ValidatorAttribute. Если вы можете, пожалуйста, удалите его, так как он вызывает только неустойчивую связь между вашей моделью представления и средством проверки.

...