Hallo Leute
Inspiration für dieses Beispiel waren folgende Threads:
Unit of Work und Repository - GetAll Methode mit generischer Klasse und Filter
MVVM OpenSource Communityprojekt - HomeStorage
Raise Events nach oben durchreichen oder add Überladung funktioniert nicht
Vorweg muss ich ein paar Dinge dazu sagen:
1.) Dieses Beispiel ist mit Kanonen auf Spatzen geschossen. Niemand würde ein so kleines "Projekt" wie eine Notizapp mit diesem Schema aufziehen, eine Mehrschichtige Anwendung würde nur bei größeren komplexeren Projekten sinn machen. Je größer das Projekt desto mehr kommen die Vorteile von MVVM, einem generischem Repository und/oder einer Layertrennung zu tragen.
Was Anfangs viel zu komplex erscheint gewinnt mit wachsender Solutiongröße an "einfachheit" da alles an seinem Ort ist und da gewisse abhängigkeiten Abstrahiert werden sind änderungen an einem der unteren Layer umso einfacher.
2.) Sicher ist der Code und/oder die Struktur nicht das maß aller Dinge und jeder muss für sich und das Projekt bzw. die Aufgabenstellung selbst herausfinden wie es am besten zu machen ist und wie man seine Anwendung nun aufbaut. Ich setze diese Struktur allerdings bei einigen Projekten ein wo mir diese Struktur bereits mehrfach zu gute gekommen ist.
Was ist dieses Beispiel und was nicht:
Die Applikation soll eine mehrplatzfähige Notizverwaltung darstellen bei welcher Notizen angelegt, editiert oder wieder gelöscht werden können.
Es können einer Notiz Anhänge hinzugefügt werden. Es gibt eine Protokollfunktion um Änderungen an Notizen, Anhängen und Kategorien nachvollziehen zu können.
Es soll keine Eierlegende Wollmilchsau darstellen welche alles mögliche kann. Ich wollte mit dieser Testanwendung einfach nur zeigen was ICH wie machen würde um ans Ziel zu kommen ohne gewisse Pattern zu verletzen. Außerdem sollte die Anwendung durch den Einsatz und der einhaltung des MVVM Patterns zeigen wie eine Anwendung Testbar gemacht werden kann.
Eines der Vorteile von MVVM ist ja eben das eine Anwendung durch z.b. UnitTests testbar wird. Wir können also jederzeit sehen ob noch alles an unserer Anwendung korrekt funktioniert - vorausgesetzt der Teil der Anwendung wurde durch einen oder mehrere UnitTests abgedeckt.
Dennoch wollte ich auf einige schöne Features der WPF eingehen und zeigen wie soetwas realisierbar ist:
Die Settings sind solch ein Beispiel. Im Model (Datenbank) ist ein Settingswert IMMER als String gespeichert. Dennoch sind in diesem Beispiel bei jedem "Setting-Knoten" andere Controls zu sehen um die bedienung für den Enduser so einfach und intuitiv wie möglich zu halten.
Layer (Schichten):
Jeder der sich MVVM schon mal angesehen hat weis das eine Anwendung unter MVVM schon mal aus mindestens 3 Schichten (oder auch 3 Projekte innerhalb der Projektmappe) besteht.
Dem Model, dem ViewModel und der View.
Hat man ein generisches Repository kommt schon mal mindestens eine Schicht hinzu.
Ich gehe in diesem Beispiel einen Schritt weiter:
In dieser Abbildung sieht man die Layer recht schön. Es gibt ein
Dann gibt es den
Als nächstes sehen wir unser
Der Businesslogik. Diese hat nun keinen Verweis mehr auf das EntityFramework da unser Repository den Context vor der Businesslogik "versteckt". Die Businesslogik kümmert sich um die ganze Programmlogik. In dem Fall dieser einfachen Applikation ist es nicht viel was die Businesslogik macht, bei größeren Applikationen ist es allerdings so da man evtl. sehr viel an Logik implementieren muss. Beispiel: Ein Artikel wird verkauft. Es muss eine Lagerbewegung Protokolliert werden, Rabatte müssen berechnet werden, evtl. muss nachgesehen werden ob der mindestlagerbestand unterschritten wurde und die Logik muss beim Lieferanten automatisch nachbestellen. Dies würde alles die Businesslogik erledigen. So bleibt unser nächster Layer - das ViewModel - sauber von solchen Dingen.
Tja, das ViewModel, ein ViewModel stellt im Grunde das dar was im View angezeigt werden soll. Unabhängig von der Struktur des Models oder anderer Abhängigkeiten.
Oft ist es so das ich Daten völlig anders in einer Datenbank speichern möchte als ich diese anzeige. Ich möchte oder kann mein Model vieleicht nicht ändern (evtl. ist dieses sogar von EF generiert worden und ich kann gar nicht eingreifen). Das ViewModel bereitet die Daten welche die View anzeigen soll auf.
Beispiel: In meinem Model habe ich eine Klasse (Klasse = ist in unserem Fall eine Tabelle in der Datenbank) Settings. Ein Setting ist immer als String gespeichert.
Schön und gut, jetzt möchte ich aber an einer Stelle eine Listbox anzeigen welche mit Werte darstellt welche in den Settings als Kommata getrennter String stehen.
Unser ViewModel wird hierfür eine List(Of String) bereitstellen welche die "Item" beinhaltet indem es den Kommatagetrennten String umwandelt. Umgekehrt aber beim Speichern wieder zurück in einen Kommatagetrennten String. So sind wir flexibel was die anzeige im View betrifft.
Zu guter letzt die View. Die View hat keinen Verweis auf auf eine Businesslogik, ein Repository, einen DBContext oder gar EntityFramework. Auch ein Verweis auf das Model ist nicht vorhanden weil wir ja wie gerade erwähnt nicht an ein Model und deren Struktur gebunden sein sollen. Ändern wir etwas am Model haben wir später vieleicht Bindingfehler welche zur Kompiliertzeit nicht auffallen. Im ViewModel allerdings schon.
UnitTests:
Ich habe in die UnitTests jetzt nicht soo viel Arbeit investiert wie bei einem "echten" Projekt. Dennoch wollte ich aufzeigen das die komplette Applikation testbar ist.
Wir wissen also immer innerhalb Sekunden ob wir durch unsere letzte Änderung etwas defekt gemacht haben, vorausgesetzt wird haben die betreffende Stelle mit einem Test abgedeckt.
In diesem Projekt gibt es 40 UnitTests welche innerhalb von 4,19 Sekunden durchlaufen. Diese UnitTests können IMMER ausgeführt werden. Egal ob auch dem lokalen Rechner, auf einem Buildserver wie AzureDevOps oder am Mond. Es wird kein Internet, keine Datenbank oder sonst etwas benötigt. Würden die UnitTests eine Datenbankn benötigen würde diese VIEL länger brauchen da ja für jeden Test die DB neu erstellt und mit Beispieldaten befüllt werden müsste.
Hier ein Beispieltest:
Dieser Test testet ob beim anlegen einer neuen Notizkategorie ein Protokoll in die Protokolltabelle für diese Kategorie geschrieben wird.
OK, genug der vielen Wort - ich möchte auch nicht zu sehr ins Detail gehen. Wenn jemand Fragen hat soll er am besten einfach Fragen.
Ich werde dann so gut wie ich nur kann versuchen die Frage zu beantworten. Gerne gehe ich hier dann auch auf gewisse Dinge detailierter ein.
Das Projekt ist außerdem hier gehostet.
So, jetzt noch Bilder der App:
Inspiration für dieses Beispiel waren folgende Threads:
Unit of Work und Repository - GetAll Methode mit generischer Klasse und Filter
MVVM OpenSource Communityprojekt - HomeStorage
Raise Events nach oben durchreichen oder add Überladung funktioniert nicht
Vorweg muss ich ein paar Dinge dazu sagen:
1.) Dieses Beispiel ist mit Kanonen auf Spatzen geschossen. Niemand würde ein so kleines "Projekt" wie eine Notizapp mit diesem Schema aufziehen, eine Mehrschichtige Anwendung würde nur bei größeren komplexeren Projekten sinn machen. Je größer das Projekt desto mehr kommen die Vorteile von MVVM, einem generischem Repository und/oder einer Layertrennung zu tragen.
Was Anfangs viel zu komplex erscheint gewinnt mit wachsender Solutiongröße an "einfachheit" da alles an seinem Ort ist und da gewisse abhängigkeiten Abstrahiert werden sind änderungen an einem der unteren Layer umso einfacher.
2.) Sicher ist der Code und/oder die Struktur nicht das maß aller Dinge und jeder muss für sich und das Projekt bzw. die Aufgabenstellung selbst herausfinden wie es am besten zu machen ist und wie man seine Anwendung nun aufbaut. Ich setze diese Struktur allerdings bei einigen Projekten ein wo mir diese Struktur bereits mehrfach zu gute gekommen ist.
Was ist dieses Beispiel und was nicht:
Die Applikation soll eine mehrplatzfähige Notizverwaltung darstellen bei welcher Notizen angelegt, editiert oder wieder gelöscht werden können.
Es können einer Notiz Anhänge hinzugefügt werden. Es gibt eine Protokollfunktion um Änderungen an Notizen, Anhängen und Kategorien nachvollziehen zu können.
Es soll keine Eierlegende Wollmilchsau darstellen welche alles mögliche kann. Ich wollte mit dieser Testanwendung einfach nur zeigen was ICH wie machen würde um ans Ziel zu kommen ohne gewisse Pattern zu verletzen. Außerdem sollte die Anwendung durch den Einsatz und der einhaltung des MVVM Patterns zeigen wie eine Anwendung Testbar gemacht werden kann.
Eines der Vorteile von MVVM ist ja eben das eine Anwendung durch z.b. UnitTests testbar wird. Wir können also jederzeit sehen ob noch alles an unserer Anwendung korrekt funktioniert - vorausgesetzt der Teil der Anwendung wurde durch einen oder mehrere UnitTests abgedeckt.
Dennoch wollte ich auf einige schöne Features der WPF eingehen und zeigen wie soetwas realisierbar ist:
Die Settings sind solch ein Beispiel. Im Model (Datenbank) ist ein Settingswert IMMER als String gespeichert. Dennoch sind in diesem Beispiel bei jedem "Setting-Knoten" andere Controls zu sehen um die bedienung für den Enduser so einfach und intuitiv wie möglich zu halten.
Layer (Schichten):
Jeder der sich MVVM schon mal angesehen hat weis das eine Anwendung unter MVVM schon mal aus mindestens 3 Schichten (oder auch 3 Projekte innerhalb der Projektmappe) besteht.
Dem Model, dem ViewModel und der View.
Hat man ein generisches Repository kommt schon mal mindestens eine Schicht hinzu.
Ich gehe in diesem Beispiel einen Schritt weiter:
In dieser Abbildung sieht man die Layer recht schön. Es gibt ein
Model
. Dieses beinhaltet unsere Entitäten. Diese sind reine POCO Klassen welche nichts anderes machen als Properties zur verfügung zu stellen. EntityFramework übernimmt diese "Struktur" aus der Objektorientierten Welt um daraus die relationelle Datenbank erstellen zu können.Dann gibt es den
NotesContext
welcher unser DBContext ist mit welchen wir Daten abrufen oder in die DB zurück persistieren können. Dieser besitzt einen Verweis auf Entity Framework Core.Als nächstes sehen wir unser
Repository
. Dieses ist ein generisches Repository welches auch einen Verweis auf das EntityFRamework besitzt, und stellt Methoden zum abrufen oder speichern von Daten über der DBContext bereit, allerdings OHNE den DBContext und somit EntityFramework nach aussen zu reichen. Womit wir zur nächsten Schicht kommen.Der Businesslogik. Diese hat nun keinen Verweis mehr auf das EntityFramework da unser Repository den Context vor der Businesslogik "versteckt". Die Businesslogik kümmert sich um die ganze Programmlogik. In dem Fall dieser einfachen Applikation ist es nicht viel was die Businesslogik macht, bei größeren Applikationen ist es allerdings so da man evtl. sehr viel an Logik implementieren muss. Beispiel: Ein Artikel wird verkauft. Es muss eine Lagerbewegung Protokolliert werden, Rabatte müssen berechnet werden, evtl. muss nachgesehen werden ob der mindestlagerbestand unterschritten wurde und die Logik muss beim Lieferanten automatisch nachbestellen. Dies würde alles die Businesslogik erledigen. So bleibt unser nächster Layer - das ViewModel - sauber von solchen Dingen.
Tja, das ViewModel, ein ViewModel stellt im Grunde das dar was im View angezeigt werden soll. Unabhängig von der Struktur des Models oder anderer Abhängigkeiten.
Oft ist es so das ich Daten völlig anders in einer Datenbank speichern möchte als ich diese anzeige. Ich möchte oder kann mein Model vieleicht nicht ändern (evtl. ist dieses sogar von EF generiert worden und ich kann gar nicht eingreifen). Das ViewModel bereitet die Daten welche die View anzeigen soll auf.
Beispiel: In meinem Model habe ich eine Klasse (Klasse = ist in unserem Fall eine Tabelle in der Datenbank) Settings. Ein Setting ist immer als String gespeichert.
Schön und gut, jetzt möchte ich aber an einer Stelle eine Listbox anzeigen welche mit Werte darstellt welche in den Settings als Kommata getrennter String stehen.
Unser ViewModel wird hierfür eine List(Of String) bereitstellen welche die "Item" beinhaltet indem es den Kommatagetrennten String umwandelt. Umgekehrt aber beim Speichern wieder zurück in einen Kommatagetrennten String. So sind wir flexibel was die anzeige im View betrifft.
Zu guter letzt die View. Die View hat keinen Verweis auf auf eine Businesslogik, ein Repository, einen DBContext oder gar EntityFramework. Auch ein Verweis auf das Model ist nicht vorhanden weil wir ja wie gerade erwähnt nicht an ein Model und deren Struktur gebunden sein sollen. Ändern wir etwas am Model haben wir später vieleicht Bindingfehler welche zur Kompiliertzeit nicht auffallen. Im ViewModel allerdings schon.
UnitTests:
Ich habe in die UnitTests jetzt nicht soo viel Arbeit investiert wie bei einem "echten" Projekt. Dennoch wollte ich aufzeigen das die komplette Applikation testbar ist.
Wir wissen also immer innerhalb Sekunden ob wir durch unsere letzte Änderung etwas defekt gemacht haben, vorausgesetzt wird haben die betreffende Stelle mit einem Test abgedeckt.
In diesem Projekt gibt es 40 UnitTests welche innerhalb von 4,19 Sekunden durchlaufen. Diese UnitTests können IMMER ausgeführt werden. Egal ob auch dem lokalen Rechner, auf einem Buildserver wie AzureDevOps oder am Mond. Es wird kein Internet, keine Datenbank oder sonst etwas benötigt. Würden die UnitTests eine Datenbankn benötigen würde diese VIEL länger brauchen da ja für jeden Test die DB neu erstellt und mit Beispieldaten befüllt werden müsste.
Hier ein Beispieltest:
VB.NET-Quellcode
- <TestMethod> <TestCategory("ViewModel.CategoryVm")> Public Sub SaveNewCategoryMustCreateProtocolTest()
- Using ctx As New NotesDbContext
- Using sut As New CategoryVm(New CategoryBl(New GenericRepository(Of NotesCategory)(ctx)), New NotesCategory)
- sut.Title = "NewCatTest"
- sut.Desc = "This is a Test"
- sut.DeletedFlag = True
- Task.Run(Sub() sut.Save.Execute(Nothing)).GetAwaiter.GetResult()
- Assert.AreEqual("NewCatTest", ctx.Categories.First.Title)
- Assert.AreEqual(1, ctx.Protocols.Where(Function(p) p.LinkedToCategoryId = sut.Id).Count)
- End Using
- End Using
- End Sub
Dieser Test testet ob beim anlegen einer neuen Notizkategorie ein Protokoll in die Protokolltabelle für diese Kategorie geschrieben wird.
OK, genug der vielen Wort - ich möchte auch nicht zu sehr ins Detail gehen. Wenn jemand Fragen hat soll er am besten einfach Fragen.
Ich werde dann so gut wie ich nur kann versuchen die Frage zu beantworten. Gerne gehe ich hier dann auch auf gewisse Dinge detailierter ein.
Das Projekt ist außerdem hier gehostet.
So, jetzt noch Bilder der App:
If _work = worktype.hard Then Me.Drink(Coffee)
Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.
## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##
Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.
## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##
Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Nofear23m“ ()