OpenSource: Vorstellung MetroFtpClient

In diesem Beitrag möchte ich mal ein kleines Tool für FTP-Zugriffe vorstellen. Als Ausgangsbasis für den MetroFtpClient (https://github.com/steve600/MetroFtpClient) diente das PrismMahAppsSample (https://github.com/steve600/PrismMahAppsSample) und die Standard .NET-Klassen FtpWebRequest/FtpWebRespsonse. Auch für dieses Projekt wurden wieder einige OpenSource-Projekte verwendet. Hier mal eine Übersicht:

Hier schon einmal ein erster Screenshot:

MetroFtpClient Verbindungsansicht

MetroFtpClient


Aufbau der Solution

Hier mal der grundsätzliche Aufbau der Solution:

Aufbau der Solution

Aufbau der Solution

  • MetroFtpClient.Core – in diesem Projekt sind alle Basisklassen (wie z.B. Basisklasse für ViewModels) untergebracht
  • MetroFtpClient.Infrastructure – Projekt für allgemeine Hilfsklassen (z.B. UI-Helper, Konstanten, Events, usw.)
  • MetroFtpClient.FTP – dieses Projekt ist als Prism-Modul ausgeprägt und hier ist die eigentliche FTP-Funktionalität inkl. UI implementiert
  • MetroFtpClient.FTP.Contracts – allgemeingültige Klassen und Interfaces wie z.B. Events, BusinessObjekte usw.
  • MetroFtpClient – Haupteinstiegspunkt der Applikation (Prism-Bootstrapper und MainWindow der Applikation)
  • MetroFtpClient.Styling – projektübergreifendes Styling, z.B. Icons, Typografie, usw.

User Interface (UI)

Wie bereits weiter oben erwähnt kommt Prism als Applikationsframework zum Einsatz. Mit Prism können sogenannte Regionen innerhalb eines Dialogs definiert werden. Mit Regionen definiert man das Aussehen/Raster des Dialogs und legt fest wo einzelne Views (im Normalfall innerhalb des Hauptdialogs) angezeigt werden sollen. Diese Regionen können dann von den einzelnen Prism-Modulen genutzt werden, um die dort implementierten Views innerhalb des Hauptdialogs anzuzeigen. Dabei haben die Prism-Module keinerlei Wissen darüber, wo sich die einzelnen Regionen innerhalb des Hauptdialogs befinden bzw. wie dieser aufgebaut ist. Die Module teilen dem Hauptdialog lediglich mit zeige View A in Region C. Damit ist es auf einfache Art und Weise möglich einzelne Views auszutauschen, ohne dass Änderungen am Hauptdialog notwendig sind. Das Austauschen der Views kann auch zur Laufzeit erfolgen (Stichwort Navigation). Für dieses Projekt wurden die folgenden Regionen definiert:

Prism Regionen

Prism Regionen

Prism-Regionen werden vom sogenannten RegionManager erstellt und verwaltet. Eine Region ist ein Host für ein oder mehrere Controls. Für die Verwaltung benötigt der RegionManager einen sogenannten RegionAdapter. Ein solcher RegionAdapter ist immer spezifisch für einen bestimmten Typ eines Controls (z.B. TabControl, usw.). Mit Hilfe eines RegionAdapters werden die Controls/Views dann mit der entsprechenden Region „verbunden“. Das folgende Schaubild soll die Zusammenhänge verdeutlichen:

PrismMahAppsSample_07

Zusammenhang Region, RegionAdapter und Controls (Quelle [1])

Damit UI-Controls als Region genutzt werden können muss es für den Typ des UI-Controls einen entsprechenden RegionAdapter geben. Der RegionAdapter ist für die Erzeugung der Region und die Zuweisung der Controls zur Region verantwortlich. Jedes UI-Control benötigt also seinen eigenen RegionAdapter. Out-of-the-box bietet die PRISM-Biblothek die folgenden RegionAdapter:

  • ContentControlRegionAdapter – dieser Adapter ist für Controls des Typs System.Windows.Controls.ContentControl und davon abgeleitete Klassen
  • SelectorRegionAdapter – dieser Adapter ist für Controls, die von System.Windows.Controls.Primitives.Selector ableiten (z.B. das System.Windows.Controls.TabControl)
  • ItemsControlRegionAdapter – dieser Adapter ist für Controls des Typs System.Windows.Controls.ItemsControl und davon abgeleitete Klassen

Für die MainRegion wird jetzt das Dragablz Tab-Control (https://github.com/ButchersBoy/Dragablz) verwendet und für dieses Control gibt es standardmäßig keinen RegionAdapter. Daher muss für dieses Control ein eigener RegionAdapter erstellt werden und wie das funktioniert wird jetzt beschrieben:

Erstellung eines RegionAdapters

RegionAdapter müssen das Interface IRegionAdapter-Interface implementieren. Dieses Interface enthält lediglich eine Intialize-Methode, die als Parameter das entsprechende Control erhält und eine neue Region mit dem zugewiesenen Control zurückliefert. Das Interface ist wie folgt definiert:

Die folgenden Schritte beschreiben nun die Vorgehensweise zur Implementierung des IRegionAdapter-Interface:

  1. Zunächst muss eine neue Klasse angelegt werden, z.B. TabalzControlRegionAdapter. Diese Klassen lege ich im Normalfall im Shell- oder Hauptprojekt an

    TabalzControlRegionAdapter

    TabalzControlRegionAdapter

  2. Danach werden die folgenden using-Statements benötigt:
  3. Danach ändern wir die Signatur der Klasse, so dass diese von der RegionAdapterBase Basisklasse erbt, wobei T den Typ des Controls definiert. Diese Basisklasse implementiert das Interface IRegionAdapter und stellt gleichzeitig ein paar Methoden bereit, die dann nur noch überschrieben werden müssen. Das sieht dann wie folgt aus (T ist vom Typ unseres Zielcontrols):
  4. Danach muss die CreateRegion-Methode implementiert werden. Das ist eine abstrakte Methode aus der Basisklasse RegionAdapterBase und sollte eine Region-Instanz (ein Objekt, das das IRegion-Interface implementiert) zurückliefern. Prism bietet out-of-the-box die folgenden Implementierungen:
    • Region – diese Region erlaubt mehrere aktive Views. Wird im Normalfall für Controls, die von der Selector-Klasse erben, eingesetzt
    • SingleActiveRegion – diese Region erlaubt nur einen aktiven View zur gleichen Zeit (Region für ContentControls)
    • AllActiveRegion – diese Region hält alle darin enthaltenen Views aktiv (Deaktivierung von Views ist nicht erlaubt). Wird im Normalfall für ItemsControls verwendet

    Für den benötigten TabablzControlRegionAdapter würde das dann so aussehen:

  5. Danach muss die abstrakte Adapt-Methode überschrieben werden. Diese Methode übernimmt 2-Argumente: zum einen die Region mit welcher das Control verbunden werden soll und zum anderen das Control selbst. Diese Methode übernimmt das Hinzufügen/Entfernen von Views zur eigentlichen Region. Die Implementierung für den TabablzRegionAdapter sieht wie folgt aus:

Dies reicht im Normalfall auch schon aus und die Implementierung des Adapters wäre fertig. Damit der RegionAdapter verwendet werden kann muss dieser noch registriert werden.

Registrierung eines RegionAdapters

Die Registrierung des RegionAdapters erfolgt im Bootstrapper (wie schon im ersten Artikel beschrieben übernimmt der Bootstrapper die Initialisierung einer PRISM-Applikation). Dafür muss im Boostrapper die Methode ConfigureRegionAdapterMappings überschrieben werden:

Damit wäre der Dialog für die Verwendung der Region vorbereitet, d.h. jetzt können Views mit der Region verbunden und angezeigt werden. Die geschieht über den RegionManager. Der RegionManager stellt eine Methode namens AddToRegion bereit, welche einer Region einen View hinzufügt. Dabei haben die hinzugefügten Views keinerlei Kenntnis darüber wo sie innerhalb des Dialogs angezeigt werden. Das Hinzufügen von Views kann innerhalb der Anwendung von überall erfolgen:

Das Hinzufügen des Views zur Region erfolgt in Zeile 9. Prism bietet darüber hinaus ein sehr ausgereiftes Navigationskonzept, welches es ermöglicht zu einzelnen Views oder einer Region zu navigieren. Damit wird der Dialog noch etwas flexibler. Über das Navigationskonzept von Prism werde ich bei Gelegenheit einen eigenen Artikel schreiben. Kommen wir jetzt zur eigentlichen FTP-Funktionalität.

FTP-Zugriffe

Für die eigentliche FTP-Funktionalität werden die folgenden Standard .NET-Klassen verwendet:

Diese Klassen gehören seit Version 2.0 des .NET Frameworks zum Standardumfang. Um eine existierende Datei auf dem Ftp-Server zu löschen kann wie folgt vorgegangen werden:

Die statische Klasse WebRequestMethods.Ftp stellt dafür die entsprechenden FTP Protokoll-Methoden bereit. Aktuell werden die folgenden Methoden unterstützt:

FTP-Methoden der WebRequestMethods.Ftp-Klasse

NameBeschreibung
AppendFileRepräsentiert die FTP APPE Protokoll-Methode um eine Datei an eine existierende Datei anzuhängen (z.B. Wiederaufnahme eines abgebrochenen Uploads)
DeleteRepräsentiert die FTP DELE Protokoll-Methode zum Löschen einer Datei
DownloadFileRepräsentiert die FTP RETR Protokoll-Methode zum herunterladen einer Datei
GetDateTimestampRepräsentiert die FTP MDTM Protokoll-Methode zum Ermitteln des Timestamps einer Datei auf dem FTP-Server
GetFileSizeRepräsentiert die FTP SIZE Protokoll-Methode zum Ermitteln der Größe einer Datei auf dem FTP-Server
ListDirectoryRepräsentiert die FTP NLIST Protokoll-Methode zum Ermitteln der Verzeichnisinhalts
ListDirectoryDetailsRepräsentiert die FTP LIST Protokoll-Methode zum detaillierten Ermitteln des Verzeichnisinhalts auf dem FTP-Server
MakeDirectoryRepräsentiert die FTP MKD Protokoll-Methode zum Erstellen eines Verzeichnisses auf dem FTP-Server
PrintWorkingDirectoryRepräsentiert die FTP PWD Protokoll-Methode um das aktuelle Verzeichnis zu ermitteln
RemoveDirectoryRepräsentiert die FTP RMD Protokoll-Methode um ein Verzeichnis auf dem FTP-Server zu löschen
RenameRepräsentiert die FTP RENAME Protokoll-Methode zum Umbenennen einer Datei auf dem FTP-Server
UploadFileRepräsentiert die FTP STOR Protokoll-Methode für den Upload einer Datei auf den FTP-Server
UploadFileWithUniqueNameRepräsentiert die FTP STOU Protokoll-Methode um eine Datei unter anderem Namen zum FTP-Server zu übertragen

Alle FTP-Funktionen innerhalb des MetroFtpClient sind in der Klasse FtpClient gekapselt. Somit lässt sich diese Klasse leicht erweitern bzw. austauschen. Neben den reinen FTP-Funktionen werden noch zusätzliche Funktionen wie Queue-Management, simultane Downloads, FTP-Log usw. benötigt. Diese Funktionen sind ebenfalls in einer eigenen Klasse implementiert und diese heißt aktuell FtpConnection. Die Klasse FtpConnection verwendet die Klasse FtpClient und das ViewModel für das UI wiederum verwendet die FtpConnection Klasse. Das sieht dann wie folgt aus:

Klassendiagramm FTP-Funktionalität

Klassendiagramm FTP-Funktionalität

Parsen von Verzeichis- und Dateinformationen

Beim Abrufen von Verzeichnis- und Dateiinformationen (FTP-Befehle: NLIST oder LIST) werden je nach eingesetztem FTP-Server und Betriebssystem unterschiedlich aufgebaute Zeichenketten zurückgeliefert. Für ein Linux-System sieht das in Normalfall so aus:

drwxr-xr-x 2 0 0 0 Jul 13 18:21 autofs

Jetzt werden für die unterschiedlichen FTP-Server und Betriebssysteme unterschiedliche Parser benötigt. Für ein solches Szenario gibt es diverse Lösungsansätze und ich habe mich hier für eine einfache Factory entschieden, die dann die unterschiedlichen Parser zurückliefert. Der Parser selbst muss das Interface IFtpFilesystemParser implementieren und die Parse-Methode liefert dann ein Objekt vom Typ FtpFile zurück. Das Klassendiagramm dafür sieht so aus:

Ftp-Filesystem Klassendiagramm

Klassendiagramm Ftp-Filesystem

Die Factory selbst ist aktuell noch recht einfach aufgebaut:

Damit wären wir auch schon am Ende des Artikels. Der komplette Quellcode des Projekts ist hier zu finden: https://github.com/steve600/MetroFtpClient

Literaturverzeichnis und Weblinks

Abk.Quelle
[1]Prism 5 - Developer's Guide to Microsoft Prism Library 5.0 for WPF
Composing the User Interface Using the Prism Library 5.0 for WPF
[2]FtpWebRequest
https://msdn.microsoft.com/de-de/library/system.net.ftpwebrequest(v=vs.100).aspx
[3]FtpWebResponse
https://msdn.microsoft.com/de-de/library/system.net.ftpwebresponse(v=vs.100).aspx
Fork me on GitHub