Vertical slice architecture:
- Move Features/Documents/{UseCase}/ to {UseCase}/Queries/
- Query + Handler in SAME file (co-located)
- Validator in separate file (single responsibility)
New structure:
- ValidatePdf/Queries/ValidatePdfQuery.cs (Query + Handler)
- ValidatePdf/Queries/ValidatePdfQueryValidator.cs
- SwissQrCode/Queries/ExtractSwissQrCodeQuery.cs (Query + Handler)
- SwissQrCode/Queries/ExtractSwissQrCodeQueryValidator.cs
AutoMapper integration:
- Add Common/Mapping/MappingProfile.cs
- Map PdfMetadata -> PdfValidationResult (domain -> DTO)
- Map SwissQrCodeData -> SwissQrCodeExtractionResult (domain -> DTO)
- Controllers now thin: pass request to MediatR, AutoMapper handles mapping
DTO improvements:
- Rename: ValidatePdfResponse -> PdfValidationResult (business-friendly)
- Rename: ExtractSwissQrCodeResponse -> SwissQrCodeExtractionResult
- Support BOTH byte[] and Base64Pdf string (XOR validation)
- Use modern C# 12 collection expressions
Code quality:
- Use PascalCase for primary constructor parameters
- Fix LoggingBehavior logging format
Deleted old structure:
- Features/Documents/ValidatePdf/ (old horizontal structure)
- Features/Documents/ExtractSwissQrCode/ (old horizontal structure)
- Common/DTOs/{Request|Response} (replaced with {Result})
Result: Vertical slices, AutoMapper v16.2.0, thin controllers
57 lines
1.6 KiB
C#
57 lines
1.6 KiB
C#
using MediatR;
|
|
using Microsoft.Extensions.Logging;
|
|
using System.Diagnostics;
|
|
|
|
namespace DocumentOperator.Application.Common.Behaviors;
|
|
|
|
/// <summary>
|
|
/// MediatR Pipeline Behavior that logs requests and tracks performance
|
|
/// Executes AFTER ValidationBehavior, BEFORE Handler
|
|
/// </summary>
|
|
public class LoggingBehavior<TRequest, TResponse>(ILogger<LoggingBehavior<TRequest, TResponse>> Logger) : IPipelineBehavior<TRequest, TResponse>
|
|
where TRequest : IRequest<TResponse>
|
|
{
|
|
public async Task<TResponse> Handle(
|
|
TRequest request,
|
|
RequestHandlerDelegate<TResponse> next,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var requestName = typeof(TRequest).Name;
|
|
|
|
// Performance Tracking
|
|
var stopwatch = Stopwatch.StartNew();
|
|
|
|
try
|
|
{
|
|
// Handler ausführen
|
|
var response = await next(cancellationToken);
|
|
|
|
stopwatch.Stop();
|
|
|
|
// Request Success
|
|
Logger.LogInformation(
|
|
"Handled {RequestName} in {ElapsedMs}ms",
|
|
requestName,
|
|
stopwatch.ElapsedMilliseconds
|
|
);
|
|
|
|
return response;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
stopwatch.Stop();
|
|
|
|
// Request Failed
|
|
Logger.LogError(
|
|
ex,
|
|
"Error handling {RequestName} after {ElapsedMs}ms: {ErrorMessage}",
|
|
requestName,
|
|
stopwatch.ElapsedMilliseconds,
|
|
ex.Message
|
|
);
|
|
|
|
throw; // Exception weiterwerfen (wird von Exception Middleware gefangen)
|
|
}
|
|
}
|
|
}
|