PRISM: Commands global verfügbar machen

Gerade in größeren, modularen WPF-Anwendungen möchte man einige Commands applikationsweit zur Verfügung stellen, z.B. um Messages anzuzeigen, bestimmte Dialoge zu öffnen (Hilfe oder Info-Dialoge) usw. In einer PRISM-Anwendung mit mehreren Modulen werden die einzelnen Commands evtl. nicht nur durch das Hauptprojekt (Shell) zur Verfügung gestellt, sondern durch die einzelnen Module. So wäre es denkbar, dass man die Hilfe für die Anwendung in ein eigenes Modul auslagert und ein Command zur Verfügung stellt, welches dann die Hilfe (evtl. je nach gegebenem Kontext) aufruft. In diesem Artikel wird nun beispielhaft gezeigt wie man ein solches Szenario umsetzen kann.

Globale Commands in einer PRISM-Applikation

In WPF-Anwendungen möchte man entweder direkt im XAML-Code oder in einem ViewModel auf die global zur Verfügung stehenden Commands zugreifen. Um auf Commands direkt im XAML-Code zugreifen zu können kann eine statische Klasse verwendet werden. Möchte man die Commands innerhalb des ViewsModels verwenden kann eine entsprechende Proxy-Klasse zum Einsatz kommen. Dies könnte in etwa wie folgt aussehen:

ApplicationCommandsProxy

Zunächst gibt es hier das Interface IApplicationCommands, welches die allgemeine Schnittstelle beschreibt. Daneben gibt es die statische Klasse ApplicationCommands, die den gleichen Aufbau wie das Interface IApplicationCommands besitzt. Diese Klasse verwaltet dann die einzelnen Commands und stellt diese als einfache Eigenschaften (Getter/Setter) zur Verfügung. Jetzt gibt es in diesem Modell ja noch die Klasse ApplicationCommandsProxy, die das Interface IApplicationCommands implementiert. Dies stellt eine Art Indirektionsebene dar und soll die Zugriffe auf die statische Klasse ApplicationCommands regeln (ähnlich dem Proxy-Pattern). Der Aufbau einer ApplicationCommands-Klasse könnte wie folgt aussehen:

Diese Klasse stellt die globalen Commands einfach als öffentliche Eigenschaften nach außen hin zur Verfügung. Wie zu sehen handelt es sich bei den Eigenschaften ausschließlich um DelegateCommand– und CompositeCommand-Objekte (weitere Informationen dazu: PRISM: DelegateCommand und CompositeCommand). Das ShowOnGitHub-Command wird innerhalb der Klasse schon direkt instanziiert (bei diesem Command handelt es sich um ein recht einfaches Command und direkt hier erzeugt werden). Alle anderen Commands sind aber noch ohne „Funktion“ und hier kommt jetzt die Klasse ApplicationCommandsProxy ins Spiel. Typische PRISM-Anwendungen bestehen aus einem Hauptprojekt (oftmals auch als Shell-Projekt bezeichnet) und den einzelnen lose gekoppelten Modulen. Jetzt ist es oft so, dass die globalen Commands nicht durch das Hauptprojekt sondern durch die einzelnen Module festgelegt werden. Dies macht auch Sinn, da in den einzelnen Modulen die eigentliche Logik implementiert wird, wohingegen das Hauptprojekt (Shell) lediglich Host für die einzelnen Module dient. Hier mal beispielhaft der Aufbau einer einfachen ApplicationCommandsProxy-Klasse:

Wie zu sehen ist die Klasse recht einfach aufgebaut. Die Klasse hat lediglich ein paar öffentliche Eigenschaft, die die Commands aus der statischen Klassen ApplicationCommands (diese Klasse „regelt“ also die Zugriffe auf die statische Klasse). Das Besondere hier ist die Implementierung des Interface IApplicationCommands.

Damit die einzelnen Module ihre Commands auch registrieren können muss die Klasse ApplicationCommandsProxy zunächst einmal applikationsweit zur Verfügung stehen. In einer PRISM-Anwendung nutzt man dafür am besten einen DI-Container (im folgenden Beispiel wird Unity als DI-Container eingesetzt). Die Registrierung der ApplicationCommandsProxy-Instanz erfolgt im Bootstrapper (Methode: ConfigureContainer) der Applikation (weitere Informationen: PRISM-Bootstrapper):

Damit ist es den einzelnen Modulen jetzt möglich auf diese Instanz zuzugreifen und ihre Commands zu registrieren. Um ein Command zu registrieren kann man jetzt wie folgt vorgehen:

Das obige Beispiel zeigt den FlyoutService aus dem PrismMahAppsSample (https://github.com/steve600/PrismMahAppsSample). Hier wird im Constructor des FlyoutService ein neues DelegateCommand erzeugt (Zeile 11) und dann über den ApplicationCommandsProxy registriert (Zeile 12). Die Instanz auf die Klasse ApplicationCommandsProxy wird automatisch vom Unity-Container ermittelt und an den Constructor des FlyoutService als Parameter übergeben. Dies funktioniert nur da der FlyoutService ebenfalls im DI-Container registriert wurde (siehe dazu die ConfigureContainer-Methode in der Bootstrapper-Klasse (Zeile 11)). Weitere Informationenen zu Dependency Injection mit Unity sind hier zu finden: Dependency Injection with Unity). Mit diesem Aufbau ist es jetzt möglich, dass einzelne Module Commands global zur Verfügung stellen. Durch die Indirektionsebene in Form der ApplicationCommandsProxy-Klasse können die Zugriffe auf die statische Klassen ApplicationCommands reglementiert werden, z.B. ist es möglich weitere Prüfungen in den Proxy einzubauen usw.

Doch wie kann jetzt auf diese Commans zugegriffen werden? Zunächst einmal die Zugriffe direkt aus dem XAML-Code heraus.

Zugriffe aus dem XAML-Code

Um jetzt direkt aus dem XAML-Code heraus auf die Commands zuzugreifen nutzt man einfach die oben gezeigte statische Klasse ApplicationCommands. Das sieht dann in etwa so aus:

In Zeile 7 wird zunächst einmal der entsprechende Namespace importiert und in Zeile 12 wird das Command mit dem x:Static-Attribut an einen Button gebunden. Das ist eigentlich schon alles!

Zugriffe aus dem ViewModel

Die Zugriffe aus dem ViewModel heraus sind ähnlich einfach. Wie weiter oben schon geschrieben ist die Proxy-Klasse ja innerhalb des DI-Containers registriert und kann daher ganz einfach ermittelt werden. Möchte man jetzt neue Commands registrieren oder ein Command innerhalb des ViewModels manuell ausführen kann wie folgt vorgegangen werden:

In Zeile 1 wird zunächst die Referenz auf die ApplicationCommandsProxy-Klasse ermittelt. Dann werden in Zeile 3-4 zwei neue DelgateCommand-Objekt erzeugt und anschließend in Zeile 6-7 registriert. Das alles läuft über die Klasse ApplicationCommandsProxy als Stellvertreter für die Klasse ApplicationCommands. Somit könne die einzelnen Commands bei Bedarf auch zur Laufzeit ausgetauscht werden (was auch die Testbarkeit der Anwendung erhöht). Die oben gezeigte Umsetzung wird z.B. in den folgenden Projekten verwendet:

Dieser Aufbau basiert auf dem Proxy-Pattern und daher hier noch ein paar Informationen zum Proxy-Pattern selbst.

Proxy-Pattern

Das Proxy-Pattern gehört zur Gruppe der Strukturmuster und ist auch als Stellvertreter oder Surrogat bekannt. Dieses Muster führt eine neue Indirektionsbene (den Proxy) ein, mit dem Ziel, dass ein Client nicht direkt mit dem Zielobjekt kommuniziert, sondern mit dem Proxy, der dann die Zugriffe auf das Zielobjekt regelt. Das wirkt zunächst einmal unnötig kompliziert, kann aber in manchen Situationen sehr hilfreich sein. Der Proxy selbst ist eine ganz gewöhnliche Klasse, die als Stellvertreter für eine andere Klasse agiert. Aus diesem Grund muss der Proxy die gleiche Schnittstelle wie die Zielklasse bereitstellen und erbt daher üblicherweise von der gleichen Basisklasse bzw. implementiert die gleichen Interfaces. Genau hier liegt auch schon der größte Nachteil dieses Patterns: Durch die zusätzliche Ebene wird die Operation nicht mehr direkt auf dem Zielobjekt sondern auf dem Proxy ausgeführt, welcher dann die Ausführung (als Stellvertreter) auf dem Zielobjekt übernimmt. Dies hat aber gleichzeitig den Vorteil, dass man eine zusätzliche Kontrollebene hat. Somit kann man innerhalb seiner Proxyklasse zusätzliche Prüfungen oder Funktionen ausführen bevor auf das eigentliche Zielobjekt zugegriffen wird.

UML_Proxy_Pattern
Quelle [3] – dofactory – Proxy-Pattern UML

Hier mal noch einige Erläuterungen zum Proxy-Pattern:

Proxy-Pattern in UML

Nr.Beschreibung
1Der Client möchte auf das Zielobjekt zugreifen
2Die Subject-Schnittstelle ist ein Interface oder abstrakte Basisklasse, die sowohl vom Zielobjekt ("RealSubject"), als auch dem Proxy selbst, implementiert wird.
3Innerhalb der Schnittstelle werden die jeweiligen Operationen definiert (siehe dazu im obigen Artikel das Interface IApplicationCommands)
4Eine Klasse implementiert die Schnittstelle.
5Die neue Indirektionsebene in Form der Proxy-Klasse.
6Ausführung einer Operation auf dem Proxy-Objekt.

Das Pattern selbst ist recht einfach, interessant sind die verschiedenen Anwendungsfälle (der Vollständigkeit halber erfolgt hier lediglich eine Aufzählung, ohne auf Details einzugehen):

  • Protection-Proxy – diese Art von Proxy sichert das eigentliche Zielobjekt und reglementiert die Zugriffe auf dieses
  • Remote-Proxy – ein Remote-Proxy übernimmt beispielsweise die Serialisierung eines Objekts in ein bestimmtes Format und überträgt dieses über das Netzwerk
  • Virtual-Proxy – ein virtueller Proxy dient der Verzögerung aufwändiger Operationen auf den Zeitpunkt des tatsächlichen Bedarfs (beispielsweise die Erzeugung oder die Veränderung eines komplexen Objektes

Diese nur als Information zum Proxy-Pattern. Weitere Informationen können den nachfolgenden Weblinks entnommen werden.

Literaturverzeichnis und Weblinks

Abk.Quelle
[1]Initializing Applications Using the Prism Library 5.0 for WPF
https://msdn.microsoft.com/en-us/library/gg430868%28v=pandp.40%29.aspx
[2]Dependency Injection with Unity
https://msdn.microsoft.com/en-us/library/dn178463%28v=pandp.30%29.aspx
[3]dofactory - Proxy-Pattern
http://www.dofactory.com/net/proxy-design-pattern
[4]Entwurfsmuster - Das umfassende Handbuch, Matthias Geirhos, 1. Auflage 2015, ISBN 978-3-8362-2762-9

leave your comment

Fork me on GitHub