Hard📖Теория2 min

Шаг 12: Pipeline Behaviors и регистрация DI

Создание ValidationBehavior и LoggingBehavior для MediatR Pipeline, регистрация Application Layer через DependencyInjection.cs

Шаг 12: Pipeline Behaviors и DI

Pipeline Behavior -- «станция» на конвейере, через которую проходит каждая команда перед Handler. Типичные behaviors: валидация и логирование.

Command -> [Logging] -> [Validation] -> Handler -> Response

ValidationBehavior.cs

В папке Common/Behaviors:

using FluentValidation;
using MediatR;

namespace OrderManagement.Application.Common.Behaviors;

public class ValidationBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public ValidationBehavior(
        IEnumerable<IValidator<TRequest>> validators)
        => _validators = validators;

    public async Task<TResponse> Handle(TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken ct)
    {
        if (!_validators.Any()) return await next();

        var context = new ValidationContext<TRequest>(request);
        var results = await Task.WhenAll(
            _validators.Select(v => v.ValidateAsync(context, ct)));

        var failures = results
            .SelectMany(r => r.Errors)
            .Where(f => f != null).ToList();

        if (failures.Any())
            throw new ValidationException(failures);

        return await next();
    }
}

next() вызывает следующий Behavior или Handler. Если валидация не прошла -- ValidationException и до Handler не дойдёт.

LoggingBehavior.cs

using System.Diagnostics;
using MediatR;
using Microsoft.Extensions.Logging;

namespace OrderManagement.Application.Common.Behaviors;

public class LoggingBehavior<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;

    public LoggingBehavior(
        ILogger<LoggingBehavior<TRequest, TResponse>> logger)
        => _logger = logger;

    public async Task<TResponse> Handle(TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken ct)
    {
        var name = typeof(TRequest).Name;
        _logger.LogInformation("Handling {Request}", name);

        var sw = Stopwatch.StartNew();
        var response = await next();
        sw.Stop();

        _logger.LogInformation(
            "Handled {Request} in {Ms}ms", name, sw.ElapsedMilliseconds);
        return response;
    }
}

DependencyInjection.cs

В корне OrderManagement.Application:

using FluentValidation;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using OrderManagement.Application.Common.Behaviors;

namespace OrderManagement.Application;

public static class DependencyInjection
{
    public static IServiceCollection AddApplication(
        this IServiceCollection services)
    {
        var assembly = typeof(DependencyInjection).Assembly;

        services.AddMediatR(cfg =>
            cfg.RegisterServicesFromAssembly(assembly));
        services.AddValidatorsFromAssembly(assembly);

        services.AddTransient(typeof(IPipelineBehavior<,>),
            typeof(LoggingBehavior<,>));
        services.AddTransient(typeof(IPipelineBehavior<,>),
            typeof(ValidationBehavior<,>));

        return services;
    }
}

typeof(IPipelineBehavior<,>) -- open generic: «для любых TRequest, TResponse». Порядок регистрации определяет порядок выполнения.

Проверь себя

🧪

Что произойдёт, если для команды нет зарегистрированного Validator?

🧪

Как extension method AddApplication вызывается в Program.cs?

🧪

Что такое open generic typeof(IPipelineBehavior<,>)?