Шаг 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». Порядок регистрации определяет порядок выполнения.