Teil 1: Windows 10 Apps mit MVVMbasics

Gastbeitrag von Andreas Kuntner

Dieser Artikel ist der erste Teil einer dreiteiligen Serie (Teil 2, Teil 3).

Andreas Kuntner arbeitet als .NET Entwickler mit Schwerpunkt auf Client-, UI- und Mobile Development, und betreut in seiner Freizeit das MVVMbasics Framework und den Developer-Blog https://blog.mobilemotion.eu/

Seit meinem letzten Artikel (Teil1, Teil 2, Teil 3) hat sich viel getan: Windows 10 und die Universal Windows Platform bieten dem App-Entwickler eine Menge neuer Features, dementsprechend sind auch die zugehören Werkzeuge und Bibliotheken mitgewachsen.

Anlässlich der soeben erschienenen Version 2.3 des MVVMbasics Frameworks möchte ich anhand eines kleinen Beispielprojekts zeigen, wie sich mit Hilfe der MVVM-Architektur eine moderne Windows 10 App strukturieren und entwicklen lässt. MVVM steht für Model-View-ViewModel und bezeichnet ein Designpattern, mit dem sich modulare und vor allem leicht testbare Anwendungen umsetzen lassen. MVVMbasics ist ein Framework, das die wichtigsten Komponenten eines MVVM-basierten Projekts bereits integriert und dem Entwickler dadurch viel Boilerplate Code erspart. Ein weiterer Schwerpunkt liegt auf Cross-Plattform-Entwicklung, so lassen sich mit MVVMbasics entworfene Apps auch im Nachhinein mit sehr wenig Aufwand auf zusätzliche Plattformen portieren.

Das gesamte Beispielprojekt steht übrigens unter https://www.mobilemotion.eu/downloads/MVVMbasics_demo_2.3.0.zip zum Download bereit, hier im Artikel werde ich nur die wichtigsten Codeschnipsel zeigen.

Die grundlegende Projektstruktur:

MVVMbasics steht als NuGet-Paket für UWP-Projekte zur Verfügung und kann dadurch zu jedem UWP-Projekt (und zu vielen anderen Projekttypen) hinzugefügt werden. Ich möchte hier aber eine praktische Alternative nutzen: Für die gängisten Plattformen, darunter auch Windows 10, stehen fertige Template-Projekte zur Verfügung. Diese können in Visual Studio 2015 ganz einfach über das Menü Tools -> Extensions and Updates nachinstalliert werden, indem als Suchbegriff "MVVMbasics" angegeben wird:

20151015 (1)

Im Fenster File -> New -> Project steht nun eine neue Kategorie Multi-Platform zur Verfügung. Den Anfang macht hier immer ein Projekt vom Typ Multi-Platform Application Code using MVVMbasics:

20151015 (2)

Dieses Projekt wird die Businesslogik unserer visuellen Elemente (Seiten und Fenster) beinhalten. Für die Visuals selbst fügen wir ein weiteres Projekt zur Solution hinzu, diesmal wählen wir eine Windows 10 Universal App for MVVMbasics Core project. Dieses zweite Projekt repräsentiert die Windows 10 App selbst, daher wählen wir es als Startup-Projekt der Solution aus. Die beiden Projekte beinhalten bereits die notwendigen NuGet-Referenzen auf die MVVMbasics-Bibliothek, allerdings kann es bei der ersten Installation notwendig sein die NuGet-Pakete zu aktualisieren - wir öffnen dazu den (in VS 2015 übrigens neu gestalteten) NuGet Paket Manager für die Solution und klicken auf den am oberen Rand evtl. eingeblendeten Restore Button:

20151015 (3)

Views und Viewmodels:

Die visuellen Container einer MVVM-Applikation werden als Views bezeichnet, diese werden für jede Plattform spezifisch gestaltet - im Fall einer Windows 10 App sind das Pages, diese liegen im App-Projekt im Ordner Views. Statt von der WinRT-Klasse Page sind MVVMbasics-Views von der im Framework definierten BaseView-Basisklasse abgeleitet. In unserem Beispielprojekt befindet sich außerdem eine weitere Klasse BackButtonAwareBaseView, die diese Basisklasse um Funktionalitäten für den Zurück-Knopf (der ja in jeder App eingeblendet werden kann bzw. auf Smartphones als Hardware-Button zur Verfügung steht) erweitert. Die MainPage (und alle weiteren Seiten, die wir in unsere App einbauen) erbt daher direkt von BackButtonAwareBaseView, sodass wir uns um die Zurück-Navigation nicht weiter kümmern müssen.

Bei der Arbeit mit MVVMbasics ist jeder dieser Views ein Viewmodel eindeutig zugeordnet, das die Daten aufbereitet und zur Verfügung stellt die in der jeweiligen View angezeigt werden sollen. Anders als die Views sollen diese Viewmodels auf allen Zielplattformen wiederverwendet werden können, deshalb sind sie in einem eigenen Projekt vom Typ Portable Class Library angesiedelt. (Auch hier ist bereits eine erweiterte Basisklasse NavigatorAwareBaseViewmodel vorhanden die die eigentliche Basisklasse BaseViewmodel erweitert, die dort vorhandene Logik hat wiederum mit der Behandlung des Zurück-Buttons zu tun und muss uns nicht weiter interessieren - wichtig ist lediglich, dass wir alle unsere Viewmodels von NavigatorAwareBaseViewmodel ableiten).

Die Zuordnung von Views zu Viewmodels geschieht beim Start der App, dazu gibt es ein eigenes, von MVVMbasics zur Verfügung gestelltes, NavigatorService. Im Constructor der App-Klasse ist bereits der entsprechende Code vorhanden: Ein NavigatorService-Objekt wird instanziert, und dessen RegisterAll-Methode aufgerufen: Um die Zuordnung von Views zu Viewmodels nicht manuell pflegen zu müssen, übergeben wir dieser Methode lediglich den Namespace in dem sämtliche Views zu finden sind, und das NavigatorService registriert automatisch alle View-Viewmodel-Paare die es finden kann. Es stützt sich dabei auf eine Angabe, die im Code-Behind jeder View gemacht wird - öffnen wir zum Beispiel die Datei MainPage.xaml.cs fällt auf, dass diese nicht nur von BackButtonAwareBaseView erbt, sondern auch das Interface IBindableView implementiert, und als dessen Typ-Parameter wird jenes Viewmodel angegeben, das mit dieser View verknüpft sein soll:

1 public sealed partial class MainPage : BackButtonAwareBaseView, BindableView<MainViewmodel>

Anschließend wird die soeben erzeugte NavigatorService-Instanz per Register-Methode an ein Services-Objekt übergeben - was hat es damit auf sich? Services ist ein (in MVVMbasics integrierter) simpler IoC-Container, der innerhalb der BaseApplication-Klasse (von der unsere App-Klasse abgeleitet ist) verfügbar ist. Den Viewmodels werden die gewünschten Services dann einfach im Constructor mitgegeben, aus diesem Grund hat etwa das MainViewmodel einen Constructor-Parameter vom Typ INavigatorService - hier wird die soeben registrierte NavigatorService-Instanz an das Viewmodel übergeben, das es anschließend als private Variable zur späteren Weiterverwendung speichert.

In einfachen Projekten ist dieser integrierte IoC-Container sehr praktisch, bei größeren Apps zahlt es sich aber aus ein komplettes IoC-Framework einzubinden. In diesem Fall werden die Services einfach in einem beliebigen 3rd Party IoC-Container registriert; außerdem muss die App.Resolve-Methode überschrieben werden, sodass sie die notwendige Logik enthält um die Services wieder aus diesem Container auszulesen (diese Resolve-Methode wird bei der Instanzierung jedes Viewmodels aufgerufen).

(Übrigens sind im App-Constructor sind noch einige weitere Register-Aufrufe vorhanden, ebenso sind in den Services-Ordnern der beiden Projekte weiterführende Services vordefiniert. Für dieses Beispielprojekt sind diese zusätzlichen Services nicht notwendig, wir können sie also löschen, ebenso wie die Referenzen darauf im MainViewmodel.)