Overview of the FluentValidation library. Part 6: Dependency Injection

Library FluentValidation can be used with any dependency injection library. This part will contain examples in the library Microsoft.Extensions.DependencyInjection. We have the following validators:

// Модель адреса
public class Address { ... }
// Валидатор для модели адреса
public class AddressValidator : AbstractValidator<Address> { ... }

// Модель заказа
public class Order { ... }
// Валидатор для модели заказа
public class OrderValidator : AbstractValidator<Order> { ... }

// Модель клиента
public class Customer
{
  // Имя
  public string? Forename { get; set; }
  // Адрес
  public Address? Address { get; set; }
}

// Валидатор для модели клиента
public class CustomerValidator : AbstractValidator<Customer>
{
  // Получаем через конструктор валиадтор для модели адреса (AddressValidator)
  public CustomerValidator(IValidator<Address> addressValidator)
  {
    RuleFor(customer => customer.Forename)
      .NotNull();

    // Применяем полученный валидатор
    RuleFor(customer => customer.Address)
      .SetValidator(addressValidator);
  }
}

And the following service:

// Сервис клиента
public class CustomerService
{
  private readonly IValidator<Customer> _validator;

  // Получаем CustomerValidator через конструктор
  public CustomerService(IValidator<Customer> validator)
  {
    _validator = validator;    
  }

  // Валидируем, возвращаем результат валидации (true/false)
  public bool Validate(Customer customer)
  {
    var result = _validator.Validate(customer);
    return result.IsValid;
  }
}

Manual registration.

Validators can be registered via the interface IValidator<T> Where T type of validated model:

using Microsoft.Extensions.DependencyInjection;

static void Main(string[] args)
{
  var services = new ServiceCollection();

  services.AddScoped<CustomerService>();
  // Регистрируем валидаторы
  services.AddScoped<IValidator<Customer>, CustomerValidator>();
  services.AddScoped<IValidator<Address>, AddressValidator>();
  // AddScoped можно заменить на AddTransient, AddSingleton по необходимости

  var provider = services.BuildServiceProvider();

  // Создаём модель клиента
  var customer = new Customer { };
  // Получаем CustomerService из DI контейнера
  var customerService = provider.GetRequiredService<CustomerService>();
  // Валидируем модель клиента, получаем результат валидации
  var isValid = customerService.Validate(customer);
}

Automatic registration.

To automatically register validators, you need to “pull up” the package FluentValidation.DependencyInjectionExtensions. It provides 4 public extension methods:

Extension method: AddValidatorsFromAssemblyContaining<T>
Description: Register all validators that are in the assembly T type:

static void Main(string[] args)
{
  var services = new ServiceCollection();

  // Регистрируем все валидаторы, которые есть в сборке типа CustomerValidator
  // Указываем через параметры типа
  services.AddValidatorsFromAssemblyContaining<CustomerValidator>();

  var provider = services.BuildServiceProvider();
  // ...
}

Extension method: AddValidatorsFromAssemblyContaining(Type)
Description:
Register all validators that are in the assembly Type type:

static void Main(string[] args)
{
  var services = new ServiceCollection();

  // Регистрируем все валидаторы, которые есть в сборке типа CustomerValidator
  // Работает аналогично примеру выше, только указываем через объект типа Type, 
  // а не через параметры типа
  services.AddValidatorsFromAssemblyContaining(typeof(CustomerValidator));

  var provider = services.BuildServiceProvider();
  // ...
}

Extension method: AddValidatorsFromAssembly(Assembly)
Description:
Register all validators that are in the specified assembly Assembly:

static void Main(string[] args)
{
  var services = new ServiceCollection();

  // Регистрируем все валидаторы, которые есть в сборке
  // под названием FluentValidationTests 
  services.AddValidatorsFromAssembly(Assembly.Load("FluentValidationTests"));

  var provider = services.BuildServiceProvider();
  // ...
}

Extension method: AddValidatorsFromAssemblies(IEnumerable<Assembly)
Description: Register all validators that exist in the specified assemblies Assembly:

static void Main(string[] args)
{
  var services = new ServiceCollection();

  var assemblies = new[]
  {
    "FluentValidationTests",
    "SomeAnotherAssembly"
  }.Select(Assembly.Load);

  // Регистрируем все валидаторы, которые есть в сборках под названиями:
  // FluentValidationTests и SomeAnotherAssembly
  services.AddValidatorsFromAssemblies(assemblies);

  var provider = services.BuildServiceProvider();
  // ...
}

Life cycle of validators during automatic registration.

You can optionally customize the lifecycle of validators using the parameter lifetime and enum values ServiceLifetime (default is ServiceLifetime.Scoped):

static void Main(string[] args)
{
  var services = new ServiceCollection();

  // Указываем для всех валидаторов жизненный цикл Scoped (новый объект на указанную область)
  services.AddValidatorsFromAssemblyContaining<CustomerValidator>(lifetime: ServiceLifetime.Scoped);

  var provider = services.BuildServiceProvider();
  // ...
}

Filtering validators during automatic registration.

You can optionally specify a predicate that will be used to check whether the next validator should be registered in the DI container or not. The example code below shows how to exclude validators AddressValidator And OrderValidatorwhile registering all the others:

static void Main(string[] args)
{
  var services = new ServiceCollection();

  var excludeValidators = new[]
  {
      typeof(AddressValidator),
      typeof(OrderValidator),
  };
  // Все валидаторы будут зарегистрированы, кроме тех, которые есть в массиве excludeValidators
  services.AddValidatorsFromAssemblyContaining<CustomerValidator>(filter: filter => !excludeValidators.Contains(filter.ValidatorType));

  var provider = services.BuildServiceProvider();
  // ...
}

The same example, only we exclude it through the interface IValidator<T>:

// ...

public class OrderValidator : AbstractValidator<Order> { ... }

static void Main(string[] args)
{
  var services = new ServiceCollection();

  var excludeValidators = new[]
  {
      typeof(IValidator<Address>),
      typeof(IValidator<Order>),
  };
  // Все валидаторы будут зарегистрированы, кроме тех, которые есть в массиве excludeValidators
  services.AddValidatorsFromAssemblyContaining<CustomerValidator>(filter: filter => !excludeValidators.Contains(filter.InterfaceType));

  var provider = services.BuildServiceProvider();
  // ...
}

Registration of internal validators during automatic registration.

You can optionally specify whether validators that have an access modifier specified should be registered internalthrough the parameter includeInternalTypes:

static void Main(string[] args)
{
  var services = new ServiceCollection();

  // Регистрируем все валидаторы, включая определённые с модификатором доступа
  // internal
  services.AddValidatorsFromAssemblyContaining<CustomerValidator>(includeInternalTypes: true);

  var provider = services.BuildServiceProvider();
  // ...
}

Do other extension methods have the same parameters?

Yes, I have. Each extension method above has optional parameters lifetime, filter, includeInternalTypes:

lifetime default is ServiceLifetime.Scoped

filter default is null

includeInternalTypes default is false

← Previous part

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *