Projekt: TransferX.Core
Basis Dokumente: Read Me, Clean Architecture, Coding Standards and Best Practices und TransferX Architektur
Rolle in der Architektur: Core Layer ist die zentrale Geschäftslogik-Schicht. Implementiert die Application-Ports IProviderEngine und ITransferEngine, orchestriert Domain-Objekte und lädt Plugins dynamisch. Referenziert Application, Domain, Provider.Abstractions und Transfer.Abstractions.
Projektstruktur
TransferX.Core
| TransferX.Core.csproj
|
+---Engine
| ProviderEngine.cs
| ProviderHandle.cs
| TransferEngine.cs
|
+---Loaders
| PluginLoadContext.cs
| ProviderLoader.cs
| TransferLoader.cs
|
\---Services
\---Progress
ProgressAggregator.cs
Abhängigkeiten
graph TD
Core["⚙️ TransferX.Core"]
App["📋 TransferX.Application"]
Domain["💎 TransferX.Domain"]
PAbs["📦 TransferX.Provider.Abstractions"]
TAbs["📦 TransferX.Transfer.Abstractions"]
Core --> App
Core --> Domain
Core --> PAbs
Core --> TAbs
style Core fill:#e1f5fe,stroke:#01579b,stroke-width:2px
style App fill:#fff9c4,stroke:#fbc02d,stroke-width:2px
style Domain fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
style PAbs fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
style TAbs fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
| Paket / Projekt | Version | Zweck |
|---|---|---|
TransferX.Application |
– | Ports IProviderEngine, ITransferEngine, DTOs |
TransferX.Domain |
– | Domain-Exceptions, Enumerations, Value Objects |
TransferX.Provider.Abstractions |
– | IProvider, Requests, Responses, Models, Metadata |
TransferX.Transfer.Abstractions |
– | ITransferHandler, Models, Metadata |
Microsoft.Extensions.Logging |
10.x | ILogger<T> für strukturiertes Logging |
System.Reflection.Metadata |
10.x | Assembly-Metadaten für Plugin-Discovery |
Komponenten
Engine
| Klasse | Typ | Beschreibung |
|---|---|---|
ProviderEngine |
Klasse | Implementiert IProviderEngine – lädt, initialisiert und führt Provider aus |
ProviderHandle |
Klasse | Internes opakes Handle auf einen initialisierten IProvider |
TransferEngine |
Klasse | Implementiert ITransferEngine – orchestriert Transfer-Handler und Provider |
Loaders
| Klasse | Typ | Beschreibung |
|---|---|---|
PluginLoadContext |
Klasse | Isolierter AssemblyLoadContext für Plugin-Assemblies (collectible) |
ProviderLoader |
Klasse | Lädt IProvider-Plugins via Reflection und ProviderMetadataAttribute |
TransferLoader |
Klasse | Lädt ITransferHandler-Plugins via Reflection und TransferMetadataAttribute |
Services / Progress
| Klasse | Typ | Beschreibung |
|---|---|---|
ProgressAggregator |
Klasse | Multi-Level Fortschritts-Aggregator (Byte, Datei, Transfer) – thread-sicher |
Plugin-Informationsmodelle (Records)
| Record | Beschreibung |
|---|---|
ProviderPluginInfo |
Metadaten und Instanz eines gefundenen Provider-Plugins (Typ, Name, Version, Instanz) |
TransferPluginInfo |
Metadaten und Instanz eines gefundenen Transfer-Plugins (Name, Operationen, Instanz) |
Details
Engine: ProviderEngine
Implementiert den Application-Port IProviderEngine. Kapselt alle Abhängigkeiten auf TransferX.Provider.Abstractions – die Application kennt nur ihre eigenen DTOs.
// Registrierung (Composition Root / Program.cs)
services.AddSingleton(sp => new ProviderEngine(
sp.GetRequiredService<IProviderRepository>(),
sp.GetRequiredService<ProviderLoader>(),
sp.GetRequiredService<ILogger<ProviderEngine>>(),
pluginDirectory: "./plugins/providers"));
services.AddSingleton<IProviderEngine>(sp => sp.GetRequiredService<ProviderEngine>());
| Methode | Rückgabe | Beschreibung |
|---|---|---|
ListFoldersAsync(...) |
IReadOnlyList<FolderItemDto> |
Listet Ordner unter einem Pfad – mappt FolderItem → FolderItemDto |
ListFilesAsync(...) |
IReadOnlyList<FileItemDto> |
Listet Dateien unter einem Pfad – mappt FileItem → FileItemDto |
TestConnectionAsync(...) |
Task |
Testet Verbindung durch Laden und Initialisieren des Providers |
GetProviderHandleAsync(...) |
IProviderHandle |
Gibt ein opakes Handle auf den initialisierten Provider zurück |
LoadAndInitializeProviderAsync(...) | IProvider*(internal)* | Lädt Konfiguration, erstellt Plugin-Instanz, initialisiert – fürTransferEngine` |
DTO-Mapping ProviderEngine:
| Abstractions-Typ | Application-DTO | Mapping-Ort |
|---|---|---|
FolderItem |
FolderItemDto |
ProviderEngine.ListFoldersAsync |
FileItem |
FileItemDto |
ProviderEngine.ListFilesAsync |
ProviderConfiguration |
ProviderConfigItem |
ProviderEngine.LoadAndInitializeProviderAsync |
Engine: ProviderHandle
Internes Implementierungsdetail des Core Layers. Kapselt eine geladene und initialisierte IProvider-Instanz hinter dem Application-Interface IProviderHandle.
graph LR
App["IProviderHandle (Application)"]
Handle["ProviderHandle (Core – internal)"]
Provider["IProvider (Abstractions)"]
App -->|"opakes Handle"| Handle
Handle -->|"internal Provider"| Provider
Besonderheiten:
internal sealed– nicht ausserhalb des Core Layers sichtbar- Die
TransferEnginegreift intern direkt aufProviderHandle.Providerzu
Engine: TransferEngine
Implementiert den Application-Port ITransferEngine. Orchestriert das Laden von Transfer-Handler-Plugins und Provider-Instanzen und delegiert die eigentliche Übertragung an den ITransferHandler. Der IProgress<ProgressReport> wird direkt an den ITransferHandler weitergereicht – der Handler ist verantwortlich für die Verwendung des ProgressAggregator.
// Registrierung (Composition Root / Program.cs)
services.AddSingleton(sp => new TransferEngine(
sp.GetRequiredService<ProviderEngine>(),
sp.GetRequiredService<TransferLoader>(),
sp.GetRequiredService<ILogger<TransferEngine>>(),
pluginDirectory: "./plugins/transfers"));
services.AddSingleton<ITransferEngine>(sp => sp.GetRequiredService<TransferEngine>());
| Methode | Rückgabe | Beschreibung |
|---|---|---|
ExecuteAsync(…) |
TransferResultDto |
Lädt Handler + Provider, führt Transfer aus, mappt Ergebnis auf DTO |
Ablauf ExecuteAsync:
sequenceDiagram
participant App as Application (StartTransferHandler)
participant TE as TransferEngine
participant TL as TransferLoader
participant PE as ProviderEngine
participant TH as ITransferHandler
App->>TE: ExecuteAsync(transferId, config, progress, ct)
TE->>TL: CreateHandler(pluginDirectory, operation)
TL-->>TE: ITransferHandler
TE->>PE: LoadAndInitializeProviderAsync(sourceId, ct)
PE-->>TE: IProvider (Source)
TE->>PE: LoadAndInitializeProviderAsync(targetId, ct)
PE-->>TE: IProvider (Target)
TE->>TH: ExecuteAsync(configItem, source, target, progress, ct)
TH-->>TE: TransferResult
TE-->>App: TransferResultDto
DTO-Mapping TransferEngine:
| Application-DTO | Abstractions-Typ | Mapping-Methode |
|---|---|---|
TransferConfigDto |
TransferConfigItem |
ExecuteAsync (lokal) |
TransferResult |
TransferResultDto |
MapToDto (privat) |
Loaders: PluginLoadContext
Isolierter AssemblyLoadContext für Plugin-Assemblies. Verhindert Versionskonflikte zwischen Host und Plugins durch separaten Ladebereich.
| Eigenschaft | Wert |
|---|---|
isCollectible |
true – Assemblies können entladen werden |
| Name | Dateiname der Plugin-Assembly (ohne Extension) |
| Resolver | AssemblyDependencyResolver pro Plugin |
Besonderheiten:
internal sealed– wird nur vonProviderLoaderundTransferLoaderverwendet- Transitiv abhängige Assemblies des Plugins werden automatisch aufgelöst
- Nicht aufgelöste Assemblies fallen an den Standard-
AssemblyLoadContextzurück (Host-Assemblies wieTransferX.Provider.Abstractionswerden damit korrekt geteilt)
Loaders: ProviderLoader
Lädt IProvider-Implementierungen dynamisch aus Plugin-Assemblies mittels Reflection.
// Verwendung in ProviderEngine
var provider = this._providerLoader.CreateProvider(
assemblyPath: "./plugins/providers/WebDav.dll",
providerType: "WebDav");
| Methode | Beschreibung |
|---|---|
LoadPlugins(...) |
Lädt Assembly, gibt alle IProvider-Typen mit ProviderMetadataAttribute zurück |
CreateProvider(...) |
Erstellt Instanz eines Providers anhand des technischen Typs (z.B. "WebDav") |
DiscoverAll(...) |
Durchsucht ein gesamtes Verzeichnis nach Provider-Plugins (alle *.dll) |
Plugin-Discovery Ablauf:
graph LR
PL["ProviderLoader"]
PLCtx["PluginLoadContext"]
ASS["Plugin Assembly (*.dll)"]
REFL["Reflection (GetExportedTypes)"]
META["ProviderMetadataAttribute"]
IProvider["IProvider"]
Info["ProviderPluginInfo"]
PL -->|"erstellt"| PLCtx
PLCtx -->|"lädt"| ASS
ASS -->|"scan"| REFL
REFL -->|"prüft"| META
REFL -->|"prüft"| IProvider
META -->|"erzeugt"| Info
ProviderPluginInfo Record:
| Property | Typ | Beschreibung |
|---|---|---|
ProviderType |
string |
Technischer Bezeichner (z.B. "WebDav") |
Name |
string |
Anzeigename des Providers |
Version |
string |
Versionsnummer (z.B. "1.0.0") |
Instance |
IProvider |
Bereits erstellte Provider-Instanz |
AssemblyPath |
string |
Vollständiger Pfad zur geladenen Assembly |
Loaders: TransferLoader
Lädt ITransferHandler-Implementierungen dynamisch aus Plugin-Assemblies mittels Reflection.
// Verwendung in TransferEngine
var handler = this._transferLoader.CreateHandler(
assemblyPath: "./plugins/transfers/Copy.dll",
operation: TransferOperation.Copy);
| Methode | Beschreibung |
|---|---|
LoadPlugins(...) |
Lädt Assembly, gibt alle ITransferHandler-Typen mit TransferMetadataAttribute zurück |
CreateHandler(...) |
Erstellt Instanz eines Handlers anhand der TransferOperation |
DiscoverAll(...) |
Durchsucht ein Verzeichnis nach Transfer-Handler-Plugins (alle *.dll) |
TransferPluginInfo Record:
| Property | Typ | Beschreibung |
|---|---|---|
Name |
string |
Anzeigename (z.B. "Copy") |
Version |
string |
Versionsnummer (z.B. "1.0.0") |
SupportedOperations |
IReadOnlyList<TransferOperation> |
Liste aller unterstützten Transfer-Operationen |
Instance |
ITransferHandler |
Bereits erstellte Transfer-Handler-Instanz |
AssemblyPath |
string |
Vollständiger Pfad zur geladenen Assembly |
Services: ProgressAggregator
Multi-Level Fortschritts-Aggregator für Transfers. Aggregiert Byte-, Datei- und Transfer-Level Fortschritt und meldet kombinierte ProgressReport-Objekte an den Aufrufer. Thread-sicher durch explizites lock. Wird von ITransferHandler-Plugins verwendet – nicht direkt von der TransferEngine.
// Verwendung in einem ITransferHandler (z.B. CopyTransferHandler)
var aggregator = new ProgressAggregator(config.TransferId, progress);
aggregator.Initialize(totalFiles: files.Count, totalBytes: files.Sum(f => f.Size));
foreach (var file in files)
{
aggregator.StartFile(file.Path, file.Size);
// ... Datei übertragen ...
aggregator.ReportFileProgress(file.Path, transferred, file.Size);
// ... Übertragung abgeschlossen ...
aggregator.CompleteFile(file.Path, file.Size);
}
| Methode | Beschreibung |
|---|---|
Initialize(...) |
Setzt Gesamtanzahl Dateien und Gesamtgrösse – löst erste Transfer-Meldung aus |
StartFile(...) |
Meldet Beginn einer Dateiübertragung (Status: Transferring) |
ReportFileProgress(...) |
Meldet aktuellen Byte-Fortschritt einer laufenden Übertragung |
CompleteFile(...) |
Meldet erfolgreichen Abschluss – erhöht ProcessedFiles, aktualisiert Bytes |
FailFile(...) |
Meldet fehlgeschlagene Übertragung (Status: Failed) |
Fortschritts-Stufen:
graph TD
PA["ProgressAggregator"]
FP["FileProgress (ProgressLevel.File)"]
TP["TransferProgress (ProgressLevel.Transfer)"]
PR["ProgressReport"]
CB["IProgress<ProgressReport> (Callback)"]
PA -->|"ReportFileProgress"| FP
PA -->|"ReportTransferProgress"| TP
FP -->|"eingebettet in"| PR
TP -->|"eingebettet in"| PR
PR -->|"Report()"| CB
Berechnungen in ReportTransferProgress:
| Kennzahl | Berechnung |
|---|---|
BytesPerSecond |
TransferredBytes / Elapsed.TotalSeconds |
EstimatedTimeRemaining |
RemainingBytes / BytesPerSecond (null wenn 0 B/s) |
ProgressPercent |
Berechnet im TransferProgress Value Object (Domain) |
Besonderheiten:
- Thread-sicher durch
lock(this._lock)auf alle öffentlichen Methoden _stopwatchstartet beim Erstellen desProgressAggregator– misst Gesamtdauer- Bei
null-Progress werden keine Meldungen erzeugt (Zero-Cost)
Zusammenspiel der Komponenten
graph TD
subgraph Application["📋 Application Layer"]
PE_Port["IProviderEngine"]
TE_Port["ITransferEngine"]
end
subgraph Core["⚙️ Core Layer"]
PE["ProviderEngine"]
PH["ProviderHandle\n(internal)"]
TE["TransferEngine"]
PL["ProviderLoader"]
TL["TransferLoader"]
PLCtx["PluginLoadContext\n(internal)"]
PA["ProgressAggregator"]
end
subgraph Abstractions["📦 Abstractions"]
IProvider["IProvider"]
IHandler["ITransferHandler"]
end
subgraph Domain["💎 Domain"]
DomainExc["Exceptions"]
ProgressDomain["Progress Value Objects"]
end
PE_Port --> PE
TE_Port --> TE
PE --> PL
PE --> PH
TE --> PE
TE --> TL
PL --> PLCtx
TL --> PLCtx
PLCtx --> IProvider
PLCtx --> IHandler
IHandler -.->|"verwendet"| PA
PE --> DomainExc
TE --> DomainExc
PA --> ProgressDomain
style Application fill:#fff9c4,stroke:#fbc02d,stroke-width:2px
style Core fill:#e1f5fe,stroke:#01579b,stroke-width:2px
style Abstractions fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
style Domain fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px
Architecture Decision Records (ADR)
| Entscheidung | Kontext | Begründung |
|---|---|---|
ProviderEngine als sealed class statt Interface |
Core ist Port-Implementierung | IProviderEngine ist der nach aussen sichtbare Port; intern braucht TransferEngine direkten Zugriff auf internal-Methoden |
LoadAndInitializeProviderAsync als internal |
TransferEngine und ProviderEngine liegen im selben Assembly |
Direkte Provider-Instanz nötig ohne Application-Port zu umgehen; IProviderHandle wäre unzureichend |
PluginLoadContext mit isCollectible: true |
Plugins sollen hot-swappable sein | Collectible Contexts können entladen werden; ermöglicht zukünftiges Plugin-Reload ohne Neustart |
ProviderLoader.CreateProvider sucht per Typ-String |
ProviderConfiguration.ProviderType ist ein string |
Typ-String aus der Konfiguration bestimmt das Plugin; kein Enum-Coupling zwischen Domain und Abstractions |
TransferLoader.CreateHandler sucht per TransferOperation |
TransferOperation ist Domain-Enum |
Transfer-Operationen sind im Domain definiert; kein zusätzlicher String-Vergleich nötig |
ProgressAggregator als eigenständige Klasse |
Transfer-Handler (Plugins) müssen Fortschritt melden | Trennung von Aggregations-Logik und Transfer-Logik; Handler bleiben schlank |
Keine DI-Registrierung für ProgressAggregator |
Instanz ist pro Transfer | Transient-Instanz mit transferspezifischer Guid – nicht sinnvoll als DI-Service |
PluginLoadContext als internal sealed |
Nur von Loadern verwendet | Kein öffentliches API; Implementierungsdetail des Loader-Mechanismus |
ProgressAggregator nicht in TransferEngine |
IProgress<ProgressReport> wird direkt weitergereicht |
Die TransferEngine delegiert Progress-Handling vollständig an den ITransferHandler; kein zusätzlicher Adapter nötig |
| Marker-Attribut ohne Parameter | ProviderMetadataAttribute / TransferMetadataAttribute sind parameterlos |
Metadaten (Name, Version, Typ/Operationen) werden nach Instanziierung direkt über das Interface gelesen – Single Responsibility |
Qualitätsmerkmale
| Aspekt | Umsetzung |
|---|---|
| Port-Implementierung | ProviderEngine und TransferEngine implementieren Application-Ports (IProviderEngine, ITransferEngine) |
| DTO-Entkopplung | Core mappt intern zwischen Abstractions-Typen und Application-DTOs |
| Plugin-Isolation | PluginLoadContext isoliert Plugin-Assemblies – keine Versionskonflikte mit Host |
| Thread-Sicherheit | ProgressAggregator ist thread-sicher durch explizites lock |
| Async First | Alle I/O-Operationen mit CancellationToken |
| Fehlerbehandlung | Domain-spezifische Exceptions (ProviderException, TransferException) mit when-Filter |
| C# 12 / .NET 8 | Primary Constructors, file-scoped Namespaces, Collection Expressions ([]) |
| XML-Dokumentation | Alle public Members dokumentiert (Deutsch) |
| Nullable Reference Types | Aktiviert – optionale Parameter explizit als ? deklariert |