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
2.1 KiB
C#
57 lines
2.1 KiB
C#
using AutoMapper;
|
|
using DocumentOperator.Application.Common.DTOs;
|
|
using DocumentOperator.Application.Common.Interfaces;
|
|
using MediatR;
|
|
|
|
namespace DocumentOperator.Application.SwissQrCode.Queries;
|
|
|
|
/// <summary>
|
|
/// Query for extracting Swiss QR Code from PDF (supports both byte array and Base64 input)
|
|
/// </summary>
|
|
public record ExtractSwissQrCodeQuery : IRequest<SwissQrCodeExtractionResult>
|
|
{
|
|
/// <summary>
|
|
/// PDF as byte array (direct upload)
|
|
/// </summary>
|
|
public byte[]? PdfBytes { get; init; }
|
|
|
|
/// <summary>
|
|
/// PDF as Base64 string (API clients)
|
|
/// </summary>
|
|
public string? Base64Pdf { get; init; }
|
|
|
|
/// <summary>
|
|
/// Optional reference strings (passed through to response for external tracking)
|
|
/// </summary>
|
|
public IReadOnlyList<string>? References { get; init; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for ExtractSwissQrCodeQuery
|
|
/// Orchestrates Swiss QR Code extraction using ISwissQrCodeProcessor and AutoMapper
|
|
/// </summary>
|
|
public class ExtractSwissQrCodeQueryHandler(ISwissQrCodeProcessor qrCodeProcessor, IMapper mapper)
|
|
: IRequestHandler<ExtractSwissQrCodeQuery, SwissQrCodeExtractionResult>
|
|
{
|
|
/// <summary>
|
|
/// Extracts and parses Swiss QR Code from the last page of the PDF
|
|
/// </summary>
|
|
public async Task<SwissQrCodeExtractionResult> Handle(ExtractSwissQrCodeQuery request, CancellationToken cancellationToken)
|
|
{
|
|
// Use byte[] if available, otherwise convert Base64
|
|
byte[] pdfBytes = request.PdfBytes ?? Convert.FromBase64String(request.Base64Pdf!);
|
|
|
|
// Extract and parse Swiss QR Code from last page (can throw PdfProcessingException or QrCodeNotFoundException)
|
|
var qrCodeData = await qrCodeProcessor.ExtractSwissQrCodeAsync(pdfBytes, cancellationToken);
|
|
|
|
// Map domain value object to DTO using AutoMapper
|
|
var qrCodeDto = mapper.Map<SwissQrCodeDataDto>(qrCodeData);
|
|
|
|
// Return references (passed through) + QR code data
|
|
return new SwissQrCodeExtractionResult(
|
|
References: request.References ?? [],
|
|
QrCodeData: qrCodeDto
|
|
);
|
|
}
|
|
}
|