Complete Step 1.1: ValidatePDF Application Layer
Updated PHASENPLAN and ROADMAP to reflect progress on Feature 1 - ValidatePDF (75% complete). Marked Step 1.1 as completed, including MediatR setup, pipeline behaviors (`ValidationBehavior`, `LoggingBehavior`), ValidatePDF feature (Query, Handler, Validator), and DTOs. Added `DependencyInjection.cs` for Application Layer DI configuration. Introduced `LoggingBehavior` and `ValidationBehavior` for MediatR pipelines. Implemented `ValidatePdfHandler`, `ValidatePdfQuery`, and `ValidatePdfValidator`. Created DTOs (`ValidatePdfRequest`, `ValidatePdfResponse`) for the ValidatePDF feature. Added unit tests for `ValidatePdfHandler` to verify metadata handling and exception propagation. Removed unused folder references in `DocumentOperator.Application.csproj`.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# ?? DocumentOperator - Phasenplan (Feature-Driven Development)
|
# ?? DocumentOperator - Phasenplan (Feature-Driven Development)
|
||||||
|
|
||||||
> **Stand:** 17.01.2025 | **Aktuell:** Feature 1 - ValidatePDF (Step 1.1 NEXT) | **Projektdauer:** 6 Wochen
|
> **Stand:** 17.01.2025 | **Aktuell:** Feature 1 - ValidatePDF (Step 1.2 NEXT) | **Projektdauer:** 6 Wochen
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
| Woche | Features / Concerns | Status | Fortschritt |
|
| Woche | Features / Concerns | Status | Fortschritt |
|
||||||
|-------|---------------------|--------|-------------|
|
|-------|---------------------|--------|-------------|
|
||||||
| **W1** | Feature 1: ValidatePDF | ?? In Progress | 50% (Infrastructure fertig, Application NEXT) |
|
| **W1** | Feature 1: ValidatePDF | ?? In Progress | 75% (Foundation + Application fertig, API NEXT) |
|
||||||
| **W2** | Feature 2: ExtractAttachments | ? Geplant | 0% |
|
| **W2** | Feature 2: ExtractAttachments | ? Geplant | 0% |
|
||||||
| **W2** | Feature 3: ApplyStamp | ? Geplant | 0% |
|
| **W2** | Feature 3: ApplyStamp | ? Geplant | 0% |
|
||||||
| **W3** | Feature 4: EmbedCertificate | ? Geplant | 0% |
|
| **W3** | Feature 4: EmbedCertificate | ? Geplant | 0% |
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
## ?? DETAILLIERTER PLAN
|
## ?? DETAILLIERTER PLAN
|
||||||
|
|
||||||
### WOCHE 1 - Feature 1: ValidatePDF | ?? In Progress - 50%
|
### WOCHE 1 - Feature 1: ValidatePDF | ?? In Progress - 75%
|
||||||
|
|
||||||
**Ziel:** POST /api/v1/documents/validate Endpoint im Swagger testbar
|
**Ziel:** POST /api/v1/documents/validate Endpoint im Swagger testbar
|
||||||
|
|
||||||
@@ -58,38 +58,38 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### ?? Step 1.1: Application Layer (MediatR Setup + ValidatePDF Feature) - **NEXT**
|
#### ? Step 1.1: Application Layer (MediatR Setup + ValidatePDF Feature) - **ABGESCHLOSSEN**
|
||||||
**Dauer:** ~4 Stunden
|
**Dauer:** ~4 Stunden
|
||||||
**Status:** ? NEXT
|
**Status:** ? ABGESCHLOSSEN
|
||||||
|
|
||||||
**Was wird erstellt:**
|
**Was wurde erstellt:**
|
||||||
1. **MediatR Setup**
|
1. **MediatR Setup**
|
||||||
- `Application/DependencyInjection.cs` (Service Registration)
|
- ? `Application/DependencyInjection.cs` (Service Registration)
|
||||||
- `Application/Common/Behaviors/ValidationBehavior.cs` (FluentValidation Pipeline)
|
- ? `Application/Common/Behaviors/ValidationBehavior.cs` (FluentValidation Pipeline)
|
||||||
- `Application/Common/Behaviors/LoggingBehavior.cs` (Logging Pipeline)
|
- ? `Application/Common/Behaviors/LoggingBehavior.cs` (Logging Pipeline mit ILogger<T>)
|
||||||
|
|
||||||
2. **ValidatePDF Feature (Vertical Slice)**
|
2. **ValidatePDF Feature (Vertical Slice)**
|
||||||
- `Application/Features/Documents/ValidatePdf/ValidatePdfQuery.cs`
|
- ? `Application/Features/Documents/ValidatePdf/ValidatePdfQuery.cs`
|
||||||
- `Application/Features/Documents/ValidatePdf/ValidatePdfHandler.cs`
|
- ? `Application/Features/Documents/ValidatePdf/ValidatePdfHandler.cs`
|
||||||
- `Application/Features/Documents/ValidatePdf/ValidatePdfValidator.cs`
|
- ? `Application/Features/Documents/ValidatePdf/ValidatePdfValidator.cs`
|
||||||
|
|
||||||
3. **DTOs**
|
3. **DTOs**
|
||||||
- `Application/Common/DTOs/ValidatePdfRequest.cs`
|
- ? `Application/Common/DTOs/ValidatePdfRequest.cs`
|
||||||
- `Application/Common/DTOs/ValidatePdfResponse.cs`
|
- ? `Application/Common/DTOs/ValidatePdfResponse.cs`
|
||||||
|
|
||||||
4. **Tests**
|
4. **Tests**
|
||||||
- `Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs`
|
- ? `Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs` (2 Tests - alle grün!)
|
||||||
|
|
||||||
**Akzeptanzkriterien:**
|
**Akzeptanzkriterien:**
|
||||||
- ? Build erfolgreich
|
- ? Build erfolgreich
|
||||||
- ? Tests grün (Handler Tests)
|
- ? Tests grün (Handler Tests: 2/2 passed)
|
||||||
- ? MediatR Pipeline funktioniert (Validation + Logging)
|
- ? MediatR Pipeline funktioniert (Validation + Logging)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### ? Step 1.2: API Layer (Endpoint + Exception Middleware)
|
#### ?? Step 1.2: API Layer (Endpoint + Exception Middleware) - **NEXT**
|
||||||
**Dauer:** ~3 Stunden
|
**Dauer:** ~3 Stunden
|
||||||
**Status:** ? Pending
|
**Status:** ? NEXT
|
||||||
|
|
||||||
**Was wird erstellt:**
|
**Was wird erstellt:**
|
||||||
1. **Exception Middleware**
|
1. **Exception Middleware**
|
||||||
@@ -418,7 +418,7 @@
|
|||||||
| Kategorie | Status | Fortschritt |
|
| Kategorie | Status | Fortschritt |
|
||||||
|-----------|--------|-------------|
|
|-----------|--------|-------------|
|
||||||
| **Foundation** | ? Abgeschlossen | 100% |
|
| **Foundation** | ? Abgeschlossen | 100% |
|
||||||
| **Feature 1** | ?? In Progress | 50% |
|
| **Feature 1** | ?? In Progress | 75% |
|
||||||
| **Feature 2-5** | ? Pending | 0% |
|
| **Feature 2-5** | ? Pending | 0% |
|
||||||
| **Multi-Tenancy** | ? Pending | 0% |
|
| **Multi-Tenancy** | ? Pending | 0% |
|
||||||
| **Cross-Cutting** | ? Pending | 0% |
|
| **Cross-Cutting** | ? Pending | 0% |
|
||||||
@@ -430,19 +430,19 @@
|
|||||||
|
|
||||||
### Heute (17.01.2025)
|
### Heute (17.01.2025)
|
||||||
|
|
||||||
**Feature 1 - Step 1.1: Application Layer**
|
**Feature 1 - Step 1.1: Application Layer** ? **ABGESCHLOSSEN**
|
||||||
1. ? MediatR Setup (DependencyInjection.cs)
|
1. ? MediatR Setup (DependencyInjection.cs)
|
||||||
2. ? ValidationBehavior.cs erstellen
|
2. ? ValidationBehavior.cs erstellen
|
||||||
3. ? LoggingBehavior.cs erstellen
|
3. ? LoggingBehavior.cs erstellen (mit ILogger<T>)
|
||||||
4. ? ValidatePDF Feature erstellen (Query, Handler, Validator)
|
4. ? ValidatePDF Feature erstellen (Query, Handler, Validator)
|
||||||
5. ? DTOs erstellen (Request, Response)
|
5. ? DTOs erstellen (Request, Response)
|
||||||
6. ? Tests schreiben (ValidatePdfHandlerTests.cs)
|
6. ? Tests schreiben (ValidatePdfHandlerTests.cs - 2 Tests grün)
|
||||||
7. ? Build + Tests grün
|
7. ? Build + Tests grün
|
||||||
8. ? ROADMAP + PHASENPLAN aktualisieren
|
8. ? ROADMAP + PHASENPLAN aktualisieren
|
||||||
9. ? Commit
|
9. ? Commit
|
||||||
|
|
||||||
**Danach:**
|
**Danach:**
|
||||||
? Feature 1 - Step 1.2: API Layer (Endpoint + Exception Middleware)
|
? Feature 1 - Step 1.2: API Layer (Endpoint + Exception Middleware) - **NEXT**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -455,6 +455,7 @@
|
|||||||
| 17.01.2025 | Infrastructure | DevExpressPdfProcessor.ValidateAsync implementiert |
|
| 17.01.2025 | Infrastructure | DevExpressPdfProcessor.ValidateAsync implementiert |
|
||||||
| 17.01.2025 | Tests | DevExpressPdfProcessorTests.cs erstellt (6 Tests) |
|
| 17.01.2025 | Tests | DevExpressPdfProcessorTests.cs erstellt (6 Tests) |
|
||||||
| 17.01.2025 | **PHASENPLAN** | ?? **Komplett umstrukturiert** (Feature-basiert + Datum korrigiert 23.06.2026 ? 17.01.2025) |
|
| 17.01.2025 | **PHASENPLAN** | ?? **Komplett umstrukturiert** (Feature-basiert + Datum korrigiert 23.06.2026 ? 17.01.2025) |
|
||||||
|
| 17.01.2025 | **Feature 1 - Step 1.1** | ? **ABGESCHLOSSEN** - Application Layer (MediatR, Behaviors, ValidatePDF Feature, DTOs, Tests - 2/2 grün) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# ?? DocumentOperator - Project Roadmap (Feature-Driven Development)
|
# ?? DocumentOperator - Project Roadmap (Feature-Driven Development)
|
||||||
|
|
||||||
> **Last Updated:** 17.01.2025 | **Status:** In Development | **Current Feature:** Feature 1 - ValidatePDF (NEXT)
|
> **Last Updated:** 17.01.2025 | **Status:** In Development | **Current Feature:** Feature 1 - ValidatePDF (Step 1.2 NEXT)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
| Feature | Type | Status | Swagger Testbar? |
|
| Feature | Type | Status | Swagger Testbar? |
|
||||||
|---------|------|--------|------------------|
|
|---------|------|--------|------------------|
|
||||||
| **1. ValidatePDF** | Synchron | ?? NEXT | ? |
|
| **1. ValidatePDF** | Synchron | ?? In Progress (Step 1.2 NEXT) | ? |
|
||||||
| **2. ExtractAttachments** | Synchron | ? Pending | ? |
|
| **2. ExtractAttachments** | Synchron | ? Pending | ? |
|
||||||
| **3. ApplyStamp** | Synchron | ? Pending | ? |
|
| **3. ApplyStamp** | Synchron | ? Pending | ? |
|
||||||
| **4. EmbedCertificate** | Synchron | ? Pending | ? |
|
| **4. EmbedCertificate** | Synchron | ? Pending | ? |
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ?? FEATURE 1: ValidatePDF (Synchron) - **NEXT**
|
## ?? FEATURE 1: ValidatePDF (Synchron) - **In Progress**
|
||||||
|
|
||||||
**Was macht dieses Feature?**
|
**Was macht dieses Feature?**
|
||||||
- Client sendet PDF als Base64 (JSON)
|
- Client sendet PDF als Base64 (JSON)
|
||||||
@@ -77,7 +77,7 @@ Response: { "pageCount": 5, "fileSizeBytes": 1024, "pdfVersion": "1.4", "hasAtta
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### ? Step 1.0: Foundation (bereits erledigt!)
|
### ? Step 1.0: Foundation (ABGESCHLOSSEN)
|
||||||
|
|
||||||
**Was wurde bereits erstellt:**
|
**Was wurde bereits erstellt:**
|
||||||
- ? Domain Layer (Exceptions, Enums, Value Objects)
|
- ? Domain Layer (Exceptions, Enums, Value Objects)
|
||||||
@@ -92,36 +92,36 @@ Response: { "pageCount": 5, "fileSizeBytes": 1024, "pdfVersion": "1.4", "hasAtta
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### ?? Step 1.1: Application Layer (MediatR Setup + ValidatePDF Feature) - **NEXT**
|
### ? Step 1.1: Application Layer (MediatR Setup + ValidatePDF Feature) - **ABGESCHLOSSEN**
|
||||||
|
|
||||||
**Ziel:** MediatR + FluentValidation + ValidatePDF Handler
|
**Ziel:** MediatR + FluentValidation + ValidatePDF Handler
|
||||||
|
|
||||||
**Was wird erstellt:**
|
**Was wurde erstellt:**
|
||||||
|
|
||||||
#### 1.1.1: MediatR Setup
|
#### 1.1.1: MediatR Setup
|
||||||
- **Datei:** `Application/DependencyInjection.cs`
|
- ? **Datei:** `Application/DependencyInjection.cs`
|
||||||
- Registriert MediatR
|
- Registriert MediatR
|
||||||
- Registriert FluentValidation
|
- Registriert FluentValidation
|
||||||
- Registriert Pipeline Behaviors (Validation + Logging)
|
- Registriert Pipeline Behaviors (Validation + Logging)
|
||||||
|
|
||||||
#### 1.1.2: Pipeline Behaviors
|
#### 1.1.2: Pipeline Behaviors
|
||||||
- **Datei:** `Application/Common/Behaviors/ValidationBehavior.cs`
|
- ? **Datei:** `Application/Common/Behaviors/ValidationBehavior.cs`
|
||||||
- Führt FluentValidation automatisch aus (vor jedem Handler)
|
- Führt FluentValidation automatisch aus (vor jedem Handler)
|
||||||
- Wirft `ValidationException` bei Fehler
|
- Wirft `ValidationException` bei Fehler
|
||||||
|
|
||||||
- **Datei:** `Application/Common/Behaviors/LoggingBehavior.cs`
|
- ? **Datei:** `Application/Common/Behaviors/LoggingBehavior.cs`
|
||||||
- Loggt jeden Request (mit Performance-Tracking)
|
- Loggt jeden Request (mit Performance-Tracking)
|
||||||
- Nutzt Serilog
|
- Nutzt `ILogger<T>` (Clean Architecture konform!)
|
||||||
|
|
||||||
#### 1.1.3: ValidatePDF Feature (Vertical Slice)
|
#### 1.1.3: ValidatePDF Feature (Vertical Slice)
|
||||||
- **Ordner:** `Application/Features/Documents/ValidatePdf/`
|
- ? **Ordner:** `Application/Features/Documents/ValidatePdf/`
|
||||||
|
|
||||||
- **Datei:** `ValidatePdfQuery.cs`
|
- ? **Datei:** `ValidatePdfQuery.cs`
|
||||||
```csharp
|
```csharp
|
||||||
public record ValidatePdfQuery(Base64String PdfContent) : IRequest<PdfMetadata>;
|
public record ValidatePdfQuery(Base64String PdfContent) : IRequest<PdfMetadata>;
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Datei:** `ValidatePdfHandler.cs`
|
- ? **Datei:** `ValidatePdfHandler.cs`
|
||||||
```csharp
|
```csharp
|
||||||
public class ValidatePdfHandler : IRequestHandler<ValidatePdfQuery, PdfMetadata>
|
public class ValidatePdfHandler : IRequestHandler<ValidatePdfQuery, PdfMetadata>
|
||||||
{
|
{
|
||||||
@@ -135,7 +135,7 @@ Response: { "pageCount": 5, "fileSizeBytes": 1024, "pdfVersion": "1.4", "hasAtta
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Datei:** `ValidatePdfValidator.cs`
|
- ? **Datei:** `ValidatePdfValidator.cs`
|
||||||
```csharp
|
```csharp
|
||||||
public class ValidatePdfValidator : AbstractValidator<ValidatePdfQuery>
|
public class ValidatePdfValidator : AbstractValidator<ValidatePdfQuery>
|
||||||
{
|
{
|
||||||
@@ -147,14 +147,14 @@ Response: { "pageCount": 5, "fileSizeBytes": 1024, "pdfVersion": "1.4", "hasAtta
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### 1.1.4: DTOs
|
#### 1.1.4: DTOs
|
||||||
- **Ordner:** `Application/Common/DTOs/`
|
- ? **Ordner:** `Application/Common/DTOs/`
|
||||||
|
|
||||||
- **Datei:** `ValidatePdfRequest.cs`
|
- ? **Datei:** `ValidatePdfRequest.cs`
|
||||||
```csharp
|
```csharp
|
||||||
public record ValidatePdfRequest(string Base64Pdf);
|
public record ValidatePdfRequest(string Base64Pdf);
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Datei:** `ValidatePdfResponse.cs`
|
- ? **Datei:** `ValidatePdfResponse.cs`
|
||||||
```csharp
|
```csharp
|
||||||
public record ValidatePdfResponse(
|
public record ValidatePdfResponse(
|
||||||
int PageCount,
|
int PageCount,
|
||||||
@@ -166,9 +166,9 @@ Response: { "pageCount": 5, "fileSizeBytes": 1024, "pdfVersion": "1.4", "hasAtta
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### 1.1.5: Tests
|
#### 1.1.5: Tests
|
||||||
- **Datei:** `Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs`
|
- ? **Datei:** `Tests/Unit/Application/Features/ValidatePdf/ValidatePdfHandlerTests.cs`
|
||||||
- Test: `Handle_ValidPdf_ReturnsPdfMetadata`
|
- ? Test: `Handle_ValidPdf_ReturnsPdfMetadata`
|
||||||
- Test: `Handle_NullPdfContent_ThrowsValidationException`
|
- ? Test: `Handle_PdfProcessorThrowsException_PropagatesException`
|
||||||
|
|
||||||
**Akzeptanzkriterien:**
|
**Akzeptanzkriterien:**
|
||||||
- ? Build erfolgreich
|
- ? Build erfolgreich
|
||||||
@@ -177,7 +177,7 @@ Response: { "pageCount": 5, "fileSizeBytes": 1024, "pdfVersion": "1.4", "hasAtta
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### ?? Step 1.2: API Layer (Endpoint + Exception Middleware)
|
### ?? Step 1.2: API Layer (Endpoint + Exception Middleware) - **NEXT**
|
||||||
|
|
||||||
**Ziel:** HTTP Endpoint + zentrale Exception Handling
|
**Ziel:** HTTP Endpoint + zentrale Exception Handling
|
||||||
|
|
||||||
@@ -898,10 +898,18 @@ DocumentOperator.Tests/
|
|||||||
- ? IPdfProcessor Interface
|
- ? IPdfProcessor Interface
|
||||||
- ? DevExpressPdfProcessor.ValidateAsync (mit Tests!)
|
- ? DevExpressPdfProcessor.ValidateAsync (mit Tests!)
|
||||||
|
|
||||||
|
- **Application Layer:**
|
||||||
|
- ? DependencyInjection.cs (MediatR + FluentValidation)
|
||||||
|
- ? ValidationBehavior.cs (FluentValidation Pipeline)
|
||||||
|
- ? LoggingBehavior.cs (ILogger<T> Pipeline)
|
||||||
|
- ? ValidatePDF Feature (Query, Handler, Validator)
|
||||||
|
- ? DTOs (ValidatePdfRequest, ValidatePdfResponse)
|
||||||
|
- ? Tests (ValidatePdfHandlerTests - 2 Tests grün)
|
||||||
|
|
||||||
### ?? In Progress
|
### ?? In Progress
|
||||||
|
|
||||||
- **Feature 1: ValidatePDF**
|
- **Feature 1: ValidatePDF**
|
||||||
- ? Step 1.1: Application Layer (MediatR Setup + ValidatePDF Feature) - **NEXT**
|
- ? Step 1.2: API Layer (Endpoint + Exception Middleware) - **NEXT**
|
||||||
|
|
||||||
### ? Pending
|
### ? Pending
|
||||||
|
|
||||||
@@ -995,6 +1003,7 @@ DocumentOperator.Tests/
|
|||||||
| 17.01.2025 | Tests | DevExpressPdfProcessorTests.cs erstellt (6 Tests) |
|
| 17.01.2025 | Tests | DevExpressPdfProcessorTests.cs erstellt (6 Tests) |
|
||||||
| 17.01.2025 | **ROADMAP** | ?? **Komplett umstrukturiert** (Feature-Driven Development!) |
|
| 17.01.2025 | **ROADMAP** | ?? **Komplett umstrukturiert** (Feature-Driven Development!) |
|
||||||
| 17.01.2025 | **PHASENPLAN** | ?? **Komplett umstrukturiert** (Feature-basiert + Datum korrigiert) |
|
| 17.01.2025 | **PHASENPLAN** | ?? **Komplett umstrukturiert** (Feature-basiert + Datum korrigiert) |
|
||||||
|
| 17.01.2025 | **Feature 1 - Step 1.1** | ? **ABGESCHLOSSEN** - Application Layer (MediatR, Behaviors, ValidatePDF Feature, DTOs, Tests) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
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> : IPipelineBehavior<TRequest, TResponse>
|
||||||
|
where TRequest : IRequest<TResponse>
|
||||||
|
{
|
||||||
|
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
|
||||||
|
|
||||||
|
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TResponse> Handle(
|
||||||
|
TRequest request,
|
||||||
|
RequestHandlerDelegate<TResponse> next,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var requestName = typeof(TRequest).Name;
|
||||||
|
|
||||||
|
// Request Start
|
||||||
|
_logger.LogInformation("Handling {RequestName}: {@Request}", requestName, request);
|
||||||
|
|
||||||
|
// Performance Tracking
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Handler ausführen
|
||||||
|
var response = await next();
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace DocumentOperator.Application.Common.Behaviors;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MediatR Pipeline Behavior that validates requests using FluentValidation
|
||||||
|
/// Executes BEFORE the Handler
|
||||||
|
/// </summary>
|
||||||
|
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
|
||||||
|
where TRequest : IRequest<TResponse>
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<IValidator<TRequest>> _validators;
|
||||||
|
|
||||||
|
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
|
||||||
|
{
|
||||||
|
_validators = validators;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TResponse> Handle(
|
||||||
|
TRequest request,
|
||||||
|
RequestHandlerDelegate<TResponse> next,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Wenn keine Validators registriert sind, direkt weiter zum Handler
|
||||||
|
if (!_validators.Any())
|
||||||
|
{
|
||||||
|
return await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation Context erstellen
|
||||||
|
var context = new ValidationContext<TRequest>(request);
|
||||||
|
|
||||||
|
// Alle Validators parallel ausführen
|
||||||
|
var validationResults = await Task.WhenAll(
|
||||||
|
_validators.Select(v => v.ValidateAsync(context, cancellationToken))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fehler sammeln
|
||||||
|
var failures = validationResults
|
||||||
|
.Where(r => !r.IsValid)
|
||||||
|
.SelectMany(r => r.Errors)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Bei Fehlern: ValidationException werfen (wird von Exception Middleware gefangen)
|
||||||
|
if (failures.Any())
|
||||||
|
{
|
||||||
|
throw new ValidationException(failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation erfolgreich ? weiter zum Handler
|
||||||
|
return await next();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace DocumentOperator.Application.Common.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request DTO for ValidatePdf endpoint
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Base64Pdf">PDF content as Base64 string</param>
|
||||||
|
public record ValidatePdfRequest(string Base64Pdf);
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace DocumentOperator.Application.Common.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Response DTO for ValidatePdf endpoint
|
||||||
|
/// Contains PDF metadata
|
||||||
|
/// </summary>
|
||||||
|
public record ValidatePdfResponse(
|
||||||
|
int PageCount,
|
||||||
|
long FileSizeBytes,
|
||||||
|
double FileSizeMB,
|
||||||
|
string PdfVersion,
|
||||||
|
bool HasAttachments,
|
||||||
|
int AttachmentCount
|
||||||
|
);
|
||||||
33
DocumentOperator.Application/DependencyInjection.cs
Normal file
33
DocumentOperator.Application/DependencyInjection.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace DocumentOperator.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dependency Injection configuration for Application Layer
|
||||||
|
/// </summary>
|
||||||
|
public static class DependencyInjection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers Application Layer services (MediatR, FluentValidation, Behaviors)
|
||||||
|
/// </summary>
|
||||||
|
public static IServiceCollection AddApplication(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
var assembly = typeof(DependencyInjection).Assembly;
|
||||||
|
|
||||||
|
// Register MediatR (scannt Assembly nach Handlers)
|
||||||
|
services.AddMediatR(config =>
|
||||||
|
{
|
||||||
|
config.RegisterServicesFromAssembly(assembly);
|
||||||
|
|
||||||
|
// Pipeline Behaviors (Reihenfolge wichtig!)
|
||||||
|
config.AddOpenBehavior(typeof(Common.Behaviors.ValidationBehavior<,>));
|
||||||
|
config.AddOpenBehavior(typeof(Common.Behaviors.LoggingBehavior<,>));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register FluentValidation (scannt Assembly nach Validators)
|
||||||
|
services.AddValidatorsFromAssembly(assembly);
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,15 +17,12 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Common\Behaviors\" />
|
|
||||||
<Folder Include="Common\DTOs\" />
|
|
||||||
<Folder Include="Common\Mappings\" />
|
<Folder Include="Common\Mappings\" />
|
||||||
<Folder Include="DependencyInjection\" />
|
<Folder Include="DependencyInjection\" />
|
||||||
<Folder Include="Features\Documents\ExtractAttachments\" />
|
<Folder Include="Features\Documents\ExtractAttachments\" />
|
||||||
<Folder Include="Features\Documents\ConcatenatePdfs\" />
|
<Folder Include="Features\Documents\ConcatenatePdfs\" />
|
||||||
<Folder Include="Features\Documents\ApplyStamp\" />
|
<Folder Include="Features\Documents\ApplyStamp\" />
|
||||||
<Folder Include="Features\Documents\EmbedCertificate\" />
|
<Folder Include="Features\Documents\EmbedCertificate\" />
|
||||||
<Folder Include="Features\Documents\ValidatePdf\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using DocumentOperator.Application.Common.Interfaces;
|
||||||
|
using DocumentOperator.Domain.Models.ValueObjects;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace DocumentOperator.Application.Features.Documents.ValidatePdf;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handler for ValidatePdfQuery
|
||||||
|
/// Orchestrates PDF validation using IPdfProcessor
|
||||||
|
/// </summary>
|
||||||
|
public class ValidatePdfHandler : IRequestHandler<ValidatePdfQuery, PdfMetadata>
|
||||||
|
{
|
||||||
|
private readonly IPdfProcessor _pdfProcessor;
|
||||||
|
|
||||||
|
public ValidatePdfHandler(IPdfProcessor pdfProcessor)
|
||||||
|
{
|
||||||
|
_pdfProcessor = pdfProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates PDF and returns metadata
|
||||||
|
/// </summary>
|
||||||
|
public async Task<PdfMetadata> Handle(ValidatePdfQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Value Object ? Byte Array
|
||||||
|
byte[] pdfBytes = request.PdfContent.ToByteArray();
|
||||||
|
|
||||||
|
// DevExpress Service aufrufen (kann PdfProcessingException werfen)
|
||||||
|
var metadata = await _pdfProcessor.ValidateAsync(pdfBytes);
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using DocumentOperator.Domain.Models.ValueObjects;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace DocumentOperator.Application.Features.Documents.ValidatePdf;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query to validate a PDF document and return metadata
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="PdfContent">PDF content as Base64 string (validated by Value Object)</param>
|
||||||
|
public record ValidatePdfQuery(Base64String PdfContent) : IRequest<PdfMetadata>;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace DocumentOperator.Application.Features.Documents.ValidatePdf;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validator for ValidatePdfQuery
|
||||||
|
/// Validates that PdfContent is not null (Base64String already validates format in its constructor)
|
||||||
|
/// </summary>
|
||||||
|
public class ValidatePdfValidator : AbstractValidator<ValidatePdfQuery>
|
||||||
|
{
|
||||||
|
public ValidatePdfValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.PdfContent)
|
||||||
|
.NotNull()
|
||||||
|
.WithMessage("PDF content is required");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using DocumentOperator.Application.Common.Interfaces;
|
||||||
|
using DocumentOperator.Application.Features.Documents.ValidatePdf;
|
||||||
|
using DocumentOperator.Domain.Common.Exceptions;
|
||||||
|
using DocumentOperator.Domain.Models.ValueObjects;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace DocumentOperator.Tests.Unit.Application.Features.ValidatePdf;
|
||||||
|
|
||||||
|
public class ValidatePdfHandlerTests
|
||||||
|
{
|
||||||
|
private readonly Mock<IPdfProcessor> _mockPdfProcessor;
|
||||||
|
private readonly ValidatePdfHandler _handler;
|
||||||
|
|
||||||
|
public ValidatePdfHandlerTests()
|
||||||
|
{
|
||||||
|
_mockPdfProcessor = new Mock<IPdfProcessor>();
|
||||||
|
_handler = new ValidatePdfHandler(_mockPdfProcessor.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Handle_ValidPdf_ReturnsPdfMetadata()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var base64Pdf = Convert.ToBase64String(new byte[] { 0x25, 0x50, 0x44, 0x46 }); // "%PDF"
|
||||||
|
var pdfContent = Base64String.Create(base64Pdf);
|
||||||
|
var query = new ValidatePdfQuery(pdfContent);
|
||||||
|
|
||||||
|
var expectedMetadata = new PdfMetadata(
|
||||||
|
pageCount: 5,
|
||||||
|
fileSizeBytes: 1024,
|
||||||
|
pdfVersion: "1.4",
|
||||||
|
hasAttachments: false,
|
||||||
|
attachmentCount: 0
|
||||||
|
);
|
||||||
|
|
||||||
|
_mockPdfProcessor
|
||||||
|
.Setup(x => x.ValidateAsync(It.IsAny<byte[]>()))
|
||||||
|
.ReturnsAsync(expectedMetadata);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _handler.Handle(query, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result.PageCount.Should().Be(5);
|
||||||
|
result.FileSizeBytes.Should().Be(1024);
|
||||||
|
result.PdfVersion.Should().Be("1.4");
|
||||||
|
result.HasAttachments.Should().BeFalse();
|
||||||
|
result.AttachmentCount.Should().Be(0);
|
||||||
|
|
||||||
|
_mockPdfProcessor.Verify(
|
||||||
|
x => x.ValidateAsync(It.IsAny<byte[]>()),
|
||||||
|
Times.Once
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Handle_PdfProcessorThrowsException_PropagatesException()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var base64Pdf = Convert.ToBase64String(new byte[] { 0x25, 0x50, 0x44, 0x46 });
|
||||||
|
var pdfContent = Base64String.Create(base64Pdf);
|
||||||
|
var query = new ValidatePdfQuery(pdfContent);
|
||||||
|
|
||||||
|
_mockPdfProcessor
|
||||||
|
.Setup(x => x.ValidateAsync(It.IsAny<byte[]>()))
|
||||||
|
.ThrowsAsync(new PdfProcessingException("Invalid PDF format"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Func<Task> act = async () => await _handler.Handle(query, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await act.Should().ThrowAsync<PdfProcessingException>()
|
||||||
|
.WithMessage("Invalid PDF format");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user