9. Fallstudie
9.1. Einleitung
Wir stellen eine Fallstudie vor, die zuvor in einem Artikel unter der URL [http://tahe.developpez.com/dotnet/pam-aspnet/] veröffentlicht wurde. In diesem Artikel wird die Fallstudie unter Verwendung von klassischem ASP.NET und dem NHibernate-ORM implementiert. Hier werden wir sie unter Verwendung von ASP.NET MVC und dem Entity Framework-ORM implementieren. Wie im bestehenden Artikel wird die Fallstudie als Laborübung an einer Universität präsentiert. Sie richtet sich daher an Studierende. Für alle Fragen werden Verweise auf die soeben beschriebenen Kapitel angegeben, um nützliche Literaturhinweise zu liefern.
9.2. : Das zu lösende Problem
Wir möchten eine Webanwendung schreiben, mit der ein Benutzer die Lohnabrechnung für Kinderbetreuer beim Verein „Maison de la petite enfance“ in einer örtlichen Gemeinde simulieren kann. Wir werden uns ebenso sehr auf die Organisation des .NET-Codes der Anwendung konzentrieren wie auf den Code selbst.
Die Anwendung wird eine Single-Page-Anwendung (SPA) sein und ausschließlich Ajax-Aufrufe zur Kommunikation mit dem Server verwenden. Sie wird dem Benutzer die folgenden Ansichten präsentieren:
- die Ansicht [VueSaisies], die das Simulationsformular anzeigt

- die Ansicht [VueSimulation], die zur Anzeige der detaillierten Simulationsergebnisse dient:

- die Ansicht [SimulationsView], die die vom Client durchgeführten Simulationen auflistet

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

- die Ansicht [VueErreurs], die einen oder mehrere Fehler anzeigt (hier wurde das MySQL-DBMS heruntergefahren):

9.3. Anwendungsarchitektur
Die Anwendungsarchitektur sieht wie folgt aus:
![]() |
Die [EF5]-Schicht bezieht sich auf das Entity Framework 5 ORM. Als DBMS wird MySQL verwendet.
Wir werden diese Anwendung zunächst mit einer simulierten [Business]-Schicht erstellen:
![]() |
Dadurch können wir uns ausschließlich auf die [Web]-Schicht konzentrieren. Die simulierte [Business]-Schicht wird der Schnittstelle der tatsächlichen [Business]-Schicht entsprechen. Sobald die [Web]-Schicht betriebsbereit ist, werden wir die [Business]-, [DAO]- und [EF5]-Schichten erstellen.
9.4. Die Datenbank
Die zur Erstellung der Gehaltsabrechnung benötigten statischen Daten werden in einer MySQL-Datenbank namens [dbpam_ef5] (pam = Paie Assistante Maternelle) gespeichert. Diese Datenbank hat einen Administrator namens root ohne Passwort. Sie enthält drei Tabellen:

Zwischen der Spalte EMPLOYEES(INDEMNITE_ID) und der Spalte INDEMNITIES(ID) besteht eine Fremdschlüsselbeziehung. Die Struktur dieser Datenbank wird durch ihre Verwendung mit EF5 vorgegeben. Wir werden darauf zurückkommen, wenn wir die unteren Schichten der Anwendung erstellen.
Struktur:
ID: Primärschlüssel, der vom DBMS automatisch inkrementiert wird SSN: Sozialversicherungsnummer des Mitarbeiters - eindeutiger NAME Nachname des Mitarbeiters VORNAME Vorname ADRESSE Adresse STADT Wohnort POSTLEITZAHL Postleitzahl VERSIONING eine Ganzzahl, die bei jeder Änderung des Datensatzes automatisch erhöht wird INDEMNITY_ID Fremdschlüssel auf das Feld [ID] der Tabelle [INDEMNITES]

Der Inhalt könnte wie folgt aussehen:
![]()
Aufbau:
IDPrimärschlüssel, der vom DBMS automatisch inkrementiert wirdCSGRDSProzentsatz: allgemeiner Sozialbeitrag + Beitrag zur Tilgung der SozialschuldenCSGDProzentsatz: abzugsfähiger allgemeiner SozialbeitragSECUProzentsatz: SozialversicherungPENSIONProzentsatz: Zusatzrente + ArbeitslosenversicherungVERSIONINGEine Ganzzahl, die bei jeder Änderung des Datensatzes automatisch inkrementiert wird

Der Inhalt könnte wie folgt aussehen:
![]()
Die Sozialversicherungsbeiträge sind unabhängig vom Arbeitnehmer. Die vorstehende Tabelle enthält nur eine Zeile.
ID: Primärschlüssel, der vom DBMS automatisch inkrementiert wirdINDEX: Gehaltsindex – eindeutigBASE_HOUR: Nettopreis in Euro für eine Stunde BereitschaftsdienstDAILY_MAINTENANCE: Tagespauschale in Euro pro PflegetagMEAL_DAY: Verpflegungszuschlag in Euro pro PflegetagPAID_LEAVE_ALLOWANCE: Urlaubsgeld. Dies ist ein Prozentsatz, der auf das Grundgehalt angewendet wird.VERSIONINGEine Ganzzahl, die bei jeder Änderung des Datensatzes automatisch erhöht wird

Der Inhalt könnte wie folgt aussehen:
![]()
9.5. Berechnungsmethode für das Gehalt einer Kinderbetreuerin
Wir erklären nun, wie das Monatsgehalt einer Kinderbetreuerin berechnet wird. Als Beispiel nehmen wir das Gehalt von Frau Marie Jouveinal, die während des Abrechnungszeitraums 150 Stunden an 20 Tagen gearbeitet hat.
e folgenden Faktoren werden berücksichtigt: | [TOTALHOURS]: Gesamtzahl der im Monat geleisteten Arbeitsstunden [TOTALDAYS]: Gesamtzahl der im Monat gearbeiteten Tage | [TOTALHOURS]=150 [TOTALDAYS] = 20 |
Das Grundgehalt der Kinderbetreuungskraft wird anhand der folgenden Formel berechnet: | [BASESALARY] = ([TOTALHOURS] * [HOURLYRATE]) * (1 + [CPALLOWANCE] / 100) | [BASESALARY]=(150*[2,1])*(1+0,15)= 362,25 |
Von diesem Grundgehalt müssen verschiedene Sozialversicherungsbeiträge abgezogen werden: | Allgemeiner Sozialbeitrag und Beitrag zur Tilgung der Sozialschuld: [BASESALARY]*[CSGRDS/100] Abzugsfähiger allgemeiner Sozialbeitrag: [BASESALARY]*[CSGD/100] Sozialversicherung, Witwen- und Altersrente: [BASESALARY]*[SECU/100] Zusatzrente + AGPF + Arbeitslosenversicherung: [BASESALARY]*[PENSION/100] | CSGRDS: 12,64 CSGD: 22,28 Sozialversicherung: 34,02 Rente: 28,55 |
Gesamtbeiträge zur Sozialversicherung: | [SOCIALCONTRIBUTIONS] = [BASESALARY] * (CSGRDS + CSGD + SECU + RETIREMENT) / 100 | [SOCIALCONTRIBUTIONS]=97,48 |
Darüber hinaus hat die Kinderbetreuerin Anspruch auf eine Tagegeldzulage und eine Verpflegungszulage für jeden Arbeitstag. Somit erhält sie folgende Zulagen: | [Zulagen]=[GESAMTTAGE]*(TAGESUNTERHALT+TAGESVERPFLEGUNG) | [ZULAGEN]=104 |
Letztendlich ergibt sich folgendes Nettogehalt, das an die Kinderbetreuerin zu zahlen ist: | [GRUNDGEHALT] - [SOZIALVERSICHERUNGSBEITRÄGE] + [ZULAGEN] | [NETTOGEHALT]=368,77 |
9.6. Das Visual Studio-Projekt für die [Web]-Schicht
Das Visual Web Developer-Projekt für die Anwendung sieht wie folgt aus:
![]() |
- in [1] die allgemeine Struktur des Projekts [pam-web-01];
- in [2] befindet sich der Ordner [Content], in dem die statischen Ressourcen des Projekts gespeichert sind:
- [indicator.gif]: das animierte Bild, das das Warten auf den Abschluss einer Ajax-Anfrage anzeigt,
- [standard.jpg]: das Hintergrundbild für die verschiedenen Ansichten,
- [Site.css]: das Stylesheet der Anwendung;
- in [3] der einzige Controller der Anwendung [PamController];
- in [4] die von der Anwendung benötigten Klassen, die jedoch nicht als MVC-Komponenten klassifiziert werden können:
- [ApplicationModelBinder]: die Klasse, die es ermöglicht, Daten aus dem [Application]-Bereich in das Aktionsmodell aufzunehmen,
- [SessionModelBinder]: die Klasse, die es ermöglicht, Daten aus dem [Session]-Bereich in das Aktionsmodell einzubinden,
- [Static]: eine Hilfsklasse mit statischen Methoden;
- in [5] die Anwendungsmodelle, seien es Aktions- oder Ansichtsmodelle:
- [ApplicationModel]: ein Modell, das Daten aus dem [Application]-Bereich enthält,
- [SessionModel]: ein Modell, das Daten aus dem [Session]-Bereich enthält,
- [Simulation]: eine Klasse, die die Elemente einer Gehaltsberechnungssimulation kapselt,
- [IndexModel]: Modell der ersten [Index]-Ansicht, die von der Anwendung angezeigt wird;
- in [6] die für die Globalisierung der Anwendung erforderlichen JS-Skripte;
- in [7] die JS-Skripte der JQuery-Familie, die für die Internationalisierung, die clientseitige Validierung und die AJAX-Funktionalität der Anwendung erforderlich sind;
- in [8] ist [myScripts.js] die Datei, die unsere eigenen JS-Skripte enthält;
- in [9] die Ansichten der Anwendung:
- [Index]: die Startseite,
- [Form]: Formular zur Eingabe von Mitarbeiterdaten sowie deren Arbeitszeiten und -tagen,
- [Simulation]: die Ansicht, die eine Simulation anzeigt,
- [Simulations]: die Ansicht, die die Liste der durchgeführten Simulationen anzeigt,
- [Errors]: die Ansicht, die eine Liste aller Fehler anzeigt,
- [InitFailed]: die Ansicht, die Fehlermeldungen anzeigt, wenn die Initialisierung der Anwendung fehlschlägt;
- in [10] die Anwendungs-Master-Seite [_Layout];
- in [11] die Dateien [Web.config] und [Global.asax], die zur Konfiguration der Anwendung verwendet werden.
9.7. Schritt 1 – Einrichten der simulierten [Business]-Schicht
Ab diesem Punkt beschreiben wir die Schritte, die zur Durchführung der Fallstudie erforderlich sind. Wo dies relevant ist, geben wir die Kapitelnummer an, damit Sie diese bei Bedarf zur Erledigung der Aufgabe nachschlagen können. Einige Projektelemente sind in einem Ordner [aspnetmvc-support.zip] enthalten, der auf der Website dieses Dokuments verfügbar ist. Darin finden Sie den Ordner [case-study-support] mit folgendem Inhalt:

Das Projekt enthält auch Elemente, die in früheren Kapiteln vorgestellt wurden. Sie können diese einfach abrufen, indem Sie sie zwischen diesem PDF und Visual Studio kopieren und einfügen.
9.7.1. Die Visual Studio-Lösung für die vollständige Anwendung
Zunächst erstellen wir eine Visual Studio-Lösung, in der wir zwei Projekte anlegen:
- ein Projekt für die simulierte [Business]-Schicht;
- ein Projekt für die MVC-Webschicht.
![]() |
Wir werden zwei Tools verwenden:
- Visual Studio Express 2012 for Desktop, das zum Erstellen der [Business]-Schicht verwendet wird;
- Visual Studio Express 2012 für das Web, das zum Erstellen der [Web-]Schicht verwendet wird.
Mit Visual Studio Express for Desktop erstellen wir eine [pam-td]-Lösung:
![]() |
- Wählen Sie unter [1] eine C#-Anwendung aus;
- Wählen Sie unter [2] [Konsolenanwendung] aus;
- Geben Sie in [3] einen Namen für die Lösung ein;
- Erstellen Sie in [4] ein Verzeichnis für diese Lösung;
- Benennen Sie in [5] die [Business]-Schicht;
- in [6] die generierte Lösung.
9.7.2. Die Schnittstelle der [Business]-Schicht
In einer mehrschichtigen Architektur ist es bewährte Praxis, dass die Kommunikation zwischen den Schichten über Schnittstellen erfolgt:
![]() |
Welche Schnittstelle sollte die [Business]-Schicht der [Web]-Schicht zur Verfügung stellen? 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 sollte die Liste der Mitarbeiter in [1] erscheinen. Eine vereinfachte Liste ist ausreichend (Nachname, Vorname, Sozialversicherungsnummer). Die Sozialversicherungsnummer wird benötigt, um auf zusätzliche Informationen über den ausgewählten Mitarbeiter zuzugreifen (Felder 6 bis 11).
- Die Informationen 12 bis 15 sind die verschiedenen Beitragssätze.
- Die Informationen 16 bis 19 sind die Zulagen des Mitarbeiters
- Die Angaben 20 bis 24 sind die Gehaltsbestandteile, die auf der Grundlage der Benutzereingaben 1 bis 3 berechnet wurden.
Die Schnittstelle [IPamMetier], die von der [Business]-Schicht an die [Web]-Schicht bereitgestellt wird, muss die oben genannten Anforderungen erfüllen. Es gibt viele mögliche Schnittstellen. Wir schlagen Folgendes vor:
using Pam.Metier.Entites;
namespace Pam.Metier.Service
{
public interface IPamMetier
{
// list of all employee identities
Employe[] GetAllIdentitesEmployes();
// ------- salary calculation
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
}
- Zeile 7: Die Methode, die das Kombinationsfeld [1] füllt
- Zeile 10: Die Methode, die die Informationen 6 bis 24 abruft. Diese wurden in einem Objekt vom Typ [PayrollSheet] gesammelt, das wir in Kürze beschreiben werden.
Wir werden diese Schnittstelle in einem Ordner [business/department] ablegen:
![]() |
9.7.3. Entitäten in der [business]-Schicht
Die vorherige Schnittstelle verwendet zwei Klassen, [Employee] und [PayStub], die wir definieren müssen:
- [Employee] repräsentiert eine Zeile in der Tabelle [employees] der Datenbank;
- [PayStub] steht für die Gehaltsabrechnung eines Mitarbeiters.
Die Entitäten werden im Projekt in einem Ordner [business/entities] abgelegt:
![]() |
In der endgültigen Architektur wird die [business]-Schicht die Datenbankentitätsdarstellungen bearbeiten:

Wir werden die folgenden Klassen verwenden, um die Zeilen der drei Datenbanktabellen darzustellen. Die Bedeutungen der verschiedenen Felder finden Sie in Abschnitt 9.4.
Klasse [Employee]
Sie repräsentiert eine Zeile in der Tabelle [employees]. 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; }
// signature
public override string ToString()
{
return string.Format("Employé[{0},{1},{2},{3},{4},{5}]", SS, Nom, Prenom, Adresse, Ville, CodePostal);
}
}
}
Klasse [Zulagen]
Sie repräsentiert eine Zeile in der Tabelle [indemnites]. 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; }
// signature
public override string ToString()
{
return string.Format("Indemnités[{0},{1},{2},{3},{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
}
}
}
Klasse [Beiträge]
Sie repräsentiert eine Zeile in der Tabelle [contributions]. 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; }
// signature
public override string ToString()
{
return string.Format("Cotisations[{0},{1},{2},{3}]", CsgRds, Csgd, Secu, Retraite);
}
}
}
Beachten Sie, dass die Klassen die Spalten [ID] und [VERSIONING] aus den Tabellen nicht enthalten. Diese Spalten, die bei der Verwendung des EF5-ORM nützlich sind, werden im Kontext der simulierten [Business]-Schicht nicht benötigt.
Die Klasse [FeuilleS al] kapselt die Informationen 6 bis 24 aus dem bereits vorgestellten Formular:
namespace Pam.Metier.Entites
{
public class FeuilleSalaire
{
// automatic properties
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: Felder 6 bis 11 für den Mitarbeiter, dessen Gehalt berechnet wird, und Felder 16 bis 19 für dessen Zulagen. Beachten Sie, dass ein [Employee]-Objekt ein [Allowances]-Objekt kapselt, das dessen Zulagen darstellt;
- Zeile 8: Informationen 12 bis 15;
- Zeile 9: Informationen 20 bis 24;
- Zeilen 12–14: die [ToString]-Methode.
Die Klasse [Elements Salaire] kapselt die Informationen 20 bis 24 aus dem Formular:
namespace Pam.Metier.Entites
{
public class ElementsSalaire
{
// automatic properties
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: Gehaltsbestandteile gemäß den oben beschriebenen Geschäftsregeln;
- Zeile 6: das Grundgehalt des Mitarbeiters, basierend auf der Anzahl der geleisteten Arbeitsstunden;
- Zeile 7: Von diesem Grundgehalt abgezogene Beiträge;
- Zeilen 8 und 9: Zulagen, die zum Grundgehalt hinzugerechnet werden, basierend auf der Gehaltsstufe des Mitarbeiters und der Anzahl der gearbeiteten Tage;
- Zeile 10: das auszuzahlende Nettogehalt;
- Zeilen 14–17: die [ToString]-Methode der Klasse.
9.7.4. Die Klasse [PamException]
Wir erstellen einen spezifischen Ausnahmetyp für unsere Anwendung. Es handelt sich um den folgenden Typ [PamException]:
using System;
namespace Pam.Metier.Entites
{
// exceptional class
public class PamException : Exception
{
// the error code
public int Code { get; set; }
// manufacturers
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], die einen Fehlercode darstellt;
- In unserer Anwendung werden wir zwei Arten von Konstruktoren verwenden:
- den in den Zeilen 23–27, der wie unten gezeigt verwendet werden kann:
- (Fortsetzung)
- oder den in den Zeilen 29–33, der dazu dient, eine aufgetretene Ausnahme weiterzuleiten, indem er sie in eine [PamException]-Ausnahme einbindet:
try{
....
}catch (IOException ex){
// on encapsule l'exception ex
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 [Business]-Schicht
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
{
// list of cached employees
public Employe[] Employes { get; set; }
// employees indexed by their SS number
private IDictionary<string, Employe> dicEmployes = new Dictionary<string, Employe>();
// list of employees
public Employe[] GetAllIdentitesEmployes()
{
...
// we return the list of employees
return Employes;
}
// salary calculation
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 Wörterbuch, das einen Mitarbeiter mit seiner Sozialversicherungsnummer verknüpft;
- 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:
// list of employees
public Employe[] GetAllIdentitesEmployes()
{
if (Employes == null)
{
// we create a table of three employees
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]);
// a fictitious employee who will not be included in the dictionary
// to simulate a non-existent employee
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 }
};
}
// we return the list of employees
return Employes;
}
- Zeile 4: Prüfen, ob die Liste der Mitarbeiter noch nicht erstellt wurde;
- Zeile 7: Falls nicht, erstelle ein Array mit drei Mitarbeitern;
- Zeilen 8–17: der erste Mitarbeiter;
- Zeile 18: Er wird dem Wörterbuch hinzugefügt;
- Zeilen 19–28: der zweite Mitarbeiter;
- Zeile 29: Er wird dem Wörterbuch hinzugefügt;
- Zeilen 32–42: der dritte Mitarbeiter. Dieser wird aus einem Grund, den wir noch erläutern werden, nicht zum Wörterbuch hinzugefügt.
Die Methode [GetSalary] sieht wie folgt aus:
// salary calculation
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
{
// we retrieve employee n° SS
Employe e = dicEmployes.ContainsKey(ss) ? dicEmployes[ss] : null;
// exists?
if (e == null)
{
throw new PamException(string.Format("L'employé de n° SS [{0}] n'existe pas", ss), 10);
}
// a fictitious payslip is returned
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 Sozialversicherungsnummer des Mitarbeiters, für den wir das Gehalt berechnen möchten, die Anzahl der gearbeiteten Stunden und die Anzahl der gearbeiteten Tage;
- Zeile 5: Wir suchen den Mitarbeiter im Verzeichnis. Beachten Sie, dass einer von ihnen nicht vorhanden ist;
- Zeilen 7–10: Wenn der Mitarbeiter nicht gefunden wird, wird eine [PamException] ausgelöst;
- Zeilen 12–17: Es wird eine Dummy-Lohnabrechnung zurückgegeben.
9.7.6. Der Konsolentest für die [business]-Schicht
Das Projekt der [Business]-Schicht sieht derzeit wie folgt aus:
![]() |
Die oben genannte [Program]-Klasse testet die Methoden der [IPamMetier]-Schnittstelle. 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()
{
// instantiation layer [business]
IPamMetier pamMetier = new PamMetier();
// list of employees
Employe[] employes = pamMetier.GetAllIdentitesEmployes();
Console.WriteLine("Liste des employés--------------------");
foreach (Employe e in employes)
{
Console.WriteLine(e);
}
// payslip calculations
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 [business]-Schicht;
- Zeilen 14–19: Testen der Methode [GetAllEmployeeIDs] der Schnittstelle [IPamMetier];
- Zeilen 21–31: Testen 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 zu verwenden, das wir erstellen werden, wandeln wir es in eine Klassenbibliothek um:
![]() |
- in [1], in den Eigenschaften der Datei [Program.cs];
- in [2] legen wir fest, dass die Datei nicht Teil der generierten Assembly sein soll;
- in [3, 4], in den Eigenschaften des Projekts [pam-metier-simule], unter der Option [Application] [3], legen wir fest [4], dass der Build eine Klassenbibliothek (in Form einer DLL) erzeugen soll.
![]() |
- In [5] definieren wir eine [Release]-Assembly. Der andere Typ ist [Debug]. Die Assembly enthält dann Informationen, die das Debugging erleichtern;
- Erstellen Sie in [6] das Projekt [pam-metier-simule];
![]() |
- In [7] zeigen Sie alle Dateien in der Lösung an;
- In [8], im Ordner [bin/Release], die DLL für unser Projekt.
9.8. Schritt 2: Einrichten der Webanwendung
In der vorherigen Visual Studio-Lösung erstellen wir das Projekt für die MVC-Webschicht.
![]() |
Mit Visual Studio Express for the Web öffnen wir die zuvor mit Visual Studio Express for the Desktop erstellte [pam-td]-Lösung.
![]() |
- In [1] wurde die [pam-td]-Lösung in Visual Studio Express for the Web geladen;
- In [2] sehen Sie die Lösung und das Projekt für die soeben erstellte simulierte [business]-Schicht.
In diesem neuen Schritt erstellen wir das Grundgerüst der Webanwendung.
![]() |
- In [1] fügen wir der [pam-td]-Lösung ein neues Projekt hinzu;
![]() |
- in [2] wählen wir ein ASP.NET MVC 4-Projekt aus;
- mit dem Namen [pam-web-01] [3];
- Wählen Sie in [4] die Vorlage „Basic ASP.NET MVC“ aus;
- in [5] wird das Projekt erstellt;
![]() |
- In [6] legen wir das neue Projekt als Startprojekt der Lösung fest, das ausgeführt wird, wenn wir [Strg-F5] drücken;
- In [7] ist der Name des neuen Projekts fettgedruckt, was darauf hinweist, dass es sich um das Startprojekt der Lösung handelt.
Ersetzen Sie nun mithilfe des Windows-Explorers den Ordner [Content] des Projekts durch den Ordner [étudedecas-support / web / Content]. Anschließend müssen Sie die neuen Dateien in das Projekt [pam-web-01] einbinden. Gehen Sie dazu wie folgt vor:
![]() |
- Aktualisieren Sie in [1] die Lösung;
- Zeigen Sie in [2] alle Dateien in der Lösung an;
- In [3] erscheint ein Ordner [Images];
- den Sie in [4] in das Projekt einbinden.
Fügen Sie im Ordner [Scripts] die für die clientseitige Validierung erforderlichen JQuery-Globalization-Skripte [1] hinzu.
![]() |
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: Passen Sie in Zeile 8 die jQuery-Version 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 Dezimalzahlen mit Komma erforderlich sind;
- Zeile 13: Verweis auf die für den Ajax-Modus erforderlichen Skripte;
- Zeile 14: anwendungsspezifische Skripte;
- Zeile 24: das Lade-Bild für Ajax-Aufrufe;
- 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.
Als Nächstes ändern wir die Standardroute der Anwendung:
![]() |
Die Datei [RouteConfig] enthält 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: URLs haben das Format [{controller}/{action}];
- Zeile 15: Wenn keine Aktion angegeben ist, wird die Aktion [Index] verwendet. Wenn kein Controller angegeben ist, wird der Controller [Pam] verwendet.
Aufgrund dieser Konfiguration entspricht die URL [/] der URL [/Pam/Index]. Da es sich bei unserer Anwendung um eine API handelt, ist die URL [/] ihre einzige URL.
Erstellen Sie den [Pam]-Controller:
![]()
Ändern Sie den [PamController] wie folgt:
using System.Web.Mvc;
namespace Pam.Web.Controllers
{
public class PamController : Controller
{
[HttpGet]
public ViewResult Index()
{
return View();
}
}
}
- Zeile 3: Wir platzieren den Controller im Namespace [Pam.Web.Controllers];
- Zeile 7: Die Aktion [Index] verarbeitet nur die HTTP-GET-Anfrage;
- Zeile 8: Wir geben einen Typ [ViewResult] anstelle eines Typs [ActionResult] zurück.
Erstellen Sie nun die Ansicht [Index.cshtml], die von der obigen [Index]-Aktion angezeigt wird:
![]() |
Ändern Sie [Index.cshtml] wie folgt:
@{
ViewBag.Title = "Pam";
}
<h2>Formulaire</h2>
Führen Sie die Anwendung aus, indem Sie [Strg-F5] drücken. Sie sollten die folgende Seite sehen:

Aufgabe: Erklären 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: Implementierung des SPU-Modells
Wir möchten eine Anwendung schreiben, die dem in Abschnitt 7.5 und Abschnitt 7.6 beschriebenen SPU-Modell (Single-Page Application) folgt. Die einzelne Seite ist diejenige, die vom Browser geladen wird, wenn die Anwendung startet:
![]() |
- Der obige Abschnitt [1] ist der feste Teil der einzelnen Seite. Wir haben gesehen, dass er von der Master-Seite [_Layout.cshtml] bereitgestellt wird;
- Teil [2] ist der variable Teil der einzelnen Seite. Er befindet sich innerhalb des ID-Bereichs [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 in dem 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 APU-Modell. 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. JavaScript-Entwicklertools
Denken Sie daran, dass Ihnen im Chrome-Browser eine Reihe von Tools zum Debuggen von HTML, CSS und JavaScript Ihrer Seiten zur Verfügung stehen. Diese Tools wurden teilweise in Abschnitt 7.2 vorgestellt. Im APU-Modell speichern Browser die JavaScript-Skripte, auf die die erste Seite der Anwendung verweist, im Cache. Daher müssen Sie daran denken, diesen Cache zu leeren, wenn Sie Ihre Skripte ändern; andernfalls werden die Änderungen möglicherweise nicht übernommen. So geht das in Chrome:
- Drücken Sie [Strg-Umschalt-I], um die Entwicklertools zu öffnen
![]() |
- Klicken Sie auf das Symbol [1] in der unteren rechten Ecke der Entwicklerkonsole;
- aktivieren Sie dann die Option [2], die den Cache im Entwicklermodus deaktiviert.
9.9.2. Verwendung einer Teilansicht zur Anzeige des Formulars
Das Eingabeformular ist eines der Fragmente, die von der Anwendung angezeigt werden. 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 von der Aktion [Index] angezeigt:
[HttpGet]
public ViewResult Index()
{
return View();
}
Zeile 4 oben zeigt, dass eine [View] angezeigt wird, nicht eine [PartialView]. Wir benötigen eine partielle Ansicht für das Formular, die ein Seitenfragment sein wird. Wir ändern die Ansicht [Index.cshtml] wie folgt:
@{
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 [Form.cshtml]:
![]() |
Der Code für [Form.cshtml] lautet ganz 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 [runSimulation]
Uns interessiert das Fragment, das angezeigt wird, wenn der Benutzer auf den Link [Simulation ausführen] klickt:
![]() |
- In [1] klickt der Benutzer auf den Link [Simulation ausführen];
- in [2] wird die Simulation unterhalb des Formulars angezeigt.
Wir aktualisieren die Teilansicht [Formulaire.cshtml], die das Formular anzeigt, wie folgt:
<h2>Formulaire</h2>
<div id="simulation" />
Zeile 3: Wir erstellen einen Bereich mit der ID [simulation], um das Simulationsfragment aufzunehmen.
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 [Simulation ausführen] verarbeitet. Wir folgen dabei der in Abschnitt 7.6.5 beschriebenen Vorgehensweise. Sehen wir uns zunächst den HTML-Code für den Link in [_Layout.cshtml] an:
<a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
</a>
Wir sehen, dass ein Klick auf den Link [Simulation ausführen] die Ausführung der JS-Funktion [runSimulation] auslöst. Diese Funktion wird in der Datei [myScripts.js] geschrieben, zusammen mit den anderen JS-Funktionen, die von der Anwendung benötigt werden:
// global variables
var loading;
var content;
function faireSimulation() {
// make a manual Ajax call
...
}
function effacerSimulation() {
// delete form entries
...
}
function enregistrerSimulation() {
// make a manual Ajax call
...
}
function voirSimulations() {
// make a manual Ajax call
...
}
function retourFormulaire() {
// make a manual Ajax call
...
}
function terminerSession() {
...
}
// document loading
$(document).ready(function () {
// retrieve the references of the page's various components
loading = $("#loading");
content = $("#content");
});
- Zeilen 35–39: Die jQuery-Funktion, die beim Start der Anwendung ausgeführt wird;
- Zeilen 37–38: Initialisierung der globalen Variablen aus den Zeilen 2 und 3.
Beachten Sie, dass die Elemente mit den IDs [loading] und [content] in der Master-Seite [_Layout.cshtml] definiert sind (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 JS-Funktion [faireSimulation]. Diese Funktion sendet eine Ajax-Anfrage vom Typ POST an die Aktion [/Pam/FaireSimulation]. Zu diesem Zeitpunkt werden keine Daten gesendet. Die Aktion [/Pam/FaireSimulation] gibt die Teilansicht [Simulation.cshtml] an die JS-Funktion [faireSimulation] zurück, die diese HTML-Ausgabe dann in den Bereich mit der ID [simulation] des Formulars einfügt.
Testen Sie den Link [Run Simulation] in Ihrer Anwendung.
9.9.4. Der Ajax-Aufruf [enregistrerSimulation]
Der Link [Simulation speichern] ist in [_Layout.cshtml] wie folgt definiert:
<a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la simulation<br />
</a>
Aufgabe: Schreiben Sie nach den vorherigen Schritten die JS-Funktion [enregistrerSimulation]. Diese Funktion sendet einen Ajax-Aufruf vom Typ POST an die Aktion [/Pam/EnregistrerSimulation]. Zu diesem Zeitpunkt werden keine Daten gesendet. Die Aktion [/Pam/EnregistrerSimulation] gibt die Teilansicht [Simulations.cshtml] an die JS-Funktion [enregistrerSimulation] zurück, die diesen HTML-Stream dann in den Bereich mit der ID [content] auf der Master-Seite einfügt.
Die Ansicht [Simulations.cshtml] sieht wie folgt aus:
![]() |
Ihr Inhalt sieht wie folgt aus:
<h2>Simulations</h2>
Hier ist ein Beispiel für die Ausführung:


9.9.5. Der Ajax-Aufruf [viewSimulations]
Der Link [Simulationen anzeigen] ist in [_Layout.cshtml] wie folgt definiert:
<a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
</a>
Aufgabe: Schreiben Sie gemäß dem vorherigen Verfahren die JS-Funktion [viewSimulations]. Diese Funktion sendet einen Ajax-Aufruf vom Typ POST an die Aktion [/Pam/ViewSimulations]. Es werden vorerst keine Daten gesendet. Die Aktion [/Pam/VoirSimulations] gibt die Teilansicht [Simulations.cshtml] an die JS-Funktion [voirSimulations] zurück, die diese HTML-Ausgabe dann in den Bereich mit der ID [content] auf der Master-Seite einfügt.
Die Ansicht [Simulations.cshtml] ist diejenige, die bereits in der vorherigen Frage verwendet wurde.
Hier ein Beispiel für die Ausführung:
![]() |
9.9.6. Der Ajax-Aufruf [returnToForm]
Der Link [Zurück zum Simulationsformular] ist in [_Layout.cshtml] wie folgt definiert:
<a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de simulation<br />
</a>
Aufgabe: Schreiben Sie nach den vorherigen Schritten die JS-Funktion [returnForm]. Diese Funktion sendet eine Ajax-Anfrage vom Typ POST an die Aktion [/Pam/Form]. Zu diesem Zeitpunkt werden keine Daten übermittelt. Die Aktion [/Pam/Formulaire] gibt die Teilansicht [Formulaire.cshtml] an die JS-Funktion [retourFormulaire] zurück, die diesen HTML-Stream dann in den Bereich mit der ID [content] auf der Master-Seite einfügt.
Die Ansicht [Formulaire.cshtml] wurde bereits definiert. Hier ein Ausführungsbeispiel:
![]() |
9.9.7. Der Ajax-Aufruf [endSession]
Der Link [End Session] ist in [_Layout.cshtml] wie folgt definiert:
<a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
</a>
Aufgabe: Schreiben Sie gemäß der vorherigen Vorgehensweise die JS-Funktion [terminerSession]. Diese Funktion sendet einen Ajax-Aufruf vom Typ POST an die Aktion [/Pam/TerminerSession]. Vorerst werden keine Daten übermittelt. Die Aktion [/Pam/TerminerSession] gibt die Teilansicht [Formulaire.cshtml] an die JS-Funktion [terminerSession] zurück, die diesen HTML-Stream dann in den Bereich mit der ID [content] auf der Master-Seite einfügt.
Hier ein Ausführungsbeispiel:
![]() |
9.9.8. Die JS-Funktion [clearSimulation]
Der Link [Clear Simulation] ist in [_Layout.cshtml] wie folgt definiert:
<a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
</a>
Der Zweck der JS-Funktion [clearSimulation] ist:
- das [Simulation]-Fragment auszublenden, falls es vorhanden ist;
- die Eingabefelder des Formulars auf ihren Zustand beim ersten Laden der Anwendung zurückzusetzen (sofern Eingabefelder vorhanden sind – derzeit gibt es keine).
Aufgabe: Schreiben Sie die JS-Funktion [clearSimulation]. Hier findet kein Ajax-Aufruf statt. Dieser Vorgang läuft im Browser ab und bezieht den Server nicht mit ein.
Hier ist ein Beispiel für die Ausführung:
![]() |
9.9.9. Verwaltung der Navigation zwischen Bildschirmen
Derzeit werden die Links immer angezeigt. Wir werden nun ihre Anzeige mithilfe einer JavaScript-Funktion steuern. Sehen wir uns zunächst den Code für die 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 [id]-Attribut, das es uns ermöglicht, sie in JavaScript zu verwalten. Wir ändern die JavaScript-Methode, die beim Laden der Seite ausgeführt wird, wie folgt:
// variables globales
var loading;
var content;
var lnkFaireSimulation;
var lnkEffacerSimulation
var lnkEnregistrerSimulation;
var lnkTerminerSession;
var lnkVoirSimulations;
var lnkRetourFormulaire;
var options;
...
// au chargement du document
$(document).ready(function () {
// on récupère les références des différents composants de la page
loading = $("#loading");
content = $("#content");
// les liens du menu
lnkFaireSimulation = $("#lnkFaireSimulation");
lnkEffacerSimulation = $("#lnkEffacerSimulation");
lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
lnkVoirSimulations = $("#lnkVoirSimulations");
lnkTerminerSession = $("#lnkTerminerSession");
lnkRetourFormulaire = $("#lnkRetourFormulaire");
// on les met dans un tableau
options = [lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
// on cache certains éléments de la page
loading.hide();
// on fixe le menu
setMenu([lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]);
});
- Zeilen 19–24: Abrufen der Referenzen für die sechs Links. 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 ist in Zeile 10 als globale Variable definiert;
- Zeile 28: Das animierte Bild ausblenden, das anzeigt, dass Ajax-Aufrufe ausstehen;
- Zeile 30: Die Links [lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession] werden angezeigt. Die anderen werden ausgeblendet.
Die JS-Funktion [setMenu] lautet wie folgt:
function setMenu(show) {
// display table links [show]
...
}
Aufgabe: Schreibe die JS-Funktion [setMenu].
Wenn T ein Array von Links ist:
- ist T.length die Anzahl der Links;
- T[i] ist der Link mit der Nummer i;
- T[i].show() zeigt den Link Nummer i an;
- T[i].hide() blendet den Link mit der Nummer i aus.
Mit diesen neuen JS-Funktionen sieht die beim Start angezeigte Seite wie folgt aus:

Passen Sie die JS-Funktionen [runSimulation, clearSimulation, saveSimulation, viewSimulations, returnToForm, endSession] so an, dass die folgenden Bildschirme angezeigt werden:












Nachdem das APU-Modell und die Navigationslinks nun eingerichtet sind, können wir mit dem Schreiben der serverseitigen Aktionen und Ansichten fortfahren. Im Laufe der folgenden Schritte werden Sie feststellen, dass einige der derzeit funktionierenden Ajax-Links nicht mehr funktionieren, da 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 wie vorgesehen funktionieren.
9.10. Schritt 4: Schreiben der Server-Aktion [Index]
Derzeit wird beim Start der Anwendung der folgende Bildschirm angezeigt:

Anstelle dieses Bildschirms möchten wir Folgendes sehen:

Es ist die Aktion [Index], die diese Seite generieren muss. Lassen Sie uns einige Anmerkungen machen:
- Die Seite zeigt ein Formular mit drei Eingabefeldern an:
- den Mitarbeiter, dessen Gehalt berechnet wird,
- die Anzahl der gearbeiteten Stunden,
- die Anzahl der gearbeiteten Tage;
- das Formular wird über den Link [Simulation starten] abgeschickt;
- die Gültigkeit der Eingabefelder [Gearbeitete Stunden] und [Gearbeitete Tage] muss überprüft werden;
- die Liste der Mitarbeiter stammt aus der zuvor erstellten [Business]-Ebene.
Sehen wir uns den aktuellen Code für die Aktion [Index] an:
[HttpGet]
public ViewResult Index()
{
return View();
}
die aus der Ansicht [Index.cshtml], die diese Aktion anzeigt:
@{
ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")
und die Teilansicht [Formulaire.cshtml]:
<h2>Formulaire</h2>
Änderungen werden an diesen drei Stellen vorgenommen.
9.10.1. Die Formularvorlage
Kehren wir zum URL-Verarbeitungsablauf [/Pam/Index] zurück:
![]() |
- Die HTTP-Anfrage des Clients trifft bei [1] ein;
- bei [2] werden die in der Anfrage enthaltenen Informationen in ein Aktionsmodell [3] umgewandelt, das als Eingabe für die Aktion [4] dient;
- an [4] generiert die Aktion auf der Grundlage dieses Modells eine Antwort. Diese Antwort 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 HTTP-Antwort für den Client zu generieren.
Die Aktion, die uns interessiert, ist die [Index]-Aktion, die derzeit wie folgt aussieht:
[HttpGet]
public ViewResult Index()
{
return View();
}
Die Aktion [Index] übergibt kein Modell an die Ansicht [Index.cshtml]. Daher kann die Liste der Mitarbeiter nicht angezeigt werden. Diese Liste kann von der [business]-Schicht angefordert werden. Dazu muss das Projekt [pam-web-01] eine Referenz auf das Projekt [pam-metier-simule] enthalten. Wir werden diese Referenz nun erstellen:
![]() |
- Klicken Sie in [1] im Projekt [pam-web-01] mit der rechten Maustaste auf [Referenzen] und wählen Sie dann [Referenz hinzufügen];
- in [2] wählen Sie die Option [Lösung] und anschließend in [3] das Projekt [pam-metier-simule];
- in [4] wurde das Projekt [pam-metier-simule] zu den Referenzen des Projekts [pam-web-01] hinzugefügt.
9.10.2. Das Anwendungsmodell
In Abschnitt 4.10 auf Seite 70 haben wir die wichtigen Konzepte des Anwendungsmodells und des Sitzungsmodells vorgestellt. Diese werden wir nun anwenden. Erinnern Sie sich daran, dass wir im Modell Folgendes platzieren:
- ein Anwendungsmodell, das schreibgeschützte Daten für alle Benutzer enthält. Dieses Modell bildet einen gemeinsamen Speicher für alle Anfragen aller Benutzer;
- Sitzungsdaten, die für einen bestimmten Benutzer schreib- und lesbar sind. Dieses Modell bildet einen gemeinsamen Speicher für alle Anfragen dieses Benutzers.
Was werden wir in das Anwendungsmodell aufnehmen? Schauen wir uns dessen Architektur noch einmal an:
![]() |
Die [Web]-Schicht enthält einen Verweis auf die [Business]-Schicht. Dieser kann von allen Benutzern gemeinsam genutzt werden. Wir können ihn daher in das Anwendungsmodell aufnehmen. Darüber hinaus gehen wir davon aus, dass sich die Liste der Mitarbeiter nicht ändert. Sie kann daher einmal gelesen und dann von allen Benutzern gemeinsam genutzt werden. Wir schlagen daher das folgende Anwendungsmodell vor:
![]() |
Der Code für die Klasse [ApplicationModel] könnte wie folgt aussehen:
using Pam.Metier.Entites;
using Pam.Metier.Service;
namespace PamWeb.Models
{
public class ApplicationModel
{
// --- application scope data ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
}
}
Um eine Dropdown-Liste in einer Ansicht anzuzeigen, schreiben Sie etwa Folgendes:
<!-- the drop-down list -->
<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 dieses Array auch im Anwendungsmodell platziert werden. Wir aktualisieren es wie folgt:
using Pam.Metier.Entites;
using Pam.Metier.Service;
using System.Web.Mvc;
namespace Pam.Web.Models
{
public class ApplicationModel
{
// --- application scope data ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
}
}
Wann sollte dieses Modell erstellt werden? Wir haben dies in Abschnitt 4.10 gezeigt. Es geschieht, wenn die Methode [Application_Start] in der Datei [Global.asax] ausgeführt wird:
![]() |
Die [Application_Start]-Methode 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 erweitern es wie folgt:
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()
{
// ----------Auto-generated
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// -------------------------------------------------------------------
// ---------- specific configuration
// -------------------------------------------------------------------
// application scope data
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
// instantiation layer [business]
application.PamMetier = ...
// employee roster
application.Employes = ...
// employee combo items
application.EmployesItems = ...
// model binder for [ApplicationModel]
...
}
}
}
Aufgabe: Vervollständigen Sie den Code für die 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] wird verwendet, um den folgenden HTML-Tag zu generieren: <option>
in der Dropdown-Liste. Wir stellen sicher, dass:
- text der Vorname des Mitarbeiters ist, gefolgt von seinem Nachnamen;
- aValue die Sozialversicherungsnummer des Mitarbeiters ist.
In Zeile 35 oben benötigen Sie die in Abschnitt 4.10, Seite 74 beschriebene Klasse [ApplicationModelBinder]:
![]() |
9.10.3. Der Code für die Aktion [Index]
Nachdem wir nun ein Modell für die Anwendung definiert haben, können wir den Code für die Aktion [Index] wie folgt aktualisieren:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View();
}
- Zeile 4: Das Anwendungsmodell ist nun ein Parameter der [Index]-Aktion. In Abschnitt 4.10 haben wir erläutert, wie dieser Parameter vom Framework initialisiert wird.
9.10.4. Die Ansichtsvorlage [Index.cshtml]
Nun hat die [Index]-Aktion Zugriff auf die im Anwendungsmodell gespeicherten Mitarbeiter. Sie muss diese nun an die Ansicht [Index.cshtml] übergeben, die sie anzeigen wird. Wir könnten einen Typ [ApplicationModel] als Ansichtsmodell an die Ansicht [Index.cshtml] übergeben, aber wir werden schnell feststellen, dass diese Ansicht zusätzliche Informationen benötigt, die nicht in [ApplicationModel] enthalten sind. Wir verwenden das folgende [IndexModel]-Ansichtsmodell:
![]() |
namespace Pam.Web.Models
{
public class IndexModel
{
// application scope data
public ApplicationModel Application { get; set; }
}
}
- Zeile 6: [IndexModel] enthält 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 unter Verwendung eines Typs [IndexModel] als Modell angezeigt, der mit Daten aus dem Anwendungsmodell initialisiert wird.
Wir wissen, dass die Ansicht [Index.cshtml] ein Formular anzeigen muss:

Kehren wir zum Ablauf der Anforderungsverarbeitung zurück:
![]() |
Für die Anfrage [GET /Pam/Index]:
- Die Aktion ist [Index];
- das Modell für diese Aktion ist [ApplicationModel];
- die Ansicht ist [Index.cshtml];
- das Modell für diese Ansicht ist [IndexModel].
Wenn das Formular übermittelt wird, verläuft der Verarbeitungsablauf ähnlich:
- Die Aktion ist diejenige, die den POST-Request verarbeitet;
- Das Modell erfasst die eingegebenen Werte, in diesem Fall:
- die Sozialversicherungsnummer des ausgewählten Mitarbeiters;
- die Anzahl der geleisteten Arbeitsstunden;
- die Anzahl der gearbeiteten Tage;
Wir könnten ein Aktionsmodell erstellen, das diese drei Werte kombiniert. Es ist auch üblich, das Modell wiederzuverwenden, das zur Anzeige 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
{
// application scope data
public ApplicationModel Application { get; set; }
// posted values
[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. Beachten Sie, dass [daysWorked] als Typ [double] deklariert wurde, obwohl eigentlich ein Ganzzahlwert erwartet wird. Der Typ [double] wurde eingeführt, um die clientseitige Validierung dieses Feldes zu erleichtern, da die Validierung eines [int]-Typs zu Problemen geführt hatte;
- Zeilen 12, 14, 17: Bezeichnungen für die [Html.LabelFor]-Methoden der mit dem Modell verknüpften Ansicht;
- Zeile 15: eine Anmerkung, um das Feld [HoursWorked] mit zwei Dezimalstellen anzuzeigen;
- Zeile 5: gibt an, dass die Eigenschaft mit dem Namen [Application] nicht in den übermittelten Werten enthalten ist.
9.10.5. Die Ansichten [Index.cshtml] und [Formulaire.cshtml]
Die Ansicht [Index.cshtml] wird durch die folgende [Index]-Aktion 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 [Form.cshtml] ein, wiederum ohne ihr ein Modell zu übergeben. Beim Testen wurde beobachtet, dass das an die Ansicht [Index.cshtml] übergebene Modell [IndexModel] implizit an die Teilansicht [Form.cshtml] weitergegeben wurde. Letztere Ansicht könnte nun wie folgt aussehen:
@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 ein Modell vom Typ [IndexModel];
- Zeile 3: das Formular;
- Zeilen 6–10: die Kopfzeilen der Eingabetabelle;
- Zeilen 12–14: die Eingabezeile;
- Zeilen 15–17: etwaige Fehlermeldungen.
Aufgabe: Vervollständigen Sie den Code für die Ansicht [Formulaire.cshtml]. Verwenden Sie die in Abschnitt 5.7 beschriebenen Methoden [DropDownListFor, EditorFor, LabelFor, ValidationMessageFor].
9.10.6. Testen der [Index]-Aktion
Wir haben alle Elemente der URL-Verarbeitungskette [/Pam/Index] geschrieben:
![]() |
Wir testen die Anwendung mit [Strg-F5]:


Sie müssen überprüfen, ob Ihre Dropdown-Liste mit der Liste der Mitarbeiter gefüllt wurde, die wir in der simulierten [Business]-Schicht definiert haben.
9.11. Schritt 5: Implementierung der Eingabevalidierung
9.11.1. Das Problem
Obwohl wir nichts unternommen haben, um sie zu aktivieren, ist die clientseitige Validierung bereits aktiv:


Die clientseitige Validierung ist standardmäßig aktiviert, wie aus Zeile 3 unten in der Datei [Web.config] der Anwendung hervorgeht.
<appSettings>
...
<add key="ClientValidationEnabled" value="true" />
</appSettings>
Da wir jedoch in [IndexModel] das Feld [JoursTravaillés] als Typ [double] deklariert haben:
public double JoursTravaillés { get; set; }
können wir in dieses Feld eine reelle Zahl eingeben:

Außerdem können wir in beide Felder beliebige Werte eingeben:

Das [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
{
// application scope data
public ApplicationModel Application { get; set; }
// posted values
[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 dieses Modell, um:
- benutzerdefinierte Fehlermeldungen anzuzeigen;
- Für das Feld [HoursWorked] sind nur reelle Werte im Bereich [0,400] zulässig;
- Akzeptiere für das Feld [DaysWorked] nur ganzzahlige Werte im Bereich [0,31];
Sie können sich auf das Beispiel in Abschnitt 7.6.2 beziehen. Um zu überprüfen, ob die Anzahl der Arbeitstage eine ganze Zahl ist, können Sie einen regulären Ausdruck verwenden (siehe Beispiele in Abschnitt 5.9.1).
Hier sind einige Beispiele für erwartete Werte:



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 Dezimalpunkt) sein. Das französische Format mit Komma wird nicht akzeptiert:

Dieses Problem wurde erkannt und in Abschnitt 6.1 behoben.
Aufgabe: Nehmen Sie gemäß der Vorgehensweise im oben genannten Abschnitt die erforderlichen Änderungen vor, 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. Formularvalidierung über den JavaScript-Link [Simulation ausführen]
Derzeit können ungültige Werte übermittelt werden, wie in der folgenden Abfolge gezeigt:

![]() |
Das Vorhandensein der Simulation in [1] und die Menüänderung in [2] zeigen, dass das Klicken auf den Link [Simulation ausführen] das Formular übermittelt hat, obwohl die eingegebenen Werte ungültig waren. Dieses Problem wurde in Abschnitt 7.6.5 identifiziert und behoben.
Aufgabe: Stellen Sie gemäß der in dem oben genannten Abschnitt beschriebenen Vorgehensweise sicher, dass die POST-Aktion für den Link [Simulation ausführen] nicht ausgeführt werden kann, wenn die eingegebenen Werte ungültig sind. Denken Sie daran, den Browser-Cache zu leeren, bevor Sie Ihre Änderungen testen.
Beachten Sie, dass die Teilansicht [Formulaire.cshtml] ein HTML-Formular mit der ID [formulaire] generiert (Zeile 1 unten):
@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
Dies lässt sich überprüfen, indem man den Quellcode des Formulars im Browser anzeigt:
<div id="content">
<form action="/Pam/FaireSimulation" id="formulaire" method="post">
...
</form>
<div id="simulation" />
</div>
9.12. Schritt 6: Eine Simulation ausführen
9.12.1. Das Problem
Wenn wir eine Simulation ausführen, möchten wir das folgende Ergebnis erhalten:
![]() |
Die Teilansicht [Simulation.cshtml] zeigt nun die Gehaltsabrechnung eines Mitarbeiters an.
9.12.2. Erstellen der Ansicht [Simulation.cshtml]
Die Ansicht [Simulation.cshtml] ändert 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 [PayrollSheet];
- Die Ansicht verwendet die Klassen [label, info, value], 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 legen wir, ebenfalls in [Site.css], die Zeilenhöhe der verschiedenen HTML-Tabellen in dem Bereich mit der ID [simulation] fest, insbesondere dort, wo die Gehaltsabrechnung angezeigt wird:
#simulation table tr {
height: 30px;
}
Aufgabe: Vervollständigen Sie die Ansicht [Simulation.cshtml].
Um den Euro-Betrag einer Geldsumme anzuzeigen, verwenden wir die Methode [string.Format]:
Die obige Anweisung zeigt [somme] als Geldwert [C] (Currency) mit zwei Dezimalstellen [C2] an.
Um diese Ansicht zu testen, müssen Sie ihr eine Gehaltsabrechnung bereitstellen. Diese muss von der Aktion [/Pam/FaireSimulation] bereitgestellt werden, die das Ziel des Ajax-Aufrufs über den Link [Simulation ausführen] ist. Derzeit sieht diese Aktion wie folgt aus:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
}
// make a simulation
[HttpPost]
public PartialViewResult FaireSimulation()
{
return PartialView("Simulation");
}
Im obigen Code übergibt die Aktion [FaireSimulation] kein Modell an die Ansicht [Simulation.cshtml]. Sie muss ihr eine Gehaltsabrechnung übergeben. Wir wissen, dass die [business]-Schicht die Gehaltsabrechnungen berechnet. Auf diese [business]-Schicht kann über das Anwendungsmodell [ApplicationModel] zugegriffen werden, das wir in Abschnitt 9.10.2 definiert haben:
public class ApplicationModel
{
// --- application scope data ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
}
Auf die [business]-Schicht kann über die Eigenschaft in Zeile 5 oben zugegriffen werden. Um der Aktion [RunSimulation] Zugriff auf die [business]-Schicht zu gewähren, übergeben wir ihr das Anwendungsmodell, wie wir es bereits für die Aktion [Index] getan haben. Der Code sieht dann wie folgt aus:
// make a simulation
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application)
{
return PartialView("Simulation");
}
Nun können wir innerhalb der Aktion eine fiktive Gehaltsabrechnung berechnen. Der Code sieht wie folgt aus:
// make a simulation
[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 Sozialversicherungsnummer. Diese wurde in der in Abschnitt 9.7.5 simulierten [business]-Klasse definiert. Der zweite Parameter ist die Anzahl der geleisteten Arbeitsstunden, der dritte die Anzahl der Arbeitstage;
- Zeile 6: Diese Gehaltsabrechnung wird als Vorlage an die Ansicht [Simulation.cshtml] übergeben.
Wir sind nun bereit, die Ansicht [Simulation.cshtml] zu testen:

Wir geben keine Daten ein und fordern die Simulation an. Daraufhin erhalten wir das folgende Ergebnis:

9.12.3. Berechnung des tatsächlichen Lohns
Unsere aktuelle [RunSimulation]-Aktion berechnet immer denselben Lohnzettel:
// make a simulation
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application)
{
FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
return PartialView("Simulation", feuilleSalaire);
}
Es berücksichtigt nicht die eingegebenen Informationen:
- den Mitarbeiter, dessen Gehalt berechnet wird;
- die Anzahl der gearbeiteten Stunden;
- die Anzahl der gearbeiteten Tage.
Die eingegebenen Werte werden wie folgt an die Aktion [RunSimulation] übergeben:
- Der Benutzer klickt auf den Link [Simulation ausführen]. Dies löst die Ausführung der JS-Funktion [runSimulation] aus, die wir bereits geschrieben haben;
- die JS-Funktion [faireSimulation] führt dann einen Ajax-Aufruf an die Serveraktion [/Pam/FaireSimulation] durch, an der wir gerade arbeiten. Derzeit sendet die JS-Funktion [faireSimulation] noch keine Informationen an die Serveraktion. Sie muss ihr die vom Benutzer eingegebenen Werte übermitteln;
- Die Serveraktion [/Pam/FaireSimulation] ruft die eingegebenen Werte aus den von der JS-Funktion [faireSimulation] gesendeten Daten ab.
Beginnen wir mit Punkt 2: Die JS-Funktion [faireSimulation] muss die vom Benutzer eingegebenen Werte an die Serveraktion [/Pam/FaireSimulation] senden.
Aufgabe: Vervollständigen Sie die JS-Funktion [faireSimulation] so, dass sie die vom Benutzer eingegebenen Werte übermittelt. Sie können sich auf das Beispiel in Abschnitt 7.6.5 beziehen, in dem dieses Problem behandelt wurde.
Wenden wir uns nun Punkt 3 oben zu. Die Serveraktion [/Pam/FaireSimulation] muss die von der JS-Funktion [faireSimulation] übermittelten Werte abrufen.
Aufgabe: Vervollständigen Sie die Servermethode [FaireSimulation] so, dass sie das Gehalt anhand der von der JS-Funktion [faireSimulation] übermittelten Werte berechnet. Sie können sich erneut auf das Beispiel in Abschnitt 7.6.5 beziehen, in dem dieses Problem behandelt wurde. Vorerst gehen wir davon aus, dass das aus den übermittelten Werten abgeleitete Modell immer gültig ist.
Hinweis: Die Serveraktion [FaireSimulation] entwickelt sich wie folgt:
// make a simulation
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
{
// action model creation
...
// we try to retrieve the values posted in this model
...
// salary calculation
FeuilleSalaire feuilleSalaire = ...
// the salary sheet is displayed
return PartialView("Simulation", feuilleSalaire);
}
Hier ist ein Ausführungsbeispiel:
![]() |
Wir wählen [Justine Laverti] aus. Daraufhin erhalten wir folgendes Ergebnis:
![]() |
Wir haben tatsächlich die fiktive Gehaltsabrechnung für [Justine Laverti] erhalten. Zuvor wurde lediglich die Gehaltsabrechnung für [Marie Jouveinal] berechnet. Daher wurde der für die Mitarbeiterauswahl hinterlegte Wert verwendet. Was die Anzahl der Stunden und Tage betrifft, können wir keine Aussage treffen, da unsere simulierte [Geschäfts-]Ebene diese nicht berücksichtigt.
9.12.4. Fehlerbehandlung
Betrachten wir das folgende Beispiel:
![]() |
- In [1] wählen wir einen Mitarbeiter aus, der nicht existiert (siehe die Definition der simulierten [Business]-Schicht in Abschnitt 9.7.5;
- in [2] führen wir die Simulation durch;
- in [3] unten wird eine Fehlerseite zurückgegeben.
![]() |
Was ist passiert?
Die JS-Funktion [doSimulation] wurde ausgeführt. Ihr Code sieht wie folgt aus:
function faireSimulation() {
...
// make a manual Ajax call
$.ajax({
url: '/Pam/FaireSimulation',
...
beforeSend: function () {
// wait signal on
loading.show();
},
success: function (data) {
...
},
error: function (jqXHR) {
// error display
simulation.html(jqXHR.responseText);
simulation.show();
},
complete: function () {
// wait signal off
loading.hide();
}
});
// menu
setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
Der Ajax-Aufruf ist fehlgeschlagen, und die Funktion in den Zeilen 14–18 wurde ausgeführt. Die vom Server zurückgegebene Fehlerseite [jqXHR.responseText] wurde angezeigt. Dies ist recht spezifisch. Die simulierte [business]-Schicht hat eine Ausnahme ausgelöst, da die ihr übergebene SSN nicht zu einem bestehenden Mitarbeiter gehört (siehe den Code für die simulierte [business]-Schicht in Abschnitt 9.7.5). Wir müssen diesen Fall ordnungsgemäß behandeln.
Wir erstellen eine Teilansicht [Errors.chtml], die an den JavaScript-Client zurückgegeben wird, sobald auf der Serverseite ein Fehler erkannt wird:
![]() |
Der Code für die Teilansicht [Errors.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 eine Liste von Fehlermeldungen als Modell;
- Zeilen 5–10: Diese werden in einer HTML-Liste angezeigt;
Nun ändern wir den Code für die Serveraktion [FaireSimulation] wie folgt:
// make a simulation
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
{
...
// salary calculation
FeuilleSalaire feuilleSalaire = null;
Exception exception=null;
try
{
// salary calculation
feuilleSalaire = ...
}
catch (Exception ex)
{
exception = ex;
}
// mistake?
if (exception == null)
{
// the salary sheet is displayed
return PartialView("Simulation", feuilleSalaire);
}
else
{
// the error page is displayed
return PartialView("Erreurs", Static.GetErreursForException(exception));
}
}
- Zeilen 9–17: Die Gehaltsberechnung wird nun innerhalb eines try/catch-Blocks durchgeführt;
- Zeile 27: Wenn ein Fehler aufgetreten ist, wird die Teilansicht [Errors.cshtml] angezeigt, wobei die von der statischen Methode [Static.GetErrorsForException(exception)] bereitgestellte Liste der Fehlermeldungen als Vorlage verwendet wird.
Wir gruppieren zwei statische Hilfsfunktionen [1] in der Klasse [Static]:
![]() |
using System;
using System.Collections.Generic;
using System.Web.Mvc;
namespace PamWeb.Infrastructure
{
public class Static
{
// list of exception error messages
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;
}
// list of error messages linked to an invalid model
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;
}
// the error message linked to an element of the action model
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 [GetErrorsForException] gibt die Liste der Fehler in einem Ausnahmestapel zurück;
- Zeilen 22–36: Die statische Funktion [GetErrorsForModel] gibt die Liste der Fehler für ein ungültiges Aktionsmodell zurück. Der Code für diese Funktion sowie der für die private 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] rufen wir die neue Fehlerseite auf.
Kehren wir zur Serveraktion [RunSimulation] zurück:
// make a simulation
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
{
// action model creation
IndexModel modèle = new IndexModel() { Application = application};
// we try to retrieve the values posted in the model
TryUpdateModel(modèle, data);
// salary calculation
...
}
In Zeile 8 aktualisieren wir das Modell aus Zeile 6 mit den Werten, die durch den Ajax-Aufruf übermittelt wurden. Wir validieren das Modell nicht. Dies ist notwendig, da wir nicht wissen können, woher die übermittelten Werte stammen. Jemand könnte eine POST-Anfrage manipuliert und uns ungültige Daten gesendet haben.
Aufgabe: Ändern Sie gemäß dem Muster, das wir für den Ausnahmefall entwickelt haben, die Serveraktion [FaireSimulation] so, dass sie eine Fehlerseite zurückgibt, wenn die übermittelten Daten ungültig sind. Dazu verwenden wir die statische Methode [GetErreursForModel] aus der Klasse [Static].
Wie testen Sie diese Änderung? In Abschnitt 9.11.3 haben Sie sichergestellt, dass die JS-Funktion [faireSimulation] die eingegebenen Werte nicht per POST übermittelt, wenn sie ungültig sind. Kommentieren Sie die Zeilen aus, die dies bewirken, und führen Sie dann den folgenden Test durch:
![]() |
- Führen Sie in [1] die Simulation mit ungültigen Werten durch;
- in [2] wird die soeben erstellte Fehlerseite erfolgreich angezeigt, was beweist, dass die serverseitigen Validatoren korrekt funktioniert haben.
Denken Sie als Nächstes daran, die Zeilen, die Sie gerade in der JS-Funktion [faireSimulation] auskommentiert haben, wieder zu aktivieren.
9.13. Schritt 7: Einrichten einer Benutzersitzung
Die Anwendung [Payroll Calculator] ermöglicht es dem Benutzer, über den Link [Run Simulation] verschiedene Gehaltsabrechnungssimulationen durchzuführen, diese über den Link [Save Simulation] zu speichern, über den Link [View Simulations] anzuzeigen und über den Link [Delete Simulation] zu löschen. Wir wissen, dass zwischen zwei aufeinanderfolgenden Benutzeranfragen kein Zustand besteht, es sei denn, wir erstellen einen über den Sitzungsmechanismus (siehe Abschnitt 4.10). Es ist hier ganz klar, dass wir die Liste der vom Benutzer im Laufe der Zeit gespeicherten Simulationen in der Sitzung speichern müssen. Es gibt noch weitere Daten zu speichern: Wenn der Benutzer eine Simulation durchführt, wird diese nur dann in der Liste der Simulationen gespeichert, wenn der Benutzer dies über den Link [Simulation speichern] anfordert. Wenn er dies tut, müssen wir in der Lage sein, die in der vorherigen Anfrage berechnete Simulation abzurufen. Zu diesem Zweck wird sie ebenfalls in der Sitzung gespeichert. Schließlich nummerieren wir die Simulationen beginnend mit 1. Um eine neue Simulation korrekt zu nummerieren, müssen wir die Nummer der vorherigen Simulation gespeichert haben, wiederum in der Sitzung.
In Abschnitt 4.10 haben wir das Konzept eines Sitzungsmodells als Eingabeparameter für eine Aktion vorgestellt, damit die Aktion auf die Sitzung zugreifen kann. Wir werden dieses Konzept noch einmal aufgreifen. Wir empfehlen Ihnen, den entsprechenden Abschnitt noch einmal zu lesen, falls Ihnen dieses Konzept unklar ist.
Wir erstellen die folgende [SessionModel]-Klasse:
![]() |
Der Code lautet wie folgt:
using Pam.Web.Models;
using System.Collections.Generic;
namespace Pam.Web.Models
{
public class SessionModel
{
// list of simulations
public List<Simulation> Simulations { get; set; }
// n° of next simulation
public int NumNextSimulation { get; set; }
// the last simulation
public Simulation Simulation { get; set; }
// manufacturer
public SessionModel()
{
// empty simulation list
Simulations = new List<Simulation>();
// next simulation no
NumNextSimulation = 1;
}
}
}
Die Klasse [Simulation] in den Zeilen 9 und 13 speichert Informationen zu einer Simulation. Was müssen wir speichern? Der Link [Simulation ausführen] berechnet eine Lohnabrechnung vom Typ [Payroll]. Es erscheint naheliegend, diese in die Simulation aufzunehmen. Zusätzlich müssen wir die Informationen speichern, die zu dieser Lohnabrechnung geführt haben:
- den ausgewählten Mitarbeiter. Dieser ist im Feld [PayrollSheet.Employee] zu finden. Daher muss er nicht ein zweites Mal gespeichert werden;
- die Anzahl der gearbeiteten Stunden und Tage. Diese Informationen sind nicht in der Klasse [Payroll] enthalten. Wir müssen sie daher speichern.
Schließlich wird jede Simulation durch eine Nummer identifiziert. Wir könnten daher mit der folgenden [Simulation]-Klasse beginnen:
using Pam.Metier.Entites;
namespace Pam.Web.Models
{
public class Simulation
{
// simulation no
public int Num { get; set; }
// number of hours worked
public double HeuresTravaillées { get; set; }
// number of days worked
public int JoursTravaillés { get; set; }
// payslip
public FeuilleSalaire FeuilleSalaire { get; set; }
}
}
Die Serveraktion [RunSimulation] muss zusätzlich zur Berechnung einer Gehaltsabrechnung eine Simulation erstellen und diese in die Sitzung einfügen. Dazu erhält sie das Sitzungsmodell als Parameter:
// make a simulation
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, SessionModel session, FormCollection data)
{
// action model creation
IndexModel modèle = new IndexModel() { Application = application };
// we try to retrieve the values posted in the model
TryUpdateModel(modèle, data);
// valid model?
if (!ModelState.IsValid)
{
// the error page is displayed
return PartialView("Erreurs", Static.GetErreursForModel(ModelState));
}
// salary calculation
FeuilleSalaire feuilleSalaire = null;
Exception exception = null;
try
{
// salary calculation
feuilleSalaire = application.PamMetier.GetSalaire(modèle.SS, modèle.HeuresTravaillées, (int)modèle.JoursTravaillés);
}
catch (Exception ex)
{
exception = ex;
}
// mistake?
if (exception != null)
{
// the error page is displayed
return PartialView("Erreurs", Static.GetErreursForException(exception));
}
// create a simulation and place it in the session
session.Simulation = ...
// the salary sheet is displayed
return PartialView("Simulation", feuilleSalaire);
}
- Zeile 3: Die Aktion erhält das Sitzungsmodell als Parameter;
Aufgabe 1: Vervollständigen Sie den Aktionscode, Zeile 34
Aufgabe 2: Führen Sie gemäß der Vorgehensweise in Abschnitt 4.10 die erforderlichen Schritte durch, um sicherzustellen, dass der Parameter [SessionModel session] der Aktion vom Framework ordnungsgemäß initialisiert wird. Wenn nichts unternommen wird, hat dieser Parameter einen Null-Zeiger.
9.14. Schritt 8: Speichern einer Simulation
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. Beachten Sie, dass die berechnete Gehaltsabrechnung fiktiv ist.
9.14.2. Schreiben der Serveraktion [SaveSimulation]
Der Ajax-Link [Simulation speichern] ruft die Serveraktion [SaveSimulation] auf, deren Code zuvor wie folgt lautete:
[HttpPost]
public PartialViewResult EnregistrerSimulation()
{
return PartialView("Simulations");
}
Es entwickelt sich wie folgt:
// save a simulation
[HttpPost]
public PartialViewResult EnregistrerSimulation(SessionModel session)
{
// save the last simulation run in the session's simulation list
...
// increment the number of the next simulation in the session
...
// the list of simulations is displayed
...
}
- Zeile 1: Die Aktion [SaveSimulation] benötigt Zugriff auf die Sitzung. Deshalb wird das Sitzungsmodell als Parameter übergeben.
Aufgabe: Vervollständigen Sie die Serveraktion [SaveSimulation].
9.14.3. Erstellen der Teilansicht [Simulations.cshtml]
Die vorherige Aktion [SaveSimulation] zeigt die Teilansicht [Simulations.cshtml] an, deren Modell die Liste der vom Benutzer durchgeführten Simulationen ist. Der 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 für die Teilansicht [Simulations.cshtml]. Verwenden Sie eine HTML-Tabelle, um die Simulationen anzuzeigen. Sie können sich an den Beispielen in Abschnitt 5.4 orientieren.
Hinweis: Der Link [remove] für jede Simulation in der HTML-Tabelle ist ein JavaScript-Link im folgenden Format:
wobei N die Simulationsnummer ist.
Aufgabe 2: Testen Sie Ihre Anwendung, indem Sie Simulationen ausführen. Wiederholen Sie dazu die folgende Abfolge: 1) Laden Sie die Anwendungsseite durch Drücken von [F5] neu, 2) führen Sie eine Simulation aus, 3) speichern Sie sie. Die Simulationen werden in der Sitzung gesammelt, was sich in der Ansicht [Simulations.cshtml] widerspiegeln sollte.
Aufgabe 3: Verbessern Sie die partielle Ansicht [Simulations.cshtml] so, dass sich die Farben der Zeilen in der HTML-Tabelle abwechseln.

Weisen Sie den Zeilen <tr> der HTML-Tabelle abwechselnd die CSS-Klassen [even] und [odd] zu, die im Stylesheet [/Content/Site.css] definiert sind:
.impair {
background-color: beige;
}
.pair {
background-color: lightsteelblue;
}
9.15. Schritt 9: Zurück zum Eingabeformular
9.15.1. Das Problem
Sobald wir die Liste der Simulationen erhalten haben, können wir zum Eingabeformular zurückkehren, was uns eine Weile nicht möglich war:


9.15.2. Schreiben der Serveraktion [Form]
Der Ajax-Link [Zurück zum Simulationsformular] ruft die Serveraktion [Form] auf, deren Code zuvor wie folgt lautete:
[HttpPost]
public PartialViewResult Formulaire()
{
return PartialView("Formulaire");
}
Die angezeigte Teilansicht [Form] erwartet ein [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 [Zurück zum Simulationsformular] nicht mehr.
Aufgabe: Schreiben Sie die neue Version der Serveraktion [Formular] (2 Zeilen umschreiben) und führen Sie anschließend die Tests durch.
9.15.3. Ändern der JavaScript-Funktion [returnToForm]
Mit der zuvor vorgenommenen Änderung können wir nun zum Formular zurückkehren, doch dabei tritt ein Problem auf:
![]() |
- In [1] kehren wir zum Eingabeformular zurück;
- in [2] führen wir eine Simulation mit falschen Eingaben durch. Wir stellen dann fest, dass die clientseitigen Validatoren nicht mehr funktionieren. Hier wurde der Server aufgerufen und gab dank der in Abschnitt 9.12.4 geleisteten Arbeit eine Fehlerseite zurück.
Dieses Problem wurde in Abschnitt 7.6.7 identifiziert und behoben.
Aufgabe: Korrigieren Sie gemäß der Vorgehensweise in Abschnitt 7.6.7 die JavaScript-Funktion [returnToForm] und führen Sie anschließend Tests durch, um zu überprüfen, ob die clientseitigen Validatoren wieder funktionieren.
9.16. Schritt 10: Siehe die Liste der Simulationen
9.16.1. Das Problem
Wenn Sie mit dem Simulationsformular arbeiten, können Sie die Liste der von Ihnen durchgeführten Simulationen anzeigen:


9.16.2. Schreiben der Serveraktion [ViewSimulations]
Der Ajax-Link [View Simulations] ruft die Serveraktion [ViewSimulations] auf, deren Code zuvor wie folgt lautete:
// see simulations
[HttpPost]
public PartialViewResult VoirSimulations()
{
return PartialView("Simulations");
}
Die angezeigte partielle Ansicht [Simulations] erwartet ein Modell vom Typ [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>
...
}
Deshalb funktionierte der Link [Simulationen anzeigen] nicht mehr.
Aufgabe: Schreibe die neue Version der Serveraktion [ViewSimulations] (2 Zeilen umschreiben) und führe anschließend die Tests durch.
9.17. Schritt 11: Beenden Sie die Sitzung
9.17.1. Das Problem
Sie können die Sitzung des Benutzers jederzeit über den Link [Ajax] [Sitzung beenden] beenden. Dadurch wird die aktuelle Sitzung beendet und eine neue gestartet. Außerdem kehren Sie zur Formularansicht zurück:
![]() |
![]() |
- In [1] haben wir zwei Simulationen durchgeführt und dann die Sitzung beendet;
- in [2] kehrten wir zum Eingabeformular zurück. Wir möchten die Simulationen sehen;
- in [3] ist die Liste der Simulationen aufgrund des Sitzungswechsels nun leer.
9.17.2. Schreiben der Serveraktion [EndSession]
Der Ajax-Link [End Session] ruft die Serveraktion [EndSession] auf, deren Code zuvor wie folgt lautete:
// end session
[HttpPost]
public PartialViewResult TerminerSession()
{
return PartialView("Formulaire");
}
Die angezeigte Teilansicht [Form] erwartet ein [IndexModel] (Zeile 1 unten):
@model Pam.Web.Models.IndexModel
@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
<div id="simulation" />
Deshalb funktionierte der Link [Sitzung beenden] nicht mehr.
Aufgabe: Schreiben Sie die neue Version der Serveraktion [EndSession] (2 Zeilen umschreiben) und führen Sie anschließend die Tests aus.
Hinweis: Um die Sitzung in der Aktion zu beenden, schreiben Sie:
9.17.3. Änderung der JavaScript-Funktion [terminerSession]
Mit der zuvor vorgenommenen Änderung können wir nun zum Formular zurückkehren, doch dann tritt eine Anomalie auf – diejenige, die zuvor in Abschnitt 9.15.3 beschrieben wurde.
Aufgabe: Korrigieren Sie gemäß der Vorgehensweise aus Abschnitt 9.15.3 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 erstellt wurde, kann sie über den JavaScript-Link [Clear Simulation] gelöscht werden:


9.18.2. Schreiben der clientseitigen Aktion [clearSimulation]
Die JavaScript-Funktion [clearSimulation] enthält derzeit folgenden Code:
function effacerSimulation() {
// delete form entries
// ...
// hide the simulation if it exists
$("#simulation").hide();
// menu
setMenu([lnkFaireSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
Aufgabe: Vervollständigen Sie diesen Code. Sie können das Beispiel in Abschnitt 7.6.6 als Vorlage verwenden
9.19. Schritt 13: Eine Simulation entfernen
9.19.1. Das Problem
Auf der Seite „Simulationen“ können Sie bestimmte Simulationen über den JavaScript-Link [remove] löschen:


9.19.2. Schreiben der Client-Aktion [removeSimulation]
Die [remove]-Links haben das folgende HTML-Format:
wobei N die Simulationsnummer ist.
Aufgabe: Schreiben Sie gemäß der Vorgehensweise in Abschnitt 9.9.3 die JS-Funktion [removeSimulation]. Diese Funktion sendet eine Ajax-Anfrage vom Typ POST an die Aktion [/Pam/RemoveSimulation]. Sie übermittelt die Daten N in der Form num=N.
Hinweis: Die JS-Funktion [retirerSimulation] ähnelt den anderen JS-Funktionen, die Sie geschrieben haben und die einen Ajax-Aufruf an den Server senden. Der einzige Unterschied besteht hier darin, dass ein Wert per POST gesendet wird, der nicht in einem Formular enthalten ist. Wir wissen, dass die gesendeten Werte zu einer Zeichenkette im folgenden Format zusammengefasst werden:
Die JS-Funktion [removeSimulation] hat daher folgende Form:
function retirerSimulation(N) {
// make a manual Ajax call
$.ajax({
url: '/Pam/RetirerSimulation',
...
data:"num="+N,
...
});
// menu
setMenu([lnkRetourFormulaire, lnkTerminerSession]);
}
- Zeile 6: Die Eigenschaft [data] eines jQuery-Ajax-Aufrufs stellt die an den Server gesendete Zeichenkette dar.
9.19.3. Schreiben der Serveraktion [RemoveSimulation]
Die Serveraktion [RemoveSimulation]:
- erhält einen gesendeten Parameter namens [num], der die Nummer einer Simulation angibt;
- 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.
Aufgabe: Schreiben Sie die Serveraktion [RemoveSimulation]. Lesen Sie Abschnitt 4.1, um zu erfahren, wie Sie den übermittelten Parameter namens [num] abrufen können.
9.20. Schritt 14: Verbesserung der Initialisierungsmethode der Anwendung
Unsere Webanwendung ist fertiggestellt. Sie ist mit einer simulierten [business]-Klasse funktionsfähig. Sehen wir uns die von uns entwickelte Architektur noch einmal an:
![]() |
Bevor wir zur eigentlichen Implementierung der [business]-Schicht übergehen, müssen noch einige Details geklärt werden. Dies geschieht in der Anwendungsinitialisierungsmethode: der Methode [Application_Start] in [Global.asax]:
![]() |
Die [Application_Start]-Methode in [Global.asax] wird nur einmal ausgeführt, wenn die Anwendung startet. Hier kann die Konfigurationsdatei [Web.config] genutzt werden. Vorläufig sieht unsere [Application_Start]-Methode wie folgt aus:
// application
protected void Application_Start()
{
// ----------Auto-generated
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// -------------------------------------------------------------------
// ---------- specific configuration
// -------------------------------------------------------------------
// application scope data
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
// instantiation layer [business]
application.PamMetier = new PamMetier();
...
// model binders
...
}
In Zeile 17 wird die Geschäftsschicht mit dem new-Operator instanziiert. Außerdem wird das Anwendungsmodell wie folgt definiert:
public class ApplicationModel
{
// --- application scope data ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
}
In Zeile 5 oben sehen wir, 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, die [IPamMetier] implementiert, fest codiert. Sollte die [business]-Schicht also mit einer neuen Klasse implementiert werden, die [IPamMetier] implementiert, müsste diese Zeile geändert werden. Das ist kein großes Problem, lässt sich aber vermeiden. Die Definition der Klasse, die die [IPamMetier]-Schnittstelle implementiert, kann in eine Konfigurationsdatei verschoben werden. Um die Implementierung zu ändern, ändern wir dann den Inhalt dieser Konfigurationsdatei. Der .NET-Code muss nicht geändert werden.
Hier verwenden wir den Dependency-Injection-Container [Spring.net]. Es gibt andere .NET-Frameworks, die dasselbe leisten können, vielleicht sogar besser und einfacher.
Die Projektarchitektur entwickelt sich wie folgt:
![]() |
- In [A] fordert die Initialisierungsmethode der [ASP.NET MVC]-Schicht eine Referenz auf die simulierte [Business]-Schicht von [Spring.net] an;
- in [B] erstellt [Spring.net] die simulierte [Business]-Schicht, indem es anhand seiner Konfigurationsdatei ermittelt, welche Klasse instanziiert werden soll;
- In [C] gibt [Spring.net] die Referenz auf die simulierte [Business]-Schicht an die [ASP.NET MVC]-Schicht zurück.
Beachten Sie, dass von [Spring.net] verwaltete Objekte standardmäßig Singletons sind: Es gibt jeweils nur eine Instanz. Wenn also später in unserem Beispiel der Code erneut eine Referenz auf die simulierte [Business]-Schicht von [Spring.net] anfordert, gibt [Spring.net] einfach die Referenz auf das ursprünglich erstellte Objekt zurück.
9.20.1. Hinzufügen von [Spring]-Referenzen zum Webprojekt
Wir werden [Spring.net] verwenden. Dieses Framework liegt in Form einer DLL vor, die zu den Referenzen des Projekts hinzugefügt werden muss. So geht’s:
![]() |
Klicken Sie in [1] mit der rechten Maustaste auf den Zweig [Referenzen] des Projekts und wählen Sie die Option [NuGet-Pakete verwalten]. Eine Internetverbindung ist erforderlich. Fahren Sie dann wie zuvor bei der JQuery-Bibliothek [Globalize] fort. Suchen Sie nach dem Stichwort [Spring.core] und installieren Sie dieses Paket. Die Installation umfasst zwei DLLs: [Spring.core] [2] und [Common.Logging] [3]. In den folgenden Beispielen wurde Spring Version 1.3.2 verwendet.
Hinweis: Falls Sie keine Internetverbindung haben, finden Sie diese DLLs im Ordner [lib] der Materialien zu dieser Fallstudie.
9.20.2. Konfigurieren von [web.config]
Die Definition der Implementierungsklasse für die Schnittstelle [IPamMetier] ist in der Datei [web.config] festgelegt.
<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 configuration -->
<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 <configSections>-Tag in der Datei und fügen Sie die Zeilen 4–7 darin ein;
- Zeile 4: Das Attribut [name="spring"] liefert Informationen über den Abschnitt [spring] in den Zeilen 10–17;
- Zeile 5: Definiert die Klasse [Spring.Context.Support.DefaultSectionHandler] in der DLL [Spring.Core] als diejenige, die den Abschnitt [objects] in den Zeilen 14–16 verarbeiten kann;
- Zeile 6: Definiert die Klasse [Spring.Context.Support.ContextHandler] in der DLL [Spring.Core] als diejenige, die den Abschnitt [context] in den Zeilen 11–13 verarbeiten kann;
- Zeilen 11–13: Dieser Abschnitt enthält die Angabe [<resource uri="config://spring/objects" />], die darauf hinweist, dass sich die Spring-Objekte in der Konfigurationsdatei im Abschnitt [/spring/objects] befinden, d. h. in den Zeilen 14–16;
- Zeilen 14–16: Das [objects]-Tag führt die Spring-Objekte ein;
- Zeile 15: definiert ein Objekt, das durch [id="pammetier"] identifiziert wird und eine Instanz der Klasse [Pam.Metier.Service.PamMetier] ist, die sich in der DLL [pam-metier-simule] befindet. Achten Sie darauf, hier keinen Fehler zu machen. Für das Attribut [id] können Sie einen beliebigen Namen verwenden. Sie werden diesen Bezeichner in [Global.asax] verwenden. Die Klasse [Pam.Metier.Service.PamMetier] ist die unserer simulierten [Business]-Schicht. Sie müssen zu ihrer Definition zurückkehren, um ihren vollständigen Namen zu finden:
namespace Pam.Metier.Service
{
public class PamMetier : IPamMetier
{
...
Für die DLL [pam-metier-simule] müssen Sie die Eigenschaften des C#-Projekts [pam-metier-simule] überprüfen:
![]() |
Sie müssen den in [1] angegebenen Namen verwenden.
9.20.3. Änderung von [Application_Start]
Die Methode [Application_Start] ändert sich wie folgt:
using Spring.Context.Support;
// application
protected void Application_Start()
{
// ----------Auto-generated
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// -------------------------------------------------------------------
// ---------- specific configuration
// -------------------------------------------------------------------
// application scope data
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
// instantiation layer [business]
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
...
// model binders
...
}
- Zeile 19: Wir verwenden die Spring-Klasse [ContextRegistry], die in der Lage ist, die Datei [web.config] zu verarbeiten. Dazu müssen wir den Namespace aus Zeile 1 importieren. Die statische Methode [GetContext] ruft den Inhalt der [context]-Tags ab, die angeben, wo sich die Spring-Objekte befinden. Die statische Methode [GetObject] ermöglicht es uns dann, ein bestimmtes Objekt abzurufen, das durch sein id-Attribut identifiziert wird. Beachten Sie, dass der Name der Klasse, die die Schnittstelle [IPamMetier] implementiert, nicht mehr fest im Code hinterlegt ist. Er befindet sich nun in der Datei [web.config].
Nachdem Sie all diese Änderungen vorgenommen haben, testen Sie Ihre Anwendung. Sie sollte nun funktionieren.
9.20.4. Behandlung eines Anwendungsinitialisierungsfehlers
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 wir einen Fehler beim Namen des zu instanziierenden Objekts gemacht haben;
- ein weiterer ist, dass die Instanziierung der [Business]-Schicht fehlschlägt. Dies kann bei unserer simulierten [Business]-Schicht nicht der Fall sein, könnte aber bei unserer echten [Business]-Schicht auftreten, die mit einer Datenbank verbunden ist. Das DBMS läuft möglicherweise nicht, die Informationen über die zu verwaltende Datenbank sind möglicherweise falsch usw.
Wir werden alle Ausnahmen in einem try/catch-Block behandeln. Der Code entwickelt sich wie folgt:
// application
protected void Application_Start()
{
// ----------Auto-generated
...
// -------------------------------------------------------------------
// ---------- specific configuration
// -------------------------------------------------------------------
// application scope data
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
application.InitException = null;
try
{
// instantiation layer [business]
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
catch (Exception ex)
{
application.InitException = ex;
}
//if no error
if (application.InitException == null)
{
....
}
// model binders
...
}
- In Zeile 12 fügen wir im Anwendungsmodell eine neue Eigenschaft namens [InitException] ein:
public class ApplicationModel
{
// --- application scope data ---
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 während der Initialisierung der Anwendung auftreten kann;
- Zeilen 13–21 von [Application_Start]: Die Instanziierung der [Business]-Schicht erfolgt nun innerhalb eines try/catch-Blocks;
- Zeile 20: Die Ausnahme wird abgefangen;
- Zeilen 23–26: Wenn kein Fehler aufgetreten ist, wird der vorherige Code ausgeführt;
- Zeile 28: Die [ModelBinders] werden unabhängig davon erstellt, ob ein Fehler aufgetreten ist oder nicht. Dies ist wichtig. Wir möchten sicherstellen, dass das Anwendungsmodell [ApplicationModel] vom Framework ordnungsgemäß gebunden wird.
Wir wissen, dass beim Start der Anwendung die Serveraktion [Index] ausgeführt wird. Derzeit sieht diese wie folgt aus:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
}
Zeile 2: Die Aktion [Index] erhält das Anwendungsmodell. Sie kann daher feststellen, ob die Initialisierung erfolgreich war oder nicht, und eine Fehlerseite anzeigen, falls die Initialisierung in irgendeiner Weise fehlgeschlagen ist. Wir ändern den Code wie folgt:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
// initialization error?
if (application.InitException != null)
{
// error page without menu
return View("InitFailed",Static.GetErreursForException(application.InitException));
}
// no error
return View(new IndexModel() { Application = application });
}
Zeile 8: Im Falle eines Initialisierungsfehlers zeigen wir die Ansicht [InitFailed.cshtml] an, wobei wir die Liste der Fehlermeldungen aus der während der Initialisierung aufgetretenen Ausnahme als Modell verwenden. Die Methode [Static.GetErrorsForException] 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 Ansichtsvorlage ist eine Liste von Fehlermeldungen. Diese werden in einer HTML-Liste in den Zeilen 24–29 angezeigt;
- Zeile 3: Diese Ansicht verwendet nicht die Master-Seite [_Layout.cshtml]. Der Grund dafür ist, dass wir das von diesem Dokument bereitgestellte Menü nicht verwenden möchten. Wir erstellen daher eine vollständige HTML-Seite (Zeilen 5–23).
Um dies zu testen, ändern Sie einfach die Instanziierung der [business]-Schicht in [Application_Start] wie folgt:
try
{
// instantiation layer [business]
application.PamMetier = ContextRegistry.GetContext().GetObject("xx") as IPamMetier;
}
catch (Exception ex)
{
application.InitException = ex;
}
Zeile 4: Wir suchen nach einem Objekt, das in den Spring-Objekten nicht vorhanden ist.
Wenn wir diese Änderungen speichern und die Anwendung ausführen, erhalten wir die folgende Seite:

Wir erhalten eine Fehlerseite ohne Menü. Der Benutzer kann nichts anderes tun, als den Fehler zu bestätigen. Genau das wollten wir erreichen.
9.21. Wo stehen wir jetzt?
Wir haben nun eine funktionierende Webanwendung, die mit einer simulierten Geschäftsschicht arbeitet. Ihre Architektur sieht wie folgt aus:
![]() |
Die [ASP.NET MVC]-Schicht arbeitet über die [IPamMetier]-Schnittstelle mit der simulierten Geschäftsschicht zusammen. Wenn wir diese simulierte Geschäftsschicht durch eine echte Geschäftsschicht ersetzen, die diese Schnittstelle implementiert, müssen wir den Code der Webschicht nicht ändern. Dank [Spring.net] müssen wir lediglich die Implementierungsklasse der [IPamMetier]-Schnittstelle in [web.config] ändern. Wir verfolgen diesen Ansatz weiter.
Die neue Architektur wird wie folgt aussehen:
![]() |
Wir werden nacheinander Folgendes beschreiben:
- die mit dem DBMS verbundene [EF5]-Schicht. Sie wird mit Entity Framework 5 (EF5) implementiert;
- die [DAO]-Schicht, die den Datenzugriff über die [EF5]-Schicht verwaltet. Dadurch ist sie vom DBMS unabhängig. Diese Schicht bearbeitet lediglich die Anwendungsentitäten [Employee, Contributions, Allowances];
- die [Business]-Schicht, die die Gehaltsberechnung implementiert.
Die neue Architektur ist diejenige, die ganz am Anfang dieses Dokuments in Abschnitt 1.1 vorgestellt wurde und die wir nun zusammenfassen:
![]() |
- Die [Web]-Schicht ist die Schicht, die mit dem Benutzer der Webanwendung in Kontakt steht. Der Benutzer interagiert mit der Webanwendung über Webseiten, die in einem Browser angezeigt werden. ASP.NET MVC befindet sich in dieser Schicht und ausschließlich in dieser Schicht;
- Die [Business]-Schicht implementiert die Geschäftsregeln der Anwendung, wie beispielsweise die Berechnung eines Gehalts oder einer Rechnung. Diese Schicht nutzt Daten vom Benutzer über die [Web]-Schicht und aus dem DBMS über die [DAO]-Schicht;
- Die [DAO]-Schicht (Data Access Objects), die [ORM]-Schicht (Object Relational Mapper) und der ADO.NET-Konnektor verwalten den Zugriff auf DBMS-Daten. Die [ORM]-Schicht fungiert als Brücke zwischen den von der [DAO]-Schicht verwalteten Objekten und den Zeilen und Spalten der Daten in einer relationalen Datenbank. In der .NET-Welt werden üblicherweise zwei ORMs verwendet: NHibernate (http://sourceforge.net/projects/nhibernate/) und Entity Framework (http://msdn.microsoft.com/en-us/data/ef.aspx);
- Die Integration der Schichten kann mithilfe eines Dependency-Injection-Containers wie Spring (http://www.springframework.net/) erreicht werden;
Die Schichten [business], [DAO] und [EF5] werden mithilfe von C#-Projekten implementiert. Von nun an arbeiten wir mit Visual Studio Express 2012 für Desktop.
9.22. Schritt 15: Einrichten der Entity Framework 5-Schicht
![]() |
Beim Erstellen der [EF5]-Schicht geht es weniger um Programmierung als vielmehr um Konfiguration. Um zu verstehen, wie diese Schicht geschrieben wird, lesen Sie das Dokument [Einführung in Entity Framework 5 Code First], das unter der URL [http://tahe.developpez.com/dotnet/ef5cf-02/] verfügbar ist. Es handelt sich um ein recht umfangreiches Dokument. Die Grundlagen werden in den ersten vier Kapiteln behandelt. Die spezifischen Abschnitte, die Sie lesen sollten, werden angegeben. Wenn wir auf dieses Dokument verweisen, verwenden wir die Notation [refEF5].
Außerdem werden wir gelegentlich auf C#-Konzepte zurückgreifen müssen. In diesen Fällen verweisen wir unter der Bezeichnung [refC#] auf den Kurs [Einführung in die Sprache C#], der unter der URL [http://tahe.developpez.com/dotnet/csharp/] verfügbar ist.
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.
Sehen wir uns das Datenbankschema noch einmal an. Es besteht aus drei Tabellen:

Zwischen der Spalte EMPLOYEES(INDEMNITY_ID) und der Spalte INDEMNITIES(ID) besteht eine Fremdschlüsselbeziehung. Ein Teil der Struktur dieser Datenbank wird durch ihre Verwendung mit EF5 vorgegeben.
Das SQL-Skript zum Erstellen der Datenbank lautet wie folgt:
-- phpMyAdmin SQL Dump
-- version 3.5.1
-- http://www.phpmyadmin.net
--
-- Customer: localhost
-- Generated on: Mon November 04, 2013 at 09:34 am
-- Server version: 5.5.24-log
-- Version of 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 */;
--
-- Database: `dbpam_ef5`
--
-- --------------------------------------------------------
--
-- Structure of the `contributions` table
--
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 ;
--
-- Contents of the `contributions` table
--
INSERT INTO `cotisations` (`ID`, `SECU`, `RETRAITE`, `CSGD`, `CSGRDS`, `VERSIONING`) VALUES
(11, 9.39, 7.88, 6.15, 3.49, 1);
--
-- Contribution triggers
--
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 ;
-- --------------------------------------------------------
--
-- Structure of the `employees` table
--
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 ;
--
-- Contents of the `employees` table
--
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);
--
-- Used' triggers
--
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 ;
-- --------------------------------------------------------
--
-- Structure of the `indemnities` table
--
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 ;
--
-- Contents of the `indemnities` table
--
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);
--
-- Compensation triggers
--
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 ;
--
-- Constraints for exported tables
--
--
-- Constraints for the `employees` table
--
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 folgende Punkte:
- Zeilen 30, 73, 122: Die Primärschlüssel der Tabellen befinden sich im [AUTO_INCREMENT]-Modus. Diese werden von MySQL verwaltet, nicht von EF5;
- Zeile 83: Die SS-Nummer unterliegt einer Eindeutigkeitsbeschränkung;
- Zeile 130: Die Mitarbeiter-ID unterliegt einer Eindeutigkeitsbeschränkung;
- Zeilen 168–169: Der Fremdschlüssel von der Tabelle [employees] zur Tabelle [benefits];
- Zeile 49: Ein Trigger ist ein vom DBMS eingebettetes SQL-Skript, das 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 der Spalte [VERSIONING] um eins;
- Zeilen 59–62: Der Trigger [START_VERSIONING_COTISATIONS] wird ausgelöst, bevor eine neue Zeile in die Tabelle [cotisations] eingefügt wird. Anschließend initialisiert er die Spalte [VERSIONING] auf 1;
- Letztendlich wird die Spalte [VERSIONING] auf 1 gesetzt, wenn eine Zeile in der Tabelle [contributions] angelegt wird, und anschließend bei jeder Änderung an dieser Zeile um 1 erhöht. Dieser Mechanismus ermöglicht es EF5, den gleichzeitigen Zugriff auf eine Zeile in der Tabelle [contributions] wie folgt zu verwalten:
- Ein Prozess P1 liest zum Zeitpunkt T1 eine Zeile L aus der Tabelle [contributions]. Die Zeile hat einen Wert V1 in der Spalte [VERSIONING];
- Ein Prozess P2 liest zum Zeitpunkt T2 dieselbe Zeile L aus der Tabelle [contributions]. Die Zeile hat einen Wert V1 in der Spalte [VERSIONING], da Prozess P1 seine Änderung noch nicht festgeschrieben hat;
- Prozess P1 ändert die Zeile L und bestätigt die Änderung. Die Spalte [VERSIONING] der Zeile L ändert sich daraufhin aufgrund des Triggers [INCR_VERSIONING_COTISATIONS] auf V1+1;
- Prozess P2 tut anschließend dasselbe. EF5 löst daraufhin eine Ausnahme aus, da Prozess P2 eine Zeile mit einer [VERSIONING]-Spalte mit dem Wert V1 besitzt, der sich von dem in der Datenbank gefundenen Wert V1+1 unterscheidet. Eine Zeile kann nur geändert werden, wenn sie denselben [VERSIONING]-Wert wie in der Datenbank aufweist.
Dies wird als optimistische Parallelitätskontrolle bezeichnet. Bei EF5 muss ein Feld, das diese Rolle übernimmt, die Annotation [ConcurrencyCheck] tragen.
- Ein ähnlicher Mechanismus wird für die Tabelle [employes] (Zeilen 98–113) und die Tabelle [indemnites] (Zeilen 144–159) erstellt.
Aufgabe: Erstellen Sie die MySQL-Datenbank [dbpam_ef5] mithilfe des vorherigen SQL-Skripts. Die Datenbank [dbpam_ef5] muss zuvor erstellt werden, da das Skript sie nicht anlegt. Anschließend führen wir das SQL-Skript auf dieser Datenbank aus.
9.22.2. Das Visual Studio-Projekt
Mit Visual Studio Express 2012 for Desktop laden wir die Lösung [pam-td], die beim Erstellen der [web]-Schicht verwendet wurde:
![]() |
- in [1] kann VS 2012 Express for Desktop das Webprojekt [pam-web-01] nicht laden. Dies ist normal und stellt kein Problem dar;
- in [2] fügen wir der [pam-td]-Lösung ein neues Projekt hinzu;
![]() |
- in [3] ist das Projekt vom Typ [console] und heißt [4] [pam-ef5];
- in [5] wird das Projekt erstellt. Sein Name ist nicht fettgedruckt, es handelt sich also nicht um das Startprojekt der Lösung;
![]() |
- In [6] und [7] legen wir das neue Projekt als Startprojekt fest.
9.22.3. Hinzufügen der erforderlichen Verweise zum Projekt
Betrachten wir das Projekt einmal als Ganzes:
![]() |
Unser Projekt benötigt eine Reihe von DLLs:
- die Entity Framework 5-DLL;
- die ADO.NET-Connector-DLL für das MySQL-DBMS.
In Abschnitt 4.2 von [refEF5] wird erläutert, wie diese DLLs mit dem [NuGet]-Tool installiert werden. Derzeit (Nov. 2013) ist die verfügbare Version von Entity Framework die Version 6 (EF6). Leider scheint der über [NuGet] verfügbare MySQL-ADO.NET-Connector (Stand: Nov. 2013) nicht mit EF6 kompatibel zu sein. Daher haben wir die EF5-DLL und die anderen für das [pam-ef5]-Projekt erforderlichen DLLs in einem [lib]-Ordner abgelegt [1]
![]() |
Wir haben weitere DLLs im Ordner [lib] abgelegt. Diese werden wir später verwenden. In [2] fügen wir diese neuen DLLs dem Projekt hinzu.
![]() |
- Navigieren Sie in [3] im Dateisystem zum Ordner [lib];
- Wählen Sie in [4] die drei DLLs aus und klicken Sie dann zweimal auf „OK“;
- In [5] wurden die drei DLLs zu den Projektreferenzen hinzugefügt.
Wir benötigen eine weitere DLL. Diese befindet sich im .NET Framework des Computers.
![]() |
- Fügen Sie in [1] eine neue Referenz zum Projekt hinzu;
![]() |
- Wählen Sie in [2] [Assemblies] aus;
- Geben Sie in [3] [system.component] ein;
- Wählen Sie in [4] 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. Entity Framework-Entitäten
Entity Framework-Entitäten sind Klassen, die die Zeilen der verschiedenen Datenbanktabellen kapseln. Sehen wir uns diese einmal an:

In der [Web]-Schicht haben wir die Entitäten [Employee, Contributions, Benefits] verwendet (siehe Abschnitt 9.7.3, Seite 211). Sie waren keine exakten Abbildungen der Tabellen. Daher wurden die Spalten [ID, VERSIONING] ignoriert. Hier ist das nicht der Fall, da sie vom EF5-ORM verwendet werden. Wir werden ihnen daher die fehlenden Eigenschaften hinzufügen. Wir erstellen diese Entitäten in einem [Models]-Ordner innerhalb 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; }
// signature
public override string ToString()
{
return string.Format("Cotisations[{0},{1},{2},{3}, {4}, {5}]", Id, Versioning, CsgRds, Csgd, Secu, Retraite);
}
}
}
- Zeile 3: Der Namespace wurde an das neue Projekt angepasst;
- die Eigenschaften in den Zeilen 7 und 12 wurden hinzugefügt, um die Struktur der Tabelle [contributions] widerzuspiegeln;
- Zeile 17: Die [ToString]-Methode 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; }
// signature
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 [ToString]-Methode zeigt nun die beiden neuen Felder an.
Klasse [Employee]
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; }
// signature
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 Namespace wurde an das neue Projekt angepasst;
- die Eigenschaften in den Zeilen 8 und 16 wurden hinzugefügt, um die Struktur der Tabelle [employees] widerzuspiegeln;
- Zeile 21: Die [ToString]-Methode zeigt nun die beiden neuen Felder an.
Damit sie vom EF5-ORM verwendet werden können, müssen die Eigenschaften dieser Klassen mit Annotationen versehen werden.
Aufgabe: Fügen Sie anhand von Abschnitt 3.4 [Erstellen der Datenbank aus Entitäten] aus [refEF5] die von EF5 geforderten Annotationen zu den Entitäten [Employee, Contributions, Benefits] hinzu.
Tipps:
- Sie müssen lediglich Annotationen erstellen. Befolgen Sie nicht den Abschnitt [Datenbankerstellung] des genannten Absatzes;
- Befolgen Sie für die Annotation [Table] das MySQL-Beispiel in Abschnitt 4.2 von [refEF5];
- Befolgen Sie für die Annotation [ConcurrencyCheck] auf der Eigenschaft [Versioning] das Oracle-Beispiel in Abschnitt 5.2 von [refEF5];
- Befolgen Sie für den Fremdschlüssel, den die Tabelle [employes] auf die Tabelle [indemnités] hat, Beispiel 3.4.2 aus [refEF5]. Sie fügen somit der Entität [Employe] eine neue Eigenschaft hinzu:
public int IndemniteId { get; set; }
, deren Wert dem der Spalte [INDEMNITES_ID] in der Tabelle [employes] entspricht. Sie wenden Fremdschlüssel-Annotationen auf die Eigenschaften [IndemniteId] und [Indemnites] der Entität [Employe] an. Befolgen Sie dazu Beispiel 3.4.2 in [refEF5];
- Sie werden die umgekehrten Beziehungen der Fremdschlüssel nicht verwalten;
- diese Aufgabe erfordert etwas Lektüre von [refEF5].
9.22.5. Konfigurieren des EF5-ORM
Betrachten wir das Projekt im Kontext:
![]() |
Die [EF5]-Schicht greift über den [ADO.NET]-Konnektor für das MySQL-DBMS auf die Datenbank zu. Für den Zugriff auf diese Datenbank sind bestimmte Informationen erforderlich. Diese Informationen befinden sich an verschiedenen Stellen im Projekt.
Zunächst müssen wir den Datenbankkontext erstellen. Dieser Kontext ist eine Klasse, die von der Systemklasse [System.Data.Entity.DbContext] abgeleitet ist. Er dient dazu, die Objektdarstellungen der Datenbanktabellen zu definieren. Wir werden diese Klasse zusammen mit den EF5-Entitäten im Ordner [Models] des Projekts 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 Objektdarstellungen der drei Datenbanktabellen. Ihr Typ ist [DbSet<Entity>], wobei [Entity] eine der soeben definierten Entity-Framework-Entitäten ist. Der Typ [DbSet] kann als Sammlung von Entitäten betrachtet werden. Er kann mit LINQ (Language-Integrated Query) abgefragt werden. Lesern, die mit LINQ nicht vertraut sind, wird empfohlen, Abschnitt 3.5.4 [LINQ lernen mit LINQPad] in [refEF5] zu lesen.
Wir werden die Klasse [DbPamContext] fortan als Persistenzkontext der Datenbank [dbpam_ef5] bezeichnen. Dies ist die Standardterminologie bei ORMs (Object-Relational Mappers). Dieser Persistenzkontext ist eine objektorientierte Darstellung der Datenbank. Wir beziehen uns auch auf die Synchronisation des Persistenzkontexts mit der Datenbank: Änderungen, Hinzufügungen und Löschungen, die am Persistenzkontext vorgenommen werden, werden in der Datenbank übernommen. Diese Synchronisation erfolgt zu bestimmten Zeitpunkten: beim Schließen des Persistenzkontexts, am Ende einer Transaktion oder vor einer SQL-SELECT-Abfrage an der Datenbank.
Informationen über das DBMS und die Datenbank werden in [App.config] gespeichert.
![]() |
Die erforderliche Konfiguration in [app.config] wird in den folgenden Abschnitten von [refEF5] erläutert:
- 3.4 für das SQL Server-DBMS. Hier werden die wichtigsten Grundsätze der EF5-Konfiguration dargelegt;
- 4.2 für das MySQL-DBMS.
Wir folgen diesem letzten Abschnitt 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>
<!-- configuration EF5 -->
<!-- database connection string [dbam_ef5] -->
<connectionStrings>
<add name="DbPamContext"
connectionString="Server=localhost;Database=dbpam_ef5;Uid=root;Pwd=;"
providerName="MySql.Data.MySqlClient" />
</connectionStrings>
<!-- the MySQL factory provider -->
<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 innerhalb des <configuration>-Tags in den Zeilen 2 und 22 eingefügt werden;
- Zeilen 8–12: definieren Datenbankverbindungszeichenfolgen, ein ADO.NET-Konzept (siehe Abschnitt 7.3.5 in [refC#]);
- Zeilen 9–11: definieren die Verbindungszeichenfolge zur MySQL-Datenbank [dbpam_ef5];
- Zeile 9: Der Name der Verbindungszeichenfolge. Hier können Sie nicht beliebige Angaben machen. Standardmäßig müssen Sie den Namen der Klasse eingeben, 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] müssen Sie [name="DbPamContext"] festlegen;
- Zeile 10: eine für das MySQL-DBMS spezifische Verbindungszeichenfolge:
- [Server=localhost]: IP-Adresse des Rechners, auf dem das DBMS gehostet wird. Hier ist es der lokale Rechner [localhost];
- [Database=dbpam_ef5;]: Name der Datenbank,
- [Uid=root;]: der Benutzername für die Verbindung zur Datenbank,
- [Pwd=;]: Passwort für diese Anmeldung. Hier kein Passwort;
- Zeile 10: [providerName="MySql.Data.MySqlClient"] ist der Name des zu verwendenden ADO.NET-Anbieters. Dieser Name entspricht dem Attribut [invariant] in Zeile 17. Sie können einen beliebigen Namen verwenden, solange Sie die vorstehende Regel befolgen und kein Anbieter mit demselben Invarianten bereits registriert ist;
- Zeilen 15–20: Definieren Sie eine ADO.NET-Provider-Factory. Das Konzept der [DbProviderFactory] ist mir nicht ganz klar. Dem Namen nach zu urteilen, scheint es sich um eine Klasse zu handeln, die den ADO.NET-Provider generieren kann, der den Zugriff auf das DBMS ermöglicht, in diesem Fall MySQL 5. Diese Zeilen werden in der Regel kopiert und eingefügt. Sie sind notwendig. Achten Sie auf das Attribut [Version=6.5.4.0] in Zeile 16. Diese Versionsnummer muss mit der Versionsnummer der DLL [MySql.Data] übereinstimmen, die Sie zu den Projektreferenzen hinzugefügt haben:
![]() |
- Zeile 16 ist wichtig. Da Sie nicht zwei Provider mit demselben Namen installieren können, entfernen Sie zunächst alle vorhandenen Provider, die möglicherweise denselben Namen haben wie der, den Sie in Zeile 17 installieren;
Das war’s. Beim ersten Mal ist es kompliziert und verwirrend, aber mit der Zeit wird es einfach, da es sich immer um denselben Vorgang handelt, den Sie wiederholen.
9.22.6. Testen der [EF5]-Schicht
Wir sind bereit, unsere [EF5]-Ebene zu testen. Dazu verwenden wir das vorhandene Programm [Program.cs]:
![]() |
Wir werden den Inhalt der Datenbank anzeigen. Wenn dies gelingt, ist das ein erster Hinweis darauf, dass unsere Konfiguration korrekt ist. Ein Code-Beispiel finden Sie in Abschnitt 3.5.3 von [refEF5]. Der Code für [Program.cs] sieht wie folgt aus:
using Pam.EF5.Entites;
using Pam.Models;
using System;
namespace Pam
{
class Program
{
static void Main(string[] args)
{
try
{
using (var context = new DbPamContext())
{
// display table contents
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: Alle Datenbankoperationen werden über diesen Datenbankkontext ausgeführt. Wir haben diesen Kontext mithilfe der Klasse [DbPamContext] implementiert. Wir bezeichnen ihn auch als Datenbank-Persistenzkontext;
- Zeilen 13, 31: Operationen am Persistenzkontext werden innerhalb eines [using]-Blocks ausgeführt. Der Persistenzkontext wird zu Beginn des [using]-Blocks geöffnet und automatisch geschlossen, wenn der Block endet. Das bedeutet, dass alle Änderungen, die innerhalb des [using]-Blocks am Persistenzkontext vorgenommen werden, bei Beendigung des Blocks in der Datenbank übernommen werden. Anschließend wird eine Reihe von SQL-Anweisungen innerhalb einer Transaktion an die Datenbank gesendet. Das bedeutet, dass bei einem Fehler einer SQL-Anweisung alle zuvor ausgeführten SQL-Anweisungen zurückgesetzt werden. Daraufhin löst EF5 eine Ausnahme aus;
- Zeile 17: Der Ausdruck [context.Employees] bezieht sich auf das Objektmodell der Tabelle [employees]. Erinnern Sie sich daran, dass [Employees] eine Eigenschaft des Persistenzkontexts [DbPamContext] ist:
public class DbPamContext : DbContext
{
public DbSet<Employe> Employes { get; set; }
public DbSet<Cotisations> Cotisations { get; set; }
public DbSet<Indemnites> Indemnites { get; set; }
}
- Zeile 17: Da die [foreach]-Schleife die Sammlung [context.Employees] durchläuft, werden alle Mitarbeiter aus der Datenbank in den Persistenzkontext geladen. EF5 gibt daher eine SQL-SELECT-Anweisung aus;
- Zeilen 17–20: Wir durchlaufen die Sammlung der Mitarbeiter, und in Zeile 19 verwenden wir die [ToString]-Methode der [Employee]-Klasse, um die Mitarbeiter auf der Konsole anzuzeigen;
- Zeilen 21–25: dasselbe gilt für die Sammlung „benefits“;
- Zeilen 27–30: dasselbe gilt für die Sammlung „contributions“.
Werfen wir noch einmal einen Blick auf die Definition der Entität [Employee]:
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; }
// signature
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 Sozialleistung.
Wird die Zulage eines Mitarbeiters ebenfalls in den Persistenzkontext aufgenommen, wenn dieser Mitarbeiter in den Persistenzkontext aufgenommen wird? Die Standardantwort lautet „nein“. Dies ist das Konzept des [Lazy Loading]. Entitäten, auf die innerhalb einer anderen Entität verwiesen wird, werden nicht zusammen mit dieser anderen Entität in den Persistenzkontext aufgenommen. Sie werden nur dann aufgenommen, wenn dies vom Code innerhalb eines offenen Persistenzkontexts angefordert wird. Wenn der Persistenzkontext geschlossen wird, wird eine Ausnahme ausgelöst.
Wenn also die [ToString]-Methode wie folgt auf die [Indemnites]-Eigenschaft verwiesen hätte:
// signature
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);
}
die folgende Operation in [Program.cs]:
foreach (Employe employe in context.Employes)
{
Console.WriteLine(employe);
}
hätte nicht nur die Mitarbeiter, sondern auch deren Sozialleistungen an den Persistenzkontext zurückgegeben, da in Zeile 3 die Methode [Employee.ToString] aufgerufen wird und diese auf die Entität [Benefits] verweist.
Die Ausführung von [Program.cs] liefert folgende Ergebnisse:
Was tun, wenn es nicht funktioniert? Sie haben ein Problem... Es gibt viele mögliche Fehlerquellen:
- Überprüfen Sie die EF5-Konfiguration (Abschnitt 9.22.5);
- Überprüfen Sie Ihre Entity Framework-Entitäten (Abschnitt 9.22.4).
9.22.7. [EF5] Layer-DLL
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 in Abschnitt 9.7.6 für die simulierte Geschäftsschicht beschrieben.
Aufgabe: Ändern Sie den Projekttyp [pam-ef5] in eine Klassenbibliothek und generieren Sie das Projekt anschließend neu.
9.23. Schritt 16: Implementierung der [DAO]-Schicht
9.23.1. Die Schnittstelle der [DAO]-Schicht
![]() |
Wie bei der simulierten [Business]-Schicht wird auch die [DAO]-Schicht über eine Schnittstelle zugänglich sein. Wie wird diese aussehen?
Schauen wir uns die [IPamMetier]-Schnittstelle der simulierten [Business]-Schicht an, die wir erstellt haben:
public interface IPamMetier {
// list of all employee identities
Employe[] GetAllIdentitesEmployes();
// ------- salary calculation
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
Zeile 3: Die Methode [GetAllEmployeeIDs] wird verwendet, um die Dropdown-Liste auf der Startseite zu füllen:
![]() |
Diese Mitarbeiter müssen aus der Datenbank abgerufen werden.
In Zeile 6 berechnet die Methode [GetSalary] die Gehaltsabrechnung für einen Mitarbeiter, dessen Sozialversicherungsnummer bekannt ist. Erinnern Sie sich an die Definition des Typs [PayStub]:
public class FeuilleSalaire
{
// automatic properties
public Employe Employe { get; set; }
public Cotisations Cotisations { get; set; }
public ElementsSalaire ElementsSalaire { get; set; }
}
Die Informationen in den Zeilen 5 und 6 stammen aus der Datenbank. Denken Sie daran, dass ein Mitarbeiter über eine Eigenschaft [Allowances] verfügt. Diese Informationen müssen ebenfalls abgerufen werden.
Wir könnten daher mit der folgenden Schnittstelle für die [DAO]-Schicht beginnen:
public interface IPamDao {
// list of all employee identities
Employe[] GetAllIdentitesEmployes();
// an individual employee with benefits
Employe GetEmploye(string ss);
// list of all contributions
Cotisations GetCotisations();
}
9.23.2. Das Visual Studio-Projekt
Aufgabe: Fügen Sie der Lösung [pam-td] ein neues [Konsolen]-Projekt mit dem Namen [pam-dao] hinzu. Legen Sie es als Startprojekt der Lösung fest.

9.23.3. Hinzufügen der erforderlichen Verweise zum Projekt
Betrachten wir das Projekt als Ganzes:
![]() |
Das [pam-dao]-Projekt benötigt eine Reihe von DLLs:
- alle, auf die das [pam-ef5]-Projekt verweist;
- diejenige aus dem [pam-ef5]-Projekt selbst.
Zusätzlich werden wir [Spring.net] verwenden, um die [DAO]-Schicht zu instanziieren. Dazu benötigen wir die DLLs [Spring.core] und [Common.Logging]. Diese DLLs befinden sich im Ordner [lib] der Fallstudienmaterialien.
Aufgabe: Fügen Sie diese verschiedenen Verweise zum [pam-dao]-Projekt hinzu.
![]() |
9.23.4. Implementierung der [DAO]-Schicht
![]() |
Oben ist die Klasse [PamException] diejenige, die in Abschnitt 9.7.4 definiert wurde. Wir ändern lediglich ihren Namespace (Zeile 1 unten):
namespace Pam.Dao.Entites
{
// exceptional class
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
{
// list of all employee identities
Employe[] GetAllIdentitesEmployes();
// an individual employee with benefits
Employe GetEmploye(string ss);
// list of all contributions
Cotisations GetCotisations();
}
}
Die Klasse [PamDaoEF5] implementiert diese Schnittstelle unter Verwendung des EF5-ORM. Der 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 fields
private Cotisations cotisations;
private Employe[] employes;
// Manufacturer
public PamDaoEF5()
{
// contribution
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 [contributions] und [employees] werden in den Eigenschaften der Zeilen 13–14 zwischengespeichert. Die Mitarbeiter enthalten keine Zulagen;
- Zeilen 17–28: Der Konstruktor initialisiert die Zeilen 13–14;
- Zeilen 43–52: Die Methode [GetEmployee] gibt einen Mitarbeiter zusammen mit seinen Zulagen zurück. Sie nimmt die Sozialversicherungsnummer des Mitarbeiters als Parameter entgegen. Wenn der Mitarbeiter nicht in der Datenbank vorhanden ist, gibt die Methode einen Null-Zeiger zurück.
Aufgabe: Vervollständigen Sie den Code für die Klasse [PamDaoEF5].
Orientieren Sie sich beim Konstruktor am Testcode für die [EF5]-Schicht, der in Abschnitt 9.22.6 vorgestellt wird. Orientieren Sie sich bei der [GetEmploye]-Methode am Beispiel in Abschnitt 3.5.7 [Eager und Lazy Loading] von [refEF5].
9.23.5. Konfigurieren der [DAO]-Schicht
Wie 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 einfach die Einstellungen aus der [App.config]-Datei der [EF5]-Schicht.
Unser Testprogramm wird [Spring.net] verwenden, um eine Referenz auf die [DAO]-Schicht zu erhalten.
Aufgabe 2: Ändern Sie anhand der Informationen aus Abschnitt 9.20.2 die Konfigurationsdatei [app.config] im Projekt [pam-dao] so, dass sie ein Spring-Objekt namens [pamdao] definiert, das mit der soeben erstellten Klasse [PamDaoEF5] verknüpft ist. Die Dateien [app.config] und [web.config] haben dieselbe Struktur. Stellen Sie sicher, dass das <configSections>-Tag das erste Tag ist, auf das nach dem Stamm-Tag <configuration> getroffen wird.
9.23.6. Testen der [DAO]-Schicht
Wir sind bereit, unsere [DAO]-Schicht zu testen. Dazu verwenden wir das vorhandene Programm [Program.cs]:
![]() |
Wir werden die verschiedenen Funktionen der [DAO]-Schicht-Schnittstelle testen. Der Code für [Program.cs] sieht wie folgt aus:
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
{
// layer instantiation [dao]
IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
// list of employee identities
foreach (Employe Employe in pamDao.GetAllIdentitesEmployes())
{
Console.WriteLine(Employe.ToString());
}
// an employee with benefits
Console.WriteLine("------------------------------------");
Employe e = pamDao.GetEmploye("254104940426058");
Console.WriteLine("employé= {0}, indemnités={1}", e, e.Indemnites);
Console.WriteLine("------------------------------------");
// an employee who doesn't exist
Employe employe = pamDao.GetEmploye("xx");
Console.WriteLine("Employé n° xx");
Console.WriteLine((employe == null ? "null" : employe.ToString()));
Console.WriteLine("------------------------------------");
// list of contributions
Cotisations cotisations = pamDao.GetCotisations();
Console.WriteLine(cotisations.ToString());
}
catch (Exception ex)
{
// exception display
Console.WriteLine(ex.ToString());
}
//break
Console.ReadLine();
}
}
}
- Zeile 15: Wir erhalten über [Spring.net] eine Referenz auf die [DAO]-Schicht.
Die Ergebnisse der Ausführung dieses Programms lauten wie folgt:
9.23.7. Layer-DLL [DAO]
Aufgabe: Konvertieren Sie den Projekttyp [pam-dao] in eine Klassenbibliothek und generieren Sie das Projekt anschließend neu (wiederholen Sie die Schritte in Abschnitt 9.22.7).
9.24. Schritt 17: Einrichten der [business]-Schicht
9.24.1. Die Schnittstelle der [business]-Schicht
![]() |
Die Schnittstelle der [business]-Schicht ist die [IPamMetier]-Schnittstelle der simulierten [business]-Schicht, die wir in Abschnitt 9.7.2 erstellt haben.
public interface IPamMetier {
// list of all employee identities
Employe[] GetAllIdentitesEmployes();
// ------- salary calculation
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 [Konsolen]-Projekt mit dem Namen [pam-metier] hinzu. Legen Sie es als Startprojekt der Lösung fest.

9.24.3. Hinzufügen der erforderlichen Verweise zum Projekt
Betrachten wir das Projekt als Ganzes:
![]() |
Das [pam-metier]-Projekt benötigt eine Reihe von DLLs:
- alle, auf die von den Projekten [pam-dao] und [pam-ef5] verwiesen wird;
- die DLLs aus den Projekten [pam-dao] und [pam-ef5] selbst.
Aufgabe: Fügen Sie diese verschiedenen Verweise zum [pam-metier]-Projekt hinzu.

9.24.4. Implementierung der [business]-Schicht
![]() |
Oben finden wir vier Elemente, die bereits in der simulierten [Business]-Schicht verwendet werden (siehe Abschnitt 9.7). Es kann zu Änderungen an den von diesen verschiedenen Klassen importierten Namespaces kommen. Behandeln Sie diese. 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
{
// reference to layer [DAO] initialized by Spring
public IPamDao PamDao { get; set; }
// list of all employee identities
public Employe[] GetAllIdentitesEmployes()
{
...
}
// an individual employee with benefits
public Employe GetEmploye(string ss)
{
...
}
// contributions
public Cotisations GetCotisations()
{
...
}
// wage calculation
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
{
// SS : employee's SS number
// HeuresTravaillées: number of hours worked
// Days worked: number of days worked
...
}
}
- Zeile 13: Wir haben einen Verweis auf die [DAO]-Schicht. Diese wird von Spring initialisiert, wenn die Klasse [PamMetier] instanziiert wird. Wenn also die verschiedenen Methoden ausgeführt werden, ist Zeile 13 bereits initialisiert.
Aufgabe: Vervollständigen Sie den Code für die Klasse [PamMetier]. Wenn wir in [GetSalaire] feststellen, dass der Mitarbeiter mit der Sozialversicherungsnummer nicht existiert, lösen wir eine [PamException] aus. Die Methode zur Berechnung des Gehalts wird in Abschnitt 9.5 erläutert. Achten Sie darauf, alle Zwischenberechnungen auf zwei Dezimalstellen zu runden.
9.24.5. Konfigurieren der [business]-Schicht
Wie 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 einfach die Einstellungen aus der [app.config]-Datei der [EF5]-Schicht.
Unser Testprogramm wird [Spring.net] verwenden, um eine Referenz auf die [Business-]Schicht zu erhalten.
Aufgabe 2: Ändern Sie unter Verwendung Ihrer bisherigen Arbeit aus Abschnitt 9.23.5 die Konfigurationsdatei [app.config] des Projekts [pam-metier] so, dass sie ein Spring-Objekt namens [pammetier] definiert, das mit der soeben erstellten Klasse [PamMetier] verknüpft 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 eine Herausforderung. Sie müssen nicht nur die [Business]-Schicht mit der Klasse [PamMetier] instanziieren, sondern auch deren Eigenschaft [PamDao] initialisieren:
// référence sur la couche [DAO] initialisée par Spring
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 [property]-Tag wird verwendet, um eine öffentliche Eigenschaft der Klasse [PamMetier] zu initialisieren. Das Attribut [name="PamDao"] entspricht dem Namen der Eigenschaft, die in der Klasse [PamMetier] initialisiert werden soll. Das Attribut [ref="pamdao"] gibt an, dass die Eigenschaft mit einer Referenz initialisiert wird, nämlich der des Objekts [pamdao] aus Zeile 6 und somit mit der Referenz aus der [DAO]-Schicht. Genau das wollten wir erreichen.
9.24.6. Testen der [business]-Schicht
Wir sind bereit, unsere [business]-Schicht zu testen. Dazu verwenden wir das vorhandene Programm [Program.cs]:
![]() |
Wir werden die verschiedenen Funktionen der Schnittstelle der [Business]-Schicht testen. Der Code für [Program.cs] sieht wie folgt aus:
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
{
// instantiation layer [business]
IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// list of employee identities
Console.WriteLine("Employés -----------------------------");
foreach (Employe Employe in pamMetier.GetAllIdentitesEmployes())
{
Console.WriteLine(Employe);
}
// payslip calculations
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));
}
// break
Console.ReadLine();
}
}
}
- Zeile 16: Wir erhalten über [Spring.net] eine Referenz auf die [business]-Schicht.
Die Ergebnisse der Ausführung dieses Programms lauten wie folgt:
9.24.7. DLL der [business]-Schicht
Aufgabe: Konvertieren Sie den Projekttyp [pam-business] in eine Klassenbibliothek und generieren Sie das Projekt anschließend neu (wiederholen Sie die Schritte in Abschnitt 9.22.7).
9.25. Schritt 18: Implementierung der [Web]-Schicht
Wir sind nun bei der letzten Schicht unserer Architektur angelangt, der [Web]-Schicht:
![]() |
Wir werden die [web]-Schicht wiederverwenden, die wir unter Verwendung einer simulierten [business]-Schicht entwickelt haben.
9.25.1. Das Visual Studio-Projekt
Wir kehren zu Visual Studio Express 2012 for the Web zurück, um unsere Web-Schicht mit den soeben entwickelten Schichten [Geschäftslogik, DAO, EF5] zu verbinden. Dies umfasst hauptsächlich einige Konfigurationen und ein paar Änderungen am Namespace.
Laden Sie in Visual Studio Express 2012 for Web die [pam-td]-Lösung:
![]() |
- in [1] die [pam-td]-Lösung in Visual Studio Express for Web. Das Webprojekt [pam-web-01] wird wieder sichtbar. Wir hatten es in Visual Studio Express for Desktop verloren.
- 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 entfernen wir in [2] das Projekt aus der Lösung (dadurch wird nichts aus dem Dateisystem gelöscht).
![]() |
- In [3] duplizieren wir mithilfe des Windows-Explorers den Ordner [pam-web-01] nach [pam-web-02];
- Fügen Sie in [4] das Projekt [pam-web-02] zur Lösung [pam-td] hinzu. Es wird unter dem Namen [pam-web-01] angezeigt;
- In [5] ändern wir diesen Namen in [pam-web-02] und legen 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 als Ganzes:
![]() |
Das Projekt [pam-web-02] benötigt eine Reihe von DLLs:
- alle, auf die von den Projekten [pam-metier], [pam-dao] und [pam-ef5] verwiesen wird;
- sowie diejenigen aus den Projekten [pam-metier], [pam-dao] und [pam-ef5] selbst.
Aufgabe: Fügen Sie diese verschiedenen Verweise zum Projekt [pam-web-02] hinzu. Der Verweis auf das Projekt [pam-metier-simule] muss entfernt werden. Wir wechseln zur [business]-Schicht. Einige DLLs sind bereits in den Verweisen vorhanden. Entfernen Sie diese und nehmen Sie dann Ihre Ergänzungen vor.

9.25.3. Implementierung der [web]-Schicht
Erstellen Sie das Projekt [pam-web-02]. Es werden Fehler angezeigt, wie zum Beispiel die folgenden:

Die Klasse [ApplicationModel] verwendet den Typ [Employee]. Bei der Simulation der [business]-Schicht wurde dieser Typ im Namespace [Pam.Business.Entities] definiert. Er befindet sich nun im Namespace [Pam.EF5.Entities]. Beheben Sie diese Fehler wie oben gezeigt.
9.25.4. Konfiguration der [Web]-Schicht
Wie in Abschnitt 9.24.5 beschrieben, müssen wir EF5 in der Datei [web.config] des Projekts 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 [business]-Schicht abzurufen:
try
{
// instantiation layer [business]
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
catch (Exception ex)
{
application.InitException = ex;
}
Zeile 4: Wir fordern eine Referenz auf das Spring-Objekt namens [pammetier] an. Dies ist tatsächlich der Name, der der [business]-Schicht gegeben wurde (überprüfen Sie dies in Ihrer [web.config]-Datei).
9.25.5. Testen der [web]-Schicht
Wir sind bereit, unsere [web]-Schicht zu testen. Zunächst ändern wir ihren Arbeitsport. Standardmäßig hat [pam-web-02] dieselbe Konfiguration wie [pam-web-01] und läuft daher auf demselben Port. Die Erfahrung zeigt, dass dies zu Problemen führt: IIS verwendet weiterhin den Code aus dem Projekt [pam-web-01]. Gehen Sie wie folgt vor:
![]() |
![]() |
Ändern Sie in [4] die Portnummer, beispielsweise durch Ändern der Einerstelle.
Führen Sie das Projekt [pam-web-02] aus, indem Sie [Strg-F5] drücken. Sie sehen dann die folgende Startseite:
![]() |
In [1] rufen wir die Mitarbeiter aus der Datenbank [dbpam_ef5] ab. Beachten Sie, dass der Mitarbeiter [X X], der in der simulierten [business]-Schicht vorhanden war, nicht mehr vorhanden ist. Führen wir eine Simulation durch:
![]() |
In [2] sehen wir das tatsächliche Gehalt anstelle eines fiktiven. Stoppen wir nun das MySQL5-DBMS und führen wir eine weitere Simulation durch:
![]() |
In [3] erhielten wir eine lesbare Fehlerseite, auch wenn einige Meldungen auf Englisch sind. Stoppen wir nun MySQL erneut und führen wir die Anwendung in VS mit [Strg-F5] erneut aus:

Wir erhalten die in Abschnitt 9.20.4 erstellte Ansicht [initFailed.cshtml]. Sie zeigt die Fehlermeldungen aus dem Ausnahmestapel an. Der Leser ist eingeladen, weitere Tests durchzuführen.
9.26. Schritt 19: Eine ASP.NET-Anwendung im Internet zugänglich machen
Bei der Entwicklung einer ASP.NET-Anwendung mit Visual Studio stellt die Standardkonfiguration sicher, dass die 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 beobachten:
- Notieren Sie sich in einem DOS-Fenster die IP-Adresse 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 IP-Adresse wird hier in Zeile 14 angezeigt. Wenn Sie über eine WLAN-Verbindung verfügen, wird die WLAN-Adresse des Geräts in Zeile 20 und den folgenden Zeilen angezeigt.
- Überprüfen Sie die Projekteigenschaften [Rechtsklick auf Projekt / Eigenschaften / Registerkarte „Web“]:

Die Anwendung läuft auf Port [65010] des Rechners [localhost].
- Führen Sie Ihr Projekt aus, indem Sie [Strg-F5] drücken

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

Der Server hat die Antwort [400 Bad Request] zurückgegeben. Der von Visual Studio verwendete IIS Express-Server akzeptiert nur den Namen [localhost].
Um die entwickelte Anwendung unter einer URL wie [http://adresseIP/contexte/...] zugänglich zu machen, müssen Sie einen anderen Server als IIS Express verwenden, z. B. einen IIS-Server (nicht Express). Um zu überprüfen, ob dieser verfügbar ist (normalerweise in Pro-Versionen von Windows), gehen Sie zur Systemsteuerung [Systemsteuerung\System und Sicherheit\Verwaltung]:

Diese Option ist nicht immer vorhanden. Gehen Sie in diesem Fall zu [Systemsteuerung \ Programme] und installieren Sie die Webadministrations-Tools.
![]() |
Sobald die Option [Internetinformationsdienste (IIS)-Manager] verfügbar ist, aktivieren Sie sie:
![]() |
Starten Sie die Standardwebsite. Dazu muss zunächst der [World Wide Web-Veröffentlichungsdienst] ausgeführt werden:
![]() |
Geben Sie anschließend die URL [http://localhost] in einen Browser ein. Vergewissern Sie sich zunächst, dass kein anderer Webserver Port 80 bereits belegt. Ist dies der Fall, beenden Sie ihn.
![]() |
Der IIS-Server hat geantwortet. Ersetzen Sie nun [localhost] durch die IP-Adresse Ihres Computers:
![]() |
Es funktioniert. Kehren wir nun zu Visual Studio zurück:
- Zunächst müssen Sie Visual Studio im [Administrator]-Modus starten
![]() |
Sobald das erledigt ist, müssen Sie die Konfiguration des Webprojekts ändern, das Sie bereitstellen möchten [Rechtsklick auf das Projekt / Eigenschaften / Registerkarte „Web“]:
![]() |
Sie müssen den lokalen IIS-Server als Bereitstellungsserver auswählen. Visual Studio legt die URL der Anwendung fest. Sie können diese ändern. Führen Sie das Projekt aus, indem Sie [Strg-F5] drücken:
![]() |
Ersetzen Sie nun [localhost] durch die IP-Adresse Ihres Computers:
![]() |
Wenn Sie keinen IIS-Server haben, können Sie einen kostenlosen ASP.NET-Server wie [Ultidev Web Server Pro] verwenden, der unter der URL [http://ultidev.com/Download/] verfügbar ist. Nach der Installation gibt es zwei Möglichkeiten, eine Webanwendung mit diesem Server zu starten:
Der schnelle Weg
Öffnen Sie den Windows Explorer und wählen Sie den Ordner aus, der die ASP.NET-Anwendung enthält, die Sie bereitstellen möchten:
![]() |
Der Webserver wird dann gestartet, und die Webanwendung wird in einem Browser angezeigt:
![]() |
- In [3] können Sie den Webserver anhalten oder starten;
- In [4] können Sie den Dienstport der Webanwendung ändern;
Bevor Sie den Server starten, muss der unten aufgeführte Dienst [UWS HiPriv Services] ausgeführt werden:
![]() |
Sobald der Server läuft, sieht die Benutzeroberfläche wie folgt aus:
![]() |
Wenn Sie auf den Link [6] klicken, wird die Startseite der Anwendung angezeigt:
![]() |
Anstelle von [localhost] können Sie dann die IP-Adresse des Rechners eingeben:
![]() |
Auch hier wird also nur der Name [localhost] akzeptiert.
Der lange Weg
Starten Sie die Anwendung Ultidev Web Explorer
![]() |
und führen Sie die folgenden Schritte aus:
![]() |
![]() |
![]() |
- Geben Sie in [8] den Ordner der bereitzustellenden Webanwendung an;
![]() |
- In [10-11] muss der Zugriff auf die Webanwendung über die URL [http://localhost:81/] erfolgen;
![]() |
![]() |
- Starten Sie den Webserver über [14];
![]() |
- Rufen Sie die URL [19] auf;
![]() |
- In [20] konnten wir auf die gewünschte Seite zugreifen, indem wir die lokale IP-Adresse des Rechners anstelle des Namens [localhost] verwendeten. Das war genau das, wonach wir gesucht hatten;
Der Ultidev-Server ist als Windows-Dienst installiert, der automatisch startet. Sie können den automatischen Start des Ultidev-Servers wie folgt deaktivieren:
- Gehen Sie zu [Systemsteuerung\System und Sicherheit\Verwaltung];
![]() |
- [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 die Anwendung [Ultidev Web Explorer], zum Beispiel:
![]() |
9.27. Schritt 20: Erstellen einer nativen Android-App
Wenn Sie über eine Single-Page-Anwendung (SPA) verfügen, können Sie mit dem Tool [PhoneGap] [http://phonegap.com/] eine ausführbare Datei für Mobilgeräte (Android, iOS, Windows 8 usw.) erstellen. Es gibt auch andere Möglichkeiten, dies zu tun, insbesondere mit dem Open-Source-Produkt Apache Cordova [https://cordova.apache.org/]. Das auf der PhoneGap-Website [http://build.phonegap.com/apps] verfügbare Online-Tool „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 (ASP.NET, JEE, PHP usw.) generiert worden sein. Wir beginnen damit, diese Seite zu erstellen.
9.27.1. Die Anwendungsarchitektur
Es ist wichtig, sich hier vor Augen zu halten, dass wir eine Android-App erstellen wollen. Eine solche App weist oft die folgende Architektur auf:
![]() |
- In [1] verwendet der Benutzer ein Android-Tablet, das mit einem oder mehreren Webdiensten [2] kommuniziert;
Kehren wir zum APU-Modell zurück:
![]() |
- Eine Startseite wird im Browser geladen (das obige Diagramm gibt nicht an, woher sie stammt);
- nachfolgende Ansichten werden über Ajax-Aufrufe abgerufen [1–4]. Es werden keine neuen Seiten vom Browser geladen;
Die erste Ansicht kann, muss aber nicht vom selben Server bereitgestellt werden wie die anderen Ansichten, die über Ajax-Aufrufe abgerufen werden. Wenn sie nicht vom selben Server bereitgestellt wird, muss das JavaScript auf der Startseite die URL des Webservers kennen, der die anderen Ansichten bereitstellt. Dies wird bei 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 Browserfunktionen verfügt und daher 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 läuft. Die erste Seite wird somit wie folgt aussehen:
![]() |
- in [1] die URL der Startseite der Anwendung. Diese wird vom Ultidev-Server bereitgestellt, der in Abschnitt 9.26 behandelt wird;
- in [2] muss der Benutzer die URL des Lohnabrechnungssimulators eingeben. Wir könnten diese fest in das JavaScript der Startseite einbinden, doch das würde das Testen erschweren: Sobald wir die IP-Adresse (oder den Port) des Simulators ändern, müssten wir dies auch im JavaScript-Code ändern;
- in [3] der Link [Login], der die folgende Ansicht aufruft:
![]() |
- Beachten Sie, dass sich in [4] die URL des Browsers nicht geändert hat. Es ist immer noch die der Startseite und wird dies für die gesamte Lebensdauer der Anwendung bleiben.
Sobald diese Ansicht geladen ist, funktioniert alles wie zuvor: Die verschiedenen Ansichten werden über Ajax-Aufrufe geladen. Wir werden sehen, dass nur sehr wenig Code geändert werden muss.
9.27.2. Refactoring des Projekts [pam-web-02]
Im Ordner [Content] des Projekts [pam-web-02] erstellen wir den folgenden Ordner [bootstrap] (der Name spielt keine Rolle):
![]() |
Wir haben die statische Seite [index.html] und alle dafür benötigten Ressourcen (CSS- und JS-Dateien) hinzugefügt. Die Seite [index.html] verwendet den Code aus der Master-Seite [_Layout.cshtml] des Visual Studio-Projekts, wobei alles entfernt wurde, was nicht statisch ist. Dies führt zu folgendem 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 Folgendes hinzugefügt:
- Zeilen 27–29: Wir haben die Menüoption [Login] hinzugefügt, um die Verbindung zum Simulationsdienst zu ermöglichen;
- Zeilen 55–56: das Eingabefeld für die Simulator-URL;
- Zeilen 59–63: eine Fehlermeldung, falls die Verbindung fehlschlägt;
Die Code-Umgestaltung erfolgt ausschließlich im Code von [myScripts.js] in Zeile 14 oben. Sonst ändert sich nichts. Der Code entwickelt sich wie folgt:
// au chargement du document
$(document).ready(function () {
// on récupère les références des différents composants de la page
loading = $("#loading");
content = $("#content");
erreur = $("#erreur");
erreur1 = $("#erreur1");
erreur2 = $("#erreur2");
// les liens du menu
lnkConnexion = $("#lnkConnexion");
lnkFaireSimulation = $("#lnkFaireSimulation");
lnkEffacerSimulation = $("#lnkEffacerSimulation");
lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
lnkVoirSimulations = $("#lnkVoirSimulations");
lnkTerminerSession = $("#lnkTerminerSession");
lnkRetourFormulaire = $("#lnkRetourFormulaire");
// on les met dans un tableau
options = [lnkConnexion, lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
// on cache certains éléments de la page
loading.hide();
erreur.hide();
// on fixe le menu
setMenu([lnkConnexion]);
});
- Zeilen 6–8: die IDs des Bereichs, der 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 JS-Funktion [connexion] (Zeile 1) lautet wie folgt:
var urlServiceWeb;
var erreur, erreur1, erreur2;
function connexion() {
// retrieve the urlServiceWeb from the web service
urlServiceWeb = $("#urlServiceWeb").val();
// retrieve the input form
$.ajax({
url: urlServiceWeb + '/Pam/Formulaire',
type: 'POST',
dataType: 'html',
beforeSend: function () {
// wait signal on
loading.show();
},
success: function (data) {
// displaying results
content.html(data);
// menu
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 () {
// wait signal off
loading.hide();
}
});
}
- Zeile 7: Wir rufen die vom Benutzer eingegebene URL ab. Sie ist in der globalen Variablen aus Zeile 1 gespeichert. Auf diese Weise steht sie in den anderen Funktionen der Datei zur Verfügung;
- Zeile 10: Wir führen einen Ajax-Aufruf an die URL des Simulators [/Pam/Form] durch. Diese URL rendert die Teilansicht zur Eingabe von Simulationsdaten (Mitarbeiter, geleistete Arbeitsstunden, Arbeitstage). In der ursprünglichen Version von [pam-web-02] war diese URL ausreichend. Ihr wurde automatisch die URL vorangestellt, die die Startseite aufgerufen hatte. Nun gehen wir davon aus, dass die Startseite möglicherweise von einem anderen Server als demjenigen bereitgestellt wird, auf dem der Simulator gehostet wird. Der URL [/Pam/Formulaire] muss daher die Variable [urlServiceWeb] aus Zeile 1 vorangestellt werden, die die URL des Simulators ist (zum Beispiel http://172.19.81.34/pam-web-02). Dies muss für alle Ajax-Aufrufe in der Datei erfolgen;
- Zeilen 17–22: Wenn die Verbindung erfolgreich hergestellt wurde, wird die Teilansicht [Formulaire.cshtml] angezeigt und ein Menü eingeblendet, das nur den Link [Run Simulation] enthält (Zeile 21);
- Zeilen 23–27: Wenn die Verbindung fehlschlägt:
- In Zeile 24 wird die vom Webserver gesendete HTML-Antwort angezeigt (sofern vorhanden);
- in Zeile 25 werden die vom Webserver gesendeten HTTP-Header angezeigt (sofern er geantwortet hat);
Das war's. Bei Erfolg wird die folgende Seite angezeigt:
![]() |
Wir befinden uns nun in der vorherigen Situation, in der Ansichten nun über Ajax-Aufrufe abgerufen werden. Wie oben gezeigt, wird das Klicken auf den Link [Simulation ausführen] daher durch den folgenden Code in der Datei [myScripts.js] ausgeführt:
function faireSimulation() {
// on récupère des références
var simulation = $("#simulation");
var formulaire = $("#formulaire");
// formulaire valide ?
var formValid = formulaire.validate().form();
if (!formValid) return;
// on fait un appel Ajax à la main
$.ajax({
url: urlServiceWeb + '/Pam/FaireSimulation',
type: 'POST',
data: formulaire.serialize(),
dataType: 'html',
...
});
// menu
setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
- Es wurde eine einzige Änderung vorgenommen, und zwar in Zeile 10, wo der bisherigen URL nun die URL des Simulators vorangestellt wurde;
9.27.3. Testen des umgestalteten Projekts
In Abschnitt 9.26 haben wir gezeigt, wie die Anwendung [pam-web-02] auf dem Ultidev-Server installiert wird. Wir beginnen an dieser Stelle:
![]() |
- In [6] fordern wir die Anzeige der Seite [bootstrap/index.html] an. Wir erhalten folgende Ansicht:
![]() |
Geben wir eine falsche URL ein:
![]() |
- in [10] die HTTP-Header der Serverantwort;
- in [11] das HTML-Dokument aus der Antwort des Servers;
Wenn Sie die richtige URL eingeben:
![]() |
erhalten wir die folgende Antwort:
![]() |
9.27.4. Erstellen der Android-Binärdatei
Wir erstellen die Android-Binärdatei aus der statischen Website, die wir gerade erstellt und getestet haben[1]:


Wir fügen in [2] eine [config.xml]-Datei hinzu, die zur Konfiguration des [Phonegap]-Plugins verwendet wird, welches die Android-Binärdatei generiert. 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: Geben 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, URLs außerhalb des Geräts abzufragen;
Wir komprimieren den Inhalt des Ordners [Content/bootstrap]:

Gehen Sie anschließend auf die PhoneGap-Website [http://build.phonegap.com/apps]:
![]() |
- Vor [1] müssen Sie möglicherweise ein Konto erstellen;
- unter [1] legen wir los;
- Wählen Sie unter [2] einen kostenlosen Tarif, der nur eine PhoneGap-App zulässt;
- unter [3] laden Sie die komprimierte App [4] herunter;
![]() |
![]() |
- Geben Sie unter [5] den Namen der App ein;
- Klicken Sie auf den Link [6], um die Binärdateien für iOS, Android und Windows zu erstellen. Dies kann einige Sekunden dauern;
![]() |
- Laden Sie unter [7–9] die Android-Binärdatei herunter;
![]() |
Starten Sie einen [GenyMotion]-Emulator für ein Android-Tablet (siehe Abschnitt 11.1):

Oben starten wir einen Tablet-Emulator mit Android API 21. Sobald der Emulator läuft,
- entsperren Sie ihn, indem Sie das Schloss (falls vorhanden) zur Seite ziehen und dann loslassen;
- Ziehen Sie die heruntergeladene Datei [Pam-debug.apk] mit der Maus auf den Emulator und legen Sie sie dort ab. Sie wird dann installiert und ausgeführt;
![]() |
Geben Sie [1] die URL des Simulators ein, wie in Abschnitt 9.27.3 beschrieben. Sobald dies erledigt ist, stellen Sie über den Link [2] eine Verbindung zum Simulator her:

Testen Sie die Anwendung auf dem Emulator. Sie sollte funktionieren.




















































































































































