mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-07-16 17:47:22 +00:00
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:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Shared.Application.Emails;
|
||||
|
||||
/// <summary>
|
||||
/// Cross-slice command sent by Features.Auth and handled by Features.Emails to dispatch a
|
||||
/// registration confirmation email, without either slice taking a project reference on the other.
|
||||
/// </summary>
|
||||
public record SendRegistrationEmailCommand(
|
||||
string FirstName,
|
||||
string Email,
|
||||
string ConfirmationToken
|
||||
) : IRequest;
|
||||
@@ -0,0 +1,13 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Shared.Application.Emails;
|
||||
|
||||
/// <summary>
|
||||
/// Cross-slice command sent by Features.Auth and handled by Features.Emails to dispatch a
|
||||
/// fresh confirmation-link email, without either slice taking a project reference on the other.
|
||||
/// </summary>
|
||||
public record SendResendConfirmationEmailCommand(
|
||||
string FirstName,
|
||||
string Email,
|
||||
string ConfirmationToken
|
||||
) : IRequest;
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Shared.Application</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||
<PackageReference Include="FluentValidation" Version="11.3.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
29
web/backend/Shared/Shared.Contracts/ResponseBody.cs
Normal file
29
web/backend/Shared/Shared.Contracts/ResponseBody.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace Shared.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Generic envelope used to wrap API responses that carry a data payload alongside a human-readable message.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the data payload returned in the response.</typeparam>
|
||||
public record ResponseBody<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// A human-readable message describing the outcome of the request.
|
||||
/// </summary>
|
||||
public required string Message { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The data payload associated with the response.
|
||||
/// </summary>
|
||||
public required T Payload { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Envelope used to wrap API responses that carry only a human-readable message and no data payload.
|
||||
/// </summary>
|
||||
public record ResponseBody
|
||||
{
|
||||
/// <summary>
|
||||
/// A human-readable message describing the outcome of the request.
|
||||
/// </summary>
|
||||
public required string Message { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Shared.Contracts</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user