Scaffold vertical-slice migration: Shared.Contracts, Shared.Application, MediatR

Begins the move to vertical-slice architecture. ResponseBody moves out of
API.Core into Shared.Contracts so slices won't have to depend on the API
host project for it. Shared.Application adds the MediatR pipeline's
ValidationBehavior plus the cross-slice email commands that Features.Auth
will send and Features.Emails will handle, keeping slices decoupled from
each other.
This commit is contained in:
Aaron Po
2026-06-19 23:23:10 -04:00
parent 4554fff534
commit 60a7b790d4
16 changed files with 115 additions and 8 deletions

View File

@@ -0,0 +1,44 @@
using FluentValidation;
using MediatR;
namespace Shared.Application.Behaviors;
/// <summary>
/// MediatR pipeline behavior that runs all registered FluentValidation validators for a
/// request before invoking its handler, short-circuiting with a <see cref="ValidationException"/>
/// when any validator reports failures.
/// </summary>
public class ValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken
)
{
if (!validators.Any())
{
return await next();
}
var context = new ValidationContext<TRequest>(request);
var failures = (
await Task.WhenAll(
validators.Select(v => v.ValidateAsync(context, cancellationToken))
)
)
.SelectMany(result => result.Errors)
.Where(failure => failure != null)
.ToList();
if (failures.Count > 0)
{
throw new ValidationException(failures);
}
return await next();
}
}