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
60 lines
1.7 KiB
C#
60 lines
1.7 KiB
C#
using DocumentOperator.Application.ValidatePdf.Queries;
|
|
using FluentValidation;
|
|
|
|
namespace DocumentOperator.Application.ValidatePdf.Queries;
|
|
|
|
/// <summary>
|
|
/// Validator for ValidatePdfQuery
|
|
/// Ensures exactly ONE input method is provided (either PdfBytes OR Base64Pdf, not both, not none)
|
|
/// </summary>
|
|
public class ValidatePdfQueryValidator : AbstractValidator<ValidatePdfQuery>
|
|
{
|
|
public ValidatePdfQueryValidator()
|
|
{
|
|
RuleFor(x => x)
|
|
.Must(HasExactlyOneInput)
|
|
.WithMessage("Either PdfBytes or Base64Pdf must be provided, but not both");
|
|
|
|
// Validate Base64 format if provided
|
|
When(x => !string.IsNullOrWhiteSpace(x.Base64Pdf), () =>
|
|
{
|
|
RuleFor(x => x.Base64Pdf)
|
|
.Must(BeValidBase64)
|
|
.WithMessage("Invalid Base64 format");
|
|
});
|
|
|
|
// Validate byte array if provided
|
|
When(x => x.PdfBytes != null, () =>
|
|
{
|
|
RuleFor(x => x.PdfBytes)
|
|
.NotEmpty()
|
|
.WithMessage("PdfBytes cannot be empty");
|
|
});
|
|
}
|
|
|
|
private static bool HasExactlyOneInput(ValidatePdfQuery request)
|
|
{
|
|
var hasPdfBytes = request.PdfBytes != null && request.PdfBytes.Length > 0;
|
|
var hasBase64 = !string.IsNullOrWhiteSpace(request.Base64Pdf);
|
|
|
|
// XOR: exactly one must be true
|
|
return hasPdfBytes ^ hasBase64;
|
|
}
|
|
|
|
private static bool BeValidBase64(string? base64)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(base64))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
Convert.FromBase64String(base64);
|
|
return true;
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|