Bauen der Web-App
Kurzüberblick 06
Anwendungsfunktionen
Sushi für's Hirn
ICM@I3CM
Inverted Classroom Model im Institut für Ingenieurinformatik und computergestützte Mathematik (I3CM)
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anwendungs-�funktionen
Allgemeines
Controller und Views für one-to-many-Relationen
Controller und Views für many-to-many-Relationen
- Werk bestellen
- Leihen eines Medium
- Leihen eines Werkes
- Zurückgeben und Stornieren
- Servicetheke: Bestellen für Kunden (einfach)
- Servicetheke: Bestellen für Kunden
- Servicetheke: Schnellbestellung
- Servicetheke: Rückgabe für Kunden
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anwendungsfunktionen
Anwendungsfunktionen führen in der Regel dazu, dass entweder neue Objekte erzeugt werden, oder Relationen zwischen vorhandenen Objekten erstellt werden.
Anwenderfunktionen, die neue Objekte erzeugen, lassen sich leicht aus den generierten Standard-Controllern ableiten. In der Regel werden die Objektattribute aus dem Anwendungskontext vorbefüllt und sind nicht komplett editierbar. So könnte man sich eine Funktion vorstellen, die es erlaubt mehrer Bücher des gleichen Werkes zu erfassen. Der Anwendungsdialog hätte dann keine Funktione mehr zur Auswahl des Werkes.
Häufiger führt die Ausführung einer Anwendungsfunktion dazu, dass neue Relationen geschaffen werden. Gesehen haben wir das schon beim Ausleihen eines Buches, hier wird eine Ausgeliehen-Relation zwischen Medium und Anwender erstellt, analog dazu verhält sich das Vorbestellen eines Werkes.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anwendungsfunktionen
In diesem Abschnitt soll es darum gehen Anwendungsfunktionen zu implementieren, die Relationen erstellen. Das MVC-Entwurfsmuster eignet sich dazu sehr gut, weil es konsequent zwischen den Datenobjekten (model) und den ausführenden Elementen (controller) unterscheidet.
Bei einem Objektorientierten Ansatz könnte man erwarten, dass die Funktion Ausleihen als Methode des Mediums implementiert wird. Dies führte aber zu unübersichtlichem Quellcode, denn Ausleihen ist auch eine Funktion, die ein Anwender ausführt und könnte daher auch in der User-Klasse implementiert sein.
In MVC wird dieses Problem dadurch gelöst, dass man den kompletten Aspekt des Ausleihens und der damit verbundenen Prozesse, wie z.B. das Zurückgeben in einem speziellen Controller implementiert und die Benutzungsschnittstellen dazu von extra zugeschnittenen Views darstellen lässt.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anwendungs-�funktionen
Allgemeines
Controller und Views für one-to-many-Relationen
Controller und Views für many-to-many-Relationen
- Werk bestellen
- Leihen eines Medium
- Leihen eines Werkes
- Zurückgeben und Stornieren
- Servicetheke: Bestellen für Kunden (einfach)
- Servicetheke: Bestellen für Kunden
- Servicetheke: Schnellbestellung
- Servicetheke: Rückgabe für Kunden
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
1-zu-n-Beziehung
Zur Abbildung einer 1-zu-n-Beziehung wollen wir auf der Seite mit dem ‘n’ eine Möglichkeit haben das eine Element auszuwählen. Dazu bindet der Scaffolder Drop-Down-Listen in die Web-Oberfläche ein.
Befüllt werden diese Listen mittels SelectList, die vom Controller im ViewBag abgelegt werden:
Diese werden im generierten Code stets mit allen Elementen befüllt. An dieser Stelle können je nach Funktion auch nur Teile ausgewählt werden. So könnte man bei der Neuanlage von Medien per LINQ bei der Auswahl möglicher Werke berücksichtigen, dass der Anwender nur Werke, die zu seiner Fachabteilung gehören auswählen darf.
ViewBag.WorkId = new SelectList(db.Works, "Id", "Titel");
ViewBag.UserId = new SelectList(db.Users, "Id", "Name");
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Nacharbeiten für Multiplizität 0..1
Die Seiten, die uns der Scaffolder erzeugt sind schon recht schön, haben aber noch einen Schönheitsfehler, den ich für einen Bug halte, deshalb müssen wir hier noch nacharbeiten.
Im Controller wird folgender Code generiert:
// GET: Media/Create
public ActionResult Create()
{
ViewBag.WorkId = new SelectList(db.Works, "Id", "Titel");
ViewBag.UserId = new SelectList(db.Users, "Id", "Name");
return View();
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Nacharbeiten für Multiplizität 0..1
Es werden zwei Listen erzeugt, die jeweils alle Objekte der zur Auswahl möglichen Typen enthalten. Das ist bei einer zu 1-Beziehung in Ordnung, bei einer zu 0..1-Beziehung soll es aber möglich sein, auch kein Element auszuwählen:
// GET: Media/Create
public ActionResult Create()
{
ViewBag.WorkId = new SelectList(db.Works, "Id", "Titel");
ViewBag.UserId = new[] {new SelectListItem()
{Text = "keiner", Value = ""}}.Union(
new SelectList(db.Users, "Id", "Name"));
return View();
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anwendungs-�funktionen
Allgemeines
Controller und Views für one-to-many-Relationen
Controller und Views für many-to-many-Relationen
- Werk bestellen
- Leihen eines Medium
- Leihen eines Werkes
- Zurückgeben und Stornieren
- Servicetheke: Bestellen für Kunden (einfach)
- Servicetheke: Bestellen für Kunden
- Servicetheke: Schnellbestellung
- Servicetheke: Rückgabe für Kunden
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
n-zu-m-Beziehungen
Relationen, die auf beiden Seiten eine Multiplizität mit n haben, sind nicht allgemeingültig zu generieren. Das liegt daran, dass hier verschiedene Konzepte zum Anzeigen und Generieren zum Tragen kommen können.
Grundsätzlich kann man unterscheiden zwischen dem Anlegen, Anzeigen und Löschen einer Relation im Kontext eines Objektes oder der allgemeinen Anzeige aller Relationen bzw. dem Anlegen einer Relation zwischen zwei zu wählenden Objekten.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
n-zu-m-Beziehungen
Will man z.B. ein Werk vorbestellen, dann könnte z.B. der User der Kontext sein:
“Als Kunde bin ich eingeloggt und will ein Buch aus einer Liste auswählen” (und dann wird dafür die entsprechende Vorbestellungsrelation erzeugt.)
Es könnte aber auch das Buch der Kontext sein:
“Als Bibliotheksmitarbeiter möchte ich ein Buch für einen Kunden vorbestellen. Dazu wähle ich zunächst das Buch aus und dann wähle ich den Kunden aus einer Liste aus” (und dann wird dafür die entsprechende Vorbestellungsrelation erzeugt.)
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
n-zu-m-Beziehungen
Als Übersicht könnte man sich eine Auflistung aller Vorbestellungen vorstellen:
“Als Bibliotheksleiter möchte ich alle Vorbestellungen als Übersicht ansehen können und dort ggf. einzelne Vorbestellungen löschen” (dazu wird dann die entsprechende Relation gelöscht.)
Oder man wählt in einem Zug Benutzer und Werk aus um eine Vorbestellung zu tätigen:
“Als Bibliotheksmitarbeiter möchte ich Schnellbestellungen durch direkte Auswahl von Werk und Benutzer vornehmen können.” (Nach der Auswahl wird die entsprechende Relation erstellt.)
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Vorgehen
Ziel ist es für die eigene Anwendung den passenden Arbeitsablauf zum Erstellen der Relation zu finden. Je nach Anwendung können in einem Arbeitsschritt ja auch mehrere Relationen und Objekte erzeugt oder gelöscht werden.
Die Frage zur nur, wie wählt der Anwender die Endpunkte einer zu erzeugenden oder eine zu löschende Relation aus. In unserem Beispiel sind das für die Vorbestellung ein Werk und ein User, dazu sollen hier vier Möglichkeiten aufgezeigt werden.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Beispiele
Der Anwender bestellt ein Werk, dann ist der eine Endpunkt schon implizit vorgegeben.
Der Anwender storniert eine Bestellung.
Ein Mitarbeiter bestellt für einen Kunden ein Werk, dann müssen Kunde und Werk ausgewählt werden. Reihenfolge und Art der Auswahl müssen festgelegt werden.
Ein Mitarbeiter storniert eine Bestellung für einen Kunden. Reihenfolge und Art der Auswahl müssen festgelegt werden.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anwendungs-�funktionen
Allgemeines
Controller und Views für one-to-many-Relationen
Controller und Views für many-to-many-Relationen
- Werk bestellen
- Leihen eines Medium
- Leihen eines Werkes
- Zurückgeben und Stornieren
- Servicetheke: Bestellen für Kunden (einfach)
- Servicetheke: Bestellen für Kunden
- Servicetheke: Schnellbestellung
- Servicetheke: Rückgabe für Kunden
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Benutzer als Kontext
Bestellt der Anwender ein Werk, ist der Benutzer als Kontext schon vorgegeben. Es muss also eine Möglichkeit geben Werke aufzulisten um diese auszuwählen.
Es wird daher ein Controller für Werke erzeugt und angepasst.
(Löschen von Create, Edit, Delete)
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Funktion hinzufügen
Für die Funktion ‘bestellen’ wird ein zusätzlicher View eingefügt, als Template kann Delete oder Detail verwendet werde, da diese schon einen passenden Aufbau haben:
Es wird genau ein Element übergeben um verarbeitet zu werden.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Im Controller erscheint die passende Methode Order, die als Parameter wie erwartet die id des zu bestellenden Werkes erhält und dann Werk und User aus der Datenbank ermittelt und verknüpft.
public ActionResult Order(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
}
Work work = db.Works.Find(id);
if (work == null)
{
return HttpNotFound();
}
if (!hasUser())
{
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
}
LoggendInUser.Orders.Add(work);
db.SaveChanges();
return RedirectToAction("Index");
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Im View ‘Index’ müssten dann noch die ActionLinks angepasst werden, so dass zu jedem Eintrag in der Liste auch ein Knopf zum Bestellen erscheint.
Das wäre aber für bereits bestellte Werke nicht sinnvoll, schöne wäre es, wenn hier ein Storno Knopf erscheint.
Dazu muss die Information, ob ein Werk bestellt wurde oder nicht im View ausgewertet werden. Dazu muss diese Information in den View übergeben werden.
<td>
@Html.ActionLink("Details",
"Details", new { id=item.Id }) |
@Html.ActionLink("Bestellen",
"Order", new { id = item.Id })
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Zur Übergabe zusätzlicher Information in den Controller steht ein Objekt mit dem Namen ViewBag zur Verfügung. Diesem kann man beliebige Attribute zuordnen.
Dieser Mechanismus ist unter Entwicklern nicht sehr beliebt, weil der die strenge Typisierung umgeht. Es ist aber der Mechanismus, den MVC zur Verfügung stellt.
public ActionResult Index()
{
if (!hasUser())
{
return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
}
ViewBag.OrderedIds =
LoggedInUser.Orders.Select(
o => o.Id).ToList();
return View(db.Works.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
So passt sich die Anzeige der Funktionen entsprechend des Bestellstatus an.
Dazu wird noch eine Methode ‘Storno’ eingeführt.
<td>
@Html.ActionLink(
"Details", "Details",
new { id=item.Id }) |
@if (ViewBag.OrderedIds.Contains(item.Id))
{
@Html.ActionLink(
"Stornieren", "Storno",
new { id = item.Id })
}
else
{
@Html.ActionLink(
"Bestellen", "Order",
new { id = item.Id })
}
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
View für Storno
Der View Storno basiert auch auf dem Template Details und der Klasse Work.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die Methode Storno wird analog angepasst.
public ActionResult Storno(int? id)
{
if (id == null)
{
return new
HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
if (!hasUser())
{
return new
HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Work work = user.Orders.FirstOrDefault(w => w.Id = id);
if (work == null)
{
return new
HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
LoggedInUser.Orders.Remove(work);
db.SaveChanges();
return RedirectToAction("Index");
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Trouble Shooting
Viele Fehlermeldungen, die auftreten können sind beabsichtigt, wenn z.B. durch
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
Ein falscher Aufruf angezeigt wird, dann erzeugt der WebServer daraus ein Fehlermeldung entsprechend des Protokolls http.
Grundsätzlich sollten als erstes BreakPoints in den Methoden gesetzte werden, die vermeintliche Fehler verursachen. So kann man zunächst feststellen, ob diese Methoden überhaupt aufgerufen werden und im nächsten Schritt sehen, welche Werte die einzelnen Variablen angenommen haben.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Trouble Shooting
Eine weitere Quelle von Fehlermeldungen sind Validierungsfehler. Diese treten auf, wenn Daten, die nicht den Contraints des Datenmodells folgen, in die Datenbank eingespeichert werden soll.
Dabei und bei fast allen anderen Problemen, die auftreten können, werfen die zugrunde liegenden Klassen Execeptions, die Details zum Problem enthalten.
Daher bietet es sich an zunächst try...catch-Blöcke einzuführen und im catch-Block einen BreakPoint zu setzen. Reicht dies nicht aus, kann die Exception auch in einen String umgewandelt werden, der dann im View ausgegeben wird.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Trouble Shooting
Zur Ausgabe von Fehlern stellt MVC.NET einen Mechanismus bereit, zunächst können im Controller die Fehlermeldungen in den ModelState gespeichert werden:
Im View können dann die Fehler mittels eines einzelnen Controls ausgegeben werden:
Ein Beispiel dafür findet sich im AccountController.
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
ModelState.AddModelError("", error);
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Hilfsfunktion
Mit den Methoden der Klasse ExceptionHelper können verschiedene Exceptions leicht in Strings umgewandelt werden.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anwendungs-�funktionen
Allgemeines
Controller und Views für one-to-many-Relationen
Controller und Views für many-to-many-Relationen
- Werk bestellen
- Leihen eines Medium
- Leihen eines Werkes
- Zurückgeben und Stornieren
- Servicetheke: Bestellen für Kunden (einfach)
- Servicetheke: Bestellen für Kunden
- Servicetheke: Schnellbestellung
- Servicetheke: Rückgabe für Kunden
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Leihen als Vorgang - Leihen eines Mediums
In der Realität wird es auch oft die Situation geben, dass der Kunde ein Medium in der Hand hält und dieses ausleihen will. Das könnte in der Praxis z.B. so funktionieren, dass der Kunde die Kennung des Mediums z.B. als Barcode oder RFID-Tag einliest.
Dies könnte dann als sog. REST-Call implementiert sein.
Für diese Demo soll es zunächst einfach möglich sein, aus einer Liste verfügbarer Medien eines zum Leihen auszuwählen. Dazu muss ein auf Media basierende View so angepasst werden, dass nicht nur die Kennung des Mediums, sondern auch der Titel und Author des Werkes angezeigt wird.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Leihen eines Mediums
Am Ende eines Leihvorgangs wird immer das konkrete Medium geliehen, es bietet sich daher an, mit der Implementierung dieser Funktione zu beginnen.
Dazu wird ein BorrowController erzeugt, der auf der auf Media basiert.
Aus diesem werden - wie gehabt - die nicht benötigten Funktionen (create, edit, delete) entfernt.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Listenansicht anpassen
Bei der Darstellung von Elementen in Listen kann es nötig sein, dass Attribute von verknüpften Objekten angezeigt werden sollen. Im Beispiel mit den Medien wäre eine Liste mit dem einzigen Attribute von Medium (die Kennung) wenig informativ. Bei der automatischen Erstellung der Liste versucht der CodeGenerator derartige Konstellationen zu erkennen und entsprechende Tabellen zu erzeugen. In diesem Fall würde noch der Name eines Ausleihers mit angezeigt. Das wäre in einer öffentlichen Bibliothek unerwünscht. Die Anzeige des Authors zu einem Werk, ist aber von Interesse.
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Number)
</td>
<td>
@Html.DisplayFor(
modelItem => item.Work.Titel)
</td>
<td>
@Html.DisplayFor(
modelItem => item.Work.Author)
</td>
<td>
...
</td>
</tr>
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Listenansicht anpassen
Im Bereich der Funktionen soll jetzt noch der Link für das Leihen oder Zurückgeben eines Medium eingeblendet werden. Dazu wird zunächst ausgewertet ob es einen Entleiher gibt, gibt es keinen, dann wird der “Leihen”-Link angezeigt.
@foreach (var item in Model) {
<tr>
...
<td>
@Html.ActionLink("Details", "Details",
new { id=item.Id })
@if (item.User == null)
{
@:|
@Html.ActionLink("Leihen", "Borrow",
new {id = item.Id})
}
@if (item.User == ViewBag.User)
{
@:|
@Html.ActionLink("Zurückgeben",
"Return", new {id = item.Id})
}
</td>
</tr>
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Listenansicht anpassen
Im Bereich der Funktionen soll jetzt noch der Link für das Leihen oder Zurückgeben eines Medium eingeblendet werden. Dazu wird zunächst ausgewertet ob es einen Entleiher gibt, gibt es keinen, dann wird der “Leihen”-Link angezeigt.
@foreach (var item in Model) {
<tr>
...
<td>
@Html.ActionLink("Details", "Details",
new { id=item.Id }) |
@(item.User == null
? Html.ActionLink("Leihen",
"Borrow", new {id = item.Id})
: Html.ActionLink("Zurückgeben",
"Return", new {id = item.Id}))
</td>
</tr>
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Listenansicht anpassen
Im Bereich der Funktionen soll jetzt noch der Link für das Leihen oder Zurückgeben eines Medium eingeblendet werden. Dazu wird zunächst ausgewertet ob es einen Entleiher gibt, gibt es keinen, dann wird der “Leihen”-Link angezeigt.
Dann wird geprüft ob der angemeldete User der Entleiher ist, denn dann soll der “Zurückgeben”-Link angezeigt werden. Dazu wird der User im Controller im ViewBag abgelegt.
Für die eigentliche Funktion müssen nun noch die Views “Borrow” und “Return” angelegt werden.
@foreach (var item in Model) {
<tr>
...
<td>
@Html.ActionLink("Details", "Details",
new { id=item.Id })
@if (item.User == null)
{
@:|
@Html.ActionLink("Leihen", "Borrow",
new {id = item.Id})
}
@if (item.User == ViewBag.User)
{
@:|
@Html.ActionLink("Zurückgeben",
"Return", new {id = item.Id})
}
</td>
</tr>
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Controller anpassen
Ist kein Anwender eingeloggt, dann ist eine Fehlercode-Rückgabe für unerlaubten Zugriff als Rückgabe sinnvoll.
Ansonsten werden einfach alle Medien zurückgegeben und entsprechende Entleiher und Werke gleich mit aus der Datenbank ausgelesen.
// GET: Borrow
public ActionResult Index()
{
if (!hasUser()) return new
HttpStatusCodeResult(
HttpStatusCode.Unauthorized);
var media = db.Media
.Include(m => m.Work)
.Include(m => m.User);
return View(media.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
View Leihen
Der View “Borrow” sollte auf dem Template Details basieren, weil dieser schon auf ein einzelnes Objekt zugeschnitten ist.
Im View sollte wieder die Anzeige des Ausleihenden entfernt und die Anzeige des Authors ergänzt werden.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Da Borrow auf Details basiert, erwartet der Controller eine Id für ein Medium als Parameter.
Ist kein User eingeloggt wird der Fehler für unerlaubten Zugriff erzeugt, wird keine id übergeben, dann der Fehlercode für einen falschen Zugriff. Wenn das Medium nicht gefunden wird, dann Wird der Fehler für “Nicht gefunden” ausgelöst.
Bis hierher sind die möglichen Fehler durch eine falsche Nutzung der Schnittstelle ausgelöst, die urls sind wahrscheinlich von Hand zusammengesetzt. Daher sind Standardfehlermeldungen ausreichen.
public ActionResult Borrow(int? id)
{
if (!hasUser()) return new
HttpStatusCodeResult(
HttpStatusCode.Unauthorized);
if (id == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
Media media = db.Media.Find(id);
if (media == null)
{
return HttpNotFound();
}
if (media.User != null)
{
ModelState.AddModelError("",
"Medium ist schon verliehen");
return View(media);
}
media.User = LoggedInUser;
while (LoggedInUser.Orders.Remove(media.Work)){}
db.SaveChanges();
return View(media);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die Probleme, die sonst noch beim Leihen auftreten können, ergeben sich aus dem Ablauf des Programms. Hier sollte dem Anwender eine vernünftige Fehlermeldung angezeigt werden und es sollte möglich sein, normal weiterzuarbeiten.
Dazu wird der Standardmechanismus zur Validierung verwendet, den der Codegenerator z.B. beim Editieren ohnehin erzeugt. Ist das Medium schon verliehen, dann wird eine entsprechende Fehlermeldung eingefügt und der View zurückgegeben, bevor Änderungen an den Daten vorgenommen werden.
public ActionResult Borrow(int? id)
{
if (!hasUser()) return new
HttpStatusCodeResult(
HttpStatusCode.Unauthorized);
if (id == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
Media media = db.Media.Find(id);
if (media == null)
{
return HttpNotFound();
}
if (media.User != null)
{
ModelState.AddModelError("",
"Medium ist schon verliehen");
return View(media);
}
media.User = LoggedInUser;
while (LoggedInUser.Orders.Remove(media.Work)){}
db.SaveChanges();
return View(media);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Sollte es mehrere mögliche Probleme geben, die einer Ausführung der Anforderung entgegenstehen, dann sollten alle möglichen Fehlermeldungen eingefügt werden. Für den Anwender ist es bequemer direkt alle möglichen Gründe dafür, dass seine Funktion nicht ausgeführt wird, zu sehen.
Erst wenn alle Prüfungen abgeschlossen sind und voraussichtlich keine Probleme auftreten werden, werden die Änderungen an den Daten vorgenommen und in der Datenbank gespeichert.
public ActionResult Borrow(int? id)
{
if (!hasUser()) return new
HttpStatusCodeResult(
HttpStatusCode.Unauthorized);
if (id == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
Media media = db.Media.Find(id);
if (media == null)
{
return HttpNotFound();
}
if (media.User != null)
{
ModelState.AddModelError("",
"Medium ist schon verliehen");
return View(media);
}
media.User = LoggedInUser;
while (LoggedInUser.Orders.Remove(media.Work)){}
db.SaveChanges();
return View(media);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Die Probleme, die beim Leihen auftreten können, werden über den standardisierten Mechanismus gesammelt und können daher über das Standard-Control ValidationSummary ausgegeben werden.
Außerdem wird im View noch eine Statusmeldung eingebaut. Der Ausleihvorgang ist erfolgreich, wenn der eingeloggte User am Ende derjenige ist, der das Medium ausgeliehen hat.
Die Auszeichnung der Meldungen erfolgt dabei immer über die BootStrap-Klassen.
@Html.ValidationSummary(
true,
"Medium konnte nicht ausgeliehen
werden",
new { @class = "text-danger" })
@if (Model.User == ViewBag.User)
{
<div class="text-success">
Medium ausgeliehen.
</div>
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
View Zurückgeben
Analog zum View “Borrow” wird der View “Return” angelegt, der auch entsprechend angepasst wird.
In Controller muss nun geprüft werden, ob das Medium auch vom angemeldeten User ausgeliehen ist. Sonst würde einfaches Zurückgeben zu einem fehlerhaften Zustand der Daten führen.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
View Zurückgeben
Analog zum View “Borrow” wird der View “Return” angelegt, der auch entsprechend angepasst wird.
In Controller muss nun geprüft werden, ob das Medium auch vom angemeldeten User ausgeliehen ist. Sonst würde einfaches Zurückgeben zu einem fehlerhaften Zustand der Daten führen.
public ActionResult Return(int? id)
{
if (id == null) {
return new
HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Media media = db.Media.Find(id);
if (media == null) {
return HttpNotFound();
}
if (!hasUser()) return new
HttpStatusCodeResult(HttpStatusCode.Unauthorized);
if (media.User == null) {
ModelState.AddModelError(
"", "Medium ist nicht ausgeliehen.");
}
if (media.User != LoggedInUser) {
ModelState.AddModelError(
"", "Medium ist nicht an Sie ausgeliehen.");
}
if (!ModelState.Values.SelectMany(v => v.Errors).Any()) {
media.User = null;
db.SaveChanges();
}
return View(media);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anwendungs-�funktionen
Allgemeines
Controller und Views für one-to-many-Relationen
Controller und Views für many-to-many-Relationen
- Werk bestellen
- Leihen eines Medium
- Leihen eines Werkes
- Zurückgeben und Stornieren
- Servicetheke: Bestellen für Kunden (einfach)
- Servicetheke: Bestellen für Kunden
- Servicetheke: Schnellbestellung
- Servicetheke: Rückgabe für Kunden
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Leihen als Vorgang - Leihen eines Werkes
Zum Leihen eines Werkes muss zunächst ein Medium mit dem Werk vorhanden sein. Ausleihbare Werke lassen sich also so identifizieren, dass es ein Medium, das dieses Werk enthält, gibt, welches nicht ausgeliehen ist. Im Sinne des Datenmodells heißt das, das Medium darf keinen zugeordneten Leiher haben.
Zum Leihen kann es daher verschiedene Weg geben: Der Anwender könnte aus einer Katalogliste aller Werke, eines zum Leihen auswählen. Dann müsste der entsprechende Funktionsknopf in der Katalogansicht eingeblendet werden. Alternativ könnte es auch eine Katalogansicht geben, die nur ausleihbare Werke enthält.
In beiden Fällen müsste die Funktion so implementiert sein, dass immer das erste verfügbare Medium zum Werk ausgeliehen wird.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Leihen eines Werkes
Wenn man den Leihvorgang von einem Werk ausgehend anstoßen möchte, muss vor dem eigentlichen Ausleihen ein passendes verfügbares Medium ausgewählt werden.
Dazu wird zunächst ein View für das Leihen eines Werkes erstellt, der auf dem Template Details basiert und dann entsprechend angepasst.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Listenansicht anpassen
Zum Aufrufen des Leihen, wird die Listenansicht so erweitert, dass ein Kommando ‘Leihen’ für alle verfügbaren Werke angezeigt wird.
Verfügbare Werke sind dabei solche, für die ein nicht ausgeliehenes Medium existiert.
<td>
@Html.ActionLink("Details", "Details",
new { id=item.Id }) |
@if (ViewBag.OrderedIds.Contains(item.Id))
{
@Html.ActionLink("Stornieren",
"Storno", new { id = item.Id })
}
else
{
@Html.ActionLink("Bestellen", "Order",
new { id = item.Id })
}
@if (item.Media.Any(m => m.User == null))
{
@:|
@Html.ActionLink("Leihen", "Borrow",
new { id = item.Id})
}
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Im CatalogController muss jetzt noch die Methode Borrow eingefügt werden. Auch hier gibt es wieder eine Reihe von Konstellationen, die auf einen Mißbrauch der Schnittstelle schließen lassen. Diese werden mit entsprechenden Fehlermeldungen behandelt.
public ActionResult Borrow(int? id)
{
if (id == null) { … }
var userName = User.Identity.GetUserName();
User user = db.Users.FirstOrDefault(
u => u.EMail == userName);
if (user == null) { … }
Work work = db.Works.Find(id);
if (work == null) { … }
Media media = work.Media
.FirstOrDefault(m => m.User == null);
if (media == null)
{
ModelState.AddModelError("",
"Kein verfügbares Medium gefunden.");
return View(work);
}
return RedirectToAction("Borrow", "Borrow",
new {id = media.Id});
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
In dem Fall, dass kein ausleihbares Medium mehr verfügbar ist, soll aber eine Fehlermeldung erscheinen. Diese wird wieder über den ModelState realisiert.
public ActionResult Borrow(int? id)
{
if (id == null) { … }
var userName = User.Identity.GetUserName();
User user = db.Users.FirstOrDefault(
u => u.EMail == userName);
if (user == null) { … }
Work work = db.Works.Find(id);
if (work == null) { … }
Media media = work.Media
.FirstOrDefault(m => m.User == null);
if (media == null)
{
ModelState.AddModelError("",
"Kein verfügbares Medium gefunden.");
return View(work);
}
return RedirectToAction("Borrow", "Borrow",
new {id = media.Id});
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
In dem Fall, dass kein ausleihbares Medium mehr verfügbar ist, soll aber eine Fehlermeldung erscheinen. Diese wird wieder über den ModelState realisiert.
Wenn es keine Probleme gibt, dann wird das eigentliche Leihen des Mediums im BorrowController angestoßen, indem der Anwender dahin umgeleitet wird.
public ActionResult Borrow(int? id)
{
if (id == null) { … }
var userName = User.Identity.GetUserName();
User user = db.Users.FirstOrDefault(
u => u.EMail == userName);
if (user == null) { … }
Work work = db.Works.Find(id);
if (work == null) { … }
Media media = work.Media
.FirstOrDefault(m => m.User == null);
if (media == null)
{
ModelState.AddModelError("",
"Kein verfügbares Medium gefunden.");
return View(work);
}
return RedirectToAction("Borrow", "Borrow",
new {id = media.Id});
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Der View muss im wesentlichen nur optisch angepasst werden, da er nur angezeigt wird, wenn das Leihen nicht möglich ist, wird oben direkt die Fehlermeldung eingefügt.
Im unteren Teil sollten noch die Links und deren Beschriftung angepasst werden.
<h2>Ausleihen</h2>
<div>
@Html.ValidationSummary(true,
"Werk konnte nicht ausgeliehen werden",
new { @class = "text-danger" })
<hr />
…
</div>
<p>
@Html.ActionLink("Katalog", "Index")
</p>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anwendungs-�funktionen
Allgemeines
Controller und Views für one-to-many-Relationen
Controller und Views für many-to-many-Relationen
- Werk bestellen
- Leihen eines Medium
- Leihen eines Werkes
- Zurückgeben und Stornieren
- Servicetheke: Bestellen für Kunden (einfach)
- Servicetheke: Bestellen für Kunden
- Servicetheke: Schnellbestellung
- Servicetheke: Rückgabe für Kunden
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Zurückgeben und Stornieren als Vorgang
Bis jetzt können Medien nur über die Liste alle Medien zurückgegeben werden und Werk können nur über die Liste aller Werke storniert werden. Sinnvoll wäre es eine Listenansicht zu haben, die nur die Werke enthält, die der angemeldete Benutzer bestellt hat bzw. die Medien, die dieser ausgeliehen hat.
Dazu könnte man einen neuen View anlegen, der genau dies realisiert. Der Teil, der sie Ansicht erzeugt (Razor-Template) wäre aber genau der gleiche, lediglich der Teil, in dem zu anzuzeigenden Objekte ausgewählt werden (die Methode im Controller) müsste angepasst werden. Daher soll hier nur der Controller so angepasst werden, dass zwei Methoden den gleichen View nutzen.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Medium
Im BorrowController wird eine Methode erstellt, die analog zur Index-Methode arbeitet, aber nicht alle Werke auswählt, sondern nur die, die der eingeloggten Users geliehen hat.
public ActionResult My()
{
if (!setUser()) return new
HttpStatusCodeResult(
HttpStatusCode.Unauthorized);
var media = db.Media
.Where(
m => m.User.Id == LoggedInUser.Id)
.Include(m => m.Work)
.Include(m => m.User);
return View("Index", media);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Medium
Im BorrowController wird eine Methode erstellt, die analog zur Index-Methode arbeitet, aber nicht alle Werke auswählt, sondern nur die, die der eingeloggten Users geliehen hat.
Bei der Rückgabe des Views mittels der Methode View() wurde bis hierher immer die Konvention genutzt, dass ein mit der Methode gleichnamiges Template gesucht wird. Optional kann aber auch ein bestimmter View über seinen Namen ausgewählt werden. In der Methode ‘My’ wird so das Template für Index ausgewählt, dass alle notwendigen Funktionen ohnehin schon implementiert.
public ActionResult My()
{
if (!setUser()) return new
HttpStatusCodeResult(
HttpStatusCode.Unauthorized);
var media = db.Media
.Where(
m => m.User.Id == LoggedInUser.Id)
.Include(m => m.Work)
.Include(m => m.User);
return View("Index", media);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Werke
Im CatalogController wird eine Methode erstellt, die analog zur Index-Methode arbeitet, aber nicht alle Werke auswählt, sondern nur die, die in der Bestellliste des eingeloggten Users eingetragen sind.
Im Gegensatz zum Leihen wird beim Bestellen keine Bestätigungsseite angezeigt. Der Anwender wird auf die Liste zurückgeleitet. Bisher war dies immer die Liste aller Werke. Sinnvoller wäre es dem Anwender die Liste anzuzeigen, in der er die Aktion angestoßen hat.
public ActionResult My()
{
if (!hasUser())
{
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
}
ViewBag.OrderedIds =
user.Orders
.Select(o => o.Id)
.ToList();
return View("Index",
user.Orders.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die Rückkehr zur Liste wird mit Hilfe eines zusätzlichen Parameters an den Methoden Order() und Storno() realisiert.
Wird dieser nicht übergeben, so ist das Standardverhalten, dass zur allgemeinen Liste weitergeleitet wird. Gültig sind die Werte “Index” und “My”, werden andere Werte übergeben, dann handelt es sich um einen fehlerhaften Aufruf.
Die Weiterleitung erfolgt dann an die entsprechende Action, so dass die erwartete Liste angezeigt wird.
public ActionResult Order(int? id,
string returnAction)
{
...
if (returnAction == null)
{
returnAction = "Index";
}
if ( !new[] {"Index", "My"}
.Contains(returnAction))
{
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
}
...
user.Orders.Add(work);
db.SaveChanges();
return RedirectToAction(returnAction);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anwendungs-�funktionen
Allgemeines
Controller und Views für one-to-many-Relationen
Controller und Views für many-to-many-Relationen
- Werk bestellen
- Leihen eines Medium
- Leihen eines Werkes
- Zurückgeben und Stornieren
- Servicetheke: Bestellen für Kunden ()
- Servicetheke: Bestellen für Kunden
- Servicetheke: Schnellbestellung
- Servicetheke: Rückgabe für Kunden
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Bestellen für Kunden
In der Rolle des Mitarbeiters soll es möglich sein, für Kunden Bestellungen vorzunehmen. Bisher konnte diese Relation sehr einfach angelegt werden, da das eine Ende der Relation mit dem eingeloggten User bereits vorgegeben war.
Es wird also eine Eingabemöglichkeit benötigt, bei der man für das Anlegen einer Bestellung sowohl den Besteller, als auch das Werk auswählen kann.
Die Bestellungen sind im Datenmodell nicht als eigener Objekttyp modelliert, sondern als Relation ausgeführt.
public class Order
{
[Key]
[Column(Order = 0)]
public int UserId { get; set; }
[Key]
[Column(Order = 1)]
public int WorkId { get; set; }
public User User { get; set; }
public Work Work { get; set; }
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Bestellen für Kunden
Für Relationen lassen sich keine Controller oder Views generieren, da diese nicht explizit im Modell vorkommen.
Hier bietet es sich an, ein Teilmodell zu erstellen, dass es nur im Zusammenhang mit dieser Application bzw. diesen Views gibt. Man spricht von einem ViewModel. Dafür wird auch der Begriff des MMVC verwendet um auszudrücken, dass das, was der Anwender als Modell wahrnimmt (also sieht) sich nicht unbedingt mit dem decken muss, was für Datenbank modelliert ist und dort abgelegt wird.
public class Order
{
[Key]
[Column(Order = 0)]
public int UserId { get; set; }
[Key]
[Column(Order = 1)]
public int WorkId { get; set; }
public User User { get; set; }
public Work Work { get; set; }
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Bestellen für Kunden
In diesem Beispiel wird dazu zur expliziten Darstellung einer Bestellung im Unterordner Models der WebApplication eine Klasse Order angelegt. Diese gehört nicht zum eigentlichen Modell zur Datenhaltung und liegt daher nichts in BibliothekLib.
Die Bestellung beinhaltet Attribute für Besteller und bestelltes Werk, sowie deren IDs.
public class Order
{
[Key]
[Column(Order = 0)]
public int UserId { get; set; }
[Key]
[Column(Order = 1)]
public int WorkId { get; set; }
public User User { get; set; }
public Work Work { get; set; }
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Bestellen für Kunden
Auf Basis der ViewModel-Klasse Order kann nun ein neuer Controller angelegt werden.
Obwohl die Objekt nicht dort abgespeichert werden, wird als Context trotzdem der BibliothekModelContainer angegeben, weil der Codegenerator dann den Zugriff auf die Datenbank mit generiert.
Der Codegenerator generiert dann auch einen DBSet im BibliothekModelContainer zum Zugriff auf die Bestellungen, dieser muss sofort wieder gelöscht werden, der Compiler kann die Abhängigkeiten ohnehin nicht auflösen und meldet einen Fehler.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Der generierte Controller versucht in seinen Methoden die Bestellung mittels einer Id aus der Datenbank zu laden. Das ist in diesem Fall nicht möglich.
Alle Methoden müssen so umgestellt werden, dass eine Bestellung als Objekt vom Typ Order mittels je einer Id für ein Werk und einen Besteller erzeugt werden kann.
Die Methode GetOrderOrError() übernimmt dabei auch die üblichen Prüfungen und gibt daher entweder eine Order oder einen Fehler in Form eines ActionResult zurück. Dazu wird die Hilfsklasse OrderOrError erstellt.
public class OrdersController : Controller
{
private class OrderOrError
{
public ActionResult Error;
public Order Order;
public OrderOrError (ActionResult error)
{
Error = error;
}
public OrderOrError(HttpStatusCode errorStatus)
{
Error =
new HttpStatusCodeResult(errorStatus);
}
public OrderOrError(Order order)
{
Order = order;
}
}
private OrderOrError GetOrderOrError(
int? userId, int? workId)
{...}
...
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Da die Prüfung der Übergabeparameter stets gleich ist, werden diese nicht in den einzelnen Methoden geprüft, sondern ohne Vorprüfung an GetOrderOrError() übergeben.
Hier wird zunächst auf die bekannte Weise der eingeloggt User ermittelt und im ViewBag gespeichert. Ist der User ungültig erfolgt auch hier die bekannte Rückgabe des Fehlers eines unautorisierten Zugriffs.
Dann wird jeweils geprüft ob überhaupt Ids übergeben wurden. Ist dies nicht der Fall wird die Schnittstelle nicht bestimmungsgemäß genutzt und eine entsprechende Fehlermeldung erzeugt.
private OrderOrError GetOrderOrError(
int? userId, int? workId)
{
if (!hasUser())
return new
OrderOrError(HttpStatusCode.Unauthorized);
if (userId == null)
return new
OrderOrError(HttpStatusCode.BadRequest);
if (workId == null)
return new
OrderOrError(HttpStatusCode.BadRequest);
...
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Dann werden anhand der Ids der Besteller und das zu bestellende Werk aus der Datenbank geladen. Ist dies nicht möglich liegt wieder ein fehlerhafter Aufruf vor. (Hier könnte man auch einen “Nicht gefunden”-Fehler erzeugen, dieser ist aber für das Nichtvorhandensein einer Ressource vorgesehen und hier wird ja implizit auf zwei Ressourcen zugegriffen.)
Sind beide Objekte gefunden, so wird ein Object vom Typ Order erstellt, dass eine mögliche Bestellung des Werkes durch den User repräsentiert. Was mit dieser Bestellung geschieht (erstelle, löschen, anzeigen), hängt von der jeweiligen Methode ab.
private OrderOrError GetOrderOrError(
int? userId, int? workId)
{
…
Work work = db.Works.Find(workId);
if (work == null)
return new
OrderOrError(HttpStatusCode.BadRequest);
User user = db.Users.Find(userId);
if (user == null)
return new
OrderOrError(HttpStatusCode.BadRequest);
return new OrderOrError(new Order()
{ User = user, Work = work });
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Das Laden aller Bestellungen aus der Datenbank muss so angepasst werden, dass für jede Relation ein entsprechendes Objekt erstellt wird. Am einfachsten geht dies über eine entsprechende LINQ Abfrage.
public ActionResult Index()
{
var orders =
from user in db.Users
from work in db.Works
where user.Orders.Contains(work)
select new Order()
{User = user, Work = work};
return View(orders.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen
Die Methoden Details und Delete müssen so erweitert werden, dass je eine Id für den Besteller und für das zu bestellende Werk übergeben werden können.
Im View müssen die ActionLink so angepasst werden, dass beide Ids mit in die URL des Links eingebunden werden.
Die Auswertung erfolgt dann über die schon vorgestellte Methode GetOrderOrError().
<td>
@Html.ActionLink("Details", "Details",
new { userId = item.User.Id,
workId = item.Work.Id }) |
@Html.ActionLink("Löschen", "Delete",
new { userId = item.User.Id,
workId = item.Work.Id })
</td>
-----------------------------------------------
public ActionResult Details(
int? userId, int? workId)
{
var tmp = GetOrderOrError(userId, workId);
if (tmp.Error != null) return tmp.Error;
return View(tmp.Order);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen
Die Methoden Details und Delete müssen so erweitert werden, dass je eine Id für den Besteller und für das zu bestellende Werk übergeben werden können.
Im View müssen die ActionLink so angepasst werden, dass beide Ids mit in die URL des Links eingebunden werden.
Die Auswertung erfolgt dann über die schon vorgestellte Methode GetOrderOrError().
public ActionResult Delete(
int? userId, int? workId)
{
var tmp = GetOrderOrError(userId, workId);
if (tmp.Error != null) return tmp.Error;
return View(tmp.Order);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(
int? userId, int? workId)
{
var tmp = GetOrderOrError(userId, workId);
if (tmp.Error != null) return tmp.Error;
tmp.Order.User
.Orders.Remove(tmp.Order.Work);
db.SaveChanges();
return RedirectToAction("Index");
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Als RazorTemplate für “Create” erzeugt der Codegenerator für Order analog zu den anderen 1-n-Beziehungen eine Ansicht mit zwei DropDownlisten je eine für den User und das Werk.
public ActionResult Create()
{
ViewBag.UserId =
new SelectList(
db.Users, "Id", "AsString");
ViewBag.WorkId =
new SelectList(
db.Works, "Id", "AsString");
return View();
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Als Parameter für den Aufruf von Create, in dem das Element tatsächlich erzeugt werden soll, wird immer Objekt übergeben, das Werte für die Attribute mit primitiven Typen enthält und für alle Objekte deren Ids. In diesem Fall sind also UserId und WorkId gesetzt und können getOrderOrError() übergeben werden.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(
[Bind(Include = "UserId,WorkId")] Order order)
{
var tmp = GetOrderOrError(
order.UserId, order.WorkId);
if (tmp.Error != null) return tmp.Error;
if (ModelState.IsValid)
{
tmp.Order.User
.Orders.Add(tmp.Order.Work);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.UserId = new SelectList(
db.Users, "Id", "AsString", order.UserId);
ViewBag.WorkId = new SelectList(
db.Works, "Id", "AsString", order.WorkId);
return View(order);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Tritt dabei eine Fehler auf, so wird das entsprechende ActionResult zurückgegeben.
Danach folgt die übliche Auswertung des ModelState, ist dieser nicht in Ordnung wird erneut der View, diesmal mit vorausgewählten Besteller und Werk angezeigt.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(
[Bind(Include = "UserId,WorkId")] Order order)
{
var tmp = GetOrderOrError(
order.UserId, order.WorkId);
if (tmp.Error != null) return tmp.Error;
if (ModelState.IsValid)
{
tmp.Order.User
.Orders.Add(tmp.Order.Work);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.UserId = new SelectList(
db.Users, "Id", "AsString", order.UserId);
ViewBag.WorkId = new SelectList(
db.Works, "Id", "AsString", order.WorkId);
return View(order);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Tritt dabei eine Fehler auf, so wird das entsprechende ActionResult zurückgegeben.
Danach folgt die übliche Auswertung des ModelState, ist dieser nicht in Ordnung wird erneut der View, diesmal mit vorausgewählten Besteller und Werk angezeigt.
Ist der ModelState in Ordnung, so wird die Relation erzeugt, in dem in die Liste der Bestellungen des Users in der erzeugten Order das Werk, welches für die erzeugte Order ermittelt wurde, eingefügt wird.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(
[Bind(Include = "UserId,WorkId")] Order order)
{
var tmp = GetOrderOrError(
order.UserId, order.WorkId);
if (tmp.Error != null) return tmp.Error;
if (ModelState.IsValid)
{
tmp.Order.User
.Orders.Add(tmp.Order.Work);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.UserId = new SelectList(
db.Users, "Id", "AsString", order.UserId);
ViewBag.WorkId = new SelectList(
db.Works, "Id", "AsString", order.WorkId);
return View(order);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Listenansicht
Das Erzeugen einer Bestellung mittels der Auswahl aus zwei DropDownListen ist umständlich.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anwendungs-�funktionen
Allgemeines
Controller und Views für one-to-many-Relationen
Controller und Views für many-to-many-Relationen
- Werk bestellen
- Leihen eines Medium
- Leihen eines Werkes
- Zurückgeben und Stornieren
- Servicetheke: Bestellen für Kunden (einfach)
- Servicetheke: Bestellen für Kunden
- Servicetheke: Schnellbestellung
- Servicetheke: Rückgabe für Kunden
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Listenansicht
Das Erzeugen einer Bestellung mittels der Auswahl aus zwei DropDownListen ist umständlich.
Schöner wäre es aus Listen, in denen gesucht werden kann, die entsprechenden Elemente auszusuchen.
Das gilt auch für die anderen Ansichten, daher soll hier exemplarisch ein Filter eingefügt werden, der die Daten mittels JavaScript auf der Client-Seite filtert. In der Praxis müsste dies insbesondere bei großen Datenmengen so implementiert werden, dass nur die zur Darstellung notwendigen Daten überhaupt vom Server übertragen werden.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Listenansicht
Zunächst wird der HTML-Code angepasst. Oberhalb der Tabelle wird ein Feld zur Texteingabe eingefügt.
Die Dekoration des Eingabefeldes erfolgt - wie gehabt - über die Standard-Elemente von BootStrap.
<p>
@Html.ActionLink("Neu ...", "Create")
</p>
<p>
<div class="input-group">
<span class="input-group-addon">
<i class="glyphicon glyphicon-filter">
</i>
</span>
<input type="text"
class="form-control"
id="filterText"
onkeyup="filter()"
placeholder="Filter eingeben ...">
</div>
</p>
<table class="table" id="filterTable">
…
</table>
@Scripts.Render("~/Scripts/dataFilter.js")
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Listenansicht
Zunächst wird der HTML-Code angepasst. Oberhalb der Tabelle wird ein Feld zur Texteingabe eingefügt.
Die Dekoration des Eingabefeldes erfolgt - wie gehabt - über die Standard-Elemente von BootStrap.
Das Attribute onkeyup erlaubt die Verknüpfung des Feldes mit einer lokalen JavaScript-Funktion, die jedes mal ausgeführt wird, wenn ein Tastendruck beendet wird, also ein Buchstabe eingegeben wurde.
<p>
@Html.ActionLink("Neu ...", "Create")
</p>
<p>
<div class="input-group">
<span class="input-group-addon">
<i class="glyphicon glyphicon-filter">
</i>
</span>
<input type="text"
class="form-control"
id="filterText"
onkeyup="filter()"
placeholder="Filter eingeben ...">
</div>
</p>
<table class="table" id="filterTable">
…
</table>
@Scripts.Render("~/Scripts/dataFilter.js")
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Listenansicht
Außerdem werden dem Textfeld die id “filterText” und der Tabelle die id “filterTable” zugewiesen. Über diesen einmaligen Identifier kann die JavaScript-Methode filter() die beiden Elemente finden um darauf zuzugreifen und diese zu verändern.
<p>
@Html.ActionLink("Neu ...", "Create")
</p>
<p>
<div class="input-group">
<span class="input-group-addon">
<i class="glyphicon glyphicon-filter">
</i>
</span>
<input type="text"
class="form-control"
id="filterText"
onkeyup="filter()"
placeholder="Filter eingeben ...">
</div>
</p>
<table class="table" id="filterTable">
…
</table>
@Scripts.Render("~/Scripts/dataFilter.js")
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Listenansicht
Außerdem werden dem Textfeld die id “filterText” und der Tabelle die id “filterTable” zugewiesen. Über diesen einmaligen Identifier kann die JavaScript-Methode filter() die beiden Elemente finden um darauf zuzugreifen und diese zu verändern.
Für JavaScript ist in der VisualStudio- Solution der Ordner Scripts vorgesehen. Hier wird eine Datei “dataFilter.js” angelegt, die den Quellcode der Methode filter() enthält. Der Aufruf “Scripts.Render” stellt sicher, dass das JavaScript in die HTML-Seite eingebunden wird.
<p>
@Html.ActionLink("Neu ...", "Create")
</p>
<p>
<div class="input-group">
<span class="input-group-addon">
<i class="glyphicon glyphicon-filter">
</i>
</span>
<input type="text"
class="form-control"
id="filterText"
onkeyup="filter()"
placeholder="Filter eingeben ...">
</div>
</p>
<table class="table" id="filterTable">
…
</table>
@Scripts.Render("~/Scripts/dataFilter.js")
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Listenansicht
Der Aufbau von filter() ist einfach gehalten. Beim Aufruf der Methode (nach der Eingabe eines Buchstaben) werden zunächst über die entsprechenden ids Objekte beschafft, welche das Textfeld und die Tabelle repräsentieren und einen Zugriff darauf ermöglichen.
function filter() {
var input, filter, table, tr, td, i, ii, buf;
input = document.getElementById("filterText");
filter = input.value.toUpperCase();
table = document.getElementById("filterTable");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td");
buf = "";
if (td && td.length>0){
for (ii = 0; ii < td.length-1; ii++){
buf = buf + " " +
td[ii].innerHTML.toUpperCase();
}
if (buf.indexOf(filter) > -1){
tr[i].style.display = "";
}
else{
tr[i].style.display = "none";
}
}
}
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Listenansicht
Der Aufbau von filter() ist einfach gehalten. Beim Aufruf der Methode (nach der Eingabe eines Buchstaben) werden zunächst über die entsprechenden ids Objekte beschafft, welche das Textfeld und die Tabelle repräsentieren und einen Zugriff darauf ermöglichen.
Dann werden für jede Zeile die Inhalte aller Datenzellen (td) bis auf die letzte zusammengefasst ...
function filter() {
var input, filter, table, tr, td, i, ii, buf;
input = document.getElementById("filterText");
filter = input.value.toUpperCase();
table = document.getElementById("filterTable");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td");
buf = "";
if (td && td.length>0){
for (ii = 0; ii < td.length-1; ii++){
buf = buf + " " +
td[ii].innerHTML.toUpperCase();
}
if (buf.indexOf(filter) > -1){
tr[i].style.display = "";
}
else{
tr[i].style.display = "none";
}
}
}
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Listenansicht
Der Aufbau von filter() ist einfach gehalten. Beim Aufruf der Methode (nach der Eingabe eines Buchstaben) werden zunächst über die entsprechenden ids Objekte beschafft, welche das Textfeld und die Tabelle repräsentieren und einen Zugriff darauf ermöglichen.
Dann werden für jede Zeile die Inhalte aller Datenzellen (td) bis auf die letzte zusammengefasst und geprüft, ob der Filter-Text enthalten ist. Ist der Text nicht enthalten wird die Zeile ausgeblendet, andernfalls eingeblendet.
function filter() {
var input, filter, table, tr, td, i, ii, buf;
input = document.getElementById("filterText");
filter = input.value.toUpperCase();
table = document.getElementById("filterTable");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td");
buf = "";
if (td && td.length>0){
for (ii = 0; ii < td.length-1; ii++){
buf = buf + " " +
td[ii].innerHTML.toUpperCase();
}
if (buf.indexOf(filter) > -1){
tr[i].style.display = "";
}
else{
tr[i].style.display = "none";
}
}
}
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Listenansicht
Als Konvention sind hier also festgelegt: Die ids des Textfeldes (fitlerText) und der Tabelle (filterTable) und dass jeweils in der letzten Spalte keine Daten, sondern Funktionselemente enthalten sind und diese Zellen daher für das ein- und ausblenden nicht mit ausgewertet werden dürfen.
function filter() {
var input, filter, table, tr, td, i, ii, buf;
input = document.getElementById("filterText");
filter = input.value.toUpperCase();
table = document.getElementById("filterTable");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td");
buf = "";
if (td && td.length>0){
for (ii = 0; ii < td.length-1; ii++){
buf = buf + " " +
td[ii].innerHTML.toUpperCase();
}
if (buf.indexOf(filter) > -1){
tr[i].style.display = "";
}
else{
tr[i].style.display = "none";
}
}
}
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Mehrstufige Auswahl
Für die mehrstufige Auswahl von Besteller und bestelltem Werk, wird je ein View SelectUser und ein View SelectWork zum OdersController generiert.
Vorher wird Create() so angepasst, dass es analog zum Delete() und Details() mit den Id für User und Work aufgerufen werden kann. Dieser Aufruf von Create() dient später in den Listen als Ziel für die ActionLinks.
Werden keine Ids übergeben verhält sich Create() wie gewohnt.
public ActionResult Create(
int? userId, int? workId)
{
if (userId != null && workId != null)
return Create(
new Order {
UserId = userId,
WorkId = workId});
ViewBag.UserId =
new SelectList(db.Users,
"Id", "AsString");
ViewBag.WorkId =
new SelectList(db.Works,
"Id", "AsString");
return View();
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Auswahl Besteller
Die Auswahl des Bestellers und die Auswahl des bestellten Werkes sollen in beliebiger Reihenfolge erfolgen können.
Für die Auswahl eines Bestellers wird ein zunächst ein View, der auf dem Template List basiert und User auflistet erstellt.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die dazu passende Methode SelectUser() im OrdersController wird so implementiert, dass optional eine workId für ein schon ausgewähltes Werk übergeben werden kann. Wird diese übergeben und es kann ein entsprechendes Objekt beschafft werden, dann wird dieses Werk im ViewBag als Work abgelegt.
public ActionResult SelectUser(int? workId)
{
if (workId != null)
{
var work = db.Works.Find(workId);
if (work == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
ViewBag.Work = work;
}
return View(db.Users
.Where(u => u.RoleCostumer)
.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die dazu passende Methode SelectUser() im OrdersController wird so implementiert, dass optional eine workId für ein schon ausgewähltes Werk übergeben werden kann. Wird diese übergeben und es kann ein entsprechendes Objekt beschafft werden, dann wird dieses Werk im ViewBag als Work abgelegt.
In beiden Fällen sollen nur User aufgelistet werden, die auch Kunden sind.
public ActionResult SelectUser(int? workId)
{
if (workId != null)
{
var work = db.Works.Find(workId);
if (work == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
ViewBag.Work = work;
}
return View(db.Users
.Where(u => u.RoleCostumer)
.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Der View wird so angepasst, dass er den vorgestellten Mechanismus zum Filtern von Bestellern beinhaltet.
Außerdem wird oberhalb der Tabelle überprüft, ob schon ein Werk im ViewBag abgelegt ist. Ist dies der Fall wird angezeigt, dass der passende Besteller für dieses Werk ausgewählt werden soll.
@if (ViewBag.Work != null)
{
<h2>Werk zur Vorstellung</h2>
@ViewBag.Work.ToString();
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Im Bereich der Funktionselemente für jeden möglichen Besteller wird neben den schon bekannten Schaltfläche für eine Detailansicht eine Schaltfläche für den nächsten Schritt angezeigt.
<td>
@Html.ActionLink(
"Details", "Details", "Users",
new {id = item.Id}, null) |
@if (ViewBag.Work == null)
{
@Html.ActionLink(
"Wählen", "SelectWork",
new {userId = item.Id})
}
else
{
@Html.ActionLink(
"Bestellen", "Create",
new {workId = ViewBag.Work.Id,
userId = item.Id})
}
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Im Bereich der Funktionselemente für jeden möglichen Besteller wird neben den schon bekannten Schaltfläche für eine Detailansicht eine Schaltfläche für den nächsten Schritt angezeigt.
Wurde schon ein Werk ausgewählt, liegt es im ViewBag und es wird die Funktion “Bestellen” angezeigt. Das Ziel ist hier die erweiterte Funktion Create(), die als Parameter die userId des User Objektes für diese Zeile und die Id des Werkes aus dem ViewBag als workId bekommt.
<td>
@Html.ActionLink(
"Details", "Details", "Users",
new {id = item.Id}, null) |
@if (ViewBag.Work == null)
{
@Html.ActionLink(
"Wählen", "SelectWork",
new {userId = item.Id})
}
else
{
@Html.ActionLink(
"Bestellen", "Create",
new {workId = ViewBag.Work.Id,
userId = item.Id})
}
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Im Bereich der Funktionselemente für jeden möglichen Besteller wird neben den schon bekannten Schaltfläche für eine Detailansicht eine Schaltfläche für den nächsten Schritt angezeigt.
Wurde schon ein Werk ausgewählt, liegt es im ViewBag und es wird die Funktion “Bestellen” angezeigt. Das Ziel ist hier die erweiterte Funktion Create(), die als Parameter die userId des User Objektes für diese Zeile und die Id des Werkes aus dem ViewBag als workId bekommt.
Ist kein Werk ausgewählt, ist der nächste Schritt die Wahl des Werkes über SelectWork, hier wird dann die userId als Parameter übergeben.
<td>
@Html.ActionLink(
"Details", "Details", "Users",
new {id = item.Id}, null) |
@if (ViewBag.Work == null)
{
@Html.ActionLink(
"Wählen", "SelectWork",
new {userId = item.Id})
}
else
{
@Html.ActionLink(
"Bestellen", "Create",
new {workId = ViewBag.Work.Id,
userId = item.Id})
}
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Auswahl Werk
Die Auswahl des Bestellers und die Auswahl des bestellten Werkes sollen in beliebiger Reihenfolge erfolgen können, als Gegenstück zu SelectUser wird daher noch SelectWork erstellt.
Dieser View basiert auch auf dem Template List.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die dazu passende Methode SelectWork() im OrdersController wird so implementiert, dass optional eine userId für einen schon ausgewählte Besteller übergeben werden kann.
Wird diese übergeben und es kann ein entsprechendes Objekt beschafft werden, dann wird das User Objekt im ViewBag als Costumer abgelegt.
public ActionResult SelectWork(int? userId)
{
if (userId != null)
{
var user = db.Users.Find(userId);
if (user == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
ViewBag.Costumer = user;
}
return View(db.Works.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die dazu passende Methode SelectWork() im OrdersController wird so implementiert, dass optional eine userId für einen schon ausgewählte Besteller übergeben werden kann.
public ActionResult SelectWork(int? userId)
{
if (userId != null)
{
var user = db.Users.Find(userId);
if (user == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
ViewBag.Costumer = user;
}
return View(db.Works.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die dazu passende Methode SelectWork() im OrdersController wird so implementiert, dass optional eine userId für einen schon ausgewählte Besteller übergeben werden kann.
Wird diese übergeben und es kann ein entsprechendes Objekt beschafft werden, dann wird das User Objekt im ViewBag als Costumer abgelegt.
public ActionResult SelectWork(int? userId)
{
if (userId != null)
{
var user = db.Users.Find(userId);
if (user == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
ViewBag.Costumer = user;
}
return View(db.Works.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Auch dieser View wird so angepasst, dass er den vorgestellten Mechanismus zum Filtern von Werken beinhaltet.
Außerdem wird oberhalb der Tabelle überprüft, ob schon ein Kunde im ViewBag abgelegt ist. Ist dies der Fall wird angezeigt, dass das zu bestellende Werk für diesen Kunden ausgewählt werden soll.
@if (ViewBag.Costumer != null)
{
<h2>Vorbestellung für</h2>
@ViewBag.Costumer.ToString();
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Der Bereich der Funktionselemente für jedes mögliche Werk zur Bestellung wird analog angepasst.
<td>
@Html.ActionLink(
"Details", "Details", "Works",
new {id = item.Id}, null) |
@if (ViewBag.Costumer == null)
{
@Html.ActionLink(
"Wählen", "SelectUser",
new {workId = item.Id})
}
else
{
@Html.ActionLink(
"Bestellen", "Create",
new {
userId = ViewBag.Costumer.Id,
workId = item.Id})
}
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Der Bereich der Funktionselemente für jedes mögliche Werk zur Bestellung wird analog angepasst.
Wurde schon ein Kunde ausgewählt, liegt dieser im ViewBag und es wird die Funktion “Bestellen” angezeigt. Das Ziel ist wieder die erweiterte Funktion Create(), die als Parameter die workId des Work Objektes für diese Zeile und als userId die Id des Costumers aus dem ViewBag bekommt.
<td>
@Html.ActionLink(
"Details", "Details", "Works",
new {id = item.Id}, null) |
@if (ViewBag.Costumer == null)
{
@Html.ActionLink(
"Wählen", "SelectUser",
new {workId = item.Id})
}
else
{
@Html.ActionLink(
"Bestellen", "Create",
new {
userId = ViewBag.Costumer.Id,
workId = item.Id})
}
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Der Bereich der Funktionselemente für jedes mögliche Werk zur Bestellung wird analog angepasst.
Wurde schon ein Kunde ausgewählt, liegt dieser im ViewBag und es wird die Funktion “Bestellen” angezeigt. Das Ziel ist wieder die erweiterte Funktion Create(), die als Parameter die workId des Work Objektes für diese Zeile und als userId die Id des Costumers aus dem ViewBag bekommt.
Ist kein Kunde vorausgewählt, ist der nächste Schritt die Wahl des Kunden über SelectUser, hier wird dann die workId als Parameter übergeben.
<td>
@Html.ActionLink(
"Details", "Details", "Works",
new {id = item.Id}, null) |
@if (ViewBag.Costumer == null)
{
@Html.ActionLink(
"Wählen", "SelectUser",
new {workId = item.Id})
}
else
{
@Html.ActionLink(
"Bestellen", "Create",
new {
userId = ViewBag.Costumer.Id,
workId = item.Id})
}
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anwendungs-�funktionen
Allgemeines
Controller und Views für one-to-many-Relationen
Controller und Views für many-to-many-Relationen
- Werk bestellen
- Leihen eines Medium
- Leihen eines Werkes
- Zurückgeben und Stornieren
- Servicetheke: Bestellen für Kunden (einfach)
- Servicetheke: Bestellen für Kunden
- Servicetheke: Schnellbestellung
- Servicetheke: Rückgabe für Kunden
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Schnellbestellung
In vielen Anwendungsfällen kann es notwendig sein eine große Menge von Relationen im Überblick zu haben. Insbesondere bei n-zu-m-Beziehungen bedarf es oft einer angepassten Darstellung.
Für die Funktion der Vorbestellung bzw. Deren Stornierung durch einen Mitarbeiter soll hier exemplarisch eine Benutzungsoberfläche erstellt werden.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Schnellbestellung
Alle möglichen Bestellungen sollen als Tabelle von Checkboxen dargestellt werden, wobei eine Richtung die Benutzer und die andere Werke beschreibt.
Als Basis für diese Funktion wird für ‘Oders’ ein leerer View ‘QuickEdit’ erzeugt.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
ViewModel
Analog zur Darstellung der Order, die im Datenmodell nur als Relation vorhanden ist, für die es aber trotzdem Ansichten gibt, wird zunächst ein Modell entworfen, in dem die möglichen Bestell-Relationen zwischen Werken und Kunden als Attribute dargestellt sind.
Nebenstehendes Instanzendiagramm verdeutlicht diesen Ansatz. Für die unten dargestellten Objekte müssen nun also passende Typen resp. Klassen erstellt werden.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
ViewModel
Als Basis für die Darstellung eines Zustandes aller Bestellungen im System wird die Klasse OrderSelectViewModel eingeführt.
Aus dem Instanzendiagramm ergibt sich, dass diese mit mehreren Objekten, die das jeweils das Bestellverhalten eines einzelnen Users repräsentieren, verknüpft ist.
Daher enthält die Klasse eine Liste mit UserOrderSelectViewModel, welche diese Aufgabe übernehmen.
public class OrderSelectViewModel
{
public List<UserOrderSelectViewModel>
UserOrderSelects { get; set; } =
new List<UserOrderSelectViewModel>();
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
ViewModel
Die Klasse UserOrderSelectViewModel repräsentiert das Bestellverhalten (die Summer der Bestellungen) eines einzelnen Kunden und enthält daher zunächst einen Verweis auf den User, dessen Bestellverhalten repräsentiert werden soll und eine Liste mit Elementen vom Typ WorkUserOderSelectViewModel, die wiederum repräsentieren, ob dieser User ein bestimmtes Werk bestellt hat.
Außerdem ist noch ein DisplayName enthalten, der später die Darstellung im View vereinfachen soll.
public class UserOrderSelectViewModel
{
public List<WorkUserOrderSelectViewModel>
WorkUserOrderSelects { get; set; } =
new List<WorkUserOrderSelectViewModel>();
public int? UserId { get; set; }
public User User { get; set; }
public string DisplayName { get; set; }
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
ViewModel
Die Klasse WorkUserOderSelectViewModel repräsentiert ob ein User eine Werk vorbestellt hat oder nicht und enthält daher zunächst einen Verweis auf das entsprechende Werk und einen Wert vom Typ Boolean, der diesen Zustand darstellt.
Außerdem ist noch ein DisplayName enthalten, der später die Darstellung im View vereinfachen soll.
public class WorkUserOrderSelectViewModel
{
public int? WorkId { get; set; }
public Work Work { get; set; }
public bool IsSelected { get; set; }
public string DisplayName { get; set; }
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
ViewModel
Das komplette ViewModel wird im Ordner ‘Models’ in der VisualStudio-Solution abgelegt.
public class WorkUserOrderSelectViewModel
{
public int? WorkId { get; set; }
public Work Work { get; set; }
public bool IsSelected { get; set; }
public string DisplayName { get; set; }
}
public class UserOrderSelectViewModel
{
public List<WorkUserOrderSelectViewModel>
WorkUserOrderSelects { get; set; } =
new List<WorkUserOrderSelectViewModel>();
public int? UserId { get; set; }
public User User { get; set; }
public string DisplayName { get; set; }
}
public class OrderSelectViewModel
{
public List<UserOrderSelectViewModel>
UserOrderSelects { get; set; } =
new List<UserOrderSelectViewModel>();
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die Methoden QuickEdit() sind analog zu den Methoden Create() aufgebaut. Wird die Methode ohne Parameter aufgerufen, wird ein View erzeugt, der einen Editor enthalten sollte und ein Default-Objekt als Model-Parameter übergeben bekommt.
Der Aufruf mittels der Methode Post erwartet dann das Objekt in der Form, wie es vom User bearbeitet wurde.
public ActionResult QuickEdit()
{
var viewModel = new OrderSelectViewModel();
...
return View(viewModel);
}
[HttpPost]
public ActionResult QuickEdit(
OrderSelectViewModel viewModel)
{
if (ModelState.IsValid)
{
...
db.SaveChanges();
return RedirectToAction("Index");
}
return View(viewModel);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die nächsten Schritte sind also nun ein Default-Objekt zu erstellen, dass den aktuellen Bestellzustand abbildet, einen Editor für Objekt vom Typ OrderSelectViewModel zu implementeren und die zweite QuickEdit()-Methode so anzupassen, dass anhand eine OrderSelectViewModel-Objektes die entsprechenden Relationen in der Datenbank erstellt bzw. gelöscht werden.
public ActionResult QuickEdit()
{
var viewModel = new OrderSelectViewModel();
…
return View(viewModel);
}
[HttpPost]
public ActionResult QuickEdit(
OrderSelectViewModel viewModel)
{
if (ModelState.IsValid)
{
…
db.SaveChanges();
return RedirectToAction("Index");
}
return View(viewModel);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Default Objekt erzeugen
Zum Erzeugen des Ausgangsobjektes werden zunächst die Listen aller Werke und aller Kunden (User mit RoleCostumer) beschafft. Dann wird für jeden Kunden ein UserOrderSelectViewModel erzeugt, in das dann wiederum für jedes Werk ein WorkUserOrderSelectViewModel eingefügt wird.
Der Wert für IsSelected ergibt sich hierbei aus der Abfrage, ob die Liste der Bestellungen des User das entsprechende Werk enthält.
Abschließend wird dem View das erzeugt Objekt als Model übergeben.
public ActionResult QuickEdit()
{
var viewModel = new OrderSelectViewModel();
var works = db.Works.ToList();
foreach (var user in db.Users.Where(u => u.RoleCostumer))
{
var currentUserView = new UserOrderSelectViewModel
{
UserId = user.Id,
DisplayName = user.Name + " " + user.Familyname
};
foreach (var work in works)
{
var currentUserWorkView =
new WorkUserOrderSelectViewModel
{
DisplayName = work.Titel + " von "
+ work.Author,
WorkId = work.Id,
IsSelected = user.Orders.Contains(work)
};
currentUserView.WorkUserOrderSelects
.Add(currentUserWorkView);
}
viewModel.UserOrderSelects.Add(currentUserView);
}
return View(viewModel);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Der View QuickEdit wird zunächst so angepasst, dass als Modell ein OrderSelectViewModel übergeben werden kann.
@model BibliothekWebApp.Models.OrderSelectViewModel
<h2>QuickEdit</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<div class="form-group">
<table>
<tr>
…
</tr>
@Html.EditorFor(
model => Model.UserOrderSelects)
</table>
</div>
<div class="form-group">
<div class="col-md-10">
<input type="submit"
value="Speichern"
class="btn btn-default"/>
</div>
</div>
</div>
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Der View QuickEdit wird zunächst so angepasst, dass als Modell ein OrderSelectViewModel übergeben werden kann.
Innerhalb des Views wird dann mittels der Hilfsfunktion Html.EditorFor() ein Editor für alle enthaltenen UserOrderSelectViewModel erzeugt. Diese Vorgehen ist von der Erzeugung von Editoren für Standardtypen bereits bekannt. So wird vom Code-Generator für die Funktionen Create und Update für alle Standardtypen ein entsprechender Eintrag in den Views erzeugt.
@model BibliothekWebApp.Models.OrderSelectViewModel
<h2>QuickEdit</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<div class="form-group">
<table>
<tr>
…
</tr>
@Html.EditorFor(
model => Model.UserOrderSelects)
</table>
</div>
<div class="form-group">
<div class="col-md-10">
<input type="submit"
value="Speichern"
class="btn btn-default"/>
</div>
</div>
</div>
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Der View QuickEdit wird zunächst so angepasst, dass als Modell ein OrderSelectViewModel übergeben werden kann.
Innerhalb des Views wird dann mittels der Hilfsfunktion Html.EditorFor() ein Editor für alle enthaltenen UserOrderSelectViewModel erzeugt. Diese Vorgehen ist von der Erzeugung von Editoren für Standardtypen bereits bekannt. So wird vom Code-Generator für die Funktionen Create und Update für alle Standardtypen ein entsprechender Eintrag in den Views erzeugt.
Für den Typ UserOrderSelectViewModel soll jetzt ein eigener Editor erstellt werden.
@model BibliothekWebApp.Models.OrderSelectViewModel
<h2>QuickEdit</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<div class="form-group">
<table>
<tr>
…
</tr>
@Html.EditorFor(
model => Model.UserOrderSelects)
</table>
</div>
<div class="form-group">
<div class="col-md-10">
<input type="submit"
value="Speichern"
class="btn btn-default"/>
</div>
</div>
</div>
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Erstellen Editor
Für den Typ UserOrderSelectViewModel soll jetzt ein eigener Editor erstellt werden.
Es ist ausreichend einen Editor für einen einzelnen Wert zu erzeugen, da die Hilfsmethode Html.EditorFor() automatisch eine Liste von Editoren erstellt, wenn, wie in diesem Beispiel eine Liste von Werten übergeben wird.
Die Vorlagen für die Editoren müssen im Verzeichnis Shared/EditorTemplates als Views mit dem Namen des darzustellenden Typen angelegt werden. Der Typ des Modell ist hier UserOrderSelectViewModel.
@model BibliothekWebApp.Models.
UserOrderSelectViewModel
<tr>
<td>
@Html.HiddenFor(model => Model.UserId)
@Model.DisplayName
</td>
@Html.EditorFor(model =>
Model.WorkUserOrderSelects)
</tr>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Erstellen Editor
Da Http-Post-Request, den ein Web-Browser beim abschicken (submit) eines Formulars nur sog. Key-Value-Pairs, also Paare aus Schlüssel und Datenwert zugelassen sind, erzeugt die Methode Html.EditorFor() die Schlüssel so, dass beim Zurücksenden des Formulars passende Objekte erzeugt und die Werte in den jeweils passenden Attribute abgelegt werden.
@model BibliothekWebApp.Models.
UserOrderSelectViewModel
<tr>
<td>
@Html.HiddenFor(model => Model.UserId)
@Model.DisplayName
</td>
@Html.EditorFor(model =>
Model.WorkUserOrderSelects)
</tr>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Erstellen Editor
Für alle Werte, die beim Zurücksenden wieder gebraucht werden, die der Anwender aber nicht sehen soll. Können versteckte Formularfelder erzeugt werden. In MVC.Net geschieht dies über die Methode Html.HiddenFor(), die analog zu Html.EditorFor() auch das kodieren des Schlüssels implementiert. In diesem Beispiel wird so die Id des User-Objektes erhalten.
Das Erzeugen der Zellen für die einzelnen WorkUserOrderSelectViewModel erfolgt wieder über einen eigenen Editor.
@model BibliothekWebApp.Models.
UserOrderSelectViewModel
<tr>
<td>
@Html.HiddenFor(model => Model.UserId)
@Model.DisplayName
</td>
@Html.EditorFor(model =>
Model.WorkUserOrderSelects)
</tr>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Erstellen Editor
Der Editor WorkUserOrderSelectViewModel ist analog aufgebaut und liegt ebenfalls in Shared/EditorTemplates.
Neben dem versteckten Feld für die Id des Werkes enthält der Editor noch einen Editor für das Attribute IsSelected. Da IsSelected vom Typ boolean einem Standardtyp ist, wird hier einfach der Standardeditor verwendet.
@model BibliothekWebApp.Models
.WorkUserOrderSelectViewModel
<td>
@Html.HiddenFor(model => model.WorkId)
@Html.EditorFor(model => model.IsSelected)
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Nachdem alle Editoren erstellt sind, wird nach einem Submit des Formular aus dem View QuickEdit ein Objekt vom Typ OrderSelectViewModel an die Methode QuickEdit() übergeben, welches den kompletten Zustand der Bestellungen, so wie der Anwender ihn eingegeben hat, repräsentiert.
Diese Methode muss nun die abgespeicherten Daten dieser Vorgabe anpassen.
[HttpPost]
public ActionResult QuickEdit(
OrderSelectViewModel viewModel)
{
if (ModelState.IsValid)
{
int[] userIds = viewModel.UserOrderSelects
.Select(e => e.UserId??-1).ToArray();
if (userIds.Any(v => v < 0))
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
int[] workIds = viewModel.UserOrderSelects
.SelectMany(s => s.WorkUserOrderSelects
.Select(w => w.WorkId ?? -1))
.Distinct().ToArray();
if (workIds.Any(v => v < 0))
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
...
db.SaveChanges();
return RedirectToAction("Index");
}
return View(viewModel);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Nachdem alle Editoren erstellt sind, wird nach einem Submit des Formular aus dem View QuickEdit ein Objekt vom Typ OrderSelectViewModel an die Methode QuickEdit() übergeben, welches den kompletten Zustand der Bestellungen, wie der Anwender ihn eingegeben hat, repräsentiert.
Diese Methode muss nun die abgespeicherten Daten dieser Vorgabe anpassen.
Im ersten Schritt werden alle Ids zusammengesucht und geprüft ob welche fehlen, in diesem Fall wird ein Fehler erzeugt.
[HttpPost]
public ActionResult QuickEdit(
OrderSelectViewModel viewModel)
{
if (ModelState.IsValid)
{
int[] userIds = viewModel.UserOrderSelects
.Select(e => e.UserId??-1).ToArray();
if (userIds.Any(v => v < 0))
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
int[] workIds = viewModel.UserOrderSelects
.SelectMany(s => s.WorkUserOrderSelects
.Select(w => w.WorkId ?? -1))
.Distinct().ToArray();
if (workIds.Any(v => v < 0))
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
...
db.SaveChanges();
return RedirectToAction("Index");
}
return View(viewModel);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Im zweiten Schritt werden alle betroffenen Benutzer und Werke aus der Datenbank geladen und die Objekte in einem Dictionary abgelegt.
Dictionaries sind Listen, die einen schnellen Zugriff auf Objekte über einen Schlüssel- wert implementieren. Da im Folgenden mehrfach ein User oder Work anhand der Id gesucht wird, bietet es sich an, alle Objekte die benötigt werden in einem Schritt zu laden und in einem Dictionary abzulegen.
[HttpPost]
public ActionResult QuickEdit(
OrderSelectViewModel viewModel)
{
if (ModelState.IsValid)
{
...
Dictionary<int, User> users = db.Users
.Where(u => userIds.Contains(u.Id))
.ToDictionary(k => k.Id);
Dictionary<int, Work> works = db.Works
.Where(w => workIds.Contains(w.Id))
.ToDictionary(k => k.Id);
...
db.SaveChanges();
return RedirectToAction("Index");
}
return View(viewModel);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Nach den Prüfungen erfolgt die Umsetzung in das Datenmodell, dazu wird für jede im Viewmodel enthaltene Kombination aus User und Work überprüft, ob die Vorbestellungs-Relation vorhanden sein soll (IsSelected), soll sie vorhanden sein, wird geprüft ob sie vorhanden ist, wenn nicht, wir sie erzeugt. Soll sie nicht vorhanden sein, ist aber vorhanden, wird sie entfernt.
...
foreach (var userSelect in viewModel.UserOrderSelects){
User user;
if (!users.TryGetValue(userSelect.UserId??-1, out user))
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
userSelect.User = user;
foreach (var workSelect in userSelect.WorkUserOrderSelects)
{
Work work;
if (works.TryGetValue(workSelect.WorkId??-1,
out work))
return new HttpStatusCodeResult(
HttpStatusCode.BadRequest);
workSelect.Work = work;
if (workSelect.IsSelected)
{
if (!user.Orders.Contains(work))
user.Orders.Add(work);
}
else
{
if (user.Orders.Contains(work))
user.Orders.Remove(work);
}
}
}
…
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Für ein besser Übersicht werden die Tabelleköpfe, die den Titel der Werke enthalten um 45° gedreht. Der eingefügte css-Code rotiert einen in einem Tabel Header (th) enthaltene Division (div) um 45°. Die übrigen Attribute beschreiben die grundsätzliche Ausdehnung des Header und mit overflow:hidden wird dafür gesorgt, dass überstehende Elemente abgeschnitten werden. So können auch extrem lange Titel nicht das grundsätzliche Layout zerstören.
th.rotate {
height: 140px;
white-space: nowrap;
}
th.rotate > div {
transform: translate(-5px, 55px)
rotate(315deg);
width: 30px;
}
th.rotate > div > span {
border-bottom: 1px solid #ccc;
padding: 5px 10px;
}
table {
overflow: hidden;
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Zur besseren Navigation sollen die Spalte und und die Zeile, in der sich die Maus gerade befindet markiert werden. Dann kann der Anwender leichter erkennen für welchen User und welches Werk die Checkbox unter der Maus gelten.
Das einfärben der Reihen erfolgt einfach über den Pseudo-Selektor :hover. Zum Einfärben der Spalten werden mittels des ::after operators an jede Zelle sehr hohe Boxen angehängt, welche die Breite der Zelle haben und einen gelben Hintergrund. Durch die Kombination mit :hover sind diese Boxen nur dann sichtbar, wenn sich der Mauszeiger in der Zelle befindet.
tr:hover {
background-color: #ffa;
}
td, th {
position: relative;
}
td:hover::after,
th:hover::after
{
content: "";
position: absolute;
background-color: #ffa;
left: 0;
top: -5000px;
height: 10000px;
width: 100%;
z-index: -1;
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anwendungs-�funktionen
Allgemeines
Controller und Views für one-to-many-Relationen
Controller und Views für many-to-many-Relationen
- Werk bestellen
- Leihen eines Medium
- Leihen eines Werkes
- Zurückgeben und Stornieren
- Servicetheke: Bestellen für Kunden (einfach)
- Servicetheke: Bestellen für Kunden
- Servicetheke: Schnellbestellung
- Servicetheke: Rückgabe für Kunden
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Rückgabe für Kunden
Zur Vereinfachung der Rückgabe für einen Kunden durch einen Mitarbeiter, wird zunächst der Index-View für die Medien angepasst, da dieser schon Funktionselemente zur Rückgabe bereitstellt.
<td>
@Html.ActionLink(
"Details", "Details",
new { id=item.Id })
@if (item.User == null)
{
@:|
@Html.ActionLink(
"Leihen", "Borrow",
new {id = item.Id})
}
@if (item.User == ViewBag.User ||
(item.User != null &&
ViewBag.User.RoleStaff))
{
@:|
@Html.ActionLink(
"Zurückgeben", "Return",
new {id = item.Id})
}
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Rückgabe für Kunden
Zur Vereinfachung der Rückgabe für einen Kunden durch einen Mitarbeiter, wird zunächst der Index-View für die Medien angepasst, da dieser schon Funktionselemente zur Rückgabe bereitstellt.
Bisher wurde der Knopf für Rückgabe nur eingeblendet, wenn der eingeloggte Nutzer auch der Ausleiher ist. Diese Überprüfung wird dahingehende erweitert, dass dieser Knopf nun auch angezeigt wird, wenn das Buch ausgeliehen und der eingeloggte Anwender ein Mitarbeiter ist (RoleStaff).
<td>
@Html.ActionLink(
"Details", "Details",
new { id=item.Id })
@if (item.User == null)
{
@:|
@Html.ActionLink(
"Leihen", "Borrow",
new {id = item.Id})
}
@if (item.User == ViewBag.User ||
(item.User != null &&
ViewBag.User.RoleStaff))
{
@:|
@Html.ActionLink(
"Zurückgeben", "Return",
new {id = item.Id})
}
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Rückgabe für Kunden
Dann wird im BorrowController die Methode Index so erweitert, dass hier ein optionaler Parameter zur Auswahl des Users übergeben werden kann. Der View kann nach der Anpassung für beide Anwendungsfälle genutzt werden.
public ActionResult Index(int? userId)
{
if (!setUser()) return new
HttpStatusCodeResult(
HttpStatusCode.Unauthorized);
var media = (userId == null)
? db.Media
.Include(m => m.Work)
.Include(m => m.User)
: db.Media
.Where(m => m.User.Id ==userId)
.Include(m => m.Work)
.Include(m => m.User);
return View(media.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Rückgabe für Kunden
Außerdem muss BorrowController die Methode Return so geändert werden, dass Mitarbeiter Medien zurückgeben können, die von anderen Benutzern geliehen sind.
public ActionResult Return(int? id)
{
…
if (media.User != LoggedInUser
&& !LoggedInUser.RoleStaff)
{
ModelState.AddModelError(
"", "Medium ist nicht …");
}
if (!ModelState.Values
.SelectMany(v => v.Errors).Any())
{
media.User = null;
db.SaveChanges();
}
return View(media);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Ausleihen für Kunden
Zum Ausleihen für einen Kunden wird im BorrowController die Methode Borrow so erweitert, dass auch hier ein optionaler Parameter zur Auswahl des Users übergeben werden kann. Dieser View kann für beide Anwendungsfälle genutzt werden.
public ActionResult Borrow(
int? id, int? userId)
{
...
var user = (userId != null) ?
db.Users.Find(userId) : null;
media.User = user ?? LoggedInUser;
while (LoggedInUser.Orders
.Remove(media.Work)){}
db.SaveChanges();
return View(media);
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Neuer View
Für beide Aktionen muss der Mitarbeiter zunächst einen Kunden auswählen. Dazu wird ein View Service für den BorrowController erstellt. Da dieser zur Auswahl von Usern dienen soll, basiert er auf der Klasse User und dem Template List.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die Methode Service wird so anpasst, dass nur Kunden in der Liste der Auswahl erscheinen.
public ActionResult Service()
{
return View(db.Users
.Where(u => u.RoleCostumer)
.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Der View Service wird so angepasst, dass in der Auflistung nur Attribute erscheinen, die zur Identifikation eines Kunden notwendig sind.
In der letzten Spalte scheinen dann zwei ActionLinks: Einer für die Rückgabe, der auf den schon angepassten View Index verweist und entsprechend als Parameter eine userID übergibt, so dass im nächsten Arbeitsschritt aus der Liste der von diesem Kunden ausgeliehenen Medien eines zur Rückgabe ausgewählt werden kann.
<td>
@Html.ActionLink(
"Rückgabe", "Index",
new { userId=item.Id }) |
@Html.ActionLink(
"Ausleihen", "SelectMedia",
new { userId = item.Id })
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Der View Service wird so angepasst, dass in der Auflistung nur Attribute erscheinen, die zur Identifikation eines Kunden notwendig sind.
In der letzten Spalte scheinen dann zwei ActionLinks: Einer für die Rückgabe, der auf den schon angepassten View Index verweist und entsprechend als Parameter eine userID übergibt, so dass im nächsten Arbeitsschritt aus der Liste der von diesem Kunden ausgeliehenen Medien eines zur Rückgabe ausgewählt werden kann.
Und einer für das Ausleihen, der auf einen neuen View zur Auswahl eines Mediums verweist, so dass hier als nächster Arbeitsschritt ein Medium ausgewählt werden kann.
<td>
@Html.ActionLink(
"Rückgabe", "Index",
new { userId=item.Id }) |
@Html.ActionLink(
"Ausleihen", "SelectMedia",
new { userId = item.Id })
</td>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Auswahl Medium
Soll für einen Kunden ein Medium ausgeliehen werden, dann muss im zweiten Schritt das Medium, das geliehen werden soll, ausgewählt werden. Dazu wird im BorrowController ein View “SelectMedia” angelegt. Diese basiert auf den Template List und der Modellklasse Media.
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die zugrundeliegende Methode SelectMedia() erwartet als Parameter eine userId des Kunden, für den das Medium ausgeliehen werden soll.
public ActionResult SelectMedia(int? userId)
{
if (userId == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
var user = db.Users.Find(userId);
if (user == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
ViewBag.Costumer = user;
return View(db
.Media
.Where(m=>m.User==null)
.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die zugrundeliegende Methode SelectMedia() erwartet als Parameter eine userId des Kunden, für den das Medium ausgeliehen werden soll.
Wenn es eine gültige Id ist und das User-Objekt beschafft werden kann, wird es als Costumer im Viewbag abgelegt.
public ActionResult SelectMedia(int? userId)
{
if (userId == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
var user = db.Users.Find(userId);
if (user == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
ViewBag.Costumer = user;
return View(db
.Media
.Where(m=>m.User==null)
.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen Controller
Die zugrundeliegende Methode SelectMedia() erwartet als Parameter eine userId des Kunden, für den das Medium ausgeliehen werden soll.
Wenn es eine gültige Id ist und das User-Objekt beschafft werden kann, wird es als Costumer im Viewbag abgelegt.
Der View soll dann nur noch die Medien anzeigen, die auch ausleihbar sind.
public ActionResult SelectMedia(int? userId)
{
if (userId == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
var user = db.Users.Find(userId);
if (user == null) return new
HttpStatusCodeResult(
HttpStatusCode.BadRequest);
ViewBag.Costumer = user;
return View(db
.Media
.Where(m=>m.User==null)
.ToList());
}
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Anpassen View
Der View wird dann so angepasst, dass oberhalb der Liste der Kunde angezeigt wird, für den die Aktion ausgeführt wird.
Als ActionLink für die einzelnen Medien bleibt dann die Funktion zum Leihen. Nach der Anpassung können dieser Funktion zwei Parameter übergeben werden, die id des Mediums, die der Id des Elements für die aktuelle Zeile entspricht und die userId, die über den im ViewBag abgelegten Costumer ermittelt werden kann.
@model IEnumerable<BibliothekLib.Media>
@if (ViewBag.Costumer != null)
{
<h2>Ausliehen für</h2>
@ViewBag.Costumer.ToString();
}
<table class="table">
…
@foreach (var item in Model) {
<tr>
…
<td>
@Html.ActionLink(
"Leihen", "Borrow",
new {
id=item.Id,
userId= ViewBag.Costumer.Id})
</td>
</tr>
}
</table>
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen
Prof. Dr.-Ing. Jörn Schlingensiepen
https://lehre.schlingensiepen.com
Cliparts aus der Open Clip Art Library
Sushi für's Hirn
ICM@I3CM
Inverted Classroom Model im Institut für Ingenieurinformatik und computergestützte Mathematik (I3CM)
ICM@I3CM
Prof. Dr.-Ing. Jörn Schlingensiepen