9. Fallstudie
9.1. Introduction
Wir stellen eine Fallstudie vor, die bereits in einem Artikel veröffentlicht wurde, der unter URL [http://tahe.developpez.com/dotnet/pam-aspnet/] verfügbar ist. In diesem Artikel wird die Fallstudie mit dem klassischen ASP.NET und dem ORM NHibernate durchgeführt. Wir werden sie hier mit ASP.NET, MVC und dem Entity Framework ORM durchführen. Wie im bestehenden Artikel wird die Fallstudie als TD einer Hochschule präsentiert. Sie richtet sich daher an Studierende. Bei allen Fragen wird auf die soeben erläuterten Kapitel verwiesen, um nützliche Literaturhinweise zu geben.
9.2. Die zu lösende Aufgabe
Wir möchten eine Webanwendung erstellen, mit der ein Benutzer Simulationen zur Berechnung der Vergütung der Tagesmütter des Vereins „Maison de la petite enfance“ einer Gemeinde durchführen kann. Wir werden uns sowohl mit der Struktur des DotNet-Codes der Anwendung als auch mit dem Code selbst befassen.
Die Anwendung wird vom Typ APU [Application à Page Unique] sein und ausschließlich Ajax-Aufrufe zur Kommunikation mit dem Server verwenden. Sie wird dem Benutzer folgende Ansichten präsentieren:
- die Ansicht [VueSaisies], die das Simulationsformular anzeigt

- die Ansicht [VueSimulation], die zur Anzeige des detaillierten Simulationsergebnisses dient:

- die Ansicht [VueSimulations], die die Liste der vom Kunden durchgeführten Simulationen anzeigt

- die Ansicht [VueSimulationsVides], die anzeigt, dass der Kunde keine oder keine weiteren Simulationen mehr hat:

- die Ansicht [VueErreurs], die einen oder mehrere Fehler anzeigt (hier wurden die Simulationen SGBD und MySQL abgebrochen):

9.3. Anwendungsarchitektur
Die Architektur der Anwendung sieht wie folgt aus:
![]() |
Die Schicht [EF5] bezeichnet das Entity Framework 5 ORM. Das verwendete SGBD wird MySQL sein.
Wir werden diese Anwendung zunächst mit einer simulierten [métier]-Schicht erstellen:
![]() |
Dadurch können wir uns ausschließlich auf die [web]-Schicht konzentrieren. Die simulierte [métier]-Schicht wird die Schnittstelle der tatsächlichen [métier]-Schicht einhalten. Sobald die Schicht [web] betriebsbereit ist, werden die Schichten [métier], [DAO] und [EF5] erstellt.
9.4. Die Datenbank
Die für die Erstellung der Gehaltsabrechnung erforderlichen statischen Daten werden in einer Datenbank mit dem Namen MySQL abgelegt, die als [dbpam_ef5] bezeichnet wird (pam=Gehaltsabrechnung für Tagesmütter). Diese Datenbank verfügt über einen Administrator namens „root“ ohne Passwort. Sie enthält drei Tabellen:

Es besteht eine Fremdschlüsselbeziehung zwischen der Spalte EMPLOYES (INDEMNITE_ID) und der Spalte INDEMNITES (ID). Die Struktur dieser Datenbank ergibt sich aus ihrer Verwendung mit EF5. Wir werden darauf zurückkommen, wenn wir die unteren Schichten der Anwendung aufbauen.
Struktur:
![]() |
|
Sein Inhalt könnte wie folgt aussehen:
![]()
Struktur:
![]() |
|
Der Inhalt könnte wie folgt aussehen:
![]()
Die Sozialversicherungsbeiträge sind unabhängig vom Arbeitnehmer. Die vorstehende Tabelle enthält nur eine Zeile.
![]() |
|
Der Inhalt könnte wie folgt aussehen:
![]()
9.5. Berechnungsmethode für das Gehalt einer Tagesmutter
Im Folgenden wird die Berechnungsmethode für das Monatsgehalt einer Tagesmutter vorgestellt. Als Beispiel nehmen wir das Gehalt von Frau Marie Jouveinal, die im Abrechnungsmonat 150 Stunden an 20 Tagen gearbeitet hat.
Folgende Faktoren werden berücksichtigt: | [TOTALHEURES]: Gesamtzahl der im Monat geleisteten Arbeitsstunden [TOTALJOURS]: Gesamtzahl der im Monat gearbeiteten Tage | [TOTALHEURES]=150 [TOTALJOURS]= 20 |
Das Grundgehalt der Tagesmutter ergibt sich aus folgender Formel: | [SALAIREBASE]=([TOTALHEURES]*[BASEHEURE])*(1+[INDEMNITESCP]/100) | [SALAIREBASE]=(150*[2.1])*(1+0,15)= 362,25 |
Von diesem Grundgehalt sind bestimmte Sozialbeiträge abzuziehen: | Allgemeiner Sozialbeitrag und Beitrag zur Tilgung der Sozialschuld: [SALAIREBASE]*[CSGRDS/100] Abzugsfähiger allgemeiner Sozialbeitrag: [SALAIREBASE]*[CSGD/100] Sozialversicherung, Witwenrente, Altersrente: [SALAIREBASE]*[SECU/100] Zusatzrente + AGPF + Arbeitslosenversicherung: [SALAIREBASE]*[RETRAITE/100] | CSGRDS: 12,64 CSGD: 22,28 Sozialversicherung: 34,02 Rente: 28,55 |
Gesamtbetrag der Sozialbeiträge: | [COTISATIONSSOCIALES] = [SALAIREBASE] *(CSGRDS + CSGD + SECU + RETRAITE) / 100 | [COTISATIONSSOCIALES]=97,48 |
Darüber hinaus hat die Tagesmutter für jeden gearbeiteten Tag Anspruch auf eine Unterhaltszulage sowie eine Verpflegungszulage. In diesem Zusammenhang erhält sie folgende Zulagen: | [Indemnités]=[TOTALJOURS]*(ENTRETIENJOUR+REPASJOUR) | [INDEMNITES]=104 |
Letztendlich ergibt sich folgender Nettolohn, der an die Tagesmutter zu zahlen ist: | [SALAIREBASE] – [COTISATIONSSOCIALES] + [INDEMNITÉS] | [salaire NET]=368,77 |
9.6. Das Visual Studio-Projekt der Schicht [web]
Das Visual Web Developer-Projekt der Anwendung sieht wie folgt aus:
![]() |
- in [1] entspricht die allgemeine Projektstruktur von [pam-web-01];
- in [2] ist der Ordner [Content] der Ordner, in dem die statischen Ressourcen des Projekts abgelegt werden:
- [indicator.gif]: das animierte Bild, das angezeigt wird, während auf das Ende einer Ajax-Anfrage gewartet wird,
- [standard.jpg]: das Hintergrundbild der verschiedenen Ansichten,
- [Site.css]: das Stylesheet der Anwendung;
- in [3] den einzigen Controller der Anwendung [PamController];
- in [4] die Klassen, die für die Anwendung erforderlich sind, aber nicht als Elemente von MVC katalogisiert werden können:
- [ApplicationModelBinder]: die Klasse, die es ermöglicht, die Daten des Bereichs [Application] in das Aktionsmodell einzubinden,
- [SessionModelBinder]: die Klasse, die es ermöglicht, die Daten aus dem Bereich [Session] in das Aktionsmodell einzubinden,
- [Static]: eine Hilfsklasse mit statischen Methoden;
- in [5] die Vorlagen der Anwendung, seien es Aktions- oder Ansichtsvorlagen:
- [ApplicationModel]: Vorlage, die die Daten aus dem Bereich [Application] enthält,
- [SessionModel]: Modell, das die Daten aus dem Bereich [Session] enthält,
- [Simulation]: Klasse, die die Elemente einer Gehaltsberechnungssimulation kapselt,
- [IndexModel]: Modell der ersten Ansicht [Index], die von der Anwendung angezeigt wird;
- in [6] die für die Lokalisierung der Anwendung erforderlichen Skripte JS;
- in [7] die Skripte JS aus der Familie JQuery, die für die Internationalisierung, die clientseitige Validierung und die Ajax-Implementierung der Anwendung erforderlich sind;
- in [8] ist [myScripts.js] die Datei, die unsere eigenen Skripte JS enthält;
- in [9] befinden sich die Ansichten der Anwendung:
- [Index]: die Startseite,
- [Formulaire]: Formular zur Erfassung des Mitarbeiters sowie seiner Arbeitszeiten und -tage,
- [Simulation]: Ansicht mit einer Simulation,
- [Simulations]: Ansicht mit der Liste der durchgeführten Simulationen,
- [Erreurs]: Ansicht mit der Liste eventueller Fehler,
- [InitFailed]: Ansicht, die Fehlermeldungen anzeigt, falls die Initialisierung der Anwendung fehlschlägt;
- in [10] die Startseite der Anwendung [_Layout];
- in [11] die Dateien [Web.config] und [Global.asax], die zur Konfiguration der Anwendung verwendet werden.
9.7. Schritt 1 – Einrichtung einer simulierten Ebene [métier]
Im Folgenden beschreiben wir die Schritte zur Durchführung der Fallstudie. Wo es sinnvoll ist, verweisen wir auf die Kapitelnummer, die Sie gegebenenfalls noch einmal nachlesen sollten, um die geforderte Aufgabe zu erledigen. Einige Elemente des Projekts werden Ihnen in einem Ordner mit der Bezeichnung [aspnetmvc-support.zip] zur Verfügung gestellt, der auf der Website dieses Dokuments zu finden ist. Darin befindet sich der Ordner [étudedecas-support] mit folgendem Inhalt:
![]() |
Das Projekt greift zudem Elemente aus den vorangegangenen Kapiteln auf. Diese müssen lediglich per Kopieren/Einfügen zwischen diesem Ordner „PDF“ und Visual Studio übertragen werden.
9.7.1. Die Visual Studio-Lösung der vollständigen Anwendung
Zunächst erstellen wir eine Visual Studio-Lösung, in der wir zwei Projekte anlegen:
- ein Projekt für die simulierte [métier]-Schicht;
- ein Projekt für die Webschicht MVC.
![]() |
Wir verwenden zwei Tools:
- Visual Studio Express 2012 für den Desktop, mit dem wir die Schicht [métier] erstellen werden;
- Visual Studio Express 2012 für das Web, das zur Erstellung der Webschicht [web] dient.
Mit Visual Studio Express für den Desktop erstellen wir eine Lösung [pam-td]:
![]() |
- In [1] wählen Sie eine C#-Anwendung aus;
- unter [2] wählen Sie [Application console];
- Geben Sie bei [3] einen Namen für die Lösung ein;
- in [4], erstellen Sie ein Verzeichnis für diese Lösung;
- in [5]: Geben Sie der Schicht [métier] einen Namen;
- in [6]: die generierte Lösung.
9.7.2. Die Schnittstelle der Schicht [métier]
In einer Schichtenarchitektur ist es bewährte Praxis, dass die Kommunikation zwischen den Schichten über Schnittstellen erfolgt:
![]() |
Welche Schnittstelle muss die Schicht [métier] gegenüber der Schicht [web] bereitstellen? Welche Interaktionen sind zwischen diesen beiden Schichten möglich? Erinnern wir uns an die Webschnittstelle, die dem Benutzer präsentiert wird:
![]() |
- Bei der ersten Anzeige des Formulars muss in [1] die Liste der Mitarbeiter zu finden sein. Eine vereinfachte Liste reicht aus (Nachname, Vorname, SS). Die Nummer SS ist erforderlich, um Zugriff auf die zusätzlichen Informationen zum ausgewählten Mitarbeiter zu erhalten (Informationen 6 bis 11).
- Die Angaben 12 bis 15 sind die verschiedenen Beitragssätze.
- Die Angaben 16 bis 19 beziehen sich auf die Zulagen des Mitarbeiters.
- Die Angaben 20 bis 24 sind die Gehaltsbestandteile, die anhand der vom Benutzer vorgenommenen Eingaben 1 bis 3 berechnet wurden.
Die Schnittstelle [IPamMetier], die von der Schicht [métier] für die Schicht [web] bereitgestellt wird, muss die oben genannten Anforderungen erfüllen. Es gibt zahlreiche mögliche Schnittstellen. Wir schlagen folgende vor:
using Pam.Metier.Entites;
namespace Pam.Metier.Service
{
public interface IPamMetier
{
// Liste aller Mitarbeiteridentitäten
Employe[] GetAllIdentitesEmployes();
// ------- Gehaltsberechnung
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
}
- Zeile 7: die Methode, mit der das Kombinationsfeld [1] gefüllt wird
- Zeile 10: Die Methode, mit der die Informationen 6 bis 24 abgerufen werden können. Diese wurden in einem Objekt vom Typ [FeuilleSalaire] zusammengefasst, das wir im Folgenden beschreiben werden.
Wir werden diese Schnittstelle in einem Ordner mit dem Namen [metier/service] ablegen:
![]() |
9.7.3. Die Entitäten der Ebene [métier]
Die vorstehende Schnittstelle verwendet zwei Klassen, [Employe] und [FeuilleSalaire], die wir definieren müssen:
- [Employe] ist die Darstellung einer Zeile aus der Tabelle [employes] der Datenbank;
- [FeuilleSalaire] ist die Gehaltsabrechnung eines Mitarbeiters.
Die Entitäten werden in einem Ordner [metier / entites] des Projekts abgelegt:
![]() |
In der endgültigen Architektur wird die Schicht [métier] Bildentitäten aus der Datenbank verarbeiten:

Wir werden die folgenden Klassen verwenden, um die Zeilen der drei Datenbanktabellen darzustellen. Die Bedeutung der verschiedenen Felder ist in Abschnitt 9.4 nachzuschlagen.
Klasse [Employe]
Sie repräsentiert eine Zeile der Tabelle [employes]. Ihr Code lautet wie folgt:
using System;
namespace Pam.Metier.Entites
{
public class Employe
{
public string SS { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
public string Adresse { get; set; }
public string Ville { get; set; }
public string CodePostal { get; set; }
public Indemnites Indemnites { get; set; }
// Unterschrift
public override string ToString()
{
return string.Format("Employé[{0},{1},{2},{3},{4},{5}]", SS, Nom, Prenom, Adresse, Ville, CodePostal);
}
}
}
Klasse [Indemnites]
Sie stellt eine Zeile der Tabelle [indemnites] dar. Ihr Code lautet wie folgt:
using System;
namespace Pam.Metier.Entites
{
public class Indemnites
{
public int Indice { get; set; }
public double BaseHeure { get; set; }
public double EntretienJour { get; set; }
public double RepasJour { get; set; }
public double IndemnitesCp { get; set; }
// Unterschrift
public override string ToString()
{
return string.Format("Indemnités[{0},{1},{2},{3},{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
}
}
}
Klasse [Cotisations]
Sie stellt eine Zeile der Tabelle [cotisations] dar. Ihr Code lautet wie folgt:
using System;
namespace Pam.Metier.Entites
{
public class Cotisations
{
public double CsgRds { get; set; }
public double Csgd { get; set; }
public double Secu { get; set; }
public double Retraite { get; set; }
// Unterschrift
public override string ToString()
{
return string.Format("Cotisations[{0},{1},{2},{3}]", CsgRds, Csgd, Secu, Retraite);
}
}
}
Es ist zu beachten, dass die Klassen die Spalten [ID] und [VERSIONING] der Tabellen nicht enthalten. Diese Spalten, die bei der Verwendung von ORM und EF5 nützlich sind, spielen im Kontext der simulierten Schicht [métier] keine Rolle.
Die Klasse [FeuilleSalaire] fasst die Informationen 6 bis 24 des bereits vorgestellten Formulars zusammen:
namespace Pam.Metier.Entites
{
public class FeuilleSalaire
{
// automatische Eigenschaften
public Employe Employe { get; set; }
public Cotisations Cotisations { get; set; }
public ElementsSalaire ElementsSalaire { get; set; }
// ToString
public override string ToString()
{
return string.Format("[{0},{1},{2}]", Employe, Cotisations, ElementsSalaire);
}
}
}
- Zeile 7: die Informationen 6 bis 11 zum Mitarbeiter, dessen Gehalt berechnet wird, sowie die Informationen 16 bis 19 zu seinen Zulagen. Dabei ist zu beachten, dass ein Objekt [Employe] ein Objekt [Indemnites] kapselt, das seine Zulagen darstellt;
- Zeile 8: die Informationen 12 bis 15;
- Zeile 9: die Informationen 20 bis 24;
- Zeilen 12–14: die Methode [ToString].
Die Klasse [ElementsSalaire] kapselt die Informationen 20 bis 24 des Formulars:
namespace Pam.Metier.Entites
{
public class ElementsSalaire
{
// automatische Eigenschaften
public double SalaireBase { get; set; }
public double CotisationsSociales { get; set; }
public double IndemnitesEntretien { get; set; }
public double IndemnitesRepas { get; set; }
public double SalaireNet { get; set; }
// ToString
public override string ToString()
{
return string.Format("[{0} : {1} : {2} : {3} : {4} ]", SalaireBase, CotisationsSociales, IndemnitesEntretien, IndemnitesRepas, SalaireNet);
}
}
}
- Zeilen 6–10: die Gehaltsbestandteile, wie in den zuvor beschriebenen Geschäftsregeln erläutert;
- Zeile 6: das Grundgehalt des Mitarbeiters, abhängig von der Anzahl der geleisteten Arbeitsstunden;
- Zeile 7: die von diesem Grundgehalt abgezogenen Beiträge;
- Zeilen 8 und 9: die zum Grundgehalt hinzuzurechnenden Zulagen, abhängig vom Index des Mitarbeiters und der Anzahl der gearbeiteten Tage;
- Zeile 10: das auszuzahlende Nettogehalt;
- Zeilen 14–17: die Methode [ToString] der Klasse.
9.7.4. Die Klasse [PamException]
Wir legen einen spezifischen Ausnahmetyp für unsere Anwendung an. Es handelt sich um den folgenden Typ [PamException]:
using System;
namespace Pam.Metier.Entites
{
// Ausnahmeklasse
public class PamException : Exception
{
// Fehlercode
public int Code { get; set; }
// Konstruktoren
public PamException()
{
}
public PamException(int Code)
: base()
{
this.Code = Code;
}
public PamException(string message, int Code)
: base(message)
{
this.Code = Code;
}
public PamException(string message, Exception ex, int Code)
: base(message, ex)
{
this.Code = Code;
}
}
}
- Zeile 6: Die Klasse leitet sich von der Klasse [Exception] ab;
- Zeile 10: Sie verfügt über eine öffentliche Eigenschaft [Code], bei der es sich um einen Fehlercode handelt;
- Wir werden in unserer Anwendung zwei Arten von Konstruktoren verwenden:
- den in den Zeilen 23–27, den man wie unten gezeigt verwenden kann:
- (Fortsetzung)
- oder den in den Zeilen 29–33, der dazu dient, eine aufgetretene Ausnahme weiterzuleiten, indem er in eine Ausnahme vom Typ [PamException] gekapselt wird:
try{
....
}catch (IOException ex){
// Die Ausnahme wird gekapselt, z. B.
throw new PamException("Problème d'accès aux données",ex,10);
}
Diese zweite Methode hat den Vorteil, dass die Informationen, die die erste Ausnahme möglicherweise enthält, nicht verloren gehen.
9.7.5. Implementierung der Schicht [métier]
Die Schnittstelle [IPamMetier] wird durch die folgende Klasse [PamMetier] implementiert:
using System;
using Pam.Metier.Entites;
using System.Collections.Generic;
namespace Pam.Metier.Service
{
public class PamMetier : IPamMetier
{
// Liste der Mitarbeiter im Cache
public Employe[] Employes { get; set; }
// Mitarbeiter, indiziert nach ihrer Nummer SS
private IDictionary<string, Employe> dicEmployes = new Dictionary<string, Employe>();
// Liste der Mitarbeiter
public Employe[] GetAllIdentitesEmployes()
{
...
// die Mitarbeiterliste wird angezeigt
return Employes;
}
// Lohnabrechnung
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
{
...
}
}
- Zeile 7: Die Klasse [PamMetier] implementiert die Schnittstelle [IPamMetier];
- Zeile 10: Die Klasse [PamMetier] speichert die Liste der Mitarbeiter im Cache;
- Zeile 12: Ein Dictionary, das einen Mitarbeiter seiner Sozialversicherungsnummer zuordnet;
- Zeilen 15–20: Die Methode, die die Liste der Mitarbeiter zurückgibt;
- Zeilen 23–26: Die Methode, die das Gehalt eines Mitarbeiters berechnet.
Die Methode [GetAllIdentitesEmploye] lautet wie folgt:
// Mitarbeiterliste
public Employe[] GetAllIdentitesEmployes()
{
if (Employes == null)
{
// Erstellung einer Tabelle mit drei Mitarbeitern
Employes = new Employe[3];
Employes[0] = new Employe()
{
SS = "254104940426058",
Nom = "Jouveinal",
Prenom = "Marie",
Adresse = "5 rue des oiseaux",
Ville = "St Corentin",
CodePostal = "49203",
Indemnites = new Indemnites() { Indice = 2, BaseHeure = 2.1, EntretienJour = 2.1, RepasJour = 3.1, IndemnitesCp = 15 }
};
dicEmployes.Add(Employes[0].SS, Employes[0]);
Employes[1] = new Employe()
{
SS = "260124402111742",
Nom = "Laverti",
Prenom = "Justine",
Adresse = "La brûlerie",
Ville = "St Marcel",
CodePostal = "49014",
Indemnites = new Indemnites() { Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 }
};
dicEmployes.Add(Employes[1].SS, Employes[1]);
// ein fiktiver Mitarbeiter, der nicht in das Verzeichnis aufgenommen wird
// um einen nicht existierenden Mitarbeiter zu simulieren
Employes[2] = new Employe()
{
SS = "XX",
Nom = "X",
Prenom = "X",
Adresse = "X",
Ville = "X",
CodePostal = "X",
Indemnites = new Indemnites() { Indice = 0, BaseHeure = 0, EntretienJour = 0, RepasJour = 0, IndemnitesCp = 0 }
};
}
// die Mitarbeiterliste wird ausgegeben
return Employes;
}
- Zeile 4: Es wird geprüft, ob die Liste der Mitarbeiter nicht bereits erstellt wurde;
- Zeile 7: Ist dies nicht der Fall, wird ein Array mit drei Mitarbeitern erstellt;
- Zeilen 8–17: der erste Mitarbeiter;
- Zeile 18: Er wird in das Wörterbuch aufgenommen;
- Zeilen 19–28: der zweite Mitarbeiter;
- Zeile 29: Er wird in das Wörterbuch aufgenommen;
- Zeilen 32–42: der dritte Mitarbeiter. Dieser wird aus einem Grund, den wir noch erläutern werden, nicht in das Wörterbuch aufgenommen.
Die Methode [GetSalaire] sieht wie folgt aus:
// Lohnberechnung
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
{
// der Mitarbeiter mit der Nummer SS wird abgerufen
Employe e = dicEmployes.ContainsKey(ss) ? dicEmployes[ss] : null;
// existiert?
if (e == null)
{
throw new PamException(string.Format("L'employé de n° SS [{0}] n'existe pas", ss), 10);
}
// eine fiktive Gehaltsabrechnung ausgeben
return new FeuilleSalaire()
{
Employe = e,
Cotisations = new Cotisations() { CsgRds = 3.49, Csgd = 6.15, Secu = 9.38, Retraite = 7.88 },
ElementsSalaire = new ElementsSalaire() { CotisationsSociales = 100, IndemnitesEntretien = 100, IndemnitesRepas = 100, SalaireBase = 100, SalaireNet = 100 }
};
}
- Zeile 2: Die Methode erhält die Nummer SS des Mitarbeiters, dessen Gehalt berechnet werden soll, sowie seine Anzahl an gearbeiteten Stunden und seine Anzahl an Arbeitstagen;
- Zeile 5: Der Mitarbeiter wird im Verzeichnis gesucht. Wir erinnern uns, dass einer von ihnen dort nicht vorhanden ist;
- Zeilen 7–10: Wird der Mitarbeiter nicht gefunden, wird eine Ausnahme [PamException] ausgelöst;
- Zeilen 12–17: Es wird eine fiktive Gehaltsabrechnung ausgegeben.
9.7.6. Der Konsolentest der Schicht [métier]
Das Projekt der Schicht [métier] sieht derzeit wie folgt aus:
![]() |
Die oben genannte Klasse [Program] testet die Methoden der Schnittstelle [IPamMetier]. Ein einfaches Beispiel könnte wie folgt aussehen:
using Pam.Metier.Entites;
using Pam.Metier.Service;
using System;
namespace Pam.Metier.Tests
{
class Program
{
public static void Main()
{
// Instanziierung der Ebene [métier]
IPamMetier pamMetier = new PamMetier();
// Mitarbeiterliste
Employe[] employes = pamMetier.GetAllIdentitesEmployes();
Console.WriteLine("Liste des employés--------------------");
foreach (Employe e in employes)
{
Console.WriteLine(e);
}
// Lohnabrechnungsberechnungen
Console.WriteLine("Calculs de feuilles de salaire-----------------");
Console.WriteLine(pamMetier.GetSalaire(employes[0].SS, 30, 5));
Console.WriteLine(pamMetier.GetSalaire(employes[1].SS, 150, 20));
try
{
Console.WriteLine(pamMetier.GetSalaire(employes[2].SS, 150, 20));
}
catch (PamException ex)
{
Console.WriteLine(string.Format("PamException : {0}", ex.Message));
}
}
}
}
- Zeile 12: Instanziierung der Schicht [métier];
- Zeilen 14–19: Test der Methode [GetAllIdentitesEmploye] der Schnittstelle [IPamMetier];
- Zeilen 21–31: Test der Methode [GetSalaire] der Schnittstelle [IPamMetier].
Die Ausführung dieses Konsolenprogramms liefert folgende Ergebnisse:
Der Leser ist aufgefordert, den Zusammenhang zwischen diesen Ergebnissen und dem ausgeführten Code herzustellen.
Um dieses Projekt in dem Webprojekt verwenden zu können, das wir erstellen werden, wandeln wir es in eine Klassenbibliothek um:
![]() |
- in [1], in den Eigenschaften der Datei [Program.cs];
- in [2] wird angegeben, dass die Datei nicht Teil der generierten Assembly sein wird;
- in [3, 4], in den Eigenschaften des Projekts [pam-metier-simule], in der Option [Application] [3], wird in [4] festgelegt, dass die Generierung eine Klassenbibliothek (in Form einer DLL) bereitstellen soll.
![]() |
- In [5] wird eine Assembly vom Typ [Release] angefordert. Der andere Typ ist [Debug]. Die Assembly enthält dann Informationen, die das Debuggen erleichtern;
- in [6] wird das Projekt [pam-metier-simule] generiert;
![]() |
- in [7] werden alle Dateien der Lösung angezeigt;
- In [8] wird im Ordner [bin / Release] die Datei DLL unseres Projekts angezeigt.
9.8. Schritt 2: Einrichtung der Webanwendung
In der vorherigen Visual Studio-Lösung erstellen wir das Projekt für die Webschicht MVC.
![]() |
Mit Visual Studio Express für das Web öffnen wir die zuvor mit Visual Studio Express für den Desktop erstellte Lösung [pam-td].
![]() |
- In [1] wurde die Lösung [pam-td] in Visual Studio Express für das Web geladen;
- in [2] wurden die Lösung und das Projekt für die soeben erstellte simulierte Schicht [métier] geladen.
In diesem nächsten Schritt erstellen wir das Grundgerüst der Webanwendung.
![]() |
- In [1] fügen wir der Lösung [pam-td] ein neues Projekt hinzu;
![]() |
- in [2] wählen wir ein Projekt ASP.NET MVC 4 aus;
- mit dem Namen [pam-web-01] [3];
- In [4] wählt man die Basisvorlage ASP.NET MVC aus;
- in [5] das erstellte Projekt;
![]() |
- Bei [6] wird ein neues Projekt erstellt, das Startprojekt der Lösung, das ausgeführt wird, wenn man [Ctrl-F5] ausführt;
- Bei [7] wird der Name des neuen Projekts fett dargestellt, was darauf hinweist, dass es sich um das Startprojekt der Lösung handelt.
Nun ersetzen wir im Windows-Explorer den Projektordner „[Content]“ durch den Ordner „[étudedecas-support / web / Content]“. Anschließend müssen die neuen Dateien in das Projekt „[pam-web-01]“ aufgenommen werden. Dazu gehen wir wie folgt vor:
![]() |
- in [1] aktualisieren Sie die Lösung;
- in [2] werden alle Dateien der Lösung angezeigt;
- in [3] erscheint ein Ordner [Images];
- den man in [4] in das Projekt einbindet.
Fügen Sie im Ordner „[Scripts]“ die Skripte „JQuery“ und „[1]“ hinzu, die für die clientseitige Validierung erforderlich sind.
![]() |
Die Master-Seite [_Layout.cshtml] [2] enthält folgenden Inhalt:
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" href="~/Content/Site.css" />
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
<script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<script type="text/javascript" src="~/Scripts/myScripts.js"></script>
</head>
<body>
<table>
<tbody>
<tr>
<td>
<h2>Simulateur de calcul de paie</h2>
</td>
<td style="width: 20px">
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
</td>
<td>
<a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
</a>
<a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
</a>
<a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
</a>
<a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de simulation<br />
</a>
<a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la simulation<br />
</a>
<a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
</a>
</td>
</tbody>
</table>
<hr />
<div id="content">
@RenderBody()
</div>
</body>
</html>
Hinweis: Zeile 8 – Passen Sie die Version von jQuery an Ihre Visual Studio-Version an.
- Zeile 7: Verweis auf das Stylesheet der Anwendung;
- Zeilen 8–10: Verweise auf die für die clientseitige Validierung erforderlichen Skripte;
- Zeilen 11–12: Verweise auf die Skripte, die für die Eingabe französischer reeller Zahlen mit Komma erforderlich sind;
- Zeile 13: Verweis auf die für den Ajax-Modus erforderlichen Skripte;
- Zeile 14: die anwendungsspezifischen Skripte;
- Zeile 24: das Ladebild für das Warten auf das Ende von Ajax-Aufrufen;
- Zeilen 26–39: sechs JavaScript-Links;
- Zeile 43: der Abschnitt, in dem die verschiedenen Ansichten der Anwendung angezeigt werden;
- Zeile 44: Der Hauptteil der verschiedenen Ansichten der Anwendung.
Anschließend ändern wir die Standardroute der Anwendung:
![]() |
Die Datei [RouteConfig] hat folgenden Inhalt:
using System.Web.Mvc;
using System.Web.Routing;
namespace pam_web_01
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "Pam", action = "Index" }
);
}
}
}
- Zeile 14: Die URL-Dateien haben die Form [{controller}/{action}];
- Zeile 15: Wenn keine Aktion vorhanden ist, wird die Aktion [Index] verwendet. Wenn kein Controller vorhanden ist, wird der Controller [Pam] verwendet.
Aus dieser Konfiguration ergibt sich, dass die Kombination URL [/] der Kombination URL [/Pam/Index] entspricht. Da es sich bei unserer Anwendung um den Typ APU handelt, sind URL und [/] die einzigen URL dieser Anwendung.
Erstellen Sie den Controller [Pam]:
Ändern Sie den Controller [PamController] wie folgt:
using System.Web.Mvc;
namespace Pam.Web.Controllers
{
public class PamController : Controller
{
[HttpGet]
public ViewResult Index()
{
return View();
}
}
}
- Zeile 3: Wir ordnen den Controller dem Namensraum [Pam.Web.Controllers] zu;
- Zeile 7: Die Aktion [Index] verarbeitet nur den Befehl HTTP GET;
- Zeile 8: Es wird ein Typ [ViewResult] anstelle eines Typs [ActionResult] zurückgegeben.
Erstellen Sie nun die Ansicht [Index.cshtml], die von der oben genannten Aktion [Index] angezeigt wird:
![]() |
Ändern Sie [Index.cshtml] wie folgt:
@{
ViewBag.Title = "Pam";
}
<h2>Formulaire</h2>
Führen Sie die Anwendung über [Ctrl-F5] aus. Sie sollten die folgende Seite erhalten:
![]() |
Aufgabe: Erläutern Sie, was passiert ist.
Die Anwendung verwendet ein Stylesheet, auf das in der Master-Seite [_Layout.cshtml] verwiesen wird:
<link rel="stylesheet" href="~/Content/Site.css" />
Das Stylesheet [/Content/Site.css] definiert ein Hintergrundbild für die Seiten der Anwendung:
body {
background-image: url("/Content/Images/standard.jpg");
}
![]() |
9.9. Schritt 3: Einrichtung der Vorlage APU
Wir möchten eine Anwendung nach der in Abschnitt 7.5 sowie in Abschnitt 7.6 beschriebenen Vorlage APU (Ein-Seiten-Anwendung) erstellen. Die Ein-Seiten-Anwendung ist die Seite, die vom Browser beim Start der Anwendung geladen wird:
![]() |
- Der oben genannte Teil [1] ist der feste Teil der Einzelseite. Wir haben gesehen, dass er von der Master-Seite [_Layout.cshtml] bereitgestellt wird;
- der Teil „[2]“ ist der variable Teil der Einzelseite. Er befindet sich im Bereich mit der ID „[content]“ der Master-Seite „[_Layout.cshtml]“:
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
...
<script type="text/javascript" src="~/Scripts/myScripts.js"></script>
</head>
<body>
<table>
...
</table>
<hr />
<div id="content">
@RenderBody()
</div>
</body>
</html>
Die verschiedenen Seitenfragmente der Anwendung werden im Bereich mit der ID [content] in Zeile 13 angezeigt. Sie werden über Ajax-Aufrufe angezeigt. Die JavaScript-Skripte, die diese Aufrufe ausführen, befinden sich in der Datei [myScripts.js], auf die in Zeile 6 verwiesen wird. Erstellen Sie diese Datei, die wir benötigen werden:
![]() |
Wir folgen nun dem in Abschnitt 7.6 beschriebenen Muster APU. Lesen Sie diesen Abschnitt noch einmal durch, falls Sie ihn vergessen haben. Wir werden nun die verschiedenen Seitenfragmente einrichten, die von der Anwendung angezeigt werden.
9.9.1. Die JavaScript-Entwicklertools
Wir möchten daran erinnern, dass Ihnen im Chrome-Browser eine Reihe von Tools zum Debuggen des JavaScript-Codes (HTML, CSS) Ihrer Seiten zur Verfügung steht. Diese Tools wurden teilweise in Abschnitt 7.2 vorgestellt. Im Modell APU speichern die Browser die JavaScript-Skripte, auf die die erste Seite der Anwendung verweist, im Cache. Daher sollten Sie daran denken, diesen Cache zu leeren, wenn Sie Ihre Skripte ändern, da die Änderungen sonst möglicherweise nicht berücksichtigt werden. So geht das mit Chrome:
- Führen Sie „[Ctrl-Maj-I]“ aus, um die Entwicklungsumgebung anzuzeigen
![]() |
- Klicken Sie auf das Symbol [1] unten rechts im Entwicklungsfenster;
- aktivieren Sie anschließend die Option [2], die den Cache im Entwicklungsmodus deaktiviert.
9.9.2. Verwendung einer Teilansicht zur Anzeige des Formulars
Das Eingabeformular ist eines der von der Anwendung angezeigten Fragmente. Derzeit wird dieses Formular von der Ansicht [Index.cshtml] angezeigt, bei der es sich um eine vollständige Ansicht handelt:
@{
ViewBag.Title = "Pam";
}
<h2>Formulaire</h2>
Diese Ansicht wird durch die Aktion [Index] angezeigt:
[HttpGet]
public ViewResult Index()
{
return View();
}
In Zeile 4 oben wird tatsächlich die Ansicht [View] und nicht die Teilansicht [PartialView] angezeigt. Wir benötigen eine Teilansicht für das Formular, das als Seitenfragment dienen soll. Wir passen die Ansicht [Index.cshtml] wie folgt an:
@{
ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")
Zeile 4: Das Formular ist nicht mehr Teil der Seite [Index.cshtml]. Es befindet sich nun in einer Teilansicht [Formulaire.cshtml]:
![]() |
Der Code von [Formulaire.cshtml] lautet einfach wie folgt:
<h2>Formulaire</h2>
Nehmen Sie diese Änderungen vor und überprüfen Sie, ob beim Start der Anwendung weiterhin die folgende Ansicht angezeigt wird:
![]() |
9.9.3. Der Ajax-Aufruf [faireSimulation]
Wir interessieren uns für das Fragment, das angezeigt wird, wenn der Benutzer auf den Link [Faire la simulation] klickt:
![]() |
- In [1] klickt der Benutzer auf den Link [Faire la simulation];
- bei [2] erscheint die Simulation unterhalb des Formulars.
Wir passen die Teilansicht [Formulaire.cshtml], die das Formular anzeigt, wie folgt an:
<h2>Formulaire</h2>
<div id="simulation" />
In Zeile 3 erstellen wir einen Bereich mit der ID [simulation], um das Simulationsfragment unterzubringen.
Wir erstellen die folgende Teilansicht [Simulation.cshtml]:
![]() |
Der Inhalt der Ansicht [Simulation.cshtml] lautet wie folgt:
<hr />
<h2>Simulation</h2>
Nun müssen wir den JavaScript-Code schreiben, der den Klick auf den Link [Faire la simulation] verarbeitet. Wir werden dabei wie in Abschnitt 7.6.5 beschrieben vorgehen. Schauen wir uns zunächst den Code HTML des Links in [_Layout.cshtml] an:
<a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
</a>
Wir sehen, dass ein Klick auf den Link [Faire la simulation] die Ausführung der Funktion JS [faireSimulation] auslöst. Diese Funktion wird in die Datei [myScripts.js] geschrieben, ebenso wie die anderen für die Anwendung erforderlichen Funktionen JS:
// globale Variablen
var loading;
var content;
function faireSimulation() {
// Manueller Ajax-Aufruf
...
}
function effacerSimulation() {
// Die Formulareingaben werden gelöscht
...
}
function enregistrerSimulation() {
// Manueller Ajax-Aufruf
...
}
function voirSimulations() {
// Man führt einen Ajax-Aufruf manuell durch
...
}
function retourFormulaire() {
// Es wird manuell ein Ajax-Aufruf durchgeführt
...
}
function terminerSession() {
...
}
// beim Laden des Dokuments
$(document).ready(function () {
// Die Referenzen der verschiedenen Komponenten der Seite werden abgerufen
loading = $("#loading");
content = $("#content");
});
- Zeilen 35–39: Die Funktion JQuery wird beim Start der Anwendung ausgeführt;
- Zeilen 37–38: Die globalen Variablen aus den Zeilen 2 und 3 werden initialisiert.
Zur Erinnerung: Die Elemente mit den IDs [loading] und [content] sind in der Master-Seite [_Layout.cshtml] definiert (Zeilen 14 und 21 unten):
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<table>
<tbody>
<tr>
<td>
<h2>Simulateur de calcul de paie</h2>
</td>
<td style="width: 20px">
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
</td>
...
</td>
</tbody>
</table>
<hr />
<div id="content">
@RenderBody()
</div>
</body>
</html>
Aufgabe: Schreiben Sie gemäß der in Abschnitt 7.6.5 beschriebenen Vorgehensweise die Funktion JS [faireSimulation]. Diese sendet einen Ajax-Aufruf vom Typ POST an die Aktion [/Pam/FaireSimulation]. Vorerst werden keine Daten übermittelt. DieAktion [/Pam/FaireSimulation] gibt die Teilansicht [Simulation.cshtml] an die Funktion JS [faireSimulation] zurück, die diesen Datenfluss HTML dann in den mit der ID [simulation] des Formulars ein.
Testen Sie den Link [Faire la simulation] in Ihrer Anwendung.
9.9.4. Der Ajax-Aufruf [enregistrerSimulation]
Der Link [Enregistrer la simulation] ist in [_Layout.cshtml] wie folgt definiert:
<a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la simulation<br />
</a>
Aufgabe: Schreiben Sie gemäß dem oben beschriebenen Vorgehen die Funktion JS [enregistrerSimulation]. Diese sendet einen Ajax-Aufruf vom Typ POST an die Aktion [/Pam/EnregistrerSimulation]. Es werden vorerst keine Daten übermittelt. DieAktion [/Pam/EnregistrerSimulation] gibt die Teilansicht [Simulations.cshtml] an die Funktion JS [enregistrerSimulation] zurück, die diesen Datenfluss HTML dann in den mit der ID [content] der Masterseite ein.
Die Ansicht [Simulations.cshtml] sieht wie folgt aus:
![]() |
Ihr Inhalt lautet wie folgt:
<h2>Simulations</h2>
Hier ein Ausführungsbeispiel:
![]() | ![]() |
9.9.5. Der Ajax-Aufruf [voirSimulations]
Der Link [Voir les simulations] ist in [_Layout.cshtml] wie folgt definiert:
<a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
</a>
Aufgabe: Schreiben Sie nach dem oben beschriebenen Vorgehen die Funktion JS [voirSimulations]. Diese sendet einen Ajax-Aufruf vom Typ POST an die Aktion [/Pam/VoirSimulations]. Es werden vorerst keine Daten übermittelt. DieAktion [/Pam/VoirSimulations] gibt die Teilansicht [Simulations.cshtml] an die Funktion JS [voirSimulations] zurück, die diesen Datenfluss HTML dann in den mit der ID [content] der Masterseite ein.
Die Ansicht [Simulations.cshtml] ist dieselbe, die bereits in der vorherigen Frage verwendet wurde.
Hier ein Ausführungsbeispiel:
![]() |
9.9.6. Der Ajax-Aufruf [retourFormulaire]
Der Link [Retour au formulaire de simulation] ist in [_Layout.cshtml] wie folgt definiert:
<a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de simulation<br />
</a>
Aufgabe: Schreiben Sie nach dem oben beschriebenen Vorgehen die Funktion JS [retourFormulaire]. Diese sendet einen Ajax-Aufruf vom Typ POST an die Aktion [/Pam/Formulaire]. Es werden vorerst keine Daten übermittelt. DieAktion [/Pam/Formulaire] gibt die Teilansicht [Formulaire.cshtml] an die Funktion JS [retourFormulaire] zurück, die diesen Datenfluss HTML dann in den mit der ID [content] der Master-Seite ein.
Die Ansicht [Formulaire .cshtml] wurde bereits definiert. Hier ein Ausführungsbeispiel:
![]() |
9.9.7. Der Ajax-Aufruf [terminerSession]
Der Link [Terminer la session] ist in [_Layout.cshtml] wie folgt definiert:
<a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
</a>
Aufgabe: Schreiben Sie gemäß dem oben beschriebenen Vorgehen die Funktion JS [terminerSession]. Diese sendet einen Ajax-Aufruf vom Typ POST an die Aktion [/Pam/TerminerSession]. Derzeit werden keine Daten übermittelt. DieAktion [/Pam/TerminerSession] gibt die Teilansicht [Formulaire.cshtml] an die Funktion JS [terminerSession] zurück, die diesen Datenfluss HTML dann in den mit der ID [content] der Master-Seite ein.
Hier ein Ausführungsbeispiel:
![]() |
9.9.8. Die Funktion JS [effacerSimulation]
Der Link [Effacer la simulation] ist in [_Layout.cshtml] wie folgt definiert:
<a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
</a>
Die Funktion JS [effacerSimulation] hat folgenden Zweck:
- das Fragment [Simulation] auszublenden, falls es vorhanden ist;
- die Eingabefelder des Formulars in den Zustand zurückzusetzen, in dem sie sich beim ersten Laden der Anwendung befanden (sofern es Eingabefelder gibt – derzeit gibt es keine).
Aufgabe: Schreiben Sie die Funktion JS [effacerSimulation]. Hier findet kein Ajax-Aufruf statt. Der Vorgang läuft intern im Browser ab und bezieht den Server nicht mit ein.
Hier ein Ausführungsbeispiel:
![]() |
9.9.9. Verwaltung der Navigation zwischen Bildschirmen
Derzeit werden die Links immer angezeigt. Wir werden nun ihre Anzeige mit einer JavaScript-Funktion steuern. Sehen wir uns zunächst noch einmal den Code der sechs JavaScript-Links in [_Layout.cshtml] an:
<a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
</a>
<a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
</a>
<a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
</a>
<a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de simulation<br />
</a>
<a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la simulation<br />
</a>
<a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
</a>
Alle Links verfügen über ein Attribut [id], das es uns ermöglicht, sie mit JavaScript zu steuern. Wir ändern die Methode JS, die beim Laden der Seite ausgeführt wird, wie folgt:
// globale Variablen
var loading;
var content;
var lnkFaireSimulation;
var lnkEffacerSimulation
var lnkEnregistrerSimulation;
var lnkTerminerSession;
var lnkVoirSimulations;
var lnkRetourFormulaire;
var options;
...
// beim Laden des Dokuments
$(document).ready(function () {
// Die Referenzen der verschiedenen Komponenten der Seite werden abgerufen
loading = $("#loading");
content = $("#content");
// die Menülinks
lnkFaireSimulation = $("#lnkFaireSimulation");
lnkEffacerSimulation = $("#lnkEffacerSimulation");
lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
lnkVoirSimulations = $("#lnkVoirSimulations");
lnkTerminerSession = $("#lnkTerminerSession");
lnkRetourFormulaire = $("#lnkRetourFormulaire");
// sie werden in ein Array gespeichert
options = [lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
// Bestimmte Elemente der Seite werden ausgeblendet
loading.hide();
// Das Menü wird fixiert
setMenu([lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]);
});
- Zeilen 19–24: Wir rufen die Referenzen der sechs Links ab. Diese Referenzen werden in den Zeilen 4–9 als globale Variablen definiert;
- Zeile 26: Das Array [options] wird mit den sechs Referenzen initialisiert. Dieses Array wird in Zeile 10 als globale Variable definiert;
- Zeile 28: Das animierte Bild, das das Warten auf das Ende der Ajax-Aufrufe anzeigt, wird ausgeblendet;
- Zeile 30: Die Links [lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession] werden angezeigt. Die anderen werden ausgeblendet.
Die Funktion JS [setMenu] lautet wie folgt:
function setMenu(show) {
// Die Links der Tabelle werden angezeigt [show]
...
}
Aufgabe: Schreiben Sie die Funktion JS [setMenu].
Wenn T ein Array von Links ist:
- T.length ist die Anzahl der Links;
- T[i] ist der Link Nr. i;
- T[i].show() zeigt den Link Nr. i an;
- T[i].hide() blendet den Link Nr. i aus.
Mit diesen neuen Funktionen JS wird beim Start folgende Seite angezeigt:
![]() |
Passen Sie die Funktionen JS und [faireSimulation, effacerSimulation, enregistrerSimulation, voirSimulations, retourFormulaire, terminerSession] so an, dass folgende Bildschirme angezeigt werden:
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Nachdem nun das Modell APU und die Navigationslinks eingerichtet sind, können wir mit dem Schreiben der serverseitigen Aktionen und Ansichten fortfahren. Im Laufe der einzelnen Schritte werden Sie feststellen, dass einige der Ajax-Links, die derzeit funktionieren, nicht mehr funktionieren. Dies liegt daran, dass Sie die an den Client gesendeten Teilansichten ändern werden. Während Sie die verschiedenen serverseitigen Aktionen und Ansichten erstellen, werden die clientseitigen Ajax-Links wieder so funktionieren, wie Sie es vorgesehen haben.
9.10. Schritt 4: Erstellung der Serveraktion [Index]
Derzeit wird beim Start der Anwendung der folgende Bildschirm angezeigt:
![]() |
Anstelle dieses Bildschirms möchten wir den folgenden anzeigen:
![]() |
Diese Seite soll von der Aktion [Index] generiert werden. Dazu einige Anmerkungen:
- Die Seite enthält ein Formular mit drei Eingabefeldern:
- den Mitarbeiter, dessen Gehalt berechnet wird,
- seine Anzahl an gearbeiteten Stunden,
- die Anzahl seiner Arbeitstage;
- das Formular wird über den Link [Faire la simulation] übermittelt;
- die Gültigkeit der Eingabefelder [Heures travaillées] und [Jours travaillés] muss überprüft werden;
- Die Liste der Mitarbeiter stammt aus der Ebene [métier], die wir zuvor erstellt haben.
Zur Erinnerung: Der aktuelle Code der Aktion [Index] lautet:
[HttpGet]
public ViewResult Index()
{
return View();
}
der Code der Ansicht [Index.cshtml], die diese Aktion anzeigt:
@{
ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")
und den der Teilansicht [Formulaire.cshtml]:
<h2>Formulaire</h2>
An diesen drei Stellen werden Änderungen vorgenommen.
9.10.1. Die Formularvorlage
Kehren wir zur Verarbeitungskette von URL und [/Pam/Index] zurück:
![]() |
- Die Anfrage HTTP des Kunden geht als [1] ein;
- in [2] werden die in der Anfrage enthaltenen Informationen in die Aktionsvorlage [3] umgewandelt, die als Eingabe für die Aktion [4] dient;
- In [4] generiert die Aktion anhand dieses Modells eine Antwort. Diese besteht aus zwei Komponenten: einer Ansicht V [6] und dem Modell M dieser Ansicht [5];
- Die Ansicht V [6] verwendet ihr Modell M [5], um die für den Kunden bestimmte Antwort HTTP zu generieren.
Die Aktion, die uns interessiert, ist die Aktion [Index], die derzeit wie folgt aussieht:
[HttpGet]
public ViewResult Index()
{
return View();
}
Die Aktion [Index] übergibt der Ansicht [Index.cshtml] keine Vorlage. Diese kann daher die Liste der Mitarbeiter nicht anzeigen. Diese kann bei der Ebene [métier] angefordert werden. Dazu muss das Projekt [pam-web-01] eine Referenz auf das Projekt [pam-metier-simule] enthalten. Diese Referenz erstellen wir nun:
![]() |
- in [1]: Rechtsklick auf [References] des Projekts [pam-web-01], dann auf [Ajouter une référence];
- in [2] die Option [Solution] auswählen, dann das Projekt [pam-metier-simule] in [3];
- In [4] wurde das Projekt [pam-metier-simule] zu den Referenzen des Projekts [pam-web-01] hinzugefügt.
9.10.2. Das Anwendungsmodell
Wir haben die wichtigen Begriffe „Anwendungsmodell“ und „Sitzungsmodell“ in Abschnitt 4.10 auf Seite 78 eingeführt. Diese werden wir nun verwenden. Zur Erinnerung: In das Modell werden
- im Anwendungsmodell schreibgeschützte Daten für alle Benutzer. Dieses Modell bildet einen gemeinsamen Speicher für alle Abfragen aller Benutzer;
- das Sitzungsmodell mit les- und schreibbaren Daten für einen bestimmten Benutzer. Dieses Modell bildet einen gemeinsamen Speicher für alle Abfragen dieses Benutzers.
Was werden wir in das Anwendungsmodell aufnehmen? Kehren wir zur Architektur der Anwendung zurück:
![]() |
Die Schicht [web] enthält eine Referenz auf die Schicht [métier]. Diese kann von allen Benutzern gemeinsam genutzt werden. Wir können sie daher in das Anwendungsmodell aufnehmen. Außerdem gehen wir davon aus, dass sich die Liste der Mitarbeiter nicht ändert. Sie kann daher einmalig gelesen und anschließend für alle Benutzer freigegeben werden. Wir schlagen daher das folgende Anwendungsmodell vor:
![]() |
Der Code der Klasse [ApplicationModel] könnte wie folgt lauten:
using Pam.Metier.Entites;
using Pam.Metier.Service;
namespace PamWeb.Models
{
public class ApplicationModel
{
// --- anwendungsweite Daten ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
}
}
Um eine Dropdown-Liste in einer Ansicht anzuzeigen, schreibt man etwa Folgendes:
<!-- die Dropdown-Liste -->
<tr>
<td>Liste déroulante</td>
<td>@Html.DropDownListFor(m => m.DropDownListField,
new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
</td>
</tr>
Die Methode [DropDownListFor] erwartet als zweiten Parameter einen Typ SelectListItem[], der oben durch einen Typ [SelectList] bereitgestellt wurde. Wir müssen ein solches Array mit der Liste der Mitarbeiter erstellen. Da sich die Mitarbeiter nicht ändern, kann auch dieses Array in das Anwendungsmodell aufgenommen werden. Wir passen dieses wie folgt an:
using Pam.Metier.Entites;
using Pam.Metier.Service;
using System.Web.Mvc;
namespace Pam.Web.Models
{
public class ApplicationModel
{
// --- Daten im Anwendungsbereich ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
}
}
Zu welchem Zeitpunkt muss diese Vorlage erstellt werden? Das haben wir in Abschnitt 4.10 gezeigt. Es geschieht bei der Ausführung der Methode [Application_Start] aus der Datei [Global.asax]:
![]() |
Die Methode [Application_Start] sieht derzeit wie folgt aus:
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace pam_web_01
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
}
Wir entwickeln sie wie folgt weiter:
using Pam.Metier.Entites;
using Pam.Metier.Service;
using PamWeb.Infrastructure;
using PamWeb.Models;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace pam_web_01
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// ----------Automatisch generiert
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// -------------------------------------------------------------------
// ---------- spezifische Konfiguration
// -------------------------------------------------------------------
// Daten im Anwendungsbereich
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
// Instanzierung der Schicht [métier]
application.PamMetier = ...
// Mitarbeiterliste
application.Employes = ...
// Elemente der Mitarbeiter-Combo-Box
application.EmployesItems = ...
// Model-Binder für [ApplicationModel]
...
}
}
}
Aufgabe: Vervollständigen Sie den Code der Methode [Application_Start]. Alles, was Sie dazu benötigen, finden Sie in Abschnitt 4.10. Nehmen Sie sich die Zeit, diesen langen, aber wichtigen Abschnitt noch einmal durchzulesen.
Zeile 33 besteht eigentlich aus mehreren Zeilen. Um ein Objekt vom Typ [SelectListItem] zu erstellen, können Sie die folgende Methode verwenden:
new SelectListItem() { Text = unTexte, Value = uneValeur };
Dieses [SelectListItem] dient dazu, das folgende HTML-Tag <option> zu generieren:
aus der Dropdown-Liste. Dabei ist sicherzustellen, dass:
- unTexte der Vorname, gefolgt vom Nachnamen des Mitarbeiters ist;
- uneValeur die SS-Nummer des Mitarbeiters ist.
In Zeile 35 oben benötigen Sie die in Abschnitt 4.10 auf Seite 82 beschriebene Klasse [ApplicationModelBinder]:
![]() |
9.10.3. Der Aktionscode [Index]
Nachdem wir nun ein Modell für die Anwendung definiert haben, können wir den Aktionscode [Index] wie folgt anpassen:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View();
}
- Zeile 4: Die Vorlage der Anwendung ist nun ein Parameter der Aktion [Index]. In Abschnitt 4.10 haben wir erläutert, wie dieser Parameter vom Framework initialisiert wird.
9.10.4. Das Modell der Ansicht [Index.cshtml]
Nun hat die Aktion [Index] Zugriff auf die Mitarbeiter, die im Anwendungsmodell gespeichert sind. Diese müssen nun an die Ansicht [Index.cshtml] übergeben werden, die sie anzeigen wird. Man könnte einen Typ [ApplicationModel] als Modell an die Ansicht [Index.cshtml] übergeben, aber wir werden schnell feststellen, dass diese Ansicht weitere Informationen benötigt, die in [ApplicationModel] nicht enthalten sind. Wir werden die folgende Ansichtsvorlage [IndexModel] verwenden:
![]() |
namespace Pam.Web.Models
{
public class IndexModel
{
// Anwendungsumfangsdaten
public ApplicationModel Application { get; set; }
}
}
- Zeile 6: [IndexModel] lädt das Anwendungsmodell.
Die Aktion [Index] sieht nun wie folgt aus:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
}
- Zeile 4: Die Standardansicht [Index.cshtml] wird angezeigt, wobei als Vorlage ein Typ [IndexModel] verwendet wird, der mit den Daten der Anwendungsvorlage initialisiert ist.
Wir wissen, dass die Ansicht [Index.cshtml] ein Formular anzeigen soll:

Kehren wir zur Abwicklungskette einer Anfrage zurück:
![]() |
Für die Anfrage [GET /Pam/Index]:
- lautet die Aktion [Index];
- das Modell dieser Aktion ist [ApplicationModel];
- die Ansicht lautet [Index.cshtml];
- das Modell dieser Ansicht ist [IndexModel].
Wenn das Formular abgeschickt wird, ergibt sich eine ähnliche Verarbeitungskette:
- Die Aktion ist diejenige, die POST verarbeitet;
- ihr Modell fasst die übermittelten Werte zusammen, in diesem Fall:
- die Nummer SS des ausgewählten Mitarbeiters;
- die Anzahl der gearbeiteten Stunden;
- die Anzahl der gearbeiteten Tage;
Man könnte eine Aktionsvorlage erstellen, die diese drei Werte zusammenfasst. Häufig wird auch die Vorlage wiederverwendet, die zum Anzeigen des Formulars verwendet wurde. Genau das werden wir hier tun. Die Klasse [IndexModel] entwickelt sich wie folgt:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Pam.Web.Models
{
[Bind(Exclude = "Application")]
public class IndexModel
{
// Anwendungsbereichsdaten
public ApplicationModel Application { get; set; }
// gebuchte Werte
[Display(Name = "Employé")]
public string SS { get; set; }
[Display(Name = "Heures travaillées")]
[UIHint("Decimal")]
public double HeuresTravaillées { get; set; }
[Display(Name = "Jours travaillés")]
public double JoursTravaillés { get; set; }
}
}
- Zeilen 13, 16, 18: die drei übermittelten Werte. Es ist zu beachten, dass [joursTravaillés] als Typ [double] deklariert wurde, obwohl eigentlich eine Ganzzahl erwartet wird. Der Typ [double] wurde eingeführt, um die Validierung dieses Feldes auf der Client-Seite zu erleichtern, da die Validierung eines Typs [int] Probleme bereitet hatte;
- Zeilen 12, 14, 17: Bezeichnungen für die Methoden [Html.LabelFor] der dem Modell zugeordneten Ansicht;
- Zeile 15: Eine Anmerkung, um das Feld [HeuresTravaillées] mit zwei Dezimalstellen anzuzeigen;
- Zeile 5: Es wird angegeben, dass die Eigenschaft mit dem Namen [Application] nicht zu den übermittelten Werten gehört.
9.10.5. Die Sichten [Index.cshtml] und [Formulaire.cshtml]
Die Ansicht [Index.cshtml] wird durch die folgende Aktion [Index] angezeigt:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
}
Interessanterweise bleibt die Ansicht [Index.cshtml] unverändert:
@{
ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")
- Die Ansicht deklariert kein Modell;
- Zeile 4: Sie bindet die Teilansicht [Formulaire.cshtml] ein, auch hier ohne ein Modell an diese zu übergeben. Im Laufe der Tests wurde festgestellt, dass die an die Ansicht [Index.cshtml] übergebene Vorlage [IndexModel] implizit auf die Teilansicht [Formulaire.cshtml] übertragen wurde. Diese letzte Ansicht könnte nun folgende Form haben:
@model Pam.Web.Models.IndexModel
@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
<table>
<thead>
<tr>
...
</tr>
</thead>
<tbody>
<tr>
...
</tr>
<tr>
...
</tr>
</tbody>
</table>
}
<div id="simulation" />
- Zeile 1: Die Ansicht erhält eine Vorlage vom Typ [IndexModel];
- Zeile 3: das Formular;
- Zeilen 6–10: die Überschriften der Eingabetabelle;
- Zeilen 12–14: die Eingabezeile;
- Zeilen 15–17: eventuelle Fehlermeldungen.
Aufgabe: Vervollständigen Sie den Code der Ansicht [Formulaire.cshtml]. Verwenden Sie dabei die in Abschnitt 5.7 beschriebenen Methoden von [DropDownListFor, EditorFor, LabelFor, ValidationMessageFor].
9.10.6. Test der Aktion [Index]
Wir haben alle Elemente der Verarbeitungskette von URL und [/Pam/Index] geschrieben:
![]() |
Wir testen die Anwendung mit [Ctrl-F5]:
![]() | ![]() |
Sie müssen überprüfen, ob Ihre Dropdown-Liste tatsächlich mit der Mitarbeiterliste gefüllt wurde, die wir in der simulierten Ebene [métier] definiert hatten.
9.11. Schritt 5: Einrichtung der Eingabevalidierung
9.11.1. Das Problem
Obwohl wir dafür nichts unternommen haben, sind bereits clientseitige Validierungen im Einsatz:
![]() |
![]() |
Die clientseitige Validierung ist standardmäßig aktiv, da in der Anwendungsdatei „[Web.config]“ die folgende Zeile 3 enthalten ist.
<appSettings>
...
<add key="ClientValidationEnabled" value="true" />
</appSettings>
Da jedoch in [IndexModel] das Feld [JoursTravaillés] vom Typ [double] deklariert wurde:
public double JoursTravaillés { get; set; }
kann in dieses Feld eine reelle Zahl eingegeben werden:
![]() |
Außerdem können in beiden Feldern beliebige Werte eingegeben werden:
![]() |
Die Vorlage [IndexModel] des Formulars sieht derzeit wie folgt aus:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Pam.Web.Models
{
[Bind(Exclude = "Application")]
public class IndexModel
{
// Anwendungsbereichsdaten
public ApplicationModel Application { get; set; }
// gebuchte Werte
[Display(Name = "Employé")]
public string SS { get; set; }
[Display(Name = "Heures travaillées")]
[UIHint("Decimal")]
public double HeuresTravaillées { get; set; }
[Display(Name = "Jours travaillés")]
public double JoursTravaillés { get; set; }
}
}
Aufgabe: Verbessern Sie diese Vorlage, um:
- individuelle Fehlermeldungen zu erhalten;
- im Feld [HeuresTravaillées] nur reelle Werte im Intervall [0,400] zuzulassen;
- nur ganzzahlige Werte im Bereich [0,31] für das Feld [JoursTravaillées] zuzulassen;
Hier kann das Beispiel aus Abschnitt 7.6.2 als Hilfe dienen. Um zu überprüfen, ob die Anzahl der Arbeitstage eine ganze Zahl ist, kann ein regulärer Ausdruck verwendet werden (siehe Beispiele in Abschnitt 5.9.1).
Hier sind einige Beispiele dafür, was erwartet wird:
![]() |
![]() |
![]() |
9.11.2. Eingabe von reellen Zahlen im französischen Format
In der aktuellen Version der Anwendung muss die Anzahl der gearbeiteten Stunden eine Dezimalzahl im angelsächsischen Format (mit Punkt) sein. Das französische Format mit Komma wird nicht akzeptiert:
![]() |
Dieses Problem wurde in Abschnitt 6.1 identifiziert und behoben.
Aufgabe: Führen Sie gemäß der Vorgehensweise im oben genannten Abschnitt die erforderlichen Änderungen durch, damit reelle Zahlen im französischen Dezimalformat eingegeben werden können. Testen Sie Ihre Anwendung.
Nun sieht der vorherige Bildschirm wie folgt aus:
![]() |
9.11.3. Formularüberprüfung über den JavaScript-Link [Faire la simulation]
Derzeit können ungültige Werte übermittelt werden, wie die folgende Sequenz zeigt:
![]() |
![]() |
Das Vorhandensein der Simulation in [1] und die Menüänderung in [2] zeigen, dass durch das Anklicken des Links [Faire la simulation] das Formular übermittelt wurde, obwohl die eingegebenen Werte ungültig waren. Dieses Problem wurde in Abschnitt 7.6.5 identifiziert und behoben.
Aufgabe: Stellen Sie gemäß der im oben genannten Abschnitt beschriebenen Vorgehensweise sicher, dass die Ausführung von POST über den Link [Faire la simulation] nicht möglich ist, wenn die eingegebenen Werte ungültig sind. Denken Sie daran, den Browser-Cache zu leeren, bevor Sie Ihre Änderungen testen.
Zur Erinnerung: Die Teilansicht [Formulaire.cshtml] generiert ein Formular HTML mit der ID [formulaire] (Zeile 1 unten):
@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
Dies lässt sich überprüfen, indem Sie den Quellcode des Formulars im Browser anzeigen:
<div id="content">
<form action="/Pam/FaireSimulation" id="formulaire" method="post">
...
</form>
<div id="simulation" />
</div>
9.12. Schritt 6: Eine Simulation durchführen
9.12.1. Das Problem
Wenn wir eine Simulation durchführen, möchten wir das folgende Ergebnis erhalten:
![]() |
Die Teilansicht [Simulation.cshtml] zeigt nun die Gehaltsabrechnung eines Mitarbeiters an.
9.12.2. Erstellung der Ansicht [Simulation.cshtml]
Die Ansicht [Simulation.cshtml] entwickelt sich wie folgt:
@model Pam.Metier.Entites.FeuilleSalaire
<hr />
<p><span class="info">Informations Employé</span></p>
<table>
<tbody>
<tr>
<td><span class="libellé">Nom</span>
</td>
<td><span class="libellé">Prénom</span>
</td>
<td><span class="libellé">Adresse</span>
</td>
</tr>
<tr>
<td>
<span class="valeur">@Model.Employe.Nom</span>
</td>
...
</tr>
<tr>
<td><span class="libellé">Ville</span>
</td>
<td><span class="libellé">Code Postal</span>
</td>
<td><span class="libellé">Indice</span>
</td>
</tr>
<tr>
...
</tr>
</tbody>
</table>
<br />
<p><span class="info">Informations Cotisations</span></p>
<table>
...
</tbody>
</table>
<br />
<p><span class="info">Informations Indemnités</span></p>
<table>
...
</table>
<br />
<p><span class="info">Informations Salaire</span></p>
<table>
...
</table>
<br />
<table>
...
</table>
- Zeile 1: Die Ansicht [Simulation.cshtml] basiert auf dem in Abschnitt 9.7.3 definierten Typ [FeuilleSalaire];
- Die Ansicht verwendet die Klassen [libellé, info, valeur], die im Stylesheet der Anwendung [Content / Site.css] definiert sind:
.libellé {
background-color: azure;
margin: 5px;
padding: 5px;
}
.info {
background-color: antiquewhite;
margin: 5px;
padding: 5px;
}
.valeur {
background-color: beige;
padding: 5px;
margin: 5px;
}
Außerdem wird ebenfalls in [Site.css] die Zeilenhöhe der verschiedenen Tabellen HTML im Bereich mit der ID [simulation] festgelegt, genau dort, wo die Gehaltsabrechnung angezeigt wird:
#Simulation der Tabelle tr {
height: 30px;
}
Aufgabe: Vervollständigen Sie die Ansicht [Simulation.cshtml].
Um den Euro-Betrag eines Geldbetrags anzuzeigen, verwenden Sie die Methode [string.Format]:
Die obige Anweisung zeigt [somme] als Geldwert [C] (Währung) mit zwei Dezimalstellen [C2] an.
Um diese Ansicht zu testen, muss ihr eine Gehaltsabrechnung bereitgestellt werden. Diese muss ihr über die Aktion [/Pam/FaireSimulation] bereitgestellt werden, die das Ziel des Ajax-Aufrufs des Links [Faire la simulation] ist. Derzeit lautet diese Aktion wie folgt:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
}
// Simulation durchführen
[HttpPost]
public PartialViewResult FaireSimulation()
{
return PartialView("Simulation");
}
Im obigen Beispiel übergibt die Aktion [FaireSimulation] der Ansicht [Simulation.cshtml] kein Modell. Sie muss ihr eine Gehaltsabrechnung übergeben. Es ist bekannt, dass die Schicht [métier] die Berechnung der Lohnabrechnungen durchführt. Diese Schicht [métier] ist über die Anwendungsvorlage [ApplicationModel] zugänglich, die wir in Abschnitt 9.10.2 definiert haben:
public class ApplicationModel
{
// --- Anwendungsbereichsdaten ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
}
Auf die Ebene [métier] kann über die Eigenschaft in Zeile 5 oben zugegriffen werden. Damit die Aktion [FaireSimulation] Zugriff auf die Ebene [métier] hat, übergeben wir ihr das Anwendungsmodell, wie wir es bereits für die Aktion [Index] getan haben. Der Code sieht dann wie folgt aus:
// Simulation durchführen
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application)
{
return PartialView("Simulation");
}
Nun sind wir in der Lage, innerhalb der Aktion eine fiktive Gehaltsabrechnung zu berechnen. Der Code sieht nun wie folgt aus:
// Simulation durchführen
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application)
{
FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
return PartialView("Simulation", feuilleSalaire);
}
- In Zeile 5 wird ein fiktives Gehalt berechnet. Der erste Parameter ist eine vorhandene Nummer SS. Diese wurde in der Klasse [métier] definiert, die in Abschnitt 9.7.5 simuliert wurde. Der zweite Parameter ist die Anzahl der gearbeiteten Stunden und der dritte die Anzahl der gearbeiteten Tage;
- Zeile 6: Dieser Lohnbeleg wird als Vorlage an die Ansicht [Simulation.cshtml] übergeben.
Nun sind wir bereit, die Ansicht [Simulation.cshtml] zu testen:
![]() |
Wir nehmen keine Eingaben vor und starten die Simulation. Daraufhin erhalten wir folgendes Ergebnis:
![]() |
9.12.3. Berechnung des tatsächlichen Gehalts
Unsere aktuelle Aktion [FaireSimulation] berechnet immer dieselbe Lohnabrechnung:
// Simulation durchführen
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application)
{
FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
return PartialView("Simulation", feuilleSalaire);
}
Sie berücksichtigt die eingegebenen Informationen nicht:
- den Mitarbeiter, dessen Lohn berechnet wird;
- seine Anzahl an gearbeiteten Stunden;
- die Anzahl seiner Arbeitstage.
Die eingegebenen Werte gelangen wie folgt in die Aktion [FaireSimulation]:
- Der Benutzer klickt auf den Link [Faire la simulation]. Dadurch wird die Ausführung der Funktion JS [faireSimulation] ausgelöst, die wir bereits geschrieben haben;
- Die Funktion JS [faireSimulation] führt anschließend einen Ajax-Aufruf an die Server-Aktion [/Pam/FaireSimulation] durch, an der wir derzeit arbeiten. Derzeit übermittelt die Funktion JS [faireSimulation] keine Informationen an die Serveraktion. Sie muss ihr die vom Benutzer eingegebenen Werte übermitteln;
- die Serveraktion [/Pam/FaireSimulation] wird die eingegebenen Werte aus den von den Funktionen JS und [faireSimulation] gesendeten Werten abrufen.
Beginnen wir mit Punkt 2: Die Funktion JS [faireSimulation] muss die vom Benutzer eingegebenen Werte an die Serveraktion [/Pam/FaireSimulation] übermitteln.
Aufgabe: Ergänzen Sie die Funktion JS [faireSimulation] so, dass sie die vom Benutzer eingegebenen Werte übermittelt. Dabei können Sie sich an dem Beispiel in Abschnitt 7.6.5 orientieren, in dem dieses Problem behandelt wurde.
Befassen wir uns nun mit Punkt 3 oben. Die Serveraktion [/Pam/FaireSimulation] muss die von den Funktionen JS und [faireSimulation] übermittelten Werte abrufen.
Aufgabe: Ergänzen Sie die Servermethode [FaireSimulation] so, dass sie das Gehalt anhand der von den Funktionen JS und [faireSimulation] übermittelten Werte berechnet. Man kann sich erneut auf das Beispiel aus Abschnitt 7.6.5 stützen, in dem dieses Problem behandelt wurde. Vorerst wird angenommen, dass das aus den übermittelten Werten abgeleitete Modell weiterhin gültig ist.
Hinweis: Die Serveraktion [FaireSimulation] verläuft wie folgt:
// Simulation durchführen
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
{
// Erstellung des Aktionsmodells
...
// Es wird versucht, die in diesem Modell hinterlegten Werte abzurufen
...
// Das Gehalt wird berechnet
FeuilleSalaire feuilleSalaire = ...
// Die Gehaltsabrechnung wird angezeigt
return PartialView("Simulation", feuilleSalaire);
}
Hier ein Ausführungsbeispiel:
![]() |
Wir wählen [Justine Laverti]. Daraufhin erhalten wir folgendes Ergebnis:
![]() |
Wir haben tatsächlich die fiktive Gehaltsabrechnung von [Justine Laverti] erhalten. Zuvor wurde die Gehaltsabrechnung von [Marie Jouveinal] berechnet. Der für die Mitarbeiterauswahl gebuchte Wert wurde also ausgewertet. Zur Anzahl der Stunden und Tage lässt sich nichts sagen, da unsere simulierte Ebene [métier] diese nicht berücksichtigt.
9.12.4. Fehlerbehandlung
Betrachten wir das folgende Beispiel:
![]() |
- In [1] wird ein Mitarbeiter ausgewählt, der nicht existiert (siehe die Definition der simulierten Schicht [métier] in Abschnitt 9.7.5;
- in [2] wird die Simulation durchgeführt;
- in [3] unten wird eine Fehlerseite angezeigt.
![]() |
Was ist passiert?
Die Funktion JS [faireSimulation] wurde ausgeführt. Ihr Code sieht wie folgt aus:
function faireSimulation() {
...
// Man führt manuell einen Ajax-Aufruf durch
$.ajax({
url: '/Pam/FaireSimulation',
...
beforeSend: function () {
// Warteanzeige leuchtet
loading.show();
},
success: function (data) {
...
},
error: function (jqXHR) {
// Fehleranzeige
simulation.html(jqXHR.responseText);
simulation.show();
},
complete: function () {
// Warteanzeige aus
loading.hide();
}
});
// Menü
setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
Der Ajax-Aufruf ist fehlgeschlagen, und es wurde die Funktion in den Zeilen 14–18 ausgeführt. Die vom Server zurückgegebene Fehlerseite [jqXHR.responseText] wurde angezeigt. Diese ist recht präzise. Die simulierte Schicht [métier] hat eine Ausnahme ausgelöst, da die ihr übergebene Nummer SS nicht die eines existierenden Mitarbeiters ist (siehe Code der simulierten Schicht [métier] in Abschnitt 9.7.5). Wir müssen diesen Fall ordnungsgemäß behandeln.
Wir werden eine Teilansicht [Erreurs.chtml] erstellen, die jedes Mal an den Client JS zurückgegeben wird, wenn serverseitig ein Fehler festgestellt wird:
![]() |
Der Code der Teilansicht [Erreurs.chtml] lautet wie folgt:
@model IEnumerable<string>
<hr />
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
@foreach (string msg in Model)
{
<li>@msg</li>
}
</ul>
- Zeile 1: Die Ansicht erhält als Vorlage eine Liste von Fehlermeldungen;
- Zeilen 5–10: Diese werden in einer Liste HTML angezeigt;
Ändern wir nun den Code der Serveraktion [FaireSimulation] wie folgt:
// Simulation durchführen
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
{
...
// Lohn wird berechnet
FeuilleSalaire feuilleSalaire = null;
Exception exception=null;
try
{
// Lohnberechnung
feuilleSalaire = ...
}
catch (Exception ex)
{
exception = ex;
}
// Fehler?
if (exception == null)
{
// Lohnabrechnung anzeigen
return PartialView("Simulation", feuilleSalaire);
}
else
{
// Fehlerseite anzeigen
return PartialView("Erreurs", Static.GetErreursForException(exception));
}
}
- Zeilen 9–17: Die Gehaltsberechnung erfolgt nun in einem try/catch-Block;
- Zeile 27: Wenn ein Fehler aufgetreten ist, wird die Teilansicht [Erreurs.cshtml] angezeigt, wobei die von der statischen Methode [Static.GetErreursForException(exception)] bereitgestellte Liste der Fehlermeldungen als Vorlage dient.
In der Klasse [Static] werden zwei statische Hilfsfunktionen [1] zusammengefasst:
![]() |
using System;
using System.Collections.Generic;
using System.Web.Mvc;
namespace PamWeb.Infrastructure
{
public class Static
{
// Liste der Fehlermeldungen einer Ausnahme
public static List<string> GetErreursForException(Exception ex)
{
List<string> erreurs = new List<string>();
while (ex != null)
{
erreurs.Add(ex.Message);
ex = ex.InnerException;
}
return erreurs;
}
// Liste der Fehlermeldungen im Zusammenhang mit einer ungültigen Vorlage
public static List<string> GetErreursForModel(ModelStateDictionary état)
{
List<string> erreurs = new List<string>();
if (!état.IsValid)
{
foreach (ModelState modelState in état.Values)
{
foreach (ModelError error in modelState.Errors)
{
erreurs.Add(getErrorMessageFor(error));
}
}
}
return erreurs;
}
// die Fehlermeldung zu einem Element des Aktionsmodells
static private string getErrorMessageFor(ModelError error)
{
if (error.ErrorMessage != null && error.ErrorMessage.Trim() != string.Empty)
{
return error.ErrorMessage;
}
if (error.Exception != null && error.Exception.InnerException == null && error.Exception.Message != string.Empty)
{
return error.Exception.Message;
}
if (error.Exception != null && error.Exception.InnerException != null && error.Exception.InnerException.Message != string.Empty)
{
return error.Exception.InnerException.Message;
}
return string.Empty;
}
}
}
- Zeilen 10–19: Die statische Funktion [GetErreursForException] gibt die Fehlerliste eines Ausnahmestapels zurück;
- Zeilen 22–36: Die statische Funktion [GetErreursForModel] gibt die Fehlerliste eines ungültigen Aktionsmodells zurück. Der Code dieser Funktion sowie der der privaten Methode [getErrorMessageFor] (Zeilen 39–54) wurde bereits zuvor behandelt.
Nachdem dies erledigt ist, können wir den Fehlerfall erneut testen:
![]() |
- In [1] wählen wir den Mitarbeiter aus, der nicht existiert;
- in [2] führen wir die Simulation durch;
- in [3] wird die neue Fehlerseite abgerufen.
Kehren wir zur Serveraktion [FaireSimulation] zurück:
// Simulation durchführen
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
{
// Erstellung des Aktionsmodells
IndexModel modèle = new IndexModel() { Application = application};
// Es wird versucht, die in das Modell eingetragenen Werte abzurufen
TryUpdateModel(modèle, data);
// Das Gehalt wird berechnet
...
}
In Zeile 8 aktualisieren wir das Modell aus Zeile 6 mit den vom Ajax-Aufruf übermittelten Werten. Wir überprüfen die Gültigkeit des Modells nicht. Dies ist jedoch erforderlich, da wir nicht wissen können, woher die übermittelten Werte stammen. Jemand könnte ein POST manipuliert und uns ungültige Daten gesendet haben.
Aufgabe: In Anlehnung an das Modell, das wir für den Ausnahmefall entwickelt haben, ändern Sie die Serveraktion [FaireSimulation] so, dass eine Fehlerseite gesendet wird, wenn die übermittelten Daten ungültig sind. Dazu verwenden wir die statische Methode [GetErreursForModel] der Klasse [Static].
Wie lässt sich diese Änderung testen? In Abschnitt 9.11.3 haben Sie dafür gesorgt, dass die Funktion JS [faireSimulation] die eingegebenen Werte nicht mit POST verarbeitet, wenn diese ungültig sind. Kommentieren Sie die Zeilen aus, die dies bewirken, und führen Sie dann den folgenden Test durch:
![]() |
- in [1] führen Sie die Simulation mit ungültigen Werten durch;
- in [2] wird die soeben erstellte Fehlerseite korrekt angezeigt, was beweist, dass die serverseitigen Validatoren ordnungsgemäß funktioniert haben.
Denken Sie im weiteren Verlauf daran, die Zeilen, die Sie gerade in den Funktionen JS und [faireSimulation] auskommentiert haben, wieder zu aktivieren.
9.13. Schritt 7: Einrichten einer Benutzersitzung
Die Anwendung [Simulateur de calcul de paie] ermöglicht es dem Benutzer, über den Link [Faire la simulation] verschiedene Abrechnungssimulationen durchzuführen, diese über den Link [Enregistrer la simulation] zu speichern, sie über den Link [Voir les simulations] anzuzeigen und sie über den Link [Retirer la simulation] zu löschen. Wir wissen, dass zwischen zwei aufeinanderfolgenden Anfragen des Benutzers kein Speicher vorhanden ist, es sei denn, man richtet einen über den Sitzungsmechanismus ein (siehe Abschnitt 4.10). Es ist hier ziemlich klar, dass wir die Liste der vom Benutzer im Laufe der Zeit gespeicherten Simulationen in der Sitzung aufbewahren müssen. Es gibt noch weitere Daten, die gespeichert werden müssen: Wenn der Benutzer eine Simulation durchführt, wird diese nur dann in der Liste der Simulationen gespeichert, wenn der Benutzer dies über den Link [Enregistrer la simulation] anfordert. Wenn er dies tut, muss es möglich sein, die in der vorherigen Anfrage berechnete Simulation wiederzufinden. Zu diesem Zweck wird diese ebenfalls in der Sitzung gespeichert. Schließlich werden wir die Simulationen beginnend mit 1 nummerieren. Um eine neue Simulation korrekt zu nummerieren, muss die Nummer der vorherigen Simulation gespeichert worden sein – auch dies wiederum in der Sitzung.
In Abschnitt 4.10 haben wir das Konzept des Sitzungsmodells als Eingabeparameter einer Aktion eingeführt, damit diese Zugriff auf die Sitzung hat. Wir werden dieses Konzept nun wieder aufgreifen. Sie können den entsprechenden Abschnitt gerne noch einmal nachlesen, falls Ihnen dieser Begriff unklar ist.
Wir erstellen die folgende Klasse [SessionModel]:
![]() |
Der Code lautet wie folgt:
using Pam.Web.Models;
using System.Collections.Generic;
namespace Pam.Web.Models
{
public class SessionModel
{
// Liste der Simulationen
public List<Simulation> Simulations { get; set; }
// Nummer der nächsten Simulation
public int NumNextSimulation { get; set; }
// die letzte Simulation
public Simulation Simulation { get; set; }
// Hersteller
public SessionModel()
{
// leere Simulationsliste
Simulations = new List<Simulation>();
// Nummer der nächsten Simulation
NumNextSimulation = 1;
}
}
}
Die Klasse [Simulation] in den Zeilen 9 und 13 wird Informationen zu einer Simulation speichern. Was müssen wir speichern? Die Verknüpfung [Faire la simulation] berechnet eine Gehaltsabrechnung vom Typ [FeuilleSalaire]. Es erscheint naheliegend, diesen in die Simulation aufzunehmen. Außerdem müssen wir die Informationen speichern, die zu diesem Lohnzettel geführt haben:
- den ausgewählten Mitarbeiter. Dieser ist im Feld [FeuilleSalaire.Employe] zu finden. Es ist daher nicht notwendig, ihn ein zweites Mal zu speichern;
- die Anzahl der gearbeiteten Stunden und Tage. Diese Informationen sind im Typ [FeuilleSalaire] nicht enthalten. Wir müssen sie daher speichern.
Schließlich wird jede Simulation durch eine Nummer gekennzeichnet. Wir könnten also von der folgenden Klasse [Simulation] ausgehen:
using Pam.Metier.Entites;
namespace Pam.Web.Models
{
public class Simulation
{
// Nummer der Simulation
public int Num { get; set; }
// Anzahl der gearbeiteten Stunden
public double HeuresTravaillées { get; set; }
// Anzahl der Arbeitstage
public int JoursTravaillés { get; set; }
// die Gehaltsabrechnung
public FeuilleSalaire FeuilleSalaire { get; set; }
}
}
Die Serveraktion [FaireSimulation] muss nicht nur eine Gehaltsabrechnung berechnen, sondern auch eine Simulation erstellen und diese in die Sitzung einfügen. Dazu erhält sie als Parameter das Sitzungsmodell:
// Simulation durchführen
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, SessionModel session, FormCollection data)
{
// Erstellung der Aktionsvorlage
IndexModel modèle = new IndexModel() { Application = application };
// Es wird versucht, die in der Vorlage hinterlegten Werte abzurufen
TryUpdateModel(modèle, data);
// Ist das Modell gültig?
if (!ModelState.IsValid)
{
// Die Fehlerseite wird angezeigt
return PartialView("Erreurs", Static.GetErreursForModel(ModelState));
}
// Das Gehalt wird berechnet
FeuilleSalaire feuilleSalaire = null;
Exception exception = null;
try
{
// Lohnberechnung
feuilleSalaire = application.PamMetier.GetSalaire(modèle.SS, modèle.HeuresTravaillées, (int)modèle.JoursTravaillés);
}
catch (Exception ex)
{
exception = ex;
}
// Fehler?
if (exception != null)
{
// Die Fehlerseite wird angezeigt
return PartialView("Erreurs", Static.GetErreursForException(exception));
}
// Es wird eine Simulation erstellt und in die Sitzung aufgenommen
session.Simulation = ...
// die Gehaltsabrechnung wird angezeigt
return PartialView("Simulation", feuilleSalaire);
}
- Zeile 3: Die Aktion erhält das Sitzungsmodell als Parameter;
Aufgabe 1: Vervollständigen Sie den Code der Aktion, Zeile 34
Aufgabe 2: Führen Sie gemäß der Vorgehensweise in Abschnitt 4.10 die notwendigen Schritte durch, damit der Parameter [SessionModel session] der Aktion vom Framework korrekt initialisiert wird. Wenn nichts unternommen wird, erhalten wir für diesen Parameter einen Zeiger null.
9.14. Schritt 8: Eine Simulation speichern
9.14.1. Das Problem
Wenn wir eine Simulation durchgeführt haben, können wir sie speichern:
![]() |

Die Teilansicht [Simulations.cshtml] zeigt nun die Liste der vom Benutzer durchgeführten Simulationen an. Wir weisen darauf hin, dass die berechnete Gehaltsabrechnung fiktiv ist.
9.14.2. Schreiben der Serveraktion [EnregistrerSimulation]
Der Ajax-Link [Enregistrer la simulation] ruft die Serveraktion [EnregistrerSimulation] auf, deren Code bisher wie folgt lautete:
[HttpPost]
public PartialViewResult EnregistrerSimulation()
{
return PartialView("Simulations");
}
Er ändert sich wie folgt:
// Eine Simulation speichern
[HttpPost]
public PartialViewResult EnregistrerSimulation(SessionModel session)
{
// Die zuletzt durchgeführte Simulation wird in der Simulationsliste der Sitzung gespeichert
...
// In der Sitzung wird die Nummer der nächsten Simulation erhöht
...
// Die Liste der Simulationen wird angezeigt
...
}
- Zeile 1: Die Aktion [EnregistrerSimulation] benötigt Zugriff auf die Sitzung. Deshalb wird ihr das Sitzungsmodell als Parameter übergeben.
Aufgabe: Vervollständigen Sie die Serveraktion [EnregistrerSimulation].
9.14.3. Erstellung der Teilansicht [Simulations.cshtml]
Die vorherige Aktion [EnregistrerSimulation] lässt die Teilansicht [Simulations.cshtml] anzeigen, deren Vorlage die Liste der vom Benutzer durchgeführten Simulationen ist. Ihr Code lautet wie folgt:
@model IEnumerable<Simulation>
@using Pam.Web.Models
@if (Model.Count() == 0)
{
<h2>Votre liste de simulations est vide</h2>
}
@if (Model.Count() != 0)
{
<h2>Liste des simulations</h2>
...
}
Aufgabe 1: Vervollständigen Sie den Code der Teilansicht [Simulations.cshtml]. Verwenden Sie dazu die Tabelle HTML zur Anzeige der Simulationen. Sie können sich dabei an den Beispielen aus Abschnitt 5.4 orientieren.
Hinweis: Der Link [retirer] für jede Simulation in der Tabelle HTML ist ein JavaScript-Link in folgender Form:
wobei N die Nummer der Simulation ist.
Aufgabe 2: Testen Sie Ihre Anwendung, indem Sie Simulationen durchführen. Führen Sie dazu wiederholt die folgende Abfolge durch: 1) Laden Sie die Anwendungsseite über [F5], 2) führen Sie eine Simulation durch, 3) speichern Sie diese. Die Simulationen sammeln sich in der Sitzung an, was sich in der Ansicht [Simulations.cshtml] widerspiegeln sollte.
Aufgabe 3: Optimieren Sie die Teilansicht [Simulations.cshtml] so, dass die Farben der Zeilen in der Tabelle HTML abwechselnd dargestellt werden.

Die Zeilen <tr> der Tabelle HTML, die im Stylesheet [/Content/Site.css] definierten Klassen CSS, [pair] und [impair] abwechselnd zugewiesen:
.impair {
background-color: beige;
}
.pair {
background-color: lightsteelblue;
}
9.15. Schritt 9: Zurück zum Eingabeformular
9.15.1. Das Problem
Nachdem wir die Liste der Simulationen erhalten haben, können wir zum Eingabeformular zurückkehren, was seit einiger Zeit nicht mehr möglich war:


9.15.2. Beschreibung der Serveraktion [Formulaire]
Der Ajax-Link [Retour au formulaire de simulation] ruft die Serveraktion [Formulaire] auf, deren Code bisher wie folgt lautete:
[HttpPost]
public PartialViewResult Formulaire()
{
return PartialView("Formulaire");
}
Die von ihr angezeigte Teilansicht [Formulaire] erwartet ein Template [IndexModel] (Zeile 1 unten):
@model Pam.Web.Models.IndexModel
@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
<div id="simulation" />
Aus diesem Grund funktionierte der Link [Retour au formulaire de simulation] nicht mehr.
Aufgabe: Die neue Version der Serveraktion [Formulaire] schreiben (2 Zeilen müssen umgeschrieben werden) und anschließend Tests durchführen.
9.15.3. Änderung der JavaScript-Funktion [retourFormulaire]
Mit der zuvor vorgenommenen Änderung kann man nun zum Formular zurückkehren, doch dabei tritt ein Fehler auf:
![]() |
- In [1] kehrt man zum Eingabeformular zurück;
- bei [2] wird eine Simulation mit fehlerhaften Eingaben durchgeführt. Dabei stellt man fest, dass die clientseitigen Validatoren nicht mehr funktionieren. Hier wurde der Server aufgerufen und hat dank der in Abschnitt 9.12.4 durchgeführten Arbeiten eine Fehlerseite zurückgegeben.
Dieser Fehler wurde in Abschnitt 7.6.7 identifiziert und behoben.
Aufgabe: Korrigieren Sie gemäß der Vorgehensweise in Abschnitt 7.6.7 die JavaScript-Funktion [retourFormulaire] und führen Sie anschließend Tests durch, um zu überprüfen, ob die clientseitigen Validatoren wieder funktionieren.
9.16. Schritt 10: Siehe Liste der Simulationen
9.16.1. Das Problem
Bei der Arbeit mit dem Simulationsformular kann man die Liste der durchgeführten Simulationen anzeigen:
![]() | ![]() |
9.16.2. Schreiben der Serveraktion [VoirSimulations]
Der Ajax-Link [Voir les simulations] ruft die Serveraktion [VoirSimulations] auf, deren Code bisher wie folgt lautete:
// Simulationen anzeigen
[HttpPost]
public PartialViewResult VoirSimulations()
{
return PartialView("Simulations");
}
Die von ihr angezeigte Teilansicht [Simulations] erwartet ein Template [IEnumerable<Simulation>] (Zeile 1 unten):
@model IEnumerable<Simulation>
@using Pam.Web.Models
@if (Model.Count() == 0)
{
<h2>Votre liste de simulations est vide</h2>
}
@if (Model.Count() != 0)
{
<h2>Liste des simulations</h2>
...
}
Aus diesem Grund funktionierte der Link [Voir les simulations] nicht mehr.
Aufgabe: Die neue Version der Serveraktion [VoirSimulations] schreiben (2 Zeilen müssen umgeschrieben werden) und anschließend Tests durchführen.
9.17. Schritt 11: Sitzung beenden
9.17.1. Das Problem
Die Sitzung des Benutzers kann jederzeit über den Link [Ajax] [Terminer la session] beendet werden. Dies hat zur Folge, dass die aktuelle Sitzung abgebrochen und eine neue gestartet wird. Außerdem kehrt man zur Formularansicht zurück:
![]() |
![]() |
- In [1] haben wir zwei Simulationen durchgeführt und anschließend die Sitzung beendet;
- in [2] kehrt man zum Eingabeformular zurück. Man möchte die Simulationen anzeigen;
- in [3] ist die Liste der Simulationen aufgrund des Sitzungswechsels nun leer.
9.17.2. Schreiben der Serveraktion [TerminerSession]
Der Ajax-Link [Terminer la session] ruft die Serveraktion [TerminerSession] auf, deren Code bisher wie folgt lautete:
// Die Sitzung beenden
[HttpPost]
public PartialViewResult TerminerSession()
{
return PartialView("Formulaire");
}
Die von ihr angezeigte Teilansicht [Formulaire] erwartet ein Modell [IndexModel] (Zeile 1 unten):
@model Pam.Web.Models.IndexModel
@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
<div id="simulation" />
Aus diesem Grund funktionierte der Link [Terminer la session] nicht mehr.
Aufgabe: Die neue Version der Serveraktion [TerminerSession] schreiben (2 Zeilen müssen umgeschrieben werden) und anschließend Tests durchführen.
Hinweis: Um die Sitzung in der Aktion zu beenden, schreibt man:
9.17.3. Änderung der JavaScript-Funktion [terminerSession]
Mit der zuvor vorgenommenen Änderung kann man nun zum Formular zurückkehren, doch dabei tritt ein Fehler auf, der bereits in Abschnitt 9.15.3 beschrieben wurde.
Aufgabe: Befolgen Sie die Vorgehensweise aus Abschnitt 9.15.3, korrigieren Sie die JavaScript-Funktion [terminerSession] und führen Sie anschließend Tests durch, um zu überprüfen, ob die clientseitigen Validatoren wieder funktionieren.
9.18. Schritt 12: Löschen der Simulation
9.18.1. Das Problem
Wenn eine Simulation durchgeführt wurde, kann sie über den JavaScript-Link [Effacer la simulation] gelöscht werden:
![]() | ![]() |
9.18.2. Buchung der Kundenaktion [effacerSimulation]
Die JavaScript-Funktion [effacerSimulation] hat derzeit folgenden Code:
function effacerSimulation() {
// Die Eingaben im Formular werden gelöscht
// ...
// Die Simulation wird ausgeblendet, falls vorhanden
$("#simulation").hide();
// Menü
setMenu([lnkFaireSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
Aufgabe: Vervollständigen Sie diesen Code. Sie können sich dabei am Beispiel aus Abschnitt 7.6.6 orientieren
9.19. Schritt 13: Eine Simulation entfernen
9.19.1. Das Problem
Auf der Seite mit den Simulationen können bestimmte Simulationen über den JavaScript-Link [retirer] gelöscht werden:


9.19.2. Buchung der Kundenaktion [retirerSimulation]
Die Links [retirer] haben die folgende Form: HTML
wobei N die Nummer der Simulation ist.
Aufgabe: Schreiben Sie gemäß der Vorgehensweise in den Abschnitten 9.9.3 die Funktion JS [retirerSimulation]. Diese sendet einen Ajax-Aufruf vom Typ POST an die Aktion [/Pam/RetirerSimulation]. Dabei übermittelt sie den Wert N in der Form num=N.
Hinweis: Die Funktion JS [retirerSimulation] entspricht den anderen Funktionen JS, die Sie geschrieben haben und die einen Ajax-Aufruf an den Server senden. Die einzige Neuerung hierbei ist die Übermittlung eines Werts, der nicht in einem Formular enthalten ist. Es ist bekannt, dass die übermittelten Werte in einer Zeichenkette in folgender Form zusammengefasst werden:
Die Funktion JS [retirerSimulation] hat daher folgende Form:
function retirerSimulation(N) {
// man führt manuell einen Ajax-Aufruf durch
$.ajax({
url: '/Pam/RetirerSimulation',
...
data:"num="+N,
...
});
// Menü
setMenu([lnkRetourFormulaire, lnkTerminerSession]);
}
- Zeile 6: Die Eigenschaft [data] eines Ajax-Aufrufs JQuery stellt die an den Server gesendete Zeichenkette dar.
9.19.3. Schreiben der Serveraktion [RetirerSimulation]
Die Serveraktion [RetirerSimulation]:
- erhält einen über POST übermittelten Parameter namens [num], bei dem es sich um die Nummer einer Simulation handelt;
- muss die Simulation mit dieser Nummer aus der Liste der in der Sitzung gespeicherten Simulationen entfernen;
- muss anschließend die neue Liste der Simulationen anzeigen lassen.
Aufgabe: Schreiben Sie die Serveraktion [RetirerSimulation]. Lesen Sie Abschnitt 4.1 noch einmal durch, um zu erfahren, wie Sie den über POST übermittelten Parameter namens [num] abrufen können.
9.20. Schritt 14: Verbesserung der Initialisierungsmethode der Anwendung
Unsere Webanwendung ist fertiggestellt. Sie funktioniert mit einer simulierten Klasse [métier]. Hier noch einmal die von uns entwickelte Architektur:
![]() |
Bevor wir zur eigentlichen Implementierung der Schicht [métier] übergehen, müssen noch einige Details geklärt werden. Dies geschieht in der Initialisierungsmethode der Anwendung: der Methode [Application_Start] in [Global.asax]:
![]() |
Die Methode [Application_Start] in [Global.asax] wird beim Start der Anwendung einmalig ausgeführt. Hier kann die Konfigurationsdatei [Web.config] genutzt werden. Derzeit sieht unsere Methode [Application_Start] wie folgt aus:
// Anwendung
protected void Application_Start()
{
// ----------Automatisch generiert
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// -------------------------------------------------------------------
// ---------- spezifische Konfiguration
// -------------------------------------------------------------------
// Anwendungsbereichsdaten
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
// Instanzierung der Ebene [métier]
application.PamMetier = new PamMetier();
...
// Modellbinder
...
}
In Zeile 17 wird die Geschäftssschicht mit dem Operator „new“ instanziiert. Außerdem ist das Anwendungsmodell wie folgt definiert:
public class ApplicationModel
{
// --- Anwendungsbereichsdaten ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
}
In Zeile 5 oben ist zu sehen, dass der Typ der Eigenschaft [PamMetier] dem der Schnittstelle [IPamMetier] entspricht. Das bedeutet, dass diese Eigenschaft durch jedes Objekt initialisiert werden kann, das diese Schnittstelle implementiert. In Zeile 17 von [Application_Start] haben wir jedoch den Namen einer Klasse fest codiert, die [IPamMetier] implementiert. Sollte die Schicht [métier] also mit einer neuen Klasse implementiert werden, die [IPamMetier] implementiert, müsste diese Zeile geändert werden. Das ist zwar nicht besonders wichtig, lässt sich aber vermeiden. Die Definition der Implementierungsklasse der Schnittstelle [IPamMetier] kann in eine Konfigurationsdatei ausgelagert werden. Um die Implementierung zu ändern, ändert man dann den Inhalt dieser Konfigurationsdatei. Der Code .NET muss nicht geändert werden.
Wir verwenden hier den Dependency-Injection-Container [Spring.net]. Es gibt weitere Frameworks wie .NET, die dasselbe leisten, vielleicht sogar besser und einfacher.
Die Architektur des Projekts entwickelt sich wie folgt:
![]() |
- zu [A]: Die Initialisierungsmethode der Schicht [ASP.NET MVC] fordert von [Spring.net] eine Referenz auf die simulierte Schicht [métier] an;
- in [B] erstellt [Spring.net] die simulierte Schicht [métier], indem es anhand seiner Konfigurationsdatei ermittelt, welche Klasse instanziiert werden muss;
- In [C] übergibt [Spring.net] die Referenz der simulierten Schicht [métier] an die Schicht [ASP.NET MVC].
Es ist zu beachten, dass die von [Spring.net] verwalteten Objekte standardmäßig Singletons sind: Es existiert jeweils nur ein einziges Exemplar. Wenn also später in unserem Beispiel Code erneut eine Referenz auf die simulierte Schicht [métier] von [Spring.net] anfordert, gibt [Spring.net] einfach die Referenz auf das ursprünglich erstellte Objekt zurück.
9.20.1. Hinzufügen der Referenzen [Spring] zum Webprojekt
Wir werden [Spring.net] verwenden. Dieses Framework wird in Form von DLL bereitgestellt, das zu den Referenzen des Projekts hinzugefügt werden muss. Dazu gehen Sie wie folgt vor:
![]() |
Klicken Sie in [1] mit der rechten Maustaste auf den Zweig [References] des Projekts und wählen Sie dann die Option [Gérer les packages NuGet]. Dazu ist eine Internetverbindung erforderlich. Anschließend geht man genauso vor wie zuvor bei den Bibliotheken JQuery und [Globalize]. Man sucht nach dem Schlüsselwort [Spring.core] und installiert dieses Paket. Die Installation führt zu zwei DLL: [Spring.core], [2] sowie [Common.Logging] und [3]. In den folgenden Beispielen wurde die Spring-Version 1.3.2 verwendet.
Hinweis: Wenn Sie keine Internetverbindung haben, finden Sie diese DLL-Dateien in einem Ordner namens [lib] auf dem Datenträger dieser Fallstudie.
9.20.2. Konfiguration von [web.config]
Die Definition der Implementierungsklasse der Schnittstelle [IPamMetier] erfolgt in der Datei [web.config].
<configuration>
<configSections>
...
<sectionGroup name="spring">
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
</sectionGroup>
</configSections>
<!-- Spring-Konfiguration -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier-simule"/>
</objects>
</spring>
...
- Zeilen 2–8: Suchen Sie das Tag <configSections> in der Datei und fügen Sie die Zeilen 4–7 darin ein;
- Zeile 4: Das Attribut [name="spring"] enthält Informationen zum Abschnitt [spring] der Zeilen 10–17;
- Zeile 5: Definiert die Klasse [Spring.Context.Support.DefaultSectionHandler], die sich in der Klasse DLL [Spring.Core] befindet, als diejenige, die den Abschnitt [objects] der Zeilen 14–16 verarbeiten kann;
- Zeile 6: Definiert die Klasse [Spring.Context.Support.ContextHandler], die sich in den Klassen DLL und [Spring.Core] befindet, als diejenige, die den Abschnitt [context] in den Zeilen 11–13 verarbeiten kann;
- Zeilen 11–13: Dieser Abschnitt enthält die Information [<resource uri="config://spring/objects" />], die angibt, dass sich die Spring-Objekte in der Konfigurationsdatei im Abschnitt [/spring/objects] befinden, d. h. in den Zeilen 14–16;
- Zeilen 14–16: Das Tag [objects] leitet die Spring-Objekte ein;
- Zeile 15: definiert ein Objekt mit der Kennung [id="pammetier"], bei dem es sich um eine Instanz der Klasse [Pam.Metier.Service.PamMetier] handelt, die sich in der Klasse DLL [pam-metier-simule] befindet. Hier darf man sich nicht vertun. Für das Attribut [id] können Sie einen beliebigen Wert eingeben. Diese Kennung werden Sie in [Global.asax] verwenden. Die Klasse [Pam.Metier.Service.PamMetier] ist die unserer simulierten Ebene [métier]. Um ihren vollständigen Namen zu erfahren, müssen Sie zu ihrer Definition zurückkehren:
namespace Pam.Metier.Service
{
public class PamMetier : IPamMetier
{
...
Für DLL und [pam-metier-simule] müssen die Eigenschaften des C#-Projekts [pam-metier-simule] überprüft werden:
![]() |
Es muss der in [1] angegebene Name verwendet werden.
9.20.3. Änderung an [Application_Start]
Die Methode [Application_Start] wird wie folgt angepasst:
using Spring.Context.Support;
// Anwendung
protected void Application_Start()
{
// ----------Automatisch generiert
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// -------------------------------------------------------------------
// ---------- spezifische Konfiguration
// -------------------------------------------------------------------
// Anwendungsbereichsdaten
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
// Instanziierung der [métier]-Schicht
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
...
// Modellbinder
...
}
- Zeile 19: Es wird die Spring-Klasse [ContextRegistry] verwendet, die die Datei [web.config] verarbeiten kann. Dazu muss der Namensraum aus Zeile 1 importiert werden. Die statische Methode [GetContext] ermöglicht es, den Inhalt der Tags [context] abzurufen, die angeben, wo sich die Spring-Objekte befinden. Die statische Methode [GetObject] ermöglicht es anschließend, ein bestimmtes Objekt abzurufen, das durch sein Attribut „id“ identifiziert wird. Beachten Sie, dass der Name der Klasse, die die Schnittstelle [IPamMetier] implementiert, nun nicht mehr fest im Code hinterlegt ist. Er befindet sich nun in der Datei [web.config].
Nachdem Sie alle diese Änderungen vorgenommen haben, testen Sie Ihre Anwendung. Sie sollte nun funktionieren.
9.20.4. Behandlung eines Initialisierungsfehlers der Anwendung
In der Methode [Application_Start] haben wir geschrieben:
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
Die Anweisung rechts vom Gleichheitszeichen kann fehlschlagen. Dafür gibt es verschiedene Gründe:
- Der offensichtlichste ist, dass der Name des zu instanziierenden Objekts falsch ist;
- ein weiterer Grund ist, dass die Instanziierung der Schicht [métier] fehlschlägt. Dies kann bei unserer simulierten Schicht [métier] nicht der Fall sein, wohl aber bei unserer realen Schicht [métier], die mit einer Datenbank verbunden ist. Möglicherweise wird die Schicht SGBD nicht gestartet, die Informationen zur zu verwaltenden Datenbank sind möglicherweise fehlerhaft usw.
Wir werden eine mögliche Ausnahme in einem try/catch-Block behandeln. Der Code sieht nun wie folgt aus:
// Anwendung
protected void Application_Start()
{
// ----------Automatisch generiert
...
// -------------------------------------------------------------------
// ---------- spezifische Konfiguration
// -------------------------------------------------------------------
// Anwendungsbereichsdaten
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
application.InitException = null;
try
{
// Instanzierung der Schicht [métier]
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
catch (Exception ex)
{
application.InitException = ex;
}
//, wenn kein Fehler vorliegt
if (application.InitException == null)
{
....
}
// Modellbinder
...
}
- In Zeile 12 fügen wir eine neue Eigenschaft namens [InitException] in das Anwendungsmodell ein:
public class ApplicationModel
{
// --- Anwendungsbereichsdaten ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
public Exception InitException { get; set; }
}
- Zeile 7 oben: die Ausnahme, die möglicherweise bei der Initialisierung der Anwendung auftritt;
- Zeilen 13–21 von [Application_Start]: Die Instanziierung der Schicht [métier] erfolgt nun in einem try/catch-Block;
- Zeile 20: Die Ausnahme wird gespeichert;
- Zeilen 23–26: Wenn kein Fehler aufgetreten ist, wird der zuvor festgelegte Code ausgeführt;
- Zeile 28: Die [ModelBinders]-Objekte werden unabhängig davon erstellt, ob ein Fehler aufgetreten ist oder nicht. Das ist wichtig. Wir wollen sicherstellen, dass das Anwendungsmodell [ApplicationModel] vom Framework korrekt verknüpft wird.
Wir wissen, dass beim Start der Anwendung die Serveraktion [Index] ausgeführt wird. Derzeit lautet sie wie folgt:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
}
In Zeile 2 erhält die Aktion [Index] das Anwendungsmodell. Sie kann somit feststellen, ob die Initialisierung erfolgreich war oder nicht, und eine Fehlerseite anzeigen, falls die Initialisierung aus irgendeinem Grund fehlgeschlagen ist. Wir passen den Code wie folgt an:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
// Initialisierungsfehler?
if (application.InitException != null)
{
// Fehlerseite ohne Menü
return View("InitFailed",Static.GetErreursForException(application.InitException));
}
// kein Fehler
return View(new IndexModel() { Application = application });
}
Zeile 8: Im Falle eines Initialisierungsfehlers zeigen wir die Ansicht [InitFailed.cshtml] an, deren Vorlage die Liste der Fehlermeldungen der Ausnahme ist, die bei der Initialisierung aufgetreten ist. Die Methode [Static.GetErreursForException] wurde in Abschnitt 9.12.4 vorgestellt und erläutert. Die Ansicht [InitFailed.cshtml] sieht wie folgt aus:
![]() |
Der Code lautet wie folgt:
@model IEnumerable<string>
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" href="~/Content/Site.css" />
</head>
<body>
<table>
<tbody>
<tr>
<td>
<h2>Simulateur de calcul de paie</h2>
</td>
</tbody>
</table>
<hr />
<h2>Les erreurs suivantes se sont produites à l'initialisation de l'application : </h2>
<ul>
@foreach (string msg in Model)
{
<li>@msg</li>
}
</ul>
</body>
</html>
- Zeile 1: Die Vorlage der Ansicht ist eine Liste von Fehlermeldungen. Diese werden in einer Liste HTML in den Zeilen 24–29 angezeigt;
- Zeile 3: Diese Ansicht verwendet nicht die Master-Seite [_Layout.cshtml]. Das durch dieses Dokument eingebundene Menü soll nämlich nicht verwendet werden. Daher wird eine vollständige Seite HTML erstellt (Zeilen 5–23).
Zum Testen genügt es, in [Application_Start] die Instanziierung der Ebene [métier] wie folgt zu ändern:
try
{
// Instanziierung der Schicht [métier]
application.PamMetier = ContextRegistry.GetContext().GetObject("xx") as IPamMetier;
}
catch (Exception ex)
{
application.InitException = ex;
}
In Zeile 4 wird nach einem Objekt gesucht, das in den Spring-Objekten nicht vorhanden ist.
Wenn man diese Änderungen speichert und die Anwendung startet, erhält man die folgende Seite:
![]() |
Es wird eine Fehlerseite ohne Menü angezeigt. Der Benutzer kann nichts anderes tun, als den Fehler zur Kenntnis zu nehmen. Genau das war beabsichtigt.
9.21. Wo stehen wir jetzt?
Wir verfügen nun über eine funktionsfähige Webanwendung, die mit einer simulierten Geschäftslogikschicht arbeitet. Ihre Architektur sieht wie folgt aus:
![]() |
Die Schicht [ASP.NET MVC] arbeitet über die Schnittstelle [IPamMetier] mit der simulierten Geschäftsschicht zusammen. Wenn wir diese simulierte Geschäftsschicht durch eine echte Geschäftsschicht ersetzen, die dieser Schnittstelle entspricht, müssen wir den Code der Webschicht nicht ändern. Dank [Spring.net] müssen wir in [web.config] lediglich die Implementierungsklasse der Schnittstelle [IPamMetier] ändern. Diesen Weg schlagen wir ein.
Die neue Architektur wird wie folgt aussehen:
![]() |
Wir werden nacheinander Folgendes beschreiben:
- die Schicht [EF5], die mit SGBD verbunden ist. Sie wird mit Entity Framework 5 (EF5) implementiert;
- die Schicht [DAO], die den Zugriff auf die Daten über die Schicht [EF5] verwaltet. Dadurch kann sie die Existenz von SGBD ignorieren. Diese Schicht beschränkt sich darauf, die Entitäten der Anwendung [Employe, Cotisations, Indemnites] zu bearbeiten;
- die Schicht [métier], die die Gehaltsberechnung implementiert.
Die neue Architektur ist diejenige, die ganz am Anfang dieses Dokuments in Abschnitt 1.1 vorgestellt wurde und die wir hier noch einmal zusammenfassen:
![]() |
- Die Schicht [Web] ist die Schicht, die mit dem Benutzer der Webanwendung in Kontakt steht. Dieser interagiert mit der Webanwendung über Webseiten, die in einem Browser angezeigt werden. In dieser Schicht befinden sich ASP.NET und MVC, und zwar ausschließlich in dieser Schicht;
- Die Schicht [métier] implementiert die Geschäftsregeln der Anwendung, wie beispielsweise die Berechnung eines Gehalts oder einer Rechnung. Diese Schicht nutzt Daten, die vom Benutzer über die Schicht [Web] und von SGBD über die Schicht [DAO] bereitgestellt werden;
- die Schicht [DAO] (Data Access Objects), die Schicht [ORM] (Object Relational Mapper) und der Konnektor ADO.NET verwalten den Zugriff auf die Daten der Schicht SGBD. Die Schicht [ORM] bildet eine Brücke zwischen den von der Schicht [DAO] verarbeiteten Objekten und den Zeilen und Spalten der Daten einer relationalen Datenbank. Weltweit werden üblicherweise zwei ORM verwendet: NET, NHibernate (http://sourceforge.net/projects/nhibernate/) und Entity Framework (http://msdn.microsoft.com/en-us/data/ef.aspx);
- die Integration der Schichten kann über einen Dependency-Injection-Container wie Spring (http://www.springframework.net/) erfolgen;
Die Schichten [métier], [DAO] und [EF5] werden mithilfe von C#-Projekten implementiert. Ab sofort arbeiten wir mit Visual Studio Express 2012 für den Desktop.
9.22. Schritt 15: Einrichtung der Entity Framework 5-Schicht
![]() |
Die Erstellung der Schicht [EF5] ist weniger eine Frage der Programmierung als vielmehr der Konfiguration. Um sich mit der Erstellung dieser Schicht vertraut zu machen, lesen Sie bitte das Dokument [Introduction à Entity Framework 5 Code First], das unter URL [http://tahe.developpez.com/dotnet/ef5cf-02/] verfügbar ist. Es handelt sich um ein recht umfangreiches Dokument. Die Grundlagen finden sich in den ersten vier Kapiteln. Die Abschnitte, die besonders zu beachten sind, werden noch näher erläutert. Wenn wir auf dieses Dokument verweisen, verwenden wir die Bezeichnung [refEF5].
Außerdem werden wir gelegentlich auf C#-Konzepte zurückgreifen müssen. In diesem Fall verweisen wir auf den Kurs [Introduction au langage C#], der unter URL [http://tahe.developpez.com/dotnet/csharp/] verfügbar ist, und verwenden dabei die Bezeichnung [refC#].
9.22.1. Die Datenbank
Die Datenbank der Anwendung wurde in Abschnitt 9.4 vorgestellt. Es handelt sich um eine MySQL-Datenbank mit dem Namen [dbpam_ef5] (pam=Paie Assistante Maternelle). Diese Datenbank hat einen Administrator namens „root“ ohne Passwort.
Zur Erinnerung: Die Datenbank besteht aus drei Tabellen:

Es besteht eine Fremdschlüsselbeziehung zwischen der Spalte EMPLOYES (INDEMNITE_ID) und der Spalte INDEMNITES (ID). Ein Teil der Struktur dieser Datenbank wird durch ihre Verwendung mit EF5 vorgegeben.
Das Skript SQL zur Erstellung der Datenbank lautet wie folgt:
-- phpMyAdmin SQL Dump
-- Version 3.5.1
-- http://www.phpmyadmin.net
--
-- Client: localhost
-- Erstellt am: Mo, 04. November 2013 um 09:34 Uhr
-- Serverversion: 5.5.24-log
-- Version von PHP: 5.4.3
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
--
-- Datenbank: `dbpam_ef5`
--
-- --------------------------------------------------------
--
-- Tabellenstruktur `Beiträge`
--
CREATE TABLE IF NOT EXISTS `cotisations` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`SECU` double NOT NULL,
`RETRAITE` double NOT NULL,
`CSGD` double NOT NULL,
`CSGRDS` double NOT NULL,
`VERSIONING` int(11) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=12 ;
--
-- Inhalt der Tabelle `Beiträge`
--
INSERT INTO `cotisations` (`ID`, `SECU`, `RETRAITE`, `CSGD`, `CSGRDS`, `VERSIONING`) VALUES
(11, 9.39, 7.88, 6.15, 3.49, 1);
--
-- Trigger `Beiträge`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_COTISATIONS`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_COTISATIONS` BEFORE UPDATE ON `cotisations`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_COTISATIONS`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_COTISATIONS` BEFORE INSERT ON `cotisations`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=1;
END
//
DELIMITER ;
-- --------------------------------------------------------
--
-- Struktur der Tabelle `employes`
--
CREATE TABLE IF NOT EXISTS `employes` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`PRENOM` varchar(20) CHARACTER SET latin1 NOT NULL,
`SS` varchar(15) CHARACTER SET latin1 NOT NULL,
`ADRESSE` varchar(50) CHARACTER SET latin1 NOT NULL,
`CP` varchar(5) CHARACTER SET latin1 NOT NULL,
`VILLE` varchar(30) CHARACTER SET latin1 NOT NULL,
`NOM` varchar(30) CHARACTER SET latin1 NOT NULL,
`VERSIONING` int(11) NOT NULL,
`INDEMNITE_ID` bigint(20) NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `SS` (`SS`),
KEY `FK_EMPLOYES_INDEMNITE_ID` (`INDEMNITE_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=26 ;
--
-- Inhalt der Tabelle `employes`
--
INSERT INTO `employes` (`ID`, `PRENOM`, `SS`, `ADRESSE`, `CP`, `VILLE`, `NOM`, `VERSIONING`, `INDEMNITE_ID`) VALUES
(24, 'Marie', '254104940426058', '5 rue des oiseaux', '49203', 'St Corentin', 'Jouveinal', 1, 93),
(25, 'Justine', '260124402111742', 'La Brûlerie', '49014', 'St Marcel', 'Laverti', 1, 94);
--
-- Trigger für `employes`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_EMPLOYES`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_EMPLOYES` BEFORE UPDATE ON `employes`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_EMPLOYES`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_EMPLOYES` BEFORE INSERT ON `employes`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=1;
END
//
DELIMITER ;
-- --------------------------------------------------------
--
-- Struktur der Tabelle `indemnites`
--
CREATE TABLE IF NOT EXISTS `indemnites` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`ENTRETIEN_JOUR` double NOT NULL,
`REPAS_JOUR` double NOT NULL,
`INDICE` int(11) NOT NULL,
`INDEMNITES_CP` double NOT NULL,
`BASE_HEURE` double NOT NULL,
`VERSIONING` int(11) NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `INDICE` (`INDICE`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=95 ;
--
-- Inhalt der Tabelle `indemnites`
--
INSERT INTO `indemnites` (`ID`, `ENTRETIEN_JOUR`, `REPAS_JOUR`, `INDICE`, `INDEMNITES_CP`, `BASE_HEURE`, `VERSIONING`) VALUES
(93, 2.1, 3.1, 2, 15, 2.1, 1),
(94, 2, 3, 1, 12, 1.93, 1);
--
-- Trigger für `indemnites`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_INDEMNITES`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_INDEMNITES` BEFORE UPDATE ON `indemnites`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_INDEMNITES`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_INDEMNITES` BEFORE INSERT ON `indemnites`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=1;
END
//
DELIMITER ;
--
-- Einschränkungen für die exportierten Tabellen
--
--
-- Einschränkungen für die Tabelle `employes`
--
ALTER TABLE `employes`
ADD CONSTRAINT `FK_EMPLOYES_INDEMNITE_ID` FOREIGN KEY (`INDEMNITE_ID`) REFERENCES `indemnites` (`ID`);
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
Beachten Sie bitte folgende Punkte:
- Zeilen 30, 73, 122: Die Primärschlüssel der Tabellen befinden sich im Modus [AUTO_INCREMENT]. Diese werden von MySQL verwaltet und nicht von EF5;
- Zeile 83: Die Nummer SS unterliegt einer Eindeutigkeitsbeschränkung;
- Zeile 130: Die Mitarbeiter-ID unterliegt einer Eindeutigkeitsbeschränkung;
- Zeilen 168–169: Der Fremdschlüssel von der Tabelle [employes] zur Tabelle [indemnites];
- Zeile 49: Ein Trigger namens [Trigger] ist ein Skript namens SQL, das in den Trigger SGBD eingebettet ist und zu bestimmten Zeitpunkten ausgeführt wird;
- Zeilen 51–54: Der Trigger [INCR_VERSIONING_COTISATIONS] wird vor jeder Änderung einer Zeile in der Tabelle [cotisations] ausgelöst. Er erhöht dann den Wert in der Spalte [VERSIONING] um eins;
- Zeilen 59–62: Der Trigger [START_VERSIONING_COTISATIONS] wird vor jedem Einfügen einer neuen Zeile in die Tabelle [cotisations] ausgelöst. Er setzt dann die Spalte [VERSIONING] auf 1;
- Letztendlich nimmt die Spalte [VERSIONING] den Wert 1 an, wenn eine Zeile in der Tabelle [cotisations] angelegt wird, und wird bei jeder Änderung an dieser Zeile um 1 erhöht. Dieser Mechanismus ermöglicht es EF5, die Parallelität beim Zugriff auf eine Zeile der Tabelle [cotisations] wie folgt zu verwalten:
- Ein Prozess P1 liest zum Zeitpunkt T1 eine Zeile L aus der Tabelle [cotisations]. Die Zeile enthält in der Spalte [VERSIONING] den Wert V1;
- ein Prozess P2 liest zum Zeitpunkt T2 dieselbe Zeile L aus der Tabelle [cotisations]. Die Zeile weist die Spalten [VERSIONING] und V1 auf, da der Prozess P1 seine Änderung noch nicht freigegeben hat;
- Der Prozess P1 ändert die Zeile L und bestätigt die Änderung. Die Spalte [VERSIONING] der Zeile L wechselt daraufhin aufgrund des Triggers [INCR_VERSIONING_COTISATIONS] auf V1+1;
- der Prozess P2 verfährt anschließend ebenso. EF5 löst daraufhin eine Ausnahme aus, da der Prozess P2 eine Zeile mit der Spalte [VERSIONING] enthält, deren Wert V1 sich von dem in der Datenbank gefundenen Wert V1+1. Eine Zeile kann nur geändert werden, wenn der Wert von [VERSIONING] mit dem in der Datenbank übereinstimmt.
Dies wird als optimistische Verwaltung von Parallelzugriffen bezeichnet. Bei EF5 muss ein Feld, das diese Rolle übernimmt, die Anmerkung [ConcurrencyCheck] tragen.
- Ein ähnlicher Mechanismus wird für die Tabelle [employes] (Zeilen 98–113) und die Tabelle [indemnites] (Zeilen 144–159) erstellt.
Aufgabe: Legen Sie die Datenbank MySQL [dbpam_ef5] mithilfe des vorherigen Skripts SQL an. Die Datenbank [dbpam_ef5] muss zuvor angelegt werden, da das Skript sie nicht erstellt. Anschließend wird das Skript SQL auf dieser Datenbank ausgeführt.
9.22.2. Das Visual Studio-Projekt
Mit Visual Studio Express 2012 für den Desktop laden wir die Lösung [pam-td], die beim Erstellen der Schicht [web] verwendet wurde:
![]() |
- in [1], VS – Visual Studio Express 2012 für den Desktop kann das Webprojekt [pam-web-01] nicht laden. Das ist normal und stellt kein Problem dar;
- In [2] wird der Lösung [pam-td] ein neues Projekt hinzugefügt;
![]() |
- In [3] ist das Projekt vom Typ [console] und heißt [4] [pam-ef5];
- in [5], das erstellte Projekt. Sein Name ist nicht fettgedruckt, daher handelt es sich nicht um das Startprojekt der Lösung;
![]() |
- In [6] und [7] wird das neue Projekt als Startprojekt festgelegt.
9.22.3. Hinzufügen der erforderlichen Referenzen zum Projekt
Betrachten wir das Projekt im Gesamtzusammenhang:
![]() |
Unser Projekt benötigt eine Reihe von DLL:
- die DLL von Entity Framework 5;
- die DLL des ADO.NET-Konnektors des SGBD MySQL.
In Abschnitt 4.2 von [refEF5] wird erläutert, wie diese DLL mithilfe des Tools [NuGet] installiert werden. Derzeit (Nov. 2013) ist die verfügbare Version von Entity Framework die Version 6 (EF6). Leider scheint der Connector ADO.NET des im November 2013 verfügbaren SGBD MySQL über [NuGet] nicht mit EF6 kompatibel ist. Daher wurden in einem Ordner [lib] [1] die DLL vonEF5 sowie die anderen für das Projekt [pam-ef5] erforderlichen DLL-Dateien
![]() |
Wir haben weitere DLL in den Ordner [lib] verschoben. Diese werden wir später verwenden. In [2] fügen wir diese neuen DLL zum Projekt hinzu.
![]() |
- In [3] navigieren wir im Dateisystem zum Ordner [lib];
- in [4] wählen wir die drei DLL aus und bestätigen zweimal;
- In [5] wurden die drei DLL zu den Projektreferenzen hinzugefügt.
Wir benötigen eine weitere DLL. Diese findet sich unter den Dateien des Frameworks .NET auf dem Rechner.
![]() |
- Fügen Sie unter [1] eine neue Referenz zum Projekt hinzu;
![]() |
- in [2], wählen Sie [Assemblys] aus;
- in [3] geben Sie [system.component] ein;
- bei [4] wählen Sie die Assembly [System.ComponentModel.DataAnnotations] aus;
- In „[5]“ wurde die Referenz hinzugefügt.
Wir sind nun bereit für die Programmierung und Konfiguration.
9.22.4. Die Entity-Framework-Entitäten
Entity-Framework-Entitäten sind Klassen, in denen die Zeilen der verschiedenen Datenbanktabellen gekapselt werden. Hier noch einmal zur Erinnerung:

In der Schicht [web] hatten wir die Entitäten [Employe, Cotisations, Indemnités] verwendet (siehe Abschnitt 9.7.3, Seite 219). Diese waren keine exakten Abbilder der Tabellen. Daher wurden die Spalten [ID, VERSIONING] ignoriert. Hier wird dies nicht der Fall sein, da sie von den Entitäten ORM und EF5 verwendet werden. Wir werden ihnen daher die fehlenden Eigenschaften hinzufügen. Wir erstellen diese Entitäten in einem Ordner namens [Models] des Projekts:
![]() |
Ihr neuer Code lautet nun wie folgt:
Klasse [Cotisations]
using System;
namespace Pam.EF5.Entites
{
public class Cotisations
{
public int Id { get; set; }
public double CsgRds { get; set; }
public double Csgd { get; set; }
public double Secu { get; set; }
public double Retraite { get; set; }
public int Versioning { get; set; }
// Unterschrift
public override string ToString()
{
return string.Format("Cotisations[{0},{1},{2},{3}, {4}, {5}]", Id, Versioning, CsgRds, Csgd, Secu, Retraite);
}
}
}
- Zeile 3: Der Namensraum wurde an das neue Projekt angepasst;
- die Eigenschaften in den Zeilen 7 und 12 wurden hinzugefügt, um die Struktur der Tabelle [cotisations] widerzuspiegeln;
- Zeile 17: Die Methode [ToString] zeigt nun die beiden neuen Felder an.
Klasse [Indemnites]
using System;
namespace Pam.EF5.Entites
{
public class Indemnites
{
public int Id { get; set; }
public int Indice { get; set; }
public double BaseHeure { get; set; }
public double EntretienJour { get; set; }
public double RepasJour { get; set; }
public double IndemnitesCp { get; set; }
public int Versioning { get; set; }
// Signatur
public override string ToString()
{
return string.Format("Indemnités[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Versioning, Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
}
}
}
- Zeile 3: Der Namensraum wurde an das neue Projekt angepasst;
- die Eigenschaften in den Zeilen 7 und 13 wurden hinzugefügt, um die Struktur der Tabelle [indemnites] widerzuspiegeln;
- Zeile 18: Die Methode [ToString] zeigt nun die beiden neuen Felder an.
Klasse [Employe]
using System;
namespace Pam.EF5.Entites
{
public class Employe
{
public int Id { get; set; }
public string SS { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
public string Adresse { get; set; }
public string Ville { get; set; }
public string CodePostal { get; set; }
public Indemnites Indemnites { get; set; }
public int Versioning { get; set; }
// Signatur
public override string ToString()
{
return string.Format("Employé[{0},{1},{2},{3},{4},{5}, {6}, {7}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal);
}
}
}
- Zeile 3: Der Namensraum wurde an das neue Projekt angepasst;
- die Eigenschaften in den Zeilen 8 und 16 wurden hinzugefügt, um die Struktur der Tabelle [employes] widerzuspiegeln;
- Zeile 21: Die Methode [ToString] zeigt nun die beiden neuen Felder an.
Damit sie von ORM und EF5 verwendet werden können, müssen die Eigenschaften dieser Klassen mit Annotationen versehen werden.
Aufgabe: Fügen Sie mithilfe von Abschnitt 3.4 [Création de la base à partir des entités] aus [refEF5] den Entitäten [Employe, Cotisations, Indemnites] die für EF5 erforderlichen Annotationen hinzu.
Hinweise:
- Es geht lediglich darum, Anmerkungen zu erstellen. Befolgen Sie nicht den Teil [création de base] des genannten Absatzes;
- Für die Anmerkung [Table] folgen Sie dem Beispiel MySQL aus Abschnitt 4.2 von [refEF5];
- Für die Annotation [ConcurrencyCheck] zur Eigenschaft [Versioning] folgen Sie dem Oracle-Beispiel aus Abschnitt 5.2 von [refEF5];
- Für den Fremdschlüssel, den die Tabelle [employes] auf die Tabelle [indemnités] verweist, befolgen Sie das Beispiel 3.4.2 aus [refEF5]. Auf diese Weise fügen Sie der Entität [Employe] eine neue Eigenschaft hinzu:
public int IndemniteId { get; set; }
deren Wert dem Wert der Spalte [INDEMNITES_ID] der Tabelle [employes] entspricht. Sie fügen den Eigenschaften [IndemniteId] und [Indemnites] der Entität [Employe] die Anmerkungen für Fremdschlüssel hinzu. Befolgen Sie dazu das Beispiel 3.4.2 von [refEF5];
- die umgekehrten Beziehungen der Fremdschlüssel werden nicht verwaltet;
- diese Arbeit erfordert etwas Lektüre von [refEF5].
9.22.5. Konfiguration von ORM und EF5
Betrachten wir das Projekt im Gesamtzusammenhang:
![]() |
Die Ebene [EF5] greift über den Konnektor [ADO.NET] des SGBD MySQL auf die Datenbank zu. Sie benötigt eine Reihe von Informationen, um auf diese Datenbank zugreifen zu können. Diese befinden sich an verschiedenen Stellen im Projekt.
Zunächst müssen wir den Datenbankkontext anlegen. Dieser Kontext ist eine von der Systemklasse [System.Data.Entity.DbContext] abgeleitete Klasse. Sie dient dazu, die Objektabbildungen der Datenbanktabellen zu definieren. Wir werden diese Klasse im Ordner [Models] des Projekts zusammen mit den Entitäten EF5 ablegen:
![]() |
Die Klasse [DbPamContext] sieht wie folgt aus:
using Pam.EF5.Entites;
using System.Data.Entity;
namespace Pam.Models
{
public class DbPamContext : DbContext
{
public DbSet<Employe> Employes { get; set; }
public DbSet<Cotisations> Cotisations { get; set; }
public DbSet<Indemnites> Indemnites { get; set; }
}
}
- Zeile 6: Die Klasse [DbPamContext] leitet sich von der Systemklasse [DbContext] ab;
- Zeilen 8–10: Die Bildobjekte der drei Datenbanktabellen. Ihr Typ ist [DbSet<Entity>], wobei [Entity] eine der soeben definierten Entity-Framework-Entitäten ist. Der Typ [DbSet] kann als eine Sammlung von Entitäten betrachtet werden. Er kann mit LINQ abgefragt werden (Sprache INtegrated Query). Leser, die mit LINQ nicht vertraut sind, werden gebeten, Abschnitt 3.5.4 „[Apprentissage de LINQ avec LINQPad]“ von [refEF5] zu lesen.
Im Folgenden bezeichnen wir die Klasse [DbPamContext] als Persistenzkontext der Datenbank [dbpam_ef5]. Dies ist eine gängige Terminologie in ORM (Objekt-Relational-Mapper). Dieser Persistenzkontext ist ein Objektabbild der Datenbank. Man spricht auch von der Synchronisation des Persistenzkontexts mit der Datenbank: Änderungen, Hinzufügungen und Löschungen, die am Persistenzkontext vorgenommen werden, werden auf die Datenbank übertragen. Diese Synchronisation erfolgt zu bestimmten Zeitpunkten: beim Schließen des Persistenzkontexts, am Ende einer Transaktion oder vor einer Abfrage SQL SELECT an die Datenbank.
Die Informationen zu SGBD und der Datenbank werden in [App.config] abgelegt.
![]() |
Die erforderliche Konfiguration in [app.config] wird in den folgenden Abschnitten von [refEF5] erläutert:
- 3.4 für den SGBD- und SQL-Server. Dort werden die Grundprinzipien der Konfiguration von EF5 dargelegt;
- 4.2 für den SGBD- und MySQL-Server.
Wir befolgen diesen letzten Absatz und konfigurieren die Datei [app.config] wie folgt:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<!-- Konfiguration EF5 -->
<!-- Datenbankverbindungszeichenfolge [dbam_ef5] -->
<connectionStrings>
<add name="DbPamContext"
connectionString="Server=localhost;Database=dbpam_ef5;Uid=root;Pwd=;"
providerName="MySql.Data.MySqlClient" />
</connectionStrings>
<!-- der Factory-Provider von MySQL -->
<system.data>
<DbProviderFactories>
<remove invariant="MySql.Data.MySqlClient"/>
<add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL"
type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=C5687FC88969C44D"
/>
</DbProviderFactories>
</system.data>
</configuration>
- Die Zeilen 6–21 wurden hinzugefügt. Sie müssen in das <configuration>-Tag in den Zeilen 2 und 22 eingefügt werden;
- Zeilen 8–12: definieren Verbindungszeichenfolgen zu Datenbanken, ein Konzept aus ADO.NET (siehe Abschnitt 7.3.5 in [refC#]);
- Zeilen 9–11: definieren die Verbindungszeichenfolge zur Datenbank MySQL [dbpam_ef5];
- Zeile 9: Der Name der Verbindungszeichenfolge. Hier kann nicht beliebig etwas eingegeben werden. Standardmäßig muss der Name der Klasse angegeben werden, die den Datenbankkontext implementiert:
public class DbPamContext : DbContext
{
public DbSet<Employe> Employes { get; set; }
public DbSet<Cotisations> Cotisations { get; set; }
public DbSet<Indemnites> Indemnites { get; set; }
}
Die Klasse heißt [DbPamContext]. In Zeile 9 von [app.config] muss daher [name="DbPamContext"] eingetragen werden;
- Zeile 10: eine für SGBD spezifische Verbindungszeichenfolge MySQL:
- [Server=localhost]: Adresse des Rechners, auf dem SGBD gehostet wird. Hier ist es der lokale Rechner [localhost];
- [Database=dbpam_ef5;]: Name der Datenbank,
- [Uid=root;]: Login, mit dem die Verbindung zur Datenbank hergestellt wird,
- [Pwd=;]: Passwort für diesen Benutzernamen. Hier kein Passwort;
- Zeile 10: [providerName="MySql.Data.MySqlClient"] ist der Name des zu verwendenden Konnektors ADO.NET. Dieser Name entspricht dem Attribut [invariant] in Zeile 17. Man kann einen beliebigen Namen wählen, solange die vorstehende Regel eingehalten wird und noch kein Provider mit derselben Invariantenbezeichnung registriert wurde;
- Zeilen 15–20: Definieren eine Factory für ADO.NET-Konnektoren (Provider). Das Konzept [DbProviderFactory] ist für mich etwas unklar. Wenn ich dem Namen Glauben schenke, handelt es sich um eine Klasse, die den Provider ADO.NET generieren kann, der Zugriff auf den SGBD gewährt, hier MySQL5. In der Regel kopiert man diese Zeilen und fügt sie ein. Sie sind notwendig. Achten Sie besonders auf das Attribut [Version=6.5.4.0] in Zeile 16. Diese Versionsnummer muss mit der Versionsnummer von DLL [MySql.Data] übereinstimmen, die Sie zu den Projektreferenzen hinzugefügt haben:
![]() |
- Zeile 16 ist wichtig. Da man nicht zwei Provider mit demselben Namen installieren kann, löscht man zunächst einen eventuell bereits installierten Provider, der denselben Namen wie der in Zeile 17 zu installierende Provider trägt;
Das war’s. Beim ersten Mal ist es kompliziert und unübersichtlich, aber mit der Zeit wird es einfach, da man immer wieder dasselbe wiederholt.
9.22.6. Test der Schicht [EF5]
Wir sind bereit, unsere Schicht [EF5] zu testen. Dies erfolgt mithilfe des bereits vorhandenen Programms [Program.cs]:
![]() |
Wir werden den Inhalt der Datenbank anzeigen. Wenn dies gelingt, ist das ein erster Hinweis darauf, dass unsere Konfiguration korrekt ist. Ein Beispielcode ist in Abschnitt 3.5.3 von [refEF5] verfügbar. Der Code von [Program.cs] lautet wie folgt:
using Pam.EF5.Entites;
using Pam.Models;
using System;
namespace Pam
{
class Program
{
static void Main(string[] args)
{
try
{
using (var context = new DbPamContext())
{
// Der Inhalt der Tabellen wird angezeigt
Console.WriteLine("Liste des employés ----------------------------------------");
foreach (Employe employe in context.Employes)
{
Console.WriteLine(employe);
}
Console.WriteLine("Liste des indemnités --------------------------------------");
foreach (Indemnites indemnite in context.Indemnites)
{
Console.WriteLine(indemnite);
}
Console.WriteLine("Liste des cotisations -------------------------------------");
foreach (Cotisations cotisations in context.Cotisations)
{
Console.WriteLine(cotisations);
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
return;
}
}
}
}
- Zeile 13: Jede Operation an BD erfolgt über den Kontext dieser Datenbank. Wir haben diesen Kontext mit der Klasse [DbPamContext] implementiert. Wir haben ihn auch als Persistenzkontext der Datenbank bezeichnet;
- Zeilen 13, 31: Operationen auf dem Persistenzkontext erfolgen in einer Klausel [using]. Der Persistenzkontext wird zu Beginn der Klausel [using] geöffnet und beim Verlassen dieser Klausel automatisch geschlossen. Das bedeutet, dass alle Änderungen, die im Persistenzkontext innerhalb der Klausel [using] vorgenommen werden, beim Verlassen der Klausel in die Datenbank übernommen werden. Anschließend wird innerhalb einer Transaktion eine Reihe von Befehlen SQL an den Befehl BD gesendet. Das bedeutet, dass bei einem Fehlschlag eines Befehls SQL alle zuvor gesendeten Befehle SQL storniert werden. Daraufhin wird von EF5 eine Ausnahme ausgelöst;
- Zeile 17: Der Ausdruck [context.Employes] bezeichnet das Objektbild der Tabelle [employes]. Zur Erinnerung: [Employes] ist eine Eigenschaft des Persistenzkontexts [DbPamContext]:
public class DbPamContext : DbContext
{
public DbSet<Employe> Employes { get; set; }
public DbSet<Cotisations> Cotisations { get; set; }
public DbSet<Indemnites> Indemnites { get; set; }
}
- Zeile 17: Die Tatsache, dass [foreach] die Sammlung [context.Employes] durchläuft, führt dazu, dass alle Mitarbeiter aus der Datenbank in den Persistenzkontext übernommen werden. Ein Befehl SQL SELECT wird daher von EF5 ausgegeben;
- Zeilen 17–20: Die Mitarbeiter-Sammlung wird durchlaufen, und in Zeile 19 wird die Methode [ToString] der Klasse [Employe] verwendet, um die Mitarbeiter auf der Konsole anzuzeigen;
- Zeilen 21–25: dasselbe gilt für die Sammlung der Zulagen;
- Zeilen 27–30: ebenso für die Sammlung der Beiträge.
Kehren wir zur Definition der Entität [Employe] zurück:
using System;
namespace Pam.EF5.Entites
{
public class Employe
{
public int Id { get; set; }
public string SS { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
public string Adresse { get; set; }
public string Ville { get; set; }
public string CodePostal { get; set; }
public Indemnites Indemnites { get; set; }
public int Versioning { get; set; }
// Signatur
public override string ToString()
{
return string.Format("Employé[{0},{1},{2},{3},{4},{5}, {6}, {7}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal);
}
}
}
- Zeile 15: Ein Mitarbeiter hat einen Verweis auf eine Zulage.
Wenn ein Mitarbeiter in den Persistenzkontext zurückgebracht wird, wird dann auch seine Zulage mit zurückgebracht? Die Antwort lautet standardmäßig „nein“. Dies entspricht dem Konzept von [Lazy Loading]. Entitäten, auf die innerhalb einer anderen Entität verwiesen wird, werden nicht zusammen mit dieser anderen Entität in den Persistenzkontext übernommen. Dies geschieht nur, wenn sie vom Code innerhalb eines offenen Persistenzkontexts angefordert werden. Ist der Persistenzkontext geschlossen, wird eine Ausnahme ausgelöst.
Hätte die Methode [ToString] also die Eigenschaft [Indemnites] wie folgt referenziert:
// Signatur
public override string ToString()
{
return string.Format("Employé[{0},{1},{2},{3},{4},{5},{6},{7},{8}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
}
würde die folgende Operation in [Program.cs]:
foreach (Employe employe in context.Employes)
{
Console.WriteLine(employe);
}
hätte nicht nur die Mitarbeiter, sondern auch deren Zulagen in den Persistenzkontext zurückgeführt, da in Zeile 3 die Methode [Employe.ToString] aufgerufen wird und diese auf die Entität [Indemnites] verweist.
Die Ausführung von [Program.cs] liefert folgende Ergebnisse:
Was tun, wenn es nicht funktioniert? Dann haben Sie ein Problem… Es gibt zahlreiche mögliche Fehlerquellen:
- Überprüfen Sie die Konfiguration von EF5 (Abschnitt 9.22.5);
- überprüfen Sie Ihre Entity-Framework-Entitäten (Abschnitt 9.22.4).
9.22.7. DLL der Schicht [EF5]
Wir wandeln unser Projekt in eine Klassenbibliothek um, damit bei der Generierung eine .dll-Assembly anstelle einer .exe-Datei erstellt wird. Dies erfolgt in den Projekteigenschaften, wie bereits in Abschnitt 9.7.6 für die simulierte Geschäftsschicht beschrieben.
Aufgabe: Ändern Sie den Projekttyp von [pam-ef5] in eine Klassenbibliothek und generieren Sie das Projekt anschließend neu.
9.23. Schritt 16: Einrichtung der Schicht [DAO]
9.23.1. Die Schnittstelle der Schicht [DAO]
![]() |
Wie bereits bei der simulierten Schicht [métier] wird auch die Schicht [DAO] über eine Schnittstelle zugänglich sein. Wie wird diese aussehen?
Sehen wir uns die Schnittstelle [IPamMetier] der simulierten Schicht [métier] an, die wir erstellt haben:
public interface IPamMetier {
// Liste aller Mitarbeiteridentitäten
Employe[] GetAllIdentitesEmployes();
// ------- Gehaltsabrechnung
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
In Zeile 3 dient die Methode [GetAllIdentitesEmployes] dazu, die Dropdown-Liste der Startseite zu füllen:
![]() |
Diese Mitarbeiter müssen in der Datenbank abgerufen werden.
Zeile 6: Die Methode [GetSalaire] ermöglicht die Berechnung der Gehaltsabrechnung eines Mitarbeiters, dessen Nummer SS vorliegt. Zur Erinnerung: Die Definition des Typs [FeuilleSalaire] lautet:
public class FeuilleSalaire
{
// automatische Eigenschaften
public Employe Employe { get; set; }
public Cotisations Cotisations { get; set; }
public ElementsSalaire ElementsSalaire { get; set; }
}
Die Informationen aus den Zeilen 5 und 6 stammen aus der Datenbank. Zur Erinnerung: Ein Mitarbeiter verfügt über die Eigenschaft [Indemnites]. Diese Information muss ebenfalls abgerufen werden.
Man könnte also mit der folgenden Schnittstelle für die Schicht [DAO] beginnen:
public interface IPamDao {
// Liste aller Mitarbeiteridentitäten
Employe[] GetAllIdentitesEmployes();
// ein bestimmter Mitarbeiter mit seinen Zulagen
Employe GetEmploye(string ss);
// Liste aller Beiträge
Cotisations GetCotisations();
}
9.23.2. Das Visual Studio-Projekt
Aufgabe: Fügen Sie der Lösung „[pam-td]“ ein neues Projekt vom Typ „[console]“ mit dem Namen „[pam-dao]“ hinzu. Legen Sie dieses Projekt als Startprojekt der Lösung fest.
![]() |
9.23.3. Hinzufügen der erforderlichen Verweise zum Projekt
Betrachten wir das Projekt im Gesamtzusammenhang:
![]() |
Das Projekt [pam-dao] benötigt eine bestimmte Anzahl von DLL:
- alle, die im Projekt [pam-ef5] referenziert sind;
- dasjenige des Projekts [pam-ef5] selbst.
Außerdem werden wir [Spring.net] verwenden, um die Schicht [DAO] zu instanziieren. Dazu benötigen wir die DLL, [Spring.core] und [Common.Logging]. Diese DLL befinden sich im Ordner „[lib]“ des Fallstudien-Materials.
Aufgabe: Fügen Sie diese verschiedenen Referenzen zum Projekt [pam-dao] hinzu.
![]() |
9.23.4. Implementierung der Schicht [DAO]
![]() |
Die oben genannte Klasse [PamException] ist diejenige, die in Abschnitt 9.7.4 definiert wurde. Man ändert lediglich ihren Namensraum (Zeile 1 unten):
namespace Pam.Dao.Entites
{
// Ausnahmeklasse
public class PamException : Exception
{
....
}
}
Die Schnittstelle [IPamDao] ist diejenige, die wir gerade in Abschnitt 9.23.1 definiert haben:
using Pam.EF5.Entites;
namespace Pam.Dao.Service
{
public interface IPamDao
{
// Liste aller Mitarbeiteridentitäten
Employe[] GetAllIdentitesEmployes();
// ein bestimmter Mitarbeiter mit seinen Zulagen
Employe GetEmploye(string ss);
// Liste aller Beiträge
Cotisations GetCotisations();
}
}
Die Klasse [PamDaoEF5] implementiert diese Schnittstelle mithilfe von ORM und EF5. Ihr Code lautet wie folgt:
using Pam.Dao.Entites;
using Pam.EF5.Entites;
using Pam.Models;
using System;
using System.Linq;
namespace Pam.Dao.Service
{
public class PamDaoEF5 : IPamDao
{
// private Felder
private Cotisations cotisations;
private Employe[] employes;
// Hersteller
public PamDaoEF5()
{
// Beitrag
try
{
....
}
catch (Exception e)
{
throw new PamException("Erreur système lors de la construction de la couche [DAO]", e, 1);
}
}
// GetCotisations
public Cotisations GetCotisations()
{
return cotisations;
}
// GetAllIdentitesEmploye
public Employe[] GetAllIdentitesEmployes()
{
return employes;
}
// GetEmploye
public Employe GetEmploye(string SS)
{
try
{
....
catch (Exception e)
{
throw new PamException(string.Format("Erreur système lors de la recherche de l'employé [{0}]", SS), e, 2);
}
}
}
}
Hinweis:
- Zeile 10: Die Klasse [PamDaoEF5] implementiert die Schnittstelle [IPamDao];
- die Tabellen [cotisations] und [employes] werden in den Eigenschaften der Zeilen 13–14 zwischengespeichert. Die Mitarbeiter sind ohne ihre Zulagen;
- Zeilen 17–28: Der Konstruktor initialisiert die Zeilen 13–14;
- Zeilen 43–52: Die Methode [GetEmploye] gibt einen Mitarbeiter mit seinen Zulagen zurück. Sie erhält als Parameter die Sozialversicherungsnummer dieses Mitarbeiters. Wenn der Mitarbeiter nicht in der Datenbank vorhanden ist, gibt die Methode den Zeiger als null zurück.
Aufgabe: Vervollständigen Sie den Code der Klasse [PamDaoEF5].
Für den Konstruktor orientieren wir uns am Testcode der Schicht [EF5], der in Abschnitt 9.22.6 vorgestellt wurde. Für die Methode [GetEmploye] orientieren Sie sich am Beispiel aus Abschnitt 3.5.7 [Eager and Lazy loading] von [refEF5].
9.23.5. Konfiguration der Schicht [DAO]
Wie bereits in Abschnitt 9.22.5 beschrieben, müssen wir EF5 in der Datei [App.config] des Projekts konfigurieren:
![]() |
Aufgabe 1: Konfigurieren Sie EF5 in [App.config]. Übernehmen Sie dazu einfach die Einstellungen aus der Datei [App.config] der Ebene [EF5].
Unser Testprogramm wird [Spring.net] verwenden, um eine Referenz auf die Ebene [DAO] abzurufen.
Aufgabe 2: Nutzen Sie die in Abschnitt 9.20.2 beschriebenen Schritte, ändern Sie die Konfigurationsdatei [app.config] des Projekts [pam-dao] so, dass sie ein Spring-Objekt namens [pamdao] definiert, das der soeben erstellten Klasse [PamDaoEF5] zugeordnet ist. Die Dateien [app.config] und [web.config] weisen dieselbe Struktur auf. Achten Sie darauf, dass das Tag <configSections> das erste Tag ist, das nach dem Stamm-Tag <configuration> auftritt.
9.23.6. Test der Schicht [DAO]
Wir sind bereit, unsere Schicht [DAO] zu testen. Dies erfolgt mithilfe des bereits vorhandenen Programms [Program.cs]:
![]() |
Wir werden die verschiedenen Funktionen der Benutzeroberfläche der Schicht [DAO] testen. Der Code für [Program.cs] lautet wie folgt:
using Pam.Dao.Service;
using Pam.EF5.Entites;
using Spring.Context.Support;
using System;
namespace Pam.Dao.Tests
{
public class Program
{
public static void Main()
{
try
{
// Instanzierung der Schicht [dao]
IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
// Liste der Mitarbeiteridentitäten
foreach (Employe Employe in pamDao.GetAllIdentitesEmployes())
{
Console.WriteLine(Employe.ToString());
}
// ein Mitarbeiter mit seinen Zulagen
Console.WriteLine("------------------------------------");
Employe e = pamDao.GetEmploye("254104940426058");
Console.WriteLine("employé= {0}, indemnités={1}", e, e.Indemnites);
Console.WriteLine("------------------------------------");
// ein Mitarbeiter, der nicht existiert
Employe employe = pamDao.GetEmploye("xx");
Console.WriteLine("Employé n° xx");
Console.WriteLine((employe == null ? "null" : employe.ToString()));
Console.WriteLine("------------------------------------");
// Liste der Beiträge
Cotisations cotisations = pamDao.GetCotisations();
Console.WriteLine(cotisations.ToString());
}
catch (Exception ex)
{
// Anzeige einer Ausnahme
Console.WriteLine(ex.ToString());
}
//Pause
Console.ReadLine();
}
}
}
- Zeile 15: Über [Spring.net] erhalten wir eine Referenz auf die Schicht [DAO].
Die Ergebnisse der Ausführung dieses Programms lauten wie folgt:
9.23.7. DLL aus der Ebene [DAO]
Aufgabe: Wandeln Sie den Typ des Projekts [pam-dao] in eine Klassenbibliothek um und generieren Sie das Projekt anschließend neu (wiederholen Sie die Schritte aus Abschnitt 9.22.7).
9.24. Schritt 17: Einrichtung der Ebene [métier]
9.24.1. Die Schnittstelle der Schicht [métier]
![]() |
Die Schnittstelle der Schicht [métier] entspricht der Schnittstelle [IPamMetier] der simulierten Schicht [métier], die wir in Abschnitt 9.7.2 erstellt haben.
public interface IPamMetier {
// Liste aller Mitarbeiteridentitäten
Employe[] GetAllIdentitesEmployes();
// ------- Gehaltsabrechnung
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
9.24.2. Das Visual Studio-Projekt
Aufgabe: Fügen Sie der Lösung [pam-td] ein neues Projekt vom Typ [console] mit dem Namen [pam-metier] hinzu. Legen Sie dieses Projekt als Startprojekt der Lösung fest.
![]() |
9.24.3. Hinzufügen der erforderlichen Verweise zum Projekt
Betrachten wir das Projekt im Gesamtzusammenhang:
![]() |
Das Projekt [pam-metier] benötigt eine bestimmte Anzahl von DLL:
- alle, auf die in den Projekten [pam-dao] und [pam-ef5] verwiesen wird;
- sowie die der Projekte [pam-dao] und [pam-ef5] selbst.
Aufgabe: Fügen Sie diese verschiedenen Referenzen zum Projekt [pam-metier] hinzu.
![]() |
9.24.4. Implementierung der Schicht [métier]
![]() |
Oben sind vier Elemente aufgeführt, die bereits in der simulierten Schicht [métier] verwendet wurden (siehe Abschnitt 9.7). Es kann zu Änderungen bei den von diesen verschiedenen Klassen importierten Namensräumen kommen. Passen Sie diese entsprechend an. Die Klasse [PamMetier] implementiert die Schnittstelle [IPamMetier] wie folgt:
using Pam.Dao.Service;
using Pam.EF5.Entites;
using Pam.Metier.Entites;
using System;
namespace Pam.Metier.Service
{
public class PamMetier : IPamMetier
{
// Verweis auf die von Spring initialisierte Schicht [DAO]
public IPamDao PamDao { get; set; }
// Liste aller Mitarbeiter-IDs
public Employe[] GetAllIdentitesEmployes()
{
...
}
// ein bestimmter Mitarbeiter mit seinen Zulagen
public Employe GetEmploye(string ss)
{
...
}
// die Sozialbeiträge
public Cotisations GetCotisations()
{
...
}
// Lohnberechnung
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
{
// SS: SS-Nummer des Mitarbeiters
// HeuresTravaillées: Anzahl der gearbeiteten Stunden
// Gearbeitete Tage: Anzahl der gearbeiteten Tage
...
}
}
- Zeile 13: Hier gibt es einen Verweis auf die Schicht [DAO]. Diese wird von Spring bei der Instanziierung der Klasse [PamMetier] initialisiert. Wenn also die verschiedenen Methoden ausgeführt werden, ist Zeile 13 bereits initialisiert.
Aufgabe: Vervollständigen Sie den Code der Klasse [PamMetier]. Wenn in [GetSalaire] festgestellt wird, dass der Mitarbeiter mit der Personalnummer nicht existiert, wird eine [PamException] aufgerufen. Die Art der Gehaltsberechnung wird in Abschnitt 9.5 erläutert. Achten Sie darauf, alle Zwischenberechnungen auf zwei Stellen nach dem Komma zu runden.
9.24.5. Konfiguration der Ebene [métier]
Wie bereits in Abschnitt 9.22.5 beschrieben, müssen wir EF5 in der Datei [app.config] des Projekts konfigurieren:
![]() |
Aufgabe 1: Konfigurieren Sie EF5 in [app.config]. Übernehmen Sie dazu einfach die Einstellungen aus der Datei [app.config] der Ebene [EF5].
Unser Testprogramm wird [Spring.net] verwenden, um eine Referenz auf die Ebene [métier] abzurufen.
Aufgabe 2: Nutzen Sie dazu das, was Sie zuvor in Abschnitt 9.23.5 gemacht haben, ändern Sie die Konfigurationsdatei [app.config] des Projekts [pam-metier] so, dass sie ein Spring-Objekt namens [pammetier] definiert, das der soeben erstellten Klasse [PamMetier] zugeordnet ist. Am einfachsten ist es, die Datei „[app.config]“ aus dem Projekt „[pam-dao]“ zu kopieren und die fehlenden Angaben hinzuzufügen.
Hier gibt es jedoch eine Schwierigkeit. Es muss nicht nur die Schicht [métier] mit der Klasse [PamMetier] instanziiert werden, sondern auch deren Eigenschaft [PamDao] initialisiert werden:
// Referenz auf die von Spring initialisierte Schicht [DAO]
public IPamDao PamDao { get; set; }
Die Spring-Konfiguration in [app.config] sieht dann wie folgt aus:
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="pamdao" type=" Pam.Dao.Service.PamDaoEF5, pam-dao"/>
<object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier">
<property name="PamDao" ref="pamdao" />
</object>
</objects>
</spring>
- Zeile 6: definiert das Objekt [pamdao], das der Klasse [PamDaoEF5] zugeordnet ist;
- Zeile 7: definiert das Objekt [pammetier], das der Klasse [PamMetier] zugeordnet ist;
- Zeile 8: Das Tag [property] dient dazu, eine öffentliche Eigenschaft der Klasse [PamMetier] zu initialisieren. Das Attribut [name="PamDao"] entspricht dem Namen der zu initialisierenden Eigenschaft in der Klasse [PamMetier]. Das Attribut [ref="pamdao"] gibt an, dass die Eigenschaft mit einer Referenz initialisiert wird, nämlich der des Objekts [pamdao] aus Zeile 6, also mit der Referenz der Ebene [DAO]. Genau das wollten wir erreichen.
9.24.6. Test der Schicht [métier]
Wir sind bereit, unsere Schicht [métier] zu testen. Dies erfolgt mithilfe des bereits vorhandenen Programms [Program.cs]:
![]() |
Wir werden die verschiedenen Funktionen der Benutzeroberfläche der Schicht [métier] testen. Der Code für [Program.cs] lautet wie folgt:
using System;
using Pam.Dao.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
using Pam.EF5.Entites;
namespace Pam.Metier.Tests
{
public class Program
{
public static void Main()
{
try
{
// Instanziierung der Schicht [métier]
IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// Liste der Mitarbeiteridentitäten
Console.WriteLine("Employés -----------------------------");
foreach (Employe Employe in pamMetier.GetAllIdentitesEmployes())
{
Console.WriteLine(Employe);
}
// Lohnabrechnungsberechnungen
Console.WriteLine("salaires -----------------------------");
Console.WriteLine(pamMetier.GetSalaire("260124402111742", 30, 5));
Console.WriteLine(pamMetier.GetSalaire("254104940426058", 150, 20));
try
{
Console.WriteLine(pamMetier.GetSalaire("xx", 150, 20));
}
catch (PamException ex)
{
Console.WriteLine(string.Format("PamException : {0}", ex.Message));
}
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Exception : {0}, Exception interne : {1}", ex.Message, ex.InnerException == null ? "" : ex.InnerException.Message));
}
// Pause
Console.ReadLine();
}
}
}
- Zeile 16: Über [Spring.net] erhalten wir eine Referenz auf die Schicht [métier].
Die Ergebnisse der Ausführung dieses Programms lauten wie folgt:
9.24.7. DLL aus der Ebene [métier]
Aufgabe: Wandeln Sie den Typ des Projekts [pam-metier] in eine Klassenbibliothek um und generieren Sie das Projekt anschließend neu (wiederholen Sie die Schritte aus Abschnitt 9.22.7).
9.25. Schritt 18: Einrichtung der Schicht [web]
Wir kommen nun zur letzten Schicht unserer Architektur, der Schicht [web]:
![]() |
Wir werden die Schicht [web] wiederverwenden, die wir mithilfe einer simulierten Schicht [métier] entwickelt hatten.
9.25.1. Das Visual Studio-Projekt
Wir kehren zu Visual Studio Express 2012 für das Web zurück, um unsere Webschicht mit den soeben entwickelten Schichten [métier, DAO, EF5] zu verknüpfen. Dabei sind vor allem Konfigurationsschritte und einige Änderungen an den Namespaces erforderlich.
Laden Sie in Visual Studio Express 2012 für das Web die Lösung [pam-td]:
![]() |
- in [1], die Lösung [pam-td] in VS Web Studio. Das Webprojekt [pam-web-01] ist nun wieder sichtbar. Es war in VS Studio für den Desktop verloren gegangen.
- Die Konfiguration des Webprojekts [pam-web-01] muss geändert werden. Anstatt ein funktionierendes Projekt zu ändern, nehmen wir die Änderungen an einer Kopie dieses Projekts vor. Zunächst löschen wir in [2] das Projekt aus der Lösung (dadurch wird nichts im Dateisystem gelöscht).
![]() |
- In [3] duplizieren wir mit dem Windows-Explorer den Ordner [pam-web-01] in [pam-web-02];
- in [4] wird das Projekt [pam-web-02] in die Lösung [pam-td] geladen. Es wird unter dem Namen [pam-web-01] angezeigt;
- Ändern Sie bei „[5]“ den Namen in „[pam-web-02]“ und legen Sie dieses Projekt als Startprojekt fest;
![]() |
- in [6], laden Sie das alte Projekt [pam-web-01]. Sie haben nun alle Ihre Projekte. Achten Sie darauf, mit [pam-web-02] zu arbeiten.
9.25.2. Hinzufügen der erforderlichen Referenzen zum Projekt
Betrachten wir das Projekt im Gesamtzusammenhang:
![]() |
Das Projekt [pam-web-02] benötigt eine bestimmte Anzahl von DLL:
- alle, auf die in den Projekten [pam-metier], [pam-dao] und [pam-ef5] verwiesen wird;
- sowie die der Projekte [pam-metier], [pam-dao] und [pam-ef5] selbst.
Aufgabe: Fügen Sie diese verschiedenen Referenzen zum Projekt [pam-web-02] hinzu. Die Referenz zum Projekt [pam-metier-simule] muss entfernt werden. Wir wechseln zur Ebene [métier]. Einige DLL sind bereits in den Referenzen vorhanden. Löschen Sie diese und nehmen Sie anschließend Ihre Ergänzungen vor.
![]() |
9.25.3. Implementierung der Schicht [web]
Generieren Sie das Projekt [pam-web-02]. Es werden Fehler wie der folgende angezeigt:
![]() |
Die Klasse [ApplicationModel] verwendet den Typ [Employe]. Bei der simulierten Schicht [métier] war dieser Typ im Namensraum [Pam.Metier.Entites] definiert. Er befindet sich nun im Namensraum [Pam.EF5.Entites]. Beheben Sie diese Fehler wie oben gezeigt.
9.25.4. Konfiguration der Schicht [web]
Wie bereits in Abschnitt 9.24.5 beschrieben, müssen wir EF5 in der Projektdatei [web.config] konfigurieren:
![]() |
Aufgabe 1: Ersetzen Sie den gesamten aktuellen Inhalt von [web.config] durch den Inhalt der Datei [app.config] aus dem Projekt [pam-metier].
Die Datei [Global.asax] unserer Webanwendung verwendet [Spring.net], um eine Referenz auf die Ebene [métier] abzurufen:
try
{
// Instanzierung der Schicht [métier]
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
catch (Exception ex)
{
application.InitException = ex;
}
In Zeile 4 wird eine Referenz auf das Spring-Objekt mit dem Namen [pammetier] angefordert. Dies ist genau der Name, der der Schicht [métier] zugewiesen wurde (überprüfen Sie dies in Ihrer Datei [web.config]).
9.25.5. Test der Schicht [web]
Wir sind bereit, unsere Schicht [web] zu testen. Zunächst werden wir ihren Arbeitsport ändern. Standardmäßig hat [pam-web-02] die Konfiguration von [pam-web-01] und arbeitet daher auf demselben Port. Die Erfahrung zeigt, dass dies zu Problemen führt: IIS verwendet dann weiterhin die Codes des Projekts [pam-web-01]. Gehen Sie wie folgt vor:
![]() |
![]() |
Ändern Sie in [4] die Portnummer, beispielsweise durch Ändern der Einerstelle.
Das Projekt [pam-web-02] wird über [Ctrl-F5] ausgeführt. Daraufhin wird folgende Startseite angezeigt:
![]() |
In [1] erhält man die Mitarbeiter aus der Datenbank [dbpam_ef5]. Man beachte, dass der Mitarbeiter [X X], den wir bei der simulierten Ebene [métier] hatten, nun nicht mehr vorhanden ist. Führen wir eine Simulation durch:
![]() |
In [2] erhalten wir nun tatsächlich das tatsächliche Gehalt und kein fiktives mehr. Beenden wir nun die SGBD und MySQL5 und führen wir eine weitere Simulation durch:
![]() |
Bei [3] erhalten wir eine lesbare Fehlerseite, auch wenn einige Meldungen auf Englisch sind. Beenden wir nun erneut MySQL und führen wir die Anwendung in VS über [Ctrl-F5] erneut aus:
![]() |
Man erhält die in Abschnitt 9.20.4 erstellte Ansicht [initFailed.cshtml]. Sie zeigt die Fehlermeldungen des Ausnahmestapels an. Der Leser ist aufgefordert, weitere Tests durchzuführen.
9.26. Schritt 19: Eine Anwendung ASP.NET im Internet verfügbar machen
Wenn man eine Anwendung ASP.NET mit Visual Studio entwickelt, sorgt die standardmäßig verwendete Konfiguration dafür, dass die entwickelte Anwendung nur unter der Adresse [localhost] erreichbar ist. Jede andere Adresse wird vom eingebetteten Server von Visual Studio abgelehnt, der daraufhin den Fehler [400 Bad Request] zurückgibt.
Dies lässt sich wie folgt veranschaulichen:
- Notieren Sie sich in einem Fenster mit der Nummer DOS die Adresse IP Ihres Entwicklungsrechners:
Microsoft Windows [version 6.3.9600]
(c) 2013 Microsoft Corporation. Tous droits réservés.
dos>ipconfig
Configuration IP de Windows
Carte Ethernet Connexion au réseau local :
Suffixe DNS propre à la connexion. . . : ad.univ-angers.fr
Adresse IPv6 de liaison locale. . . . .: fe80::698b:455a:925:6b13%4
Adresse IPv4. . . . . . . . . . . . . .: 172.19.81.34
Masque de sous-réseau. . . . . . . . . : 255.255.0.0
Passerelle par défaut. . . . . . . . . : 172.19.0.254
Carte réseau sans fil Wi-Fi :
Statut du média. . . . . . . . . . . . : Média déconnecté
Suffixe DNS propre à la connexion. . . :
Die Adresse IP wird hier in Zeile 14 angezeigt. Wenn Sie über eine WLAN-Verbindung verfügen, wird die WLAN-Adresse des Computers in Zeile 20 und den folgenden Zeilen angezeigt.
- Überprüfen Sie die Projekteigenschaften von [clic droit sur projet / propriétés / onglet web]:
![]() |
Die Anwendung wird auf dem Port [65010] des Rechners [localhost] ausgeführt.
- Führen Sie Ihr Projekt über [Ctrl-F5] aus

- Ersetzen Sie [localhost] durch die Adresse IP des Rechners:

Der Server hat die Antwort [400 Bad Request] zurückgegeben. Der von Visual Studio verwendete Express-Server IIS akzeptiert nur den Namen [localhost].
Um die entwickelte Anwendung für einen URL vom Typ [http://adresseIP/contexte/...] zugänglich zu machen, muss ein anderer Server als IIS Express verwendet werden, beispielsweise ein IIS-Server (nicht Express). Um zu überprüfen, ob dieser vorhanden ist (normalerweise in den Pro-Versionen von Windows), müssen Sie die Systemsteuerung [Panneau de configuration\Système et sécurité\Outils d’administration] aufrufen:

Diese Option ist nicht immer vorhanden. In diesem Fall müssen Sie zu [ Panneau de configuration \ Programmes] navigieren und die Webadministrations-Tools installieren.
![]() |
Sobald die Option [Gestionnaire des services internet (IIS)] vorhanden ist, aktivieren Sie sie:
![]() |
Die Standard-Website wird gestartet. Dazu muss zuvor der Dienst [Service de publication World Wide Web] gestartet worden sein:
![]() |
Rufen Sie anschließend die Seiten URL und [http://localhost] mit einem Browser auf. Vergewissern Sie sich zuvor, dass kein anderer Webserver bereits den Port 80 belegt. Falls ja, beenden Sie diesen.
![]() |
Der Server IIS hat uns geantwortet. Ersetzen Sie nun [localhost] durch die Adresse IP Ihres Computers:
![]() |
Es funktioniert. Kehren wir nun zu Visual Studio zurück:
- Zunächst muss Visual Studio im Modus [administrateur] gestartet werden
![]() |
Anschließend müssen Sie die Konfiguration des Webprojekts ändern, das Sie bereitstellen möchten: [clic droit sur projet / propriétés / onglet web]:
![]() |
Als Bereitstellungsserver muss der lokale Server IIS ausgewählt werden. Visual Studio legt die URL der Anwendung fest. Diese kann geändert werden. Führen Sie das Projekt über [Ctrl-F5] aus:
![]() |
Ersetzen Sie nun [localhost] durch die Adresse IP Ihres Computers:
![]() |
Falls der Server IIS nicht verfügbar ist, können Sie einen kostenlosen Server wie ASP.NET nutzen, der unter URL und [http://ultidev.com/Download/ ] verfügbar ist. Nach der Installation gibt es zwei Möglichkeiten, eine Webanwendung mit diesem Server zu starten:
Die schnelle Methode
Öffnen Sie einen Windows-Explorer und wählen Sie den Ordner der bereitzustellenden Anwendung ASP.NET aus:
![]() |
Der Webserver wird daraufhin gestartet und die Webanwendung in einem Browser angezeigt:
![]() |
- Unter [3] kann der Webserver gestoppt bzw. gestartet werden;
- in [4] kann der Dienstport der Webanwendung geändert werden;
Bevor der Server gestartet wird, muss der unten aufgeführte Dienst [UWS HiPriv Services] gestartet werden:
![]() |
Sobald der Server gestartet ist, sieht die Benutzeroberfläche wie folgt aus:
![]() |
Ein Klick auf den Link [6] zeigt die erste Seite der Anwendung an:
![]() |
Man kann nun die Adresse IP des Rechners anstelle von [localhost] eingeben:
![]() |
Auch hier wird also nur der Name [localhost] akzeptiert.
Der umständliche Weg
Starten Sie die Anwendung Ultidev Web Explorer
![]() |
und führen Sie anschließend die folgenden Schritte aus:
![]() |
![]() |
![]() |
- Geben Sie in [8] den Ordner der bereitzustellenden Webanwendung an;
![]() |
- Aufgrund von [10-11] muss die Webanwendung mit den Werten URL und [http://localhost:81/] angefordert werden;
![]() |
![]() |
- Starten Sie den Webserver mit [14];
![]() |
- Rufen Sie URL und [19] auf;
![]() |
- Bei [20] wurde die gewünschte Seite aufgerufen, indem die lokale Adresse IP des Rechners anstelle des Namens [localhost] verwendet wurde. Genau das wollten wir erreichen;
Der Ultidev-Server wurde als Windows-Dienst installiert, der automatisch gestartet wird. Sie können den automatischen Start des Ultidev-Servers wie folgt deaktivieren:
- Wählen Sie die Option [Panneau de configuration\Système et sécurité\Outils d’administration];
![]() |
- [1, 2]: Wählen Sie die Eigenschaften des Dienstes [Ultidev Web Server Pro] aus;
- [3]: Stellen Sie den Start auf „Manuell“ ein.
Um den Server manuell zu starten, verwenden Sie beispielsweise die Anwendung [Ultidev Web Explorer]:
![]() |
9.27. Schritt 20: Erstellen einer nativen Android-App
Wenn Sie über eine Webanwendung vom Typ APU (Single-Page-Anwendung) verfügen, können Sie mit dem Tool [Phonegap] eine ausführbare Datei für Mobilgeräte (Android, IoS, Windows 8, ...) mit dem Tool [Phonegap] [http://phonegap.com/] erstellen. Es gibt auch andere Möglichkeiten, insbesondere mit dem Open-Source-Produkt Apache Cordova [https://cordova.apache.org/]. Das auf der PhoneGap-Website verfügbare Online-Tool [http://build.phonegap.com/apps] „lädt“ die ZIP-Datei der zu konvertierenden Website hoch. Die Startseite muss den Namen [index.html] tragen und eine statische Seite sein, d. h. sie darf nicht von einem Web-Framework generiert werden (ASP.NET, JEE, PHP, ...) generiert wird. Wir beginnen damit, diese Seite zu erstellen.
9.27.1. Die Architektur der Anwendung
Man sollte hier bedenken, dass wir eine Android-App erstellen wollen. Eine solche App hat oft die folgende Architektur:
![]() |
- In [1] nutzt der Benutzer ein Android-Tablet, das mit einem oder mehreren Webdiensten ([2]) kommuniziert;
Kehren wir zum Modell APU zurück:
![]() |
- Eine Startseite wird im Browser geladen (das obige Schema gibt nicht an, woher sie stammt);
- Die folgenden Ansichten werden über Ajax-Aufrufe [1-4] abgerufen. Es wird keine neue Seite vom Browser geladen;
Die Startansicht kann vom selben Server bereitgestellt werden wie die anderen, über Ajax-Aufrufe abgerufenen Ansichten – muss aber nicht. Wenn sie nicht vom selben Server bereitgestellt wird, muss das JavaScript der Startansicht die URL des Webservers kennen, der die anderen Ansichten bereitstellt. Dies wird in der Android-Anwendung der Fall sein, die wir erstellen werden:
![]() |
- Die statische Seite [index.html] wird in eine native Android-Anwendung [1] eingebettet, die über die Funktionen eines Browsers verfügt und somit in der Lage ist, das in der Seite [index.html] eingebettete JavaScript auszuführen;
- Diese Seite ruft die anderen Ansichten über Ajax-Aufrufe an den Server [2] ab. Dazu muss sie die URL des Webservers kennen;
Wir werden die Anwendung [pam-web-02] so umgestalten, dass sie in diesem Modus funktioniert. Die erste Seite sieht dann wie folgt aus:
![]() |
- in [1], das URL der Startseite der Anwendung. Es wird uns vom Ultidev-Server bereitgestellt, der in Abschnitt 9.26 behandelt wird;
- in [2] muss der Benutzer die URL des Lohnrechners eingeben. Man könnte sie fest im JavaScript-Code der Startseite hinterlegen, doch dies würde die Tests erschweren: Sobald man die Adresse des Simulators IP (oder den Port) ändern würde, müsste man sie auch im JavaScript-Code ändern;
- in „[3]“, den Link „[Connexion]“, der die folgende Ansicht abruft:
![]() |
- Es ist zu beachten, dass sich bei [4] der URL des Browsers nicht geändert hat. Es ist immer noch derjenige der Startseite und bleibt während der gesamten Laufzeit der Anwendung unverändert.
Sobald diese Ansicht abgerufen wurde, funktioniert alles wie zuvor: Die verschiedenen Ansichten werden über Ajax-Aufrufe abgerufen. Wir werden sehen, dass nur sehr wenig Code geändert werden muss.
9.27.2. Refactoring des Projekts [pam-web-02]
Innerhalb des Ordners „[Content]“ des Projekts „[pam-web-02]“ erstellen wir den folgenden Ordner „[bootstrap]“ (der Name spielt keine Rolle):
![]() |
Darin haben wir die statische Seite [index.html] sowie alle dafür erforderlichen Ressourcen (die Dateien CSS und JS) aufgenommen. Die Seite [index.html] übernimmt den Code der Master-Seite [_Layout.cshtml] aus dem Visual Studio-Projekt und entfernt dabei alles, was nicht statisch ist. Daraus ergibt sich folgender Code:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Simulateur de paie</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" href="Site.css" />
<script type="text/javascript" src="jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="jquery.validate.min.js"></script>
<script type="text/javascript" src="jquery.validate.unobtrusive.min.js"></script>
<script type="text/javascript" src="globalize.js"></script>
<script type="text/javascript" src="globalize.culture.fr-FR.js"></script>
<script type="text/javascript" src="jquery.unobtrusive-ajax.min.js"></script>
<script type="text/javascript" src="myScripts.js"></script>
</head>
<body>
<table>
<tbody>
<tr>
<td>
<h2>Simulateur de calcul de paie</h2>
</td>
<td style="width: 20px">
<img id="loading" style="display: none" src="indicator.gif" />
</td>
<td>
<a id="lnkConnexion" href="javascript:connexion()">
| Connexion<br />
</a>
<a id="lnkFaireSimulation" href="javascript:faireSimulation()">
| Faire la simulation<br />
</a>
<a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">
| Effacer la simulation<br />
</a>
<a id="lnkVoirSimulations" href="javascript:voirSimulations()">
| Voir les simulations<br />
</a>
<a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">
| Retour au formulaire de simulation<br />
</a>
<a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">
| Enregistrer la simulation<br />
</a>
<a id="lnkTerminerSession" href="javascript:terminerSession()">
| Terminer la session<br />
</a>
</td>
</tbody>
</table>
<hr />
<div id="content">
<table>
<tr>
<td>URL du simulateur</td>
<td><input type="text" id="urlServiceWeb" name="urlServiceWeb" size="80"></td>
</tr>
</table>
<div id="erreur">
<h3>Réponse du serveur :</h3>
<div id="erreur1"></div>
<div id="erreur2"></div>
</div>
</div>
</body>
</html>
Wir haben folgende Punkte hinzugefügt:
- Zeilen 27–29: Wir haben den Menüpunkt [Connexion] hinzugefügt, um die Verbindung zum Simulationsdienst zu ermöglichen;
- Zeilen 55–56: Eingabe der URL des Simulators;
- Zeilen 59–63: ein Fehlerfeld für den Fall, dass die Verbindung fehlschlägt;
Die Umgestaltung des Codes erfolgt ausschließlich im Code „[myScripts.js]“ in Zeile 14 oben. Ansonsten ändert sich nichts. Der Code wird wie folgt angepasst:
// beim Laden des Dokuments
$(document).ready(function () {
// Die Referenzen der verschiedenen Komponenten der Seite werden abgerufen
loading = $("#loading");
content = $("#content");
erreur = $("#erreur");
erreur1 = $("#erreur1");
erreur2 = $("#erreur2");
// die Menü-Links
lnkConnexion = $("#lnkConnexion");
lnkFaireSimulation = $("#lnkFaireSimulation");
lnkEffacerSimulation = $("#lnkEffacerSimulation");
lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
lnkVoirSimulations = $("#lnkVoirSimulations");
lnkTerminerSession = $("#lnkTerminerSession");
lnkRetourFormulaire = $("#lnkRetourFormulaire");
// diese werden in ein Array gespeichert
options = [lnkConnexion, lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
// Bestimmte Elemente der Seite werden ausgeblendet
loading.hide();
erreur.hide();
// Das Menü wird fixiert
setMenu([lnkConnexion]);
});
- Zeilen 6–8: Die Bezeichner des Bereichs, der die Verbindungsfehler auf der Seite [index.html] anzeigt;
- Zeile 10: der neue Link für die Verbindung zum Simulator;
- Zeile 21: Der Fehlerbereich ist zunächst ausgeblendet;
- Zeile 23: Es wird nur der Verbindungslink angezeigt;
Auf der Seite [index.html] ist der Verbindungslink wie folgt definiert:
<a id="lnkConnexion" href="javascript:connexion()">
| Connexion<br />
</a>
Die Funktion JS [connexion] (Zeile 1) lautet wie folgt:
var urlServiceWeb;
var erreur, erreur1, erreur2;
function connexion() {
// Rufen den urlServiceWeb vom Webdienst ab
urlServiceWeb = $("#urlServiceWeb").val();
// Das Eingabeformular wird abgerufen
$.ajax({
url: urlServiceWeb + '/Pam/Formulaire',
type: 'POST',
dataType: 'html',
beforeSend: function () {
// Warteanzeige leuchtet
loading.show();
},
success: function (data) {
// Anzeige der Ergebnisse
content.html(data);
// Menü
setMenu([lnkFaireSimulation]);
},
error: function (jqXHR) {
erreur2.html(jqXHR.responseText);
erreur1.html(jqXHR.getAllResponseHeaders().replace(/\r\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/\n/g, "<br/>"));
erreur.show();
},
complete: function () {
// Warteanzeige aus
loading.hide();
}
});
}
- Zeile 7: Der vom Benutzer eingegebene Wert URL wird abgerufen. Er wird in die globale Variable aus Zeile 1 gespeichert. So ist er in den anderen Funktionen der Datei verfügbar;
- Zeile 10: Es wird ein Ajax-Aufruf an die URL [/Pam/Formulaire] des Simulators gesendet. Diese URL liefert die Teilansicht der eingegebenen Simulationsdaten (Mitarbeiter, geleistete Arbeitsstunden, Arbeitstage). In der ursprünglichen Version von [pam-web-02] reichte diese URL aus. Ihr wurde automatisch die URL vorangestellt, die die Startseite geladen hatte. Nun wird davon ausgegangen, dass die Startseite von einem anderen Server bereitgestellt werden kann als dem, der den Simulator hostet. Dann muss der URL [/Pam/Formulaire] mit der Variablen [urlServiceWeb] aus Zeile 1 vorangestellt werden, die dem URL des Simulators entspricht (z. B. http://172.19.81.34/pam-web-02). Dies muss für alle Ajax-Aufrufe in der Datei erfolgen;
- Zeilen 17–22: Bei erfolgreicher Verbindung wird die Teilansicht [Formulaire.cshtml] angezeigt und ein Menü mit dem einzigen Link [Faire la simulation] (Zeile 21) eingeblendet;
- Zeilen 23–27: Bei fehlgeschlagener Verbindung:
- In Zeile 24 wird die vom Webserver gesendete Antwort HTML angezeigt (sofern vorhanden);
- In Zeile 25 werden die vom Webserver gesendeten Header HTTP angezeigt (sofern der Webserver geantwortet hat);
Das war’s. Bei Erfolg erhält man die folgende Seite:
![]() |
Wir befinden uns nun in der vorherigen Situation, in der die Ansichten nun über Ajax-Aufrufe abgerufen werden. Wie oben beschrieben, wird der Klick auf den Link [Faire la simulation] durch den folgenden Code aus der Datei [myScripts.js] ausgeführt:
function faireSimulation() {
// Referenzen werden abgerufen
var simulation = $("#simulation");
var formulaire = $("#formulaire");
// Formular gültig?
var formValid = formulaire.validate().form();
if (!formValid) return;
// Ajax-Aufruf wird manuell ausgeführt
$.ajax({
url: urlServiceWeb + '/Pam/FaireSimulation',
type: 'POST',
data: formulaire.serialize(),
dataType: 'html',
...
});
// Menü
setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
- Es wurde eine einzige Änderung vorgenommen, und zwar in Zeile 10, wo dem bisherigen URL nun das Präfix des Simulators vorangestellt wird;
9.27.3. Test des umgestalteten Projekts
In Abschnitt 9.26 haben wir gezeigt, wie die Anwendung [pam-web-02] auf dem Ultidev-Server installiert wird. Wir werden an dieser Stelle ansetzen:
![]() |
- in [6] rufen wir die Seite [bootstrap/index.html] auf. Wir erhalten folgende Ansicht:
![]() |
Geben wir eine fehlerhafte URL „URL“ ein:
![]() |
- in [10] die Kopfzeilen HTTP aus der Serverantwort;
- in [11], das Dokument HTML aus der Serverantwort;
Wenn man die richtige URL eingibt:
![]() |
erhält man folgende Antwort:
![]() |
9.27.4. Erstellung der Android-Binärdatei
Wir werden die Android-Binärdatei anhand der soeben erstellten statischen Website erstellen und testen: [1]:
![]() | ![]() |
Wir fügen in [2] eine Datei [config.xml] hinzu, die zur Konfiguration des Plugins [Phonegap] dient, welches die Android-Binärdatei generieren wird. Der Code lautet wie folgt:
<?xml version='1.0' encoding='utf-8'?>
<widget id="android.exemples.pam" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>Pam</name>
<description>
IstiA - Université d'Angers
</description>
<author email="serge.tahe@univ-angers.fr">
Serge Tahé
</author>
<content src="index.html" />
<access origin="*" />
<allow-navigation href="*" />
<allow-intent href="*" />
<plugin name="cordova-plugin-whitelist" />
</widget>
- Zeilen 7–9: Tragen Sie hier Ihre Kontaktdaten ein;
- Zeilen 11–13: Diese Zeilen ermöglichen es dem in die Webanwendung eingebetteten JavaScript, das auf dem Android-Gerät ausgeführt wird, Anfragen an URL-Dateien außerhalb dieses Geräts zu stellen;
Wir komprimieren den Inhalt des Ordners [Content/bootstrap]:
![]() |
Anschließend rufen wir die PhoneGap-Website [http://build.phonegap.com/apps] auf:
![]() |
- Vor [1] müssen Sie möglicherweise ein Konto erstellen;
- bei [1] geht es los;
- Bei [2] wählen Sie einen kostenlosen Tarif, der nur eine PhoneGap-App zulässt;
- in [3] laden Sie die gezippte App [4] herunter;
![]() |
![]() |
- In [5] geben Sie den Namen der App ein;
- Klicken Sie auf den Link [6], um die Binärdateien für OS, IoS, Android und Windows zu erstellen. Dies kann einige Sekunden dauern;
![]() |
- Laden Sie unter [7-9] die Android-Binärdatei herunter;
![]() |
Starten Sie einen Emulator [GenyMotion] für ein Android-Tablet (siehe Abschnitt 11.1):
![]() |
Oben wird ein Tablet-Emulator mit Android-Version API 21 gestartet. Sobald der Emulator gestartet ist,
- entsperren Sie ihn, indem Sie den Riegel (falls vorhanden) an der Seite ziehen und dann loslassen;
- Ziehen Sie mit der Maus die heruntergeladene Datei „[Pam-debug.apk]“ auf den Emulator und legen Sie sie dort ab. Die Datei wird daraufhin installiert und ausgeführt;
![]() |
Fügen Sie [1] in den Emulator ein, wie in Abschnitt 9.27.3 beschrieben. Melden Sie sich anschließend über den Link [2] beim Emulator an:
![]() |
Testen Sie die Anwendung auf dem Emulator. Sie sollte funktionieren.








































































































































































































