Projekt: TransferX.Api
Basis Dokumente: Read Me, Clean Architecture, Coding Standards and Best Practices und TransferX Architektur
Rolle in der Architektur: Presentation Layer – REST-API für externe Systeme, Automatisierung und Integrationen. Kommuniziert ausschliesslich über Application Features (CQS-Muster) mit der Business-Logik. Enthält keine Geschäftslogik.
Projektstruktur
TransferX.Api
│ appsettings.json
│ appsettings.Development.json
│ Program.cs
│ TransferX.Api.csproj
│
├───Controllers
│ ProvidersController.cs
│ TransferConfigsController.cs
│ TransfersController.cs
│
├───Extensions
│ ResultExtensions.cs
│
└───Hubs
ProgressHub.cs
Abhängigkeiten
graph TD
API["TransferX.Api (Composition Root, Controller)"]
App["TransferX.Application (Features, IProviderEngine, ITransferEngine, DTOs)"]
Core["TransferX.Core (ProviderEngine, TransferEngine, DI-Extensions)"]
Infra["TransferX.Infrastructure (Repositories, SignalR, Security, DI-Extensions)"]
API -->|"referenziert (Use Cases aufrufen)"| App
API -->|"referenziert (DI registrieren)"| Core
API -->|"referenziert (DI registrieren)"| Infra
style API fill:#ffff4,stroke:#f1f12d,stroke-width:2px
style App fill:#fff9c4,stroke:#fbc02d,stroke-width:2px
style Core fill:#e1f5fe,stroke:#01579b,stroke-width:2px
style Infra fill:#ffe0b2,stroke:#e65100,stroke-width:2px
| Paket / Projekt | Version | Zweck |
|---|---|---|
TransferX.Application |
– | Features (Commands/Queries), Interfaces, Models |
TransferX.Core |
– | Engine-Implementierungen (ProviderEngine, TransferEngine) |
TransferX.Infrastructure |
– | Repositories, SignalR Reporter, Security |
Microsoft.AspNetCore.OpenApi |
8.0.23 | OpenAPI-Unterstützung |
Microsoft.AspNetCore.SignalR |
1.2.9 | Echtzeit-Fortschrittsmeldungen via WebSocket |
Swashbuckle.AspNetCore |
10.1.3 | Swagger UI und OpenAPI-Dokumentation |
Konfiguration (appsettings)
Die API liest ihre Konfiguration aus appsettings.json bzw. appsettings.Development.json:
| Schlüssel | Beschreibung | Beispielwert (Development) |
|---|---|---|
TransferX:DataDir |
Verzeichnis für persistierte Daten | C:\Data\Projects\TransferX\Data |
TransferX:ProviderPluginDir |
Verzeichnis für Provider-Plugins (.dll) | C:\Data\Projects\TransferX\Plugins\Providers |
TransferX:TransferPluginDir |
Verzeichnis für Transfer-Plugins (.dll) | C:\Data\Projects\TransferX\Plugins\Transfers |
TransferX:Security:AesKey |
AES-Schlüssel (Base64, 32 Byte) für Credentials | (Base64-Wert) |
TransferX:Security:AesIv |
AES-IV (Base64, 16 Byte) für Credentials | (Base64-Wert) |
Sicherheitshinweis:
AesKeyundAesIvmüssen in Produktionsumgebungen durch sichere Secret-Management-Lösungen (z.B. Azure Key Vault, User Secrets) ersetzt werden. Die Platzhalterwerte inappsettings.jsondürfen nicht in Produktion verwendet werden.
Composition Root (Program.cs)
Program.cs ist der einzige Ort, an dem Abhängigkeiten registriert werden (Composition Root). Alle ICommandHandler<,> und IQueryHandler<,> werden explizit per AddScoped registriert.
graph TD
PR["Program.cs (Composition Root)"]
Infra["AddTransferXInfrastructure"]
Core["AddTransferXCore"]
AppCmds["Application Commands / Queries (Scoped)"]
SignalR["SignalR"]
Swagger["Swagger / OpenAPI"]
MW["Middleware Pipeline"]
PR --> Infra
PR --> Core
PR --> AppCmds
PR --> SignalR
PR --> Swagger
PR --> MW
style PR fill:#f9f,stroke:#333,stroke-width:2px
style Infra fill:#ffe0b2,stroke:#e65100
style Core fill:#e1f5fe,stroke:#01579b
style AppCmds fill:#fff9c4,stroke:#fbc02d
Middleware-Pipeline
| Middleware | Bedingung | Beschreibung |
|---|---|---|
UseSwagger |
Development only | Swagger JSON-Endpoint |
UseSwaggerUI |
Development only | Swagger-Oberfläche unter /swagger |
UseHttpsRedirection |
immer | Umleitung auf HTTPS |
UseAuthorization |
immer | Autorisierungs-Middleware |
MapControllers |
immer | Routet HTTP-Anfragen an Controller |
MapHub<ProgressHub> |
immer | SignalR-Hub unter /hubs/progress |
Controllers
Alle Controller erben von ControllerBase und sind mit [ApiController] und [Route(...)] dekoriert. Sie delegieren vollständig an Application-Features – keine Geschäftslogik im Controller.
ProvidersController – api/providers
Verwaltet registrierte Provider-Konfigurationen und Provider-Operationen.
| Methode | Route | HTTP | Rückgabe | Beschreibung |
|---|---|---|---|---|
GetAll |
api/providers |
GET | 200 IReadOnlyList<ProviderDto> |
Alle registrierten Provider |
GetById |
api/providers/{id} |
GET | 200 ProviderDto / 404 |
Details eines einzelnen Providers |
GetPlugins |
api/providers/plugins |
GET | 200 IReadOnlyList<ProviderPluginDto> |
Verfügbare Provider-Plugins im Plugin-Verzeichnis |
Browse |
api/providers/{id}/browse |
GET | 200 BrowseResultDto / 400 |
Ordner und Dateien unter einem Pfad auflisten |
Register |
api/providers |
POST | 200 Guid / 400 |
Neuen Provider registrieren |
Update |
api/providers/{id} |
PUT | 204 / 400 |
Konfiguration und Zugangsdaten aktualisieren |
Delete |
api/providers/{id} |
DELETE | 204 / 400 |
Provider löschen |
TestConnection |
api/providers/{id}/test |
POST | 204 / 400 |
Verbindung zum Provider testen |
Request-Records:
| Record | Properties | Beschreibung |
|---|---|---|
UpdateProviderConfigRequest |
Name, Username, Password |
Body für Update |
TransferConfigsController – api/transfer-configs
Verwaltet gespeicherte Transfer-Konfigurationen (wiederverwendbare Quell/Ziel-Definitionen).
| Methode | Route | HTTP | Rückgabe | Beschreibung |
|---|---|---|---|---|
GetAll |
api/transfer-configs |
GET | 200 IReadOnlyList<TransferConfigDto> |
Alle Transfer-Konfigurationen |
Save |
api/transfer-configs |
POST | 200 Guid / 400 |
Neue Konfiguration erstellen |
Rename |
api/transfer-configs/{id}/rename |
PATCH | 204 / 400 |
Konfiguration umbenennen |
Delete |
api/transfer-configs/{id} |
DELETE | 204 / 400 |
Konfiguration löschen |
Request-Records:
| Record | Properties | Beschreibung |
|---|---|---|
SaveTransferConfigRequest |
Name, SourceProviderId, SourcePath, TargetProviderId, TargetPath, Operation |
Body für Save |
RenameTransferConfigRequest |
NewName |
Body für Rename |
TransfersController – api/transfers
Steuert Transfer-Ausführung, Status und Fortschrittsabfragen.
| Methode | Route | HTTP | Rückgabe | Beschreibung |
|---|---|---|---|---|
GetAll |
api/transfers |
GET | 200 IReadOnlyList<TransferListDto> |
Alle gespeicherten Transfers |
GetStatus |
api/transfers/{id}/status |
GET | 200 TransferStatusDto / 404 |
Aktueller Status eines Transfers |
GetProgress |
api/transfers/{id}/progress |
GET | 200 TransferProgressDto / 404 |
Aktueller Fortschritt eines laufenden Transfers |
Start |
api/transfers/start |
POST | 200 Guid / 400 |
Transfer direkt starten (Fire-and-Forget) |
StartConfig |
api/transfers/start-config/{configId} |
POST | 200 Guid / 404 / 400 |
Transfer anhand einer gespeicherten Konfiguration starten |
Cancel |
api/transfers/{id}/cancel |
POST | 204 / 400 |
Laufenden oder ausstehenden Transfer abbrechen |
Request-Records:
| Record | Properties | Beschreibung |
|---|---|---|
StartTransferRequest |
SourceProviderId, SourcePath, TargetProviderId, TargetPath, Operation |
Body für Start |
Extensions
ResultExtensions
Erweiterungsmethoden zur Umwandlung von Application-Result-Objekten in HTTP-Antworten. Zentralisieren die Mapping-Logik und vermeiden Wiederholungen in Controllers.
| Methode | Eingabe | Erfolg | Fehler |
|---|---|---|---|
ToActionResult<TValue> |
Result<TValue> |
200 OK |
400 BadRequest |
ToActionResult |
OperationResult |
204 NoContent |
400 BadRequest |
ToNotFoundActionResult<TValue> |
Result<TValue> |
200 OK |
404 NotFound |
graph LR
R1["Result<T>.IsSuccess"]
R2["Result<T>.IsFailure"]
R3["OperationResult.IsSuccess"]
R4["OperationResult.IsFailure"]
R1 -->|"ToActionResult"| OK["200 OK + Value"]
R2 -->|"ToActionResult"| BR["400 BadRequest + error"]
R1 -->|"ToNotFoundActionResult"| OK2["200 OK + Value"]
R2 -->|"ToNotFoundActionResult"| NF["404 NotFound + error"]
R3 -->|"ToActionResult"| NC["204 NoContent"]
R4 -->|"ToActionResult"| BR2["400 BadRequest + error"]
SignalR – ProgressHub
Der ProgressHub (/hubs/progress) ermöglicht Echtzeit-Fortschrittsmeldungen für laufende Transfers.
Client-Methoden (aufrufbar vom Client)
| Methode | Parameter | Beschreibung |
|---|---|---|
SubscribeToTransfer |
Guid transferId |
Client abonniert Fortschritt für einen bestimmten Transfer |
UnsubscribeFromTransfer |
Guid transferId |
Client kündigt Abonnement für einen Transfer |
Server-Events (vom Server an Client gesendet)
| Event | Beschreibung |
|---|---|
ProgressUpdate |
Fortschrittsmeldung für einen Transfer (via IHubContext) |
Ablauf
sequenceDiagram
participant C as SignalR Client
participant H as ProgressHub
participant E as TransferEngine
participant R as IProgressReporter
C->>H: Verbinden (/hubs/progress)
C->>H: SubscribeToTransfer(transferId)
H->>H: AddToGroup(connectionId, transferId)
Note over E,R: Transfer läuft im Hintergrund
E->>R: ReportAsync(progress)
R->>C: ProgressUpdate (Push via Gruppe)
C->>H: UnsubscribeFromTransfer(transferId)
H->>H: RemoveFromGroup(connectionId, transferId)
Swagger / OpenAPI
Swagger ist im Development-Modus aktiv (/swagger). Die XML-Dokumentation (/// summary, /// param etc.) wird über IncludeXmlComments eingebunden.
| Einstellung | Wert |
|---|---|
| Swagger-Titel | TransferX API |
| Version | v1 |
| XML-Kommentare | aus Assembly-XML-Datei |
| Erreichbar (Development) | /swagger |
XML-Dokumentation wird automatisch beim Build erzeugt (
<GenerateDocumentationFile>true</GenerateDocumentationFile>).
Tests (TransferX.Api.Tests)
Das Testprojekt TransferX.Api.Tests enthält Unit Tests für Controllers und Extensions.
Teststruktur
TransferX.Api.Tests
├───Controllers
│ ProvidersControllerTests.cs
│ TransferConfigsControllerTests.cs
│ TransfersControllerTests.cs
│
├───Extensions
│ ResultExtensionsTests.cs
│
└───Fakes
FakeCommandHandler.cs
FakeQueryHandler.cs
Test-Strategie
Controller werden direkt instanziiert (kein HTTP-Stack, kein DI-Container). ICommandHandler und IQueryHandler werden durch generische Fakes ersetzt:
| Fake-Klasse | Implementiert | Beschreibung |
|---|---|---|
FakeCommandHandler<TCommand, TResult> |
ICommandHandler<TCommand, TResult> |
Gibt konfigurierbares Result zurück, zeichnet empfangenen Command auf |
FakeQueryHandler<TQuery, TResult> |
IQueryHandler<TQuery, TResult> |
Gibt konfigurierbares Result zurück, zeichnet empfangene Query auf |
Test-Abdeckung
| Testklasse | Getestete Szenarien |
|---|---|
ProvidersControllerTests |
GetAll, GetById (gefunden/nicht gefunden), GetPlugins, Browse, Register, Update, Delete, TestConnection (je Erfolg/Fehler) |
TransferConfigsControllerTests |
GetAll, Save, Rename, Delete (je Erfolg/Fehler) |
TransfersControllerTests |
GetAll, GetStatus, GetProgress, Start, StartConfig, Cancel (je Erfolg/Fehler) |
ResultExtensionsTests |
ToActionResult<T>, ToActionResult (OperationResult), ToNotFoundActionResult<T> (je Erfolg/Fehler) |
Testframework
| Paket | Version | Zweck |
|---|---|---|
MSTest.Sdk |
3.6.4 | Testframework (MSTest) |
Microsoft.AspNetCore.Mvc.Testing |
8.0.0 | WebApplicationFactory für Integrationstests |
Sequenz: Transfer starten via API
sequenceDiagram
participant Client as HTTP Client
participant TC as TransfersController
participant H as StartTransferHandler
participant R as ITransferRepository
participant E as ITransferEngine
participant SH as ProgressHub (SignalR)
Client->>TC: POST /api/transfers/start
TC->>H: HandleAsync(StartTransferCommand)
H->>R: AddAsync(transfer)
H->>R: UpdateAsync(transfer – Running)
H-->>TC: Result (Transfer-ID)
TC-->>Client: 200 OK (Transfer-ID)
Note over H,E: Fire-and-Forget im Hintergrund
H-)E: ExecuteAsync(config, progress)
E->>SH: IProgressReporter.ReportAsync(progress)
SH-->>Client: ProgressUpdate (SignalR Push)
E-->>H: TransferResultDto
H->>R: UpdateAsync(transfer – Completed/Failed)
Architecture Decision Records (ADR)
| Entscheidung | Kontext | Begründung |
|---|---|---|
| Keine MediatR-Abhängigkeit | Commands/Queries direkt via ICommandHandler / IQueryHandler |
Kein Framework-Overhead, direkte Testbarkeit, Kontrolle über DI-Registrierung |
| Explizite DI-Registrierung | Alle Handler in Program.cs per AddScoped registriert |
Sichtbar, nachvollziehbar, kein Magic-Scanning |
| ResultExtensions statt Controller-Logik | HTTP-Status-Mapping zentralisiert in ResultExtensions |
DRY-Prinzip, einheitliche HTTP-Semantik in allen Controllers |
| Fire-and-Forget bei Start | Transfer-Ausführung ist langläufig | Sofortige Rückgabe der Transfer-ID, Fortschritt via SignalR oder /progress |
| SignalR für Echtzeit-Fortschritt | Polling ist ineffizient für grosse Transfers | Push-basiertes Update, geringer Client-Overhead |
| Swagger nur in Development | Produktions-APIs sollen keine Swagger-Oberfläche exponieren | Sicherheit und Reduktion der Angriffsfläche |
| InternalsVisibleTo für Tests | FakeCommandHandler / FakeQueryHandler sind internal |
Testzugriff ohne public, keine unnötige API-Exposition |
Qualitätsmerkmale
| Aspekt | Umsetzung |
|---|---|
| Keine Geschäftslogik | Controller delegieren vollständig an Application Features |
| CQS-Muster | ICommandHandler / IQueryHandler trennen Schreib- und Lesezugriffe |
| Result-Muster | Result<T> und OperationResult – keine Exceptions für erwartbare Fehler |
| Async First | Alle Controller-Methoden async mit CancellationToken |
| XML-Dokumentation | Alle public Members dokumentiert, in Swagger eingebunden |
| Nullable Reference Types | Aktiviert – optionale Properties explizit als ? deklariert |
| Sealed Controller | Alle Controller sealed – kein unbeabsichtigtes Erben |
| Sealed Request-Records | Alle Request-Bodies als sealed record – immutabel, strukturell vergleichbar |
| Unit Tests | Controller direkt instanziiert, kein HTTP-Stack, schnelle Ausführung |