Использование FluentValidation в .NET Core с внедрением зависимостей - PullRequest
1 голос
/ 18 мая 2019

У меня есть приложение .NET Core Web Api, которое организовано следующим образом -

  1. Уровень контроллера, который внедряет Business Service
  2. Бизнес-сервис, который вводит единицу работы (для взаимодействия с базой данных)
  3. Бизнес-служба также может позвонить в класс FluentValidation
  4. FluentValidation будет вводить единицу работы для проверки базы данных (существует и т. Д.)

Итак, сказав все это, вот пример. Если я хочу создать пользователя в системе, у меня есть маршрут / метод под названием «PostUser», расположенный внутри «UsersController». «UsersController» внедряет «UserService». «UserService» имеет метод под названием «CreateUser». Итак, внутри метода «PostUser» контроллера это выглядит так -

var user = _userService.CreateUser(user);

Теперь внутри метода «CreateUser» это выглядит так -

UserValidation validation = new UserValidation(UnitOfWork, DatabaseOperation.Create);
ValidationResult validationResult = await validation.ValidateAsync(user);

Таким образом, UnitOfWork передается в UserService посредством внедрения зависимостей, а затем передается в класс FluentValidation «UserValidation», чтобы класс проверки мог выполнять проверки базы данных. Я также передаю перечисление в класс UserValidation, чтобы указать, предназначена ли проверка для обновления или создания.

Объект User, который я пытаюсь проверить, будет иметь такие свойства, как "Role" и "Company", и у меня также есть отдельные классы проверки для каждого (RoleValidation и CompanyValidation). Оба эти класса проверки также будут передавать UnitOfWork, независимо от того, является ли это созданием или обновлением.

Вот пример моего класса UserValidation -

public class UserValidation : AbstractValidator<UserDTO> 
{
     private IUnitOfWork _unitOfWork;
     public UserValidation(IUnitOfWork unitOfWork, DatabaseOperation databaseOperation)
     {
         _unitOfWork = unitOfWork;

         if (databaseOperation == DatabaseOperation.Create)
         {
              // Do Create specific validation
         }

         RuleFor(x => x.Company)
            .SetValidator(new CompanyValidator(_unitOfWork, databaseOperation));

     }
}

Теперь, понимая все это, я хотел создать модульные тесты для своего класса "UserService". Но я считаю, что для правильного выполнения этого в некоторых случаях мне нужно было бы создать класс FluentValidation, и, как вы можете видеть в моем методе CreateUser «UserService», я создаю конкретный класс для своей проверки. Поэтому для этого мне нужно создать интерфейс для каждого из моих классов fluentvalidation и внедрить их в бизнес-сервисы, которые их используют. Поэтому я сделал следующее в моем файле Startup.cs -

services.AddScoped<IValidator<User>>(x => new UserValidation(x.GetRequiredService<IUnitOfWork>()));

Так что теперь после этого я могу внедрить IValidator в свой конструктор UserService и использовать его вместо создания класса Concrete внутри моих методов UserService.

Итак, это заставляет меня задавать следующие вопросы.

  1. По вашему мнению, как я уже структурировал свой проект, это лучший способ использовать внедрение зависимостей с FluentValidation и позволить для модульного тестирования метода обслуживания наряду с модульным тестированием класса FluentValidation?
  2. Есть ли лучший способ использовать внедрение зависимостей с FluentValidation, чтобы сделать все это, и в то же время сообщить классу FluentValidation, является ли он «Create» или «Update», вместо создания одного класса с именем «UserCreateValidation» "и" UserUpdateValidation "или передача переменной" DatabaseOperation "в конструктор валидатора?
  3. Присоединение к (2) при попытке установить инъекцию FluentValidation DependencyInjection У меня возникают проблемы при передаче переменной DatabaseOperation services.AddScoped<IValidator<User>>(x => new UserValidation(x.GetRequiredService<IUnitOfWork>(), <How to figure out if its a create or an update>));
  4. Кроме того, мне придется также добавить две строки в файл «Startup.cs», чтобы создать Scoped DependencyInjection для «CompanyValidation» и «RoleValidation», которые будут использоваться внутри «UserValidation» и обеих эти классы проверки также будут передаваться независимо от того, является ли это обновлением или созданием.

Любая помощь / предложения будут оценены. Я действительно застрял в этом вопросе. Если кому-то нужно больше разъяснений по вопросам, с которыми я сталкиваюсь, пожалуйста, не стесняйтесь спрашивать.

Спасибо

1 Ответ

0 голосов
/ 22 мая 2019

У меня похожая проблема. Однако вы мне помогли.

То, что я сделал по-другому / сделал бы по-другому. вместо Create или Update вы можете использовать RuleSets, в зависимости от имени, которое будет выполнять различные RuleSets, это позволит вам идентифицировать операцию при ее проверке: https://fluentvalidation.net/start#rulesets. Вы не должны вводить что-либо зависимое на результат во время выполнения в этот момент такая индикация, если это создание или обновление.

Отвечая на ваши вопросы:

Вопрос 1. Кажется, я указал на одну ошибку выше. В противном случае выглядит хорошо для меня. Нет необходимости создавать оболочку для модульного тестирования вашей проверки, вы можете просто сделать это, как в этом примере:

 [Test]
    public void Should_have_error_when_val_is_zero()
    {
        validator = new TestModelValidator();
        TestModel testRequest = new TestModel();
        //populate with dummy data
        var result = validator.Validate(testRequest);
        Assert.That(result.Errors.Any(o => o.PropertyName== "ParentVal"));
    }

Вопрос 2: Я бы внедрил в валидатор всего одну scopedFactory и позволил бы ей самостоятельно разрешать свои зависимости, а не вводить все, что ему нужно. Однако что вы делаете внутри new CompanyValidator(_unitOfWork, databaseOperation)? Мне кажется странным вводить что-либо в Validator, поскольку на самом деле это не то, что вы вводите, чтобы разрешить правило. Я не уверен, в чем ваша причина, но в противном случае, как я уже сказал, для этого нужно добавить scopedFactory или класс Nested.

Вопрос 3: Мне кажется, я уже ответил на этот вопрос.

Вопрос 4: Я бы попытался создать обобщенную инъекцию зависимостей или внедрить Массив валидаторов в какую-то фабрику, которая будет разрешаться в зависимости от типа.

services.AddScoped (typeof (IValidationFactory <>), typeof (ValidationFactory <>));

Что решит, какой валидатор мне нужен на основе типа.

Надеюсь, это имеет смысл.

UPDATE

Таким образом, внутри CreateMethod передайте имя RuleSet методу validate, чтобы он мог решить, является ли это Create или Update. О заводе с ограниченной ответственностью https://csharp.hotexamples.com/examples/-/IServiceScopeFactory/-/php-iservicescopefactory-class-examples.html

Например: Вместо этого: ValidationResult validationResult = ожидание проверки. ValidateAsync (пользователь);

Вы можете сделать это:

validator.Validate(person, ruleSet: "Create");

Также вы можете разрешить зависимости и ввести необходимый валидатор, например, вот так (я решаю по типу запроса, вы можете использовать строковый ключ, если необходимо):

  services.AddSingleton<IValidator, Validator1>();
            services.AddSingleton<IValidator, Validator2>();
            services.AddSingleton<IValidator, Validator3>();

            services.AddScoped<Func<Type, IValidator>>(serviceProvider => typeKey =>
            {
                if (typeKey == typeof(Validator1))
                {
                    return serviceProvider.GetService<Validator1>();
                }
                if (typeKey == typeof(Validator2))
                {
                    return serviceProvider.GetService<Validator2>();
                }

                if (typeKey == typeof(Validator3))
                {
                    return serviceProvider.GetService<Validator3>();
                }

                return null;
            });

А это пример использования:

 public GenericValidator(Func<Type, IValidator> validatorFactory)
        {
            _validatorFactory = validatorFactory ?? throw new ArgumentNullException(nameof(validatorFactory));
        }


 public async Task<IEnumerable<string>> ValidateAsync<T, TK>(TK objectToValidate) where TK : class
        {
            var validator = _validatorFactory(typeof(T));

            if (validator == null)
            {
                throw new ValidationException($"Failed to get validator for type: {typeof(T)}");
            }

            var validationResult = await validator.ValidateAsync(objectToValidate);

            return validationResult.Errors.Select(x => x.ErrorMessage);
        }

И введите: IServiceScopeFactory serviceScopeFactory в ваш валидатор, который поможет разрешить любые внешние зависимости. Вы можете найти примеры здесь: https://csharp.hotexamples.com/examples/-/IServiceScopeFactory/-/php-iservicescopefactory-class-examples.html

...