9. Casestudy
9.1. Introduction
We zullen een casestudy presenteren die al is gepubliceerd in een artikel dat beschikbaar is op de URL [http://tahe.developpez.com/dotnet/pam-aspnet/]. In dit artikel wordt de casestudie uitgevoerd met klassieke ASP.NET en de ORM NHibernate. We zullen deze hier uitvoeren met ASP.NET, MVC en ORM Entity Framework. Net als in het bestaande artikel wordt de casestudy gepresenteerd als een universitair project. Het is dus bedoeld voor studenten. Bij alle vragen wordt verwezen naar de hoofdstukken die we zojuist hebben besproken, om nuttige literatuur aan te geven.
9.2. Het op te lossen probleem
We willen een webapplicatie schrijven waarmee een gebruiker simulaties kan uitvoeren voor de loonberekening van de kinderopvangmedewerkers van de vereniging „Maison de la petite enfance“ in een gemeente. We zullen zowel aandacht besteden aan de structuur van de DotNet-code van de applicatie als aan de code zelf.
De applicatie zal van het type APU [Application à Page Unique] zijn en uitsluitend gebruikmaken van Ajax-aanroepen om met de server te communiceren. De applicatie zal de gebruiker de volgende weergaven tonen:
- de weergave [VueSaisies], die het simulatieformulier toont

- de weergave [VueSimulation] die wordt gebruikt om het gedetailleerde resultaat van de simulatie weer te geven:

- de weergave [VueSimulations], die de lijst met door de klant uitgevoerde simulaties toont

- de weergave [VueSimulationsVides] die aangeeft dat de klant geen of geen simulaties meer heeft:

- de weergave [VueErreurs] die één of meerdere fouten aangeeft (hier zijn de SGBD en MySQL gestopt):

9.3. Architectuur van de applicatie
De architectuur van de applicatie ziet er als volgt uit:
![]() |
De laag [EF5] verwijst naar het Entity Framework 5 van ORM. Het gebruikte SGBD zal MySQL zijn.
We bouwen deze applicatie eerst met een gesimuleerde [métier]-laag:
![]() |
Zo kunnen we ons volledig concentreren op de [web]-laag. De gesimuleerde [métier]-laag zal de interface van de daadwerkelijke [métier]-laag naleven. Zodra de laag [web] operationeel is, zullen we de lagen [métier], [DAO] en [EF5] opbouwen.
9.4. De database
De statische gegevens die nodig zijn voor het opstellen van de loonstrook worden opgeslagen in een database met de naam MySQL, genaamd [dbpam_ef5] (pam=Paie Assistante Maternelle). Deze database heeft een beheerder met de naam root zonder wachtwoord. De database bevat drie tabellen:

Er is een vreemde-sleutelrelatie tussen de kolom EMPLOYES (INDEMNITE_ID) en de kolom INDEMNITES (ID). De structuur van deze database wordt bepaald door het gebruik ervan in combinatie met EF5. We komen hierop terug wanneer we de lagere lagen van de applicatie gaan bouwen.
Structuur:
![]() |
|
De inhoud zou als volgt kunnen zijn:
![]()
Structuur:
![]() |
|
De inhoud zou er als volgt uit kunnen zien:
![]()
De tarieven van de sociale premies zijn onafhankelijk van de werknemer. De vorige tabel bevat slechts één rij.
![]() |
|
De inhoud zou er als volgt uit kunnen zien:
![]()
9.5. Berekeningswijze van het -salaris van een kinderoppas
Hieronder wordt de berekeningswijze van het maandsalaris van een kinderoppas toegelicht. Als voorbeeld nemen we het salaris van mevrouw Marie Jouveinal, die in de te betalen maand 150 uur over 20 dagen heeft gewerkt.
Er wordt rekening gehouden met de volgende elementen: | [TOTALHEURES]: totaal aantal gewerkte uren in de maand [TOTALJOURS]: totaal aantal gewerkte dagen in de maand | [TOTALHEURES]=150 [TOTALJOURS]= 20 |
Het basisloon van de kinderoppas wordt berekend aan de hand van de volgende formule: | [SALAIREBASE]=([TOTALHEURES]*[BASEHEURE])*(1+[INDEMNITESCP]/100) | [SALAIREBASE]=(150*[2.1])*(1+0,15)= 362,25 |
Op dit basisloon moeten een aantal sociale premies worden ingehouden: | Algemene sociale bijdrage en bijdrage aan de aflossing van de sociale schuld: [SALAIREBASE]*[CSGRDS/100] Aftrekbare algemene sociale bijdrage: [SALAIREBASE]*[CSGD/100] Sociale zekerheid, weduwschap, ouderdom: [SALAIREBASE]*[SECU/100] Aanvullend pensioen + AGPF + Werkloosheidsverzekering: [SALAIREBASE]*[RETRAITE/100] | CSGRDS: 12,64 CSGD: 22,28 Sociale zekerheid: 34,02 Pensioen: 28,55 |
Totaal sociale premies: | [COTISATIONSSOCIALES] = [SALAIREBASE] *(CSGRDS + CSGD + SECU + RETRAITE) / 100 | [COTISATIONSSOCIALES]=97,48 |
Bovendien heeft de kinderopvangmedewerkster voor elke gewerkte dag recht op een verblijfsvergoeding en een maaltijdvergoeding. In dit kader ontvangt zij de volgende vergoedingen: | [Indemnités]=[TOTALJOURS]*(ENTRETIENJOUR+REPASJOUR) | [INDEMNITES]=104 |
Uiteindelijk is het aan de kinderoppas uit te betalen nettoloon als volgt: | [SALAIREBASE] - [COTISATIONSSOCIALES] + [INDEMNITÉS] | [salaire NET]=368,77 |
9.6. Het Visual Studio-project van de laag [web]
Het Visual Web Developer-project van de applicatie ziet er als volgt uit:
![]() |
- in [1], de algemene structuur van het project [pam-web-01];
- in [2] is de map [Content] de map waarin de statische bronnen van het project worden geplaatst:
- [indicator.gif]: de geanimeerde afbeelding die wordt weergegeven terwijl er wordt gewacht op het einde van een Ajax-verzoek,
- [standard.jpg]: de achtergrondafbeelding voor de verschillende weergaven,
- [Site.css]: het stylesheet van de applicatie;
- in [3], de enige controller van de applicatie [PamController];
- in [4], klassen die nodig zijn voor de applicatie maar niet kunnen worden gecategoriseerd als onderdelen van MVC:
- [ApplicationModelBinder]: de klasse waarmee de gegevens uit het bereik [Application] in het actiemodel kunnen worden opgenomen,
- [SessionModelBinder]: de klasse waarmee de gegevens uit het bereik [Session] in het actiemodel kunnen worden opgenomen,
- [Static]: een hulpklasse met statische methoden;
- in [5], de sjablonen van de applicatie, of het nu gaat om actiesjablonen of weergavesjablonen:
- [ApplicationModel]: model dat de gegevens uit het bereik [Application] bevat,
- [SessionModel]: model met de gegevens uit het bereik van [Session],
- [Simulation]: klasse die de elementen van een simulatie voor loonberekening omvat,
- [IndexModel]: model van het eerste scherm [Index] dat door de applicatie wordt weergegeven;
- in [6], de scripts JS die nodig zijn voor de globalisatie van de applicatie;
- in [7], de scripts JS uit de familie JQuery die nodig zijn voor de internationalisering, de validatie aan de clientzijde en de ajax-implementatie van de applicatie;
- in [8] is [myScripts.js] het bestand dat onze eigen scripts JS bevat;
- in [9], de weergaven van de applicatie:
- [Index]: de startpagina,
- [Formulaire]: het formulier voor het invoeren van de werknemer en zijn gewerkte uren en dagen,
- [Simulation]: de weergave met een simulatie,
- [Simulations]: de weergave met de lijst van uitgevoerde simulaties,
- [Erreurs]: het scherm met de lijst van eventuele fouten,
- [InitFailed]: het scherm met foutmeldingen als het opstarten van de applicatie mislukt;
- in [10], de hoofdpagina van de applicatie [_Layout];
- in [11], de bestanden [Web.config] en [Global.asax] die worden gebruikt om de applicatie te configureren.
9.7. Stap 1 – opzetten van de gesimuleerde laag [métier]
Vanaf nu beschrijven we de stappen die moeten worden gevolgd om de casestudie uit te voeren. Waar nodig vermelden we het hoofdstuknummer, zodat u dit eventueel nog eens kunt nalezen om de gevraagde opdracht uit te voeren. Sommige onderdelen van het project worden u verstrekt in een map [aspnetmvc-support.zip] die u kunt vinden op de website van dit document. Daarin vindt u de map [étudedecas-support] met de volgende inhoud:
![]() |
Het project bevat bovendien elementen die in de voorgaande hoofdstukken zijn behandeld. U hoeft deze alleen maar te kopiëren en in te plakken tussen dit bestand PDF en Visual Studio.
9.7.1. De Visual Studio-oplossing voor de volledige applicatie
We gaan eerst een Visual Studio-oplossing aanmaken waarin we twee projecten gaan maken:
- een project voor de gesimuleerde [métier]-laag;
- een project voor de weblaag MVC.
![]() |
We gebruiken twee tools:
- Visual Studio Express 2012 voor desktop, waarmee we de laag [métier] gaan bouwen;
- Visual Studio Express 2012 voor het web, waarmee we de laag [web] gaan bouwen.
Met Visual Studio Express voor desktop maken we een oplossing [pam-td] aan:
![]() |
- in [1], kies een C#-toepassing;
- in [2], kies je [Application console];
- in [3], geef de oplossing een naam;
- in [4], maak een map aan voor deze oplossing;
- in [5], geef de laag [métier] een naam;
- in [6], de gegenereerde oplossing.
9.7.2. De interface van de laag [métier]
In een gelaagde architectuur is het een goede praktijk dat de communicatie tussen lagen via interfaces verloopt:
![]() |
Welke interface moet de laag [métier] aanbieden aan de laag [web]? Welke interacties zijn mogelijk tussen deze twee lagen? Laten we nog eens kijken naar de webinterface die aan de gebruiker wordt getoond:
![]() |
- Bij de eerste weergave van het formulier moet in [1] de lijst met medewerkers te vinden zijn. Een vereenvoudigde lijst volstaat (Achternaam, Voornaam, SS). Het nummer SS is nodig om toegang te krijgen tot aanvullende informatie over de geselecteerde medewerker (gegevens 6 tot en met 11).
- De gegevens 12 tot en met 15 zijn de verschillende premietarieven.
- De gegevens 16 tot en met 19 zijn de vergoedingen van de werknemer
- De gegevens 20 tot en met 24 zijn de looncomponenten die zijn berekend op basis van de door de gebruiker ingevoerde gegevens 1 tot en met 3.
De interface [IPamMetier] die door de laag [métier] aan de laag [web] wordt aangeboden, moet aan de bovenstaande eisen voldoen. Er zijn talrijke mogelijke interfaces. Wij stellen de volgende voor:
using Pam.Metier.Entites;
namespace Pam.Metier.Service
{
public interface IPamMetier
{
// lijst met alle identiteiten van de werknemers
Employe[] GetAllIdentitesEmployes();
// ------- de salarisberekening
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
}
- regel 7: de methode waarmee de keuzelijst [1] kan worden gevuld
- regel 10: de methode waarmee de gegevens 6 tot en met 24 kunnen worden opgehaald. Deze zijn verzameld in een object van het type [FeuilleSalaire], dat we straks zullen beschrijven.
We zullen deze interface in een map met de naam [metier/service] plaatsen:
![]() |
9.7.3. De entiteiten van de laag [métier]
De vorige interface maakt gebruik van twee klassen, [Employe] en [FeuilleSalaire], die we moeten definiëren:
- [Employe] is de afbeelding van een rij uit de tabel [employes] in de database;
- [FeuilleSalaire] is de loonstrook van een werknemer.
De entiteiten worden in een map [metier / entites] van het project geplaatst:
![]() |
In de uiteindelijke architectuur zal de laag [métier] de afbeeldingsentiteiten uit de database verwerken:

We zullen de volgende klassen gebruiken om de rijen van de drie tabellen in de database weer te geven. Zie paragraaf 9.4 voor de betekenis van de verschillende velden.
Klasse [Employe]
Deze klasse vertegenwoordigt een rij uit de tabel [employes]. De code ervan is als volgt:
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; }
// handtekening
public override string ToString()
{
return string.Format("Employé[{0},{1},{2},{3},{4},{5}]", SS, Nom, Prenom, Adresse, Ville, CodePostal);
}
}
}
Klasse [Indemnites]
Dit vertegenwoordigt een rij uit de tabel [indemnites]. De code is als volgt:
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; }
// handtekening
public override string ToString()
{
return string.Format("Indemnités[{0},{1},{2},{3},{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
}
}
}
Klasse [Cotisations]
Dit vertegenwoordigt een rij uit de tabel [cotisations]. De code is als volgt:
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; }
// handtekening
public override string ToString()
{
return string.Format("Cotisations[{0},{1},{2},{3}]", CsgRds, Csgd, Secu, Retraite);
}
}
}
Merk op dat de klassen de kolommen [ID] en [VERSIONING] uit de tabellen niet overnemen. Deze kolommen, die nuttig zijn bij het gebruik van de ORM en EF5, zijn dat niet in de context van de gesimuleerde laag [métier].
De klasse [FeuilleSalaire] bevat de gegevens 6 tot en met 24 van het eerder getoonde formulier:
namespace Pam.Metier.Entites
{
public class FeuilleSalaire
{
// automatische eigenschappen
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);
}
}
}
- regel 7: de gegevens 6 tot en met 11 over de werknemer wiens salaris wordt berekend en de gegevens 16 tot en met 19 over zijn vergoedingen. Hierbij moet worden opgemerkt dat een object [Employe] een object [Indemnites] omvat dat zijn vergoedingen vertegenwoordigt;
- regel 8: de gegevens 12 tot en met 15;
- regel 9: de gegevens 20 tot en met 24;
- regels 12-14: de methode [ToString].
De klasse [ElementsSalaire] omvat de gegevens 20 tot en met 24 van het formulier:
namespace Pam.Metier.Entites
{
public class ElementsSalaire
{
// automatische eigenschappen
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);
}
}
}
- regels 6-10: de salariscomponenten zoals uitgelegd in de eerder beschreven bedrijfsregels;
- regel 6: het basisloon van de werknemer, afhankelijk van het aantal gewerkte uren;
- regel 7: de premies die op dit basisloon worden ingehouden;
- regels 8 en 9: de toeslagen die bij het basisloon moeten worden opgeteld, afhankelijk van de index van de werknemer en het aantal gewerkte dagen;
- regel 10: het uit te betalen nettoloon;
- regels 14-17: de methode [ToString] van de klasse.
9.7.4. De klasse [PamException]
We maken een specifiek uitzonderingstype aan voor onze toepassing. Dit is het volgende type [PamException]:
using System;
namespace Pam.Metier.Entites
{
// uitzonderingsklasse
public class PamException : Exception
{
// de foutcode
public int Code { get; set; }
// constructors
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;
}
}
}
- regel 6: de klasse is afgeleid van de klasse [Exception];
- regel 10: de klasse heeft een openbare eigenschap [Code], die een foutcode is;
- we zullen in onze applicatie twee soorten constructors gebruiken:
- die van de regels 23-27, die we kunnen gebruiken zoals hieronder weergegeven:
- (vervolg)
- of die in de regels 29-33, bedoeld om een opgetreden uitzondering door te geven door deze in te kapselen in een uitzondering van het type [PamException]:
try{
....
}catch (IOException ex){
// de uitzondering wordt ingekapseld ex
throw new PamException("Problème d'accès aux données",ex,10);
}
Deze tweede methode heeft als voordeel dat de informatie die de eerste uitzondering kan bevatten, niet verloren gaat.
9.7.5. Implementatie van de laag [métier]
De interface [IPamMetier] wordt geïmplementeerd door de volgende klasse [PamMetier]:
using System;
using Pam.Metier.Entites;
using System.Collections.Generic;
namespace Pam.Metier.Service
{
public class PamMetier : IPamMetier
{
// lijst met werknemers in de cache
public Employe[] Employes { get; set; }
// medewerkers geïndexeerd op hun nummer SS
private IDictionary<string, Employe> dicEmployes = new Dictionary<string, Employe>();
// lijst met medewerkers
public Employe[] GetAllIdentitesEmployes()
{
...
// de lijst met werknemers weergeven
return Employes;
}
// salarisberekening
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
{
...
}
}
- regel 7: de klasse [PamMetier] implementeert de interface [IPamMetier];
- regel 10: de klasse [PamMetier] houdt de lijst met werknemers in de cache bij;
- regel 12: een woordenboek dat een werknemer koppelt aan zijn socialezekerheidsnummer;
- regels 15-20: de methode die de lijst met werknemers retourneert;
- regels 23-26: de methode die het salaris van een werknemer berekent.
De methode [GetAllIdentitesEmploye] is als volgt:
// lijst met werknemers
public Employe[] GetAllIdentitesEmployes()
{
if (Employes == null)
{
// we maken een tabel met drie werknemers
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]);
// een fictieve medewerker die niet in het woordenboek wordt opgenomen
// om een niet-bestaande werknemer te simuleren
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 geven de lijst met werknemers weer
return Employes;
}
- regel 4: er wordt gecontroleerd of de lijst met werknemers nog niet is samengesteld;
- regel 7: als dat niet het geval is, wordt er een array met drie werknemers aangemaakt;
- regels 8-17: de eerste medewerker;
- regel 18: deze wordt aan het woordenboek toegevoegd;
- regels 19-28: de tweede medewerker;
- regel 29: hij wordt aan het woordenboek toegevoegd;
- regels 32-42: de derde medewerker. Deze wordt om een reden die we zullen uitleggen niet aan het woordenboek toegevoegd.
De methode [GetSalaire] ziet er als volgt uit:
// salaris berekenen
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
{
// de medewerker met nr. SS wordt opgehaald
Employe e = dicEmployes.ContainsKey(ss) ? dicEmployes[ss] : null;
// bestaat?
if (e == null)
{
throw new PamException(string.Format("L'employé de n° SS [{0}] n'existe pas", ss), 10);
}
// een fictieve loonstrook weergeven
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 }
};
}
- regel 2: de methode ontvangt het nummer SS van de werknemer wiens salaris we willen berekenen, het aantal gewerkte uren en het aantal gewerkte dagen;
- regel 5: we zoeken de werknemer op in het woordenboek. We herinneren ons dat een van hen er niet in staat;
- regels 7-10: als de werknemer niet wordt gevonden, wordt een uitzondering [PamException] gegenereerd;
- regels 12-17: er wordt een fictieve loonstrook weergegeven.
9.7.6. De consoletest van de laag [métier]
Het ontwerp van de laag [métier] ziet er momenteel als volgt uit:
![]() |
De bovenstaande klasse [Program] zal de methoden van de interface [IPamMetier] testen. Een eenvoudig voorbeeld zou het volgende kunnen zijn:
using Pam.Metier.Entites;
using Pam.Metier.Service;
using System;
namespace Pam.Metier.Tests
{
class Program
{
public static void Main()
{
// instantie van laag [métier]
IPamMetier pamMetier = new PamMetier();
// lijst van werknemers
Employe[] employes = pamMetier.GetAllIdentitesEmployes();
Console.WriteLine("Liste des employés--------------------");
foreach (Employe e in employes)
{
Console.WriteLine(e);
}
// loonstrookberekeningen
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));
}
}
}
}
- regel 12: instantiëren van de laag [métier];
- regels 14-19: het testen van de methode [GetAllIdentitesEmploye] van de interface [IPamMetier];
- regels 21-31: testen van de methode [GetSalaire] van de interface [IPamMetier].
De uitvoering van dit consoleprogramma levert de volgende resultaten op:
De lezer wordt uitgenodigd om het verband te leggen tussen deze resultaten en de uitgevoerde code.
Om dit project te kunnen gebruiken in het webproject dat we gaan bouwen, maken we er een klassenbibliotheek van:
![]() |
- in [1], in de eigenschappen van het bestand [Program.cs];
- in [2] geven we aan dat het bestand geen deel zal uitmaken van de gegenereerde assembly;
- in [3, 4], in de eigenschappen van het project [pam-metier-simule], in de optie [Application] [3], wordt aangegeven dat de generatie een klassenbibliotheek moet opleveren (in de vorm van een DLL).
![]() |
- in [5] wordt gevraagd om een assembly van het type [Release]. Het andere type is [Debug]. De assembly bevat dan informatie die het debuggen vergemakkelijkt;
- in [6] wordt het project [pam-metier-simule] gegenereerd;
![]() |
- in [7] worden alle bestanden van de oplossing weergegeven;
- in [8], in de map [bin / Release], de DLL van ons project.
9.8. Stap 2: het opzetten van de webapplicatie
In de vorige Visual Studio-oplossing gaan we het project voor de weblaag MVC aanmaken.
![]() |
Met Visual Studio Express voor het web openen we de oplossing [pam-td] die eerder met Visual Studio Express voor desktop is aangemaakt.
![]() |
- in [1] is de oplossing [pam-td] geladen in Visual Studio Express voor het web;
- in [2] zijn de oplossing en het project voor de gesimuleerde laag [métier] geladen die we zojuist hebben aangemaakt.
In deze nieuwe stap gaan we het raamwerk van de webapplicatie maken.
![]() |
- in [1] voegen we een nieuw project toe aan de oplossing [pam-td];
![]() |
- in [2] kiezen we een project met de naam ASP.NET MVC 4;
- met de naam [pam-web-01] [3];
- in [4] kiest men het basismodel ASP.NET MVC;
- in [5], het aangemaakte project;
![]() |
- in [6] maken we een nieuw project aan, het startproject van de oplossing, dat wordt uitgevoerd wanneer we [Ctrl-F5] uitvoeren;
- in [7] is de naam van het nieuwe project vetgedrukt, wat aangeeft dat dit het startproject van de oplossing is.
Vervolgens vervangen we met Windows Verkenner de map [Content] van het project door de map [étudedecas-support / web / Content]. Zodra dit is gebeurd, moeten de nieuwe bestanden worden toegevoegd aan het project [pam-web-01]. Dit doen we als volgt:
![]() |
- in [1] vernieuw je de oplossing;
- in [2] laat u alle bestanden van de oplossing weergeven;
- in [3] verschijnt een map [Images];
- dat wordt opgenomen in het project in [4].
Voeg in de map [Scripts] de scripts JQuery Globalization en [1] toe die nodig zijn voor de validatie aan de clientzijde.
![]() |
De masterpagina [_Layout.cshtml] [2] bevat het volgende:
<!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>
Opmerking: regel 8, pas de versie van jQuery aan aan die van uw versie van Visual Studio.
- regel 7: verwijzing naar het stylesheet van de applicatie;
- regels 8-10: verwijzingen naar de scripts die nodig zijn voor validatie aan de clientzijde;
- regels 11-12: verwijzingen naar de scripts die nodig zijn voor het invoeren van Franse reële getallen met een komma;
- regel 13: verwijzing naar de scripts die nodig zijn voor de Ajax-modus;
- regel 14: de scripts die specifiek zijn voor de applicatie;
- regel 24: de laadafbeelding voor het wachten op het voltooien van Ajax-verzoeken;
- regels 26-39: zes JavaScript-links;
- regel 43: het gedeelte waar de verschillende weergaven van de applicatie zullen worden getoond;
- regel 44: de inhoud van de verschillende weergaven van de applicatie.
Vervolgens passen we de standaardroute van de applicatie aan:
![]() |
Het bestand [RouteConfig] krijgt de volgende inhoud:
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" }
);
}
}
}
- regel 14: de URL krijgen de vorm [{controller}/{action}];
- regel 15: als er geen actie is, wordt de actie [Index] gebruikt. Als er geen controller is, wordt de controller [Pam] gebruikt.
Uit deze configuratie volgt dat de URL [/] gelijkwaardig is aan de URL [/Pam/Index]. Aangezien onze toepassing van het type APU is, zullen de URL en [/] de enige URL van deze toepassing zijn.
Maak de controller [Pam] aan:
Wijzig de controller [PamController] als volgt:
using System.Web.Mvc;
namespace Pam.Web.Controllers
{
public class PamController : Controller
{
[HttpGet]
public ViewResult Index()
{
return View();
}
}
}
- regel 3: we plaatsen de controller in de naamruimte [Pam.Web.Controllers];
- regel 7: de actie [Index] verwerkt alleen de opdracht HTTP GET;
- regel 8: we retourneren een type [ViewResult] in plaats van een type [ActionResult].
Maak nu de weergave [Index.cshtml] aan die wordt weergegeven door de bovenstaande actie [Index]:
![]() |
Wijzig [Index.cshtml] als volgt:
@{
ViewBag.Title = "Pam";
}
<h2>Formulaire</h2>
Voer de toepassing uit via [Ctrl-F5]. U zou de volgende pagina moeten zien:
![]() |
Opdracht: Leg uit wat er is gebeurd.
De applicatie maakt gebruik van een stylesheet waarnaar wordt verwezen in de masterpagina [_Layout.cshtml]:
<link rel="stylesheet" href="~/Content/Site.css" />
Het stylesheet [/Content/Site.css] definieert een achtergrondafbeelding voor de pagina's van de applicatie:
body {
background-image: url("/Content/Images/standard.jpg");
}
![]() |
9.9. Stap 3: het sjabloon APU instellen
We willen een applicatie schrijven volgens het sjabloon APU (applicatie met één pagina) zoals beschreven in paragraaf 7.5 en paragraaf 7.6. De enkele pagina is de pagina die door de browser wordt geladen bij het opstarten van de applicatie:
![]() |
- het bovenstaande gedeelte [1] is het vaste gedeelte van de enkele pagina. We hebben gezien dat dit wordt geleverd door de masterpagina [_Layout.cshtml];
- het gedeelte [2] is het variabele gedeelte van de unieke pagina. Dit bevindt zich in het gebied met id [content] van de hoofdpagina [_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>
De verschillende paginadeelstukken van de applicatie worden weergegeven in het gebied met id [content] op regel 13. Ze worden weergegeven via Ajax-aanroepen. De JavaScript-scripts die deze aanroepen uitvoeren, staan in het bestand [myScripts.js] waarnaar in regel 6 wordt verwezen. Maak dit bestand aan, dat we nodig zullen hebben:
![]() |
We volgen nu het sjabloon APU dat in paragraaf 7.6 wordt beschreven. Lees deze paragraaf nog eens door als je het vergeten bent. We gaan nu de verschillende paginadeelstukken implementeren die door de applicatie worden weergegeven.
9.9.1. De JavaScript-ontwikkelaarstools
Ter herinnering: met de Chrome-browser beschikt u over een reeks hulpmiddelen om de JavaScript-code van uw pagina's (HTML, CSS) te debuggen. Deze tools zijn gedeeltelijk besproken in paragraaf 7.2. In het model APU slaan browsers de JavaScript-scripts op in de cache waarnaar op de eerste pagina van de applicatie wordt verwezen. Vergeet daarom niet deze cache te legen wanneer u uw scripts wijzigt, anders worden de wijzigingen mogelijk niet doorgevoerd. Zo doet u dit in Chrome:
- voer [Ctrl-Maj-I] in om de ontwikkelomgeving weer te geven
![]() |
- klik op het pictogram [1] rechtsonder in het ontwikkelvenster;
- vink vervolgens de optie [2] aan, waarmee de cache in de ontwikkelingsmodus wordt uitgeschakeld.
9.9.2. Een deelweergave gebruiken om het formulier weer te geven
Het invoerformulier is een van de fragmenten die door de applicatie worden weergegeven. Op dit moment wordt dit formulier weergegeven door de weergave [Index.cshtml], een volledige weergave:
@{
ViewBag.Title = "Pam";
}
<h2>Formulaire</h2>
Deze weergave wordt weergegeven door de actie [Index]:
[HttpGet]
public ViewResult Index()
{
return View();
}
In regel 4 hierboven wordt inderdaad een weergave [View] weergegeven en geen gedeeltelijke weergave [PartialView]. We hebben een gedeeltelijke weergave nodig voor het formulier, dat een fragment van een pagina zal zijn. We passen de weergave [Index.cshtml] als volgt aan:
@{
ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")
Regel 4: het formulier maakt geen deel meer uit van de pagina [Index.cshtml]. Het is nu ondergebracht in een deelweergave [Formulaire.cshtml]:
![]() |
De code van [Formulaire.cshtml] is simpelweg de volgende:
<h2>Formulaire</h2>
Breng deze wijzigingen aan en controleer of u bij het opstarten van de applicatie nog steeds het volgende scherm te zien krijgt:
![]() |
9.9.3. De Ajax-aanroep [faireSimulation]
We kijken naar het fragment dat wordt weergegeven wanneer de gebruiker op de link [Faire la simulation] klikt:
![]() |
- in [1] klikt de gebruiker op de link [Faire la simulation];
- in [2] verschijnt de simulatie onder het formulier.
We passen de deelweergave [Formulaire.cshtml], waarin het formulier wordt weergegeven, als volgt aan:
<h2>Formulaire</h2>
<div id="simulation" />
Op regel 3 maken we een regio met id [simulation] aan om het fragment van de simulatie in onder te brengen.
We maken de volgende deelweergave [Simulation.cshtml] aan:
![]() |
De inhoud van de weergave [Simulation.cshtml] is als volgt:
<hr />
<h2>Simulation</h2>
Nu moeten we de JavaScript-code schrijven die de klik op de link [Faire la simulation] afhandelt. We volgen de werkwijze die in paragraaf 7.6.5 wordt beschreven. Laten we eerst eens kijken naar de code HTML van de link in [_Layout.cshtml]:
<a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
</a>
We zien dat een klik op de link [Faire la simulation] de uitvoering van de functie JS [faireSimulation] in gang zet. Deze functie wordt opgeslagen in het bestand [myScripts.js], samen met de andere functies JS die nodig zijn voor de toepassing:
// globale variabelen
var loading;
var content;
function faireSimulation() {
// we voeren handmatig een Ajax-aanroep uit
...
}
function effacerSimulation() {
// de invoer in het formulier wordt gewist
...
}
function enregistrerSimulation() {
// handmatig een Ajax-verzoek uitvoeren
...
}
function voirSimulations() {
// we voeren handmatig een Ajax-aanroep uit
...
}
function retourFormulaire() {
// er wordt handmatig een Ajax-verzoek gedaan
...
}
function terminerSession() {
...
}
// bij het laden van het document
$(document).ready(function () {
// de referenties van de verschillende componenten van de pagina worden opgehaald
loading = $("#loading");
content = $("#content");
});
- regels 35-39: de functie JQuery die bij het opstarten van de toepassing wordt uitgevoerd;
- regels 37-38: de globale variabelen van de regels 2 en 3 worden geïnitialiseerd.
Ter herinnering: de elementen met id [loading] en [content] zijn gedefinieerd in de masterpagina [_Layout.cshtml] (regels 14 en 21 hieronder):
<!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>
Opdracht: schrijf, volgens de procedure beschreven in paragraaf 7.6.5, de functie JS [faireSimulation]. Deze functie zal een Ajax-aanroep van het type POST doen naar de actie [/Pam/FaireSimulation]. Er worden voorlopig geen gegevens verzonden. Deactie [/Pam/FaireSimulation] stuurt de deelweergave [Simulation.cshtml] terug naar de functie JS [faireSimulation], die vervolgens deze gegevensstroom HTML in het met id [simulation] van het formulier.
Test de link [Faire la simulation] in uw applicatie.
9.9.4. De Ajax-aanroep [enregistrerSimulation]
De link [Enregistrer la simulation] is als volgt gedefinieerd in [_Layout.cshtml]:
<a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la simulation<br />
</a>
Opdracht: volg de bovenstaande stappen en schrijf de functie JS [enregistrerSimulation]. Deze functie zal een Ajax-aanroep van het type POST naar de actie [/Pam/EnregistrerSimulation] verzenden. Er worden voorlopig geen gegevens verzonden. Deactie [/Pam/EnregistrerSimulation] stuurt de deelweergave [Simulations.cshtml] terug naar de functie JS [enregistrerSimulation], die vervolgens deze stream HTML in het met id [content] van de masterpagina.
De weergave [Simulations.cshtml] ziet er als volgt uit:
![]() |
De inhoud ervan is als volgt:
<h2>Simulations</h2>
Hier volgt een voorbeeld van de uitvoering:
![]() | ![]() |
9.9.5. De Ajax-aanroep [voirSimulations]
De link [Voir les simulations] is als volgt gedefinieerd in [_Layout.cshtml]:
<a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
</a>
Opdracht: volg de bovenstaande procedure en schrijf de functie JS [voirSimulations]. Deze functie zal een Ajax-aanroep van het type POST naar de actie [/Pam/VoirSimulations] verzenden. Er worden voorlopig geen gegevens verzonden. Deactie [/Pam/VoirSimulations] stuurt de deelweergave [Simulations.cshtml] terug naar de functie JS [voirSimulations], die vervolgens deze stream HTML in het met id [content] van de masterpagina.
De weergave [Simulations.cshtml] is dezelfde die al in de vorige vraag werd gebruikt.
Hier volgt een uitvoervoorbeeld:
![]() |
9.9.6. De Ajax-aanroep [retourFormulaire]
De link [Retour au formulaire de simulation] is als volgt gedefinieerd in [_Layout.cshtml]:
<a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de simulation<br />
</a>
Opdracht: volg de bovenstaande procedure en schrijf de functie JS [retourFormulaire]. Deze functie zal een Ajax-aanroep van het type POST naar de actie [/Pam/Formulaire] verzenden. Er worden voorlopig geen gegevens verzonden. Deactie [/Pam/Formulaire] stuurt de deelweergave [Formulaire.cshtml] terug naar de functie JS [retourFormulaire], die vervolgens deze stream HTML in het met id [content] van de masterpagina.
De weergave [Formulaire .cshtml] is al gedefinieerd. Hier volgt een uitvoervoorbeeld:
![]() |
9.9.7. De Ajax-aanroep [terminerSession]
De link [Terminer la session] is als volgt gedefinieerd in [_Layout.cshtml]:
<a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
</a>
Opdracht: volg de vorige stappen en schrijf de functie JS [terminerSession]. Deze functie zal een Ajax-aanroep van het type POST naar de actie [/Pam/TerminerSession] verzenden. Er worden voorlopig geen gegevens verzonden. Deactie [/Pam/TerminerSession] stuurt de deelweergave [Formulaire.cshtml] terug naar de functie JS [terminerSession], die vervolgens deze stream HTML in het met id [content] van de masterpagina.
Hier volgt een uitvoervoorbeeld:
![]() |
9.9.8. De functie JS [effacerSimulation]
De koppeling [Effacer la simulation] is als volgt gedefinieerd in [_Layout.cshtml]:
<a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
</a>
De functie JS [effacerSimulation] heeft als doel:
- het fragment [Simulation] te verbergen, indien dit bestaat;
- de invoervelden van het formulier terug te zetten in de toestand waarin ze zich bevonden bij het eerste laden van de applicatie (wanneer er invoervelden zijn – op dit moment zijn die er niet).
Opdracht: schrijf de functie JS [effacerSimulation]. Er is hier geen sprake van een Ajax-aanroep. Wat er gebeurt, vindt plaats binnen de browser en heeft geen betrekking op de server.
Hier is een voorbeeld van de uitvoering:
![]() |
9.9.9. Beheer van de navigatie tussen schermen
Op dit moment worden de links nog steeds weergegeven. We gaan nu de weergave ervan regelen met een JavaScript-functie. Laten we eerst nog eens de code van de zes JavaScript-links in [_Layout.cshtml] bekijken:
<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 hebben een attribuut [id] waarmee we ze in JavaScript kunnen beheren. We passen de methode JS, die bij het laden van de pagina wordt uitgevoerd, als volgt aan:
// globale variabelen
var loading;
var content;
var lnkFaireSimulation;
var lnkEffacerSimulation
var lnkEnregistrerSimulation;
var lnkTerminerSession;
var lnkVoirSimulations;
var lnkRetourFormulaire;
var options;
...
// bij het laden van het document
$(document).ready(function () {
// de referenties van de verschillende componenten van de pagina worden opgehaald
loading = $("#loading");
content = $("#content");
// de menu-links
lnkFaireSimulation = $("#lnkFaireSimulation");
lnkEffacerSimulation = $("#lnkEffacerSimulation");
lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
lnkVoirSimulations = $("#lnkVoirSimulations");
lnkTerminerSession = $("#lnkTerminerSession");
lnkRetourFormulaire = $("#lnkRetourFormulaire");
// we plaatsen ze in een tabel
options = [lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
// bepaalde elementen van de pagina worden verborgen
loading.hide();
// het menu wordt vastgezet
setMenu([lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]);
});
- regels 19-24: we halen de verwijzingen naar de zes links op. Deze verwijzingen worden in regels 4-9 als globale variabelen gedefinieerd;
- regel 26: de array [options] wordt geïnitialiseerd met de zes verwijzingen. Deze array wordt op regel 10 als globale variabele gedefinieerd;
- regel 28: de geanimeerde afbeelding die aangeeft dat er wordt gewacht tot de Ajax-verzoeken zijn voltooid, wordt verborgen;
- regel 30: de links [lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession] worden weergegeven. De overige worden verborgen.
De functie JS [setMenu] is als volgt:
function setMenu(show) {
// de links in de tabel weergeven [show]
...
}
Opdracht: schrijf de functie JS [setMenu].
Als T een array van links is:
- T.length is het aantal links;
- T[i] is link nr. i;
- T[i].show() geeft link nr. i weer;
- T[i].hide() verbergt link nr. i.
Met deze nieuwe functies JS wordt bij het opstarten de volgende pagina weergegeven:
![]() |
Pas de functies JS en [faireSimulation, effacerSimulation, enregistrerSimulation, voirSimulations, retourFormulaire, terminerSession] aan om de volgende schermen te krijgen:
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Nu het model APU en de navigatielinks zijn geïmplementeerd, kunnen we verdergaan met het schrijven van de server-side acties en weergaven. Tijdens de volgende stappen zult u merken dat sommige van de Ajax-links die nu werken, niet meer werken. Dit komt doordat u de deelweergaven gaat aanpassen die naar de client worden verzonden. Naarmate je de verschillende acties en weergaven aan de serverzijde bouwt, zullen de Ajax-links aan de clientzijde weer werken zoals je ze hebt ingesteld.
9.10. Stap 4: de serveractie [Index] schrijven
Momenteel zien we bij het opstarten van de applicatie het volgende scherm:
![]() |
In plaats van dit scherm zouden we graag het volgende scherm willen zien:
![]() |
Het is de actie [Index] die deze pagina moet genereren. Enkele opmerkingen:
- de pagina bevat een formulier met drie invoervelden:
- de werknemer wiens salaris wordt berekend,
- het aantal gewerkte uren,
- het aantal gewerkte dagen;
- het formulier wordt verzonden via de link [Faire la simulation];
- de geldigheid van de invoervelden [Heures travaillées] en [Jours travaillés] moet worden gecontroleerd;
- de lijst met werknemers is afkomstig uit de laag [métier] die we eerder hebben opgebouwd.
Laten we de huidige code van de actie [Index] nog eens bekijken:
[HttpGet]
public ViewResult Index()
{
return View();
}
die van de weergave [Index.cshtml] die deze actie weergeeft:
@{
ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")
en die van de deelweergave [Formulaire.cshtml]:
<h2>Formulaire</h2>
Op deze drie plaatsen zullen wijzigingen plaatsvinden.
9.10.1. Het formuliermodel
Laten we teruggaan naar de verwerkingsketen van URL [/Pam/Index]:
![]() |
- de aanvraag HTTP van de klant komt binnen als [1];
- in [2] wordt de informatie in het verzoek omgezet in het actiemodel [3], dat als invoer dient voor de actie [4];
- in [4] genereert de actie op basis van dit model een antwoord. Dit antwoord bestaat uit twee onderdelen: een weergave V [6] en het model M van deze weergave [5];
- de weergave V [6] gebruikt haar model M [5] om het antwoord HTTP voor de klant te genereren.
De actie waarin we geïnteresseerd zijn, is de actie [Index], die op dit moment als volgt is:
[HttpGet]
public ViewResult Index()
{
return View();
}
De actie [Index] geeft geen sjabloon door aan de weergave [Index.cshtml]. Deze kan dus de lijst met medewerkers niet weergeven. Deze lijst kan worden opgevraagd bij de laag [métier]. Hiervoor moet het project [pam-web-01] een verwijzing bevatten naar het project [pam-metier-simule]. We maken deze verwijzing nu aan:
![]() |
- in [1], klik met de rechtermuisknop op [References] van het project [pam-web-01] en vervolgens op [Ajouter une référence];
- in [2] de optie [Solution] selecteren en vervolgens het project [pam-metier-simule] in [3];
- in [4] is het project [pam-metier-simule] toegevoegd aan de referenties van het project [pam-web-01].
9.10.2. Het applicatiemodel
In paragraaf 4.10, op pagina 78, hebben we de belangrijke begrippen applicatiemodel en sessiemodel geïntroduceerd. We gaan deze nu gebruiken. Ter herinnering: in het model plaatsen we:
- in het applicatiemodel: alleen-lezen gegevens voor alle gebruikers. Dit model vormt een gedeeld geheugen voor alle verzoeken van alle gebruikers;
- in het sessiemodel: gegevens die zowel gelezen als geschreven kunnen worden voor een bepaalde gebruiker. Dit model vormt een gedeeld geheugen voor alle verzoeken van deze gebruiker.
Wat gaan we in het applicatiemodel opnemen? Laten we terugkeren naar de architectuur ervan:
![]() |
De laag [web] bevat een verwijzing naar de laag [métier]. Deze kan door alle gebruikers worden gedeeld. We kunnen deze dus in het applicatiemodel opnemen. Daarnaast gaan we ervan uit dat de lijst met medewerkers niet verandert. Deze kan dus eenmalig worden ingelezen en vervolgens met alle gebruikers worden gedeeld. We stellen dan het volgende applicatiemodel voor:
![]() |
De code van de klasse [ApplicationModel] zou als volgt kunnen zijn:
using Pam.Metier.Entites;
using Pam.Metier.Service;
namespace PamWeb.Models
{
public class ApplicationModel
{
// --- gegevens op applicatieniveau ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
}
}
Om een vervolgkeuzelijst in een weergave weer te geven, schrijft men iets als het volgende:
<!-- de vervolgkeuzelijst -->
<tr>
<td>Liste déroulante</td>
<td>@Html.DropDownListFor(m => m.DropDownListField,
new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
</td>
</tr>
De methode [DropDownListFor] verwacht als tweede parameter een type SelectListItem[], dat hierboven was geleverd door een type [SelectList]. We moeten zo’n array samenstellen met de lijst van medewerkers. Aangezien de medewerkers niet veranderen, kan ook deze array in het applicatiemodel worden geplaatst. We passen dit als volgt aan:
using Pam.Metier.Entites;
using Pam.Metier.Service;
using System.Web.Mvc;
namespace Pam.Web.Models
{
public class ApplicationModel
{
// --- gegevens op applicatieniveau ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
}
}
Op welk moment moet dit sjabloon worden opgebouwd? Dat hebben we in paragraaf 4.10 laten zien. Dit gebeurt tijdens de uitvoering van de methode [Application_Start] uit het bestand [Global.asax]:
![]() |
De methode [Application_Start] ziet er momenteel als volgt uit:
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);
}
}
}
We passen deze als volgt aan:
using Pam.Metier.Entites;
using Pam.Metier.Service;
using PamWeb.Infrastructure;
using PamWeb.Models;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace pam_web_01
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// ----------Automatisch gegenereerd
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// -------------------------------------------------------------------
// ---------- specifieke configuratie
// -------------------------------------------------------------------
// gegevens op applicatieniveau
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
// instantie van de [métier]-laag
application.PamMetier = ...
// medewerkerslijst
application.Employes = ...
// elementen van de werknemerscombo
application.EmployesItems = ...
// modelbinder voor [ApplicationModel]
...
}
}
}
Opdracht: vul de code van de methode [Application_Start] aan. Alles wat u nodig hebt, vindt u in paragraaf 4.10. Neem de tijd om deze lange maar belangrijke paragraaf nog eens door te lezen.
Regel 33 bestaat in feite uit meerdere regels. Om een object van het type [SelectListItem] aan te maken, kunt u de volgende methode gebruiken:
new SelectListItem() { Text = unTexte, Value = uneValeur };
Deze [SelectListItem] wordt gebruikt om de volgende HTML-tag <option> te genereren:
uit de vervolgkeuzelijst. We zorgen ervoor dat:
- unTexte de voornaam gevolgd door de achternaam van de medewerker is;
- uneValeur het SS-nummer van de werknemer is.
In regel 35 hierboven hebt u de klasse [ApplicationModelBinder] nodig, zoals beschreven in paragraaf 4.10, pagina 82:
![]() |
9.10.3. De actiecode [Index]
Nu we een sjabloon voor de applicatie hebben gedefinieerd, kunnen we de actiecode [Index] als volgt aanpassen:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View();
}
- regel 4: het applicatiemodel is nu een parameter van de actie [Index]. In paragraaf 4.10 hebben we uitgelegd hoe deze parameter door het framework wordt geïnitialiseerd.
9.10.4. Het model van de weergave [Index.cshtml]
Nu heeft de actie [Index] toegang tot de medewerkers die in het applicatiemodel zijn opgeslagen. Deze moeten nu worden doorgegeven aan de weergave [Index.cshtml] die de actie gaat weergeven. We zouden een type [ApplicationModel] kunnen doorgeven als model voor de weergave [Index.cshtml], maar we zullen snel zien dat deze weergave andere informatie nodig heeft die niet in [ApplicationModel] staat. We gaan het volgende weergavesjabloon [IndexModel] gebruiken:
![]() |
namespace Pam.Web.Models
{
public class IndexModel
{
// toepassingsbereikgegevens
public ApplicationModel Application { get; set; }
}
}
- regel 6: [IndexModel] laadt het applicatiesjabloon.
De actie [Index] wordt als volgt:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
}
- regel 4: de standaardweergave [Index.cshtml] wordt weergegeven met als sjabloon een type [IndexModel] dat is geïnitialiseerd met de gegevens van het applicatiesjabloon.
We weten dat de weergave [Index.cshtml] een formulier moet weergeven:

Laten we teruggaan naar de verwerkingsketen van een verzoek:
![]() |
Voor de aanvraag [GET /Pam/Index]:
- is de actie [Index];
- het model van deze actie is [ApplicationModel];
- de weergave is [Index.cshtml];
- het model van deze weergave is [IndexModel].
Wanneer het formulier wordt verzonden, ontstaat er een vergelijkbare verwerkingsketen:
- de actie is degene die POST verwerkt;
- het sjabloon verzamelt de verzonden waarden, in dit geval:
- het nummer SS van de geselecteerde werknemer;
- het aantal gewerkte uren;
- het aantal gewerkte dagen;
We zouden een actiesjabloon kunnen maken waarin deze drie waarden worden samengevoegd. Het komt ook vaak voor dat het sjabloon dat is gebruikt om het formulier weer te geven, wordt hergebruikt. Dat gaan we hier doen. De klasse [IndexModel] wordt als volgt aangepast:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Pam.Web.Models
{
[Bind(Exclude = "Application")]
public class IndexModel
{
// gegevens over het toepassingsbereik
public ApplicationModel Application { get; set; }
// geboekte waarden
[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; }
}
}
- regels 13, 16, 18: de drie verzonden waarden. Merk op dat [joursTravaillés] is gedeclareerd als type [double], terwijl in werkelijkheid een geheel getal wordt verwacht. Het type [double] is geïntroduceerd om de validatie van dit veld aan de clientzijde te vergemakkelijken, aangezien de validatie van het type [int] problemen opleverde;
- regels 12, 14, 17: labels voor de methoden [Html.LabelFor] van de weergave die aan het model is gekoppeld;
- regel 15: een opmerking om het veld [HeuresTravaillées] met twee decimalen weer te geven;
- regel 5: er wordt aangegeven dat de eigenschap met de naam [Application] geen deel uitmaakt van de verzonden waarden.
9.10.5. De weergaven [Index.cshtml] en [Formulaire.cshtml]
De weergave [Index.cshtml] wordt weergegeven door de volgende actie [Index]:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
}
Interessant genoeg blijft de weergave [Index.cshtml] ongewijzigd:
@{
ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")
- de weergave specificeert geen sjabloon;
- regel 4: het bevat de deelweergave [Formulaire.cshtml], ook hier zonder dat er een sjabloon aan wordt doorgegeven. Tijdens het testen is gebleken dat het model [IndexModel], dat aan de weergave [Index.cshtml] is doorgegeven, impliciet werd doorgegeven aan de deelweergave [Formulaire.cshtml]. Deze laatste weergave zou nu de volgende vorm kunnen hebben:
@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" />
- regel 1: de weergave ontvangt een sjabloon van het type [IndexModel];
- regel 3: het formulier;
- regels 6-10: de kopteksten van de invoertabel;
- regels 12-14: de invoerregel;
- regels 15-17: eventuele foutmeldingen.
Opdracht: vul de code van de weergave [Formulaire.cshtml] aan. Gebruik daarbij de methoden [DropDownListFor, EditorFor, LabelFor, ValidationMessageFor] die in paragraaf 5.7 worden beschreven.
9.10.6. Test van de actie [Index]
We hebben alle elementen van de verwerkingsketen van URL en [/Pam/Index] geschreven:
![]() |
We testen de applicatie met [Ctrl-F5]:
![]() | ![]() |
U moet controleren of uw keuzelijst inderdaad is gevuld met de lijst van medewerkers die we hadden gedefinieerd in de gesimuleerde laag [métier].
9.11. Stap 5: invoervalidatie instellen
9.11.1. Het probleem
Hoewel we hier niets voor hebben gedaan, zijn er al validaties aan de clientzijde actief:
![]() |
![]() |
De validatie aan de clientzijde is standaard actief vanwege regel 3 hieronder in het bestand [Web.config] van de applicatie.
<appSettings>
...
<add key="ClientValidationEnabled" value="true" />
</appSettings>
Omdat in [IndexModel] het veld [JoursTravaillés] echter is gedeclareerd als type [double]:
public double JoursTravaillés { get; set; }
kan in dit veld een reëel getal worden ingevoerd:
![]() |
Bovendien kunnen in beide velden willekeurige waarden worden ingevoerd:
![]() |
Het sjabloon [IndexModel] van het formulier ziet er momenteel als volgt uit:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Pam.Web.Models
{
[Bind(Exclude = "Application")]
public class IndexModel
{
// gegevens over het toepassingsgebied
public ApplicationModel Application { get; set; }
// geboekte waarden
[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; }
}
}
Opdracht: verbeter dit sjabloon zodat:
- aangepaste foutmeldingen te krijgen;
- alleen reële waarden in het bereik [0,400] te accepteren voor het veld [HeuresTravaillées];
- alleen gehele getallen in het bereik [0,31] te accepteren voor het veld [JoursTravaillées];
We kunnen het voorbeeld uit paragraaf 7.6.2 gebruiken. Om te controleren of het aantal gewerkte dagen een geheel getal is, kunnen we een reguliere expressie gebruiken (zie voorbeelden in paragraaf 5.9.1).
Hier volgen enkele voorbeelden van wat er verwacht wordt:
![]() |
![]() |
![]() |
9.11.2. Invoer van reële getallen in het Franse formaat
In de huidige versie van de applicatie moet het aantal gewerkte uren een decimaal getal zijn in het Angelsaksische formaat (met een punt als decimaalteken). Het Franse formaat met een komma wordt niet geaccepteerd:
![]() |
Dit probleem is vastgesteld en opgelost in paragraaf 6.1.
Opdracht: volg de werkwijze uit de bovengenoemde paragraaf en breng de nodige wijzigingen aan zodat reële getallen in het Franse decimale formaat kunnen worden ingevoerd. Test uw applicatie.
Het vorige scherm ziet er nu als volgt uit:
![]() |
9.11.3. Validatie van het formulier via de JavaScript-link [Faire la simulation]
Momenteel kunnen ongeldige waarden worden verzonden, zoals blijkt uit de volgende reeks:
![]() |
![]() |
De aanwezigheid van de simulatie in [1] en de menuwijziging in [2] tonen aan dat het klikken op de link [Faire la simulation] het formulier heeft verzonden, terwijl de ingevoerde waarden ongeldig waren. Dit probleem is vastgesteld en opgelost in paragraaf 7.6.5.
Opdracht: volg de procedure die in de bovengenoemde paragraaf wordt beschreven en zorg ervoor dat de POST van de link [Faire la simulation] niet kan worden uitgevoerd als de ingevoerde waarden ongeldig zijn. Vergeet niet de cache van de browser te legen voordat u uw wijzigingen test.
Ter herinnering: het deeloverzicht [Formulaire.cshtml] genereert een formulier HTML met id [formulaire] (regel 1 hieronder):
@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
Dit kun je controleren door de broncode van het formulier in de browser weer te geven:
<div id="content">
<form action="/Pam/FaireSimulation" id="formulaire" method="post">
...
</form>
<div id="simulation" />
</div>
9.12. Stap 6: een simulatie uitvoeren
9.12.1. Het probleem
Wanneer we een simulatie uitvoeren, willen we het volgende resultaat verkrijgen:
![]() |
Het deeloverzicht [Simulation.cshtml] toont nu de loonstrook van een werknemer.
9.12.2. Het schrijven van de weergave [Simulation.cshtml]
De weergave [Simulation.cshtml] verandert als volgt:
@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>
- regel 1: de weergave [Simulation.cshtml] is gebaseerd op het type [FeuilleSalaire] dat in paragraaf 9.7.3 is gedefinieerd;
- de weergave maakt gebruik van de klassen [libellé, info, valeur] die zijn gedefinieerd in het stylesheet van de toepassing [Content / Site.css]:
.libellé {
background-color: azure;
margin: 5px;
padding: 5px;
}
.info {
background-color: antiquewhite;
margin: 5px;
padding: 5px;
}
.valeur {
background-color: beige;
padding: 5px;
margin: 5px;
}
Bovendien wordt, eveneens in [Site.css], de rijhoogte vastgelegd van de verschillende tabellen HTML in het gebied met id [simulation], precies daar waar de loonstrook wordt weergegeven:
#simulatie tabel tr {
height: 30px;
}
Opdracht: vul de weergave [Simulation.cshtml] aan.
Om het bedrag in euro's weer te geven, gebruiken we de methode [string.Format]:
De bovenstaande instructie geeft [somme] weer als geldwaarde [C] (Currency) met twee decimalen [C2].
Om deze weergave te testen, moet er een loonstrook worden aangeleverd. Deze moet worden aangeleverd door de actie [/Pam/FaireSimulation], die het doel is van de Ajax-aanroep van de link [Faire la simulation]. Momenteel ziet deze actie er als volgt uit:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
}
// een simulatie uitvoeren
[HttpPost]
public PartialViewResult FaireSimulation()
{
return PartialView("Simulation");
}
Hierboven geeft de actie [FaireSimulation] geen sjabloon door aan de weergave [Simulation.cshtml]. Deze moet er een loonstrook aan doorgeven. We weten dat de laag [métier] de loonstroken berekent. Deze laag [métier] is toegankelijk via het applicatiesjabloon [ApplicationModel] dat we in paragraaf 9.10.2 hebben gedefinieerd:
public class ApplicationModel
{
// --- gegevens over het toepassingsbereik ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
}
De laag [métier] is toegankelijk via de eigenschap van regel 5 hierboven. Om ervoor te zorgen dat de actie [FaireSimulation] toegang heeft tot de laag [métier], gaan we het applicatiemodel aan deze actie doorgeven, net zoals we dat hebben gedaan voor de actie [Index]. De code ziet er dan als volgt uit:
// een simulatie uitvoeren
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application)
{
return PartialView("Simulation");
}
Nu kunnen we binnen de actie een fictieve loonstrook berekenen. De code ziet er als volgt uit:
// een simulatie uitvoeren
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application)
{
FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
return PartialView("Simulation", feuilleSalaire);
}
- In regel 5 wordt een fictief salaris berekend. De eerste parameter is een bestaand nummer SS. Dit is gedefinieerd in de klasse [métier] die in paragraaf 9.7.5 is gesimuleerd. De tweede parameter is het aantal gewerkte uren en de derde het aantal gewerkte dagen;
- regel 6: deze loonstaat wordt als sjabloon doorgegeven aan de weergave [Simulation.cshtml].
We zijn nu klaar om de weergave [Simulation.cshtml] te testen:
![]() |
We voeren geen gegevens in en vragen om de simulatie. We krijgen dan het volgende resultaat:
![]() |
9.12.3. Berekening van het werkelijke salaris
Onze huidige actie [FaireSimulation] berekent altijd dezelfde loonstrook:
// een simulatie uitvoeren
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application)
{
FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
return PartialView("Simulation", feuilleSalaire);
}
Deze houdt geen rekening met de ingevoerde gegevens:
- de werknemer voor wie het loon wordt berekend;
- het aantal gewerkte uren;
- het aantal gewerkte dagen.
De ingevoerde waarden komen als volgt terecht in de actie [FaireSimulation]:
- de gebruiker klikt op de link [Faire la simulation]. Dit activeert de uitvoering van de functie JS [faireSimulation] die we al hebben geschreven;
- De functie JS [faireSimulation] voert vervolgens een Ajax-aanroep uit naar de serveractie [/Pam/FaireSimulation], waaraan we momenteel werken. Op dit moment stuurt de functie JS [faireSimulation] geen informatie door naar de serveractie. Deze functie moet de door de gebruiker ingevoerde waarden doorgeven;
- de serveractie [/Pam/FaireSimulation] haalt de ingevoerde waarden op uit de gegevens die door de functie JS en [faireSimulation] zijn verzonden.
Laten we beginnen met punt 2: de functie JS [faireSimulation] moet de door de gebruiker ingevoerde waarden doorsturen naar de serveractie [/Pam/FaireSimulation].
Opdracht: vul de functie JS [faireSimulation] aan, zodat deze de door de gebruiker ingevoerde waarden verstuurt. Je kunt daarbij gebruikmaken van het voorbeeld uit paragraaf 7.6.5, waar dit probleem is behandeld.
Laten we nu punt 3 hierboven behandelen. De serveractie [/Pam/FaireSimulation] moet de waarden ophalen die door de functie JS [faireSimulation] zijn gepost.
Opdracht: vul de servermethode [FaireSimulation] aan, zodat deze het salaris berekent met de waarden die door de functies JS en [faireSimulation] zijn doorgegeven. We kunnen opnieuw gebruikmaken van het voorbeeld uit paragraaf 7.6.5, waar dit probleem is behandeld. Voorlopig gaan we ervan uit dat het model op basis van de doorgegeven waarden nog steeds geldig is.
Tip: de serveractie [FaireSimulation] verloopt als volgt:
// een simulatie uitvoeren
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
{
// het actiemodel aanmaken
...
// we proberen de in dit model geboekte waarden op te halen
...
// het salaris wordt berekend
FeuilleSalaire feuilleSalaire = ...
// de loonstrook wordt weergegeven
return PartialView("Simulation", feuilleSalaire);
}
Hier volgt een voorbeeld van de uitvoering:
![]() |
We kiezen [Justine Laverti]. We krijgen dan het volgende resultaat:
![]() |
We hebben inderdaad de fictieve loonstrook van [Justine Laverti] verkregen. Eerder werd de enige loonstrook berekend die van [Marie Jouveinal] was. De geboekte waarde voor de keuze van de werknemer is dus verwerkt. Over het aantal uren en het aantal dagen kunnen we niets zeggen, aangezien onze gesimuleerde laag [métier] hiermee geen rekening houdt.
9.12.4. Foutbeheer
Laten we het volgende voorbeeld eens bekijken:
![]() |
- in [1] kiezen we een medewerker die niet bestaat (zie de definitie van de gesimuleerde laag [métier] in paragraaf 9.7.5;
- in [2] voeren we de simulatie uit;
- in [3] hieronder krijgen we een foutpagina te zien.
![]() |
Wat is er gebeurd?
De functie JS [faireSimulation] is uitgevoerd. De code ziet er als volgt uit:
function faireSimulation() {
...
// er wordt handmatig een Ajax-aanroep gedaan
$.ajax({
url: '/Pam/FaireSimulation',
...
beforeSend: function () {
// wachtindicator brandt
loading.show();
},
success: function (data) {
...
},
error: function (jqXHR) {
// foutmelding weergeven
simulation.html(jqXHR.responseText);
simulation.show();
},
complete: function () {
// wachtlampje uit
loading.hide();
}
});
// menu
setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
De Ajax-aanroep is mislukt en de functie op de regels 14-18 is uitgevoerd. De door de server teruggestuurde foutpagina [jqXHR.responseText] is weergegeven. Deze is vrij gedetailleerd. De gesimuleerde laag [métier] heeft een uitzondering gegenereerd omdat het opgegeven nummer SS niet overeenkomt met dat van een bestaande medewerker (zie de code van de gesimuleerde laag [métier] in paragraaf 9.7.5). We moeten dit geval op de juiste manier afhandelen.
We gaan een deelweergave [Erreurs.chtml] aanmaken die naar de client JS wordt teruggestuurd telkens wanneer er aan de serverzijde een fout wordt gedetecteerd:
![]() |
De code van de deelweergave [Erreurs.chtml] is als volgt:
@model IEnumerable<string>
<hr />
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
@foreach (string msg in Model)
{
<li>@msg</li>
}
</ul>
- regel 1: de weergave ontvangt als sjabloon een lijst met foutmeldingen;
- regels 5-10: deze worden weergegeven in een lijst HTML;
Laten we nu de code van de serveractie [FaireSimulation] als volgt aanpassen:
// een simulatie uitvoeren
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
{
...
// het salaris wordt berekend
FeuilleSalaire feuilleSalaire = null;
Exception exception=null;
try
{
// salarisberekening
feuilleSalaire = ...
}
catch (Exception ex)
{
exception = ex;
}
// fout?
if (exception == null)
{
// de loonstrook weergeven
return PartialView("Simulation", feuilleSalaire);
}
else
{
// de foutpagina weergeven
return PartialView("Erreurs", Static.GetErreursForException(exception));
}
}
- regels 9-17: de salarisberekening vindt nu plaats in een try/catch;
- regel 27: als er een fout is opgetreden, wordt de deelweergave [Erreurs.cshtml] weergegeven, met als sjabloon de lijst met foutmeldingen die wordt geleverd door de statische methode [Static.GetErreursForException(exception)].
In de klasse [Static] worden twee statische hulpfuncties [1] gebundeld:
![]() |
using System;
using System.Collections.Generic;
using System.Web.Mvc;
namespace PamWeb.Infrastructure
{
public class Static
{
// lijst met foutmeldingen van een uitzondering
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;
}
// lijst met foutmeldingen in verband met een ongeldig sjabloon
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;
}
// de foutmelding met betrekking tot een element van het actiemodel
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;
}
}
}
- regels 10-19: de statische functie [GetErreursForException] geeft de lijst met fouten van een uitzonderingsstapel weer;
- regels 22-36: de statische functie [GetErreursForModel] geeft de lijst met fouten van een ongeldig actiemodel weer. De code van deze functie en die van de privémethode [getErrorMessageFor] (regels 39-54) is al eerder aan bod gekomen.
Nu we dit hebben gedaan, kunnen we het foutgeval opnieuw testen:
![]() |
- in [1] kiezen we de medewerker die niet bestaat;
- in [2] voeren we de simulatie uit;
- in [3] wordt de nieuwe foutpagina opgehaald.
Laten we teruggaan naar de serveractie [FaireSimulation]:
// een simulatie uitvoeren
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
{
// het actie-model aanmaken
IndexModel modèle = new IndexModel() { Application = application};
// er wordt geprobeerd de in het model geplaatste waarden op te halen
TryUpdateModel(modèle, data);
// het salaris wordt berekend
...
}
In regel 8 werken we het model van regel 6 bij met de waarden die via de Ajax-aanroep zijn verzonden. We controleren de geldigheid van het model niet. Dit moet wel gebeuren, omdat we niet kunnen weten waar de verzonden waarden vandaan komen. Iemand zou een POST in elkaar kunnen hebben geknutseld en ons ongeldige gegevens kunnen sturen.
Opdracht: volg het model dat we voor het uitzonderingsgeval hebben ontwikkeld en pas de serveractie [FaireSimulation] aan, zodat er een foutpagina wordt verzonden wanneer de verzonden gegevens ongeldig zijn. Hiervoor gebruiken we de statische methode [GetErreursForModel] van de klasse [Static].
Hoe test je deze wijziging? In paragraaf 9.11.3 heb je ervoor gezorgd dat de functie JS [faireSimulation] de ingevoerde waarden niet verwerkt (POST) als deze ongeldig waren. Zet de regels die hiervoor zorgen in commentaar en voer vervolgens de volgende test uit:
![]() |
- in [1] voeren we de simulatie uit met ongeldige waarden;
- in [2] krijgen we inderdaad de foutpagina te zien die we zojuist hebben opgebouwd, wat bewijst dat de validaties aan de serverzijde goed hebben gewerkt.
Vergeet vervolgens niet de regels die u zojuist hebt uitgecommentarieerd in de functie JS [faireSimulation] weer te activeren.
9.13. Stap 7: een gebruikerssessie opzetten
Met de toepassing [Simulateur de calcul de paie] kan de gebruiker via de link [Faire la simulation] diverse loonsimulaties uitvoeren, deze via de link [Enregistrer la simulation] opslaan, ze weer te geven via de link [Voir les simulations] en ze te verwijderen via de link [Retirer la simulation]. We weten dat er tussen twee opeenvolgende verzoeken van de gebruiker geen geheugen is, tenzij we dit aanmaken via het sessiemechanisme (zie paragraaf 4.10). Het is hier vrij duidelijk dat we in de sessie de lijst moeten bewaren van de simulaties die de gebruiker in de loop van de tijd heeft opgeslagen. Er zijn nog andere gegevens die moeten worden opgeslagen: wanneer de gebruiker een simulatie uitvoert, wordt deze alleen in de lijst met simulaties opgeslagen als de gebruiker hierom vraagt via de link [Enregistrer la simulation]. Wanneer hij dat doet, moeten we de berekende simulatie uit de vorige aanvraag kunnen terugvinden. Daarom wordt deze ook in de sessie opgeslagen. Ten slotte gaan we de simulaties nummeren vanaf 1. Om een nieuwe simulatie correct te nummeren, moeten we het nummer van de vorige simulatie hebben bewaard, ook hier weer in de sessie.
In paragraaf 4.10 hebben we het concept van een sessiemodel als invoerparameter van een actie geïntroduceerd, zodat deze toegang heeft tot de sessie. We zullen dit concept opnieuw gebruiken. We raden u aan de betreffende paragraaf nog eens door te lezen als dit begrip u onduidelijk is.
We maken de volgende klasse [SessionModel] aan:
![]() |
De code ervan is als volgt:
using Pam.Web.Models;
using System.Collections.Generic;
namespace Pam.Web.Models
{
public class SessionModel
{
// de lijst met simulaties
public List<Simulation> Simulations { get; set; }
// nummer van de volgende simulatie
public int NumNextSimulation { get; set; }
// de laatste simulatie
public Simulation Simulation { get; set; }
// ontwikkelaar
public SessionModel()
{
// lege lijst met simulaties
Simulations = new List<Simulation>();
// nummer van de volgende simulatie
NumNextSimulation = 1;
}
}
}
De klasse [Simulation] in de regels 9 en 13 zal informatie over een simulatie opslaan. Wat moeten we opslaan? De koppeling [Faire la simulation] berekent een loonstrook van het type [FeuilleSalaire]. Het lijkt logisch om deze in de simulatie op te nemen. Daarnaast moeten we de gegevens opslaan die tot deze loonstrook hebben geleid:
- de geselecteerde werknemer. Deze is te vinden in het veld [FeuilleSalaire.Employe]. Het is dus niet nodig om deze nogmaals op te slaan;
- het aantal gewerkte uren en dagen. Deze gegevens staan niet in het type [FeuilleSalaire]. We moeten ze dus opslaan.
Ten slotte wordt elke simulatie aangeduid met een nummer. We zouden dus kunnen uitgaan van de volgende klasse [Simulation]:
using Pam.Metier.Entites;
namespace Pam.Web.Models
{
public class Simulation
{
// nummer van de simulatie
public int Num { get; set; }
// het aantal gewerkte uren
public double HeuresTravaillées { get; set; }
// aantal gewerkte dagen
public int JoursTravaillés { get; set; }
// de loonstrook
public FeuilleSalaire FeuilleSalaire { get; set; }
}
}
De serveractie [FaireSimulation] moet, naast het berekenen van een loonstrook, een simulatie aanmaken en deze in de sessie plaatsen. Hiervoor ontvangt ze als parameter het sessiemodel:
// een simulatie uitvoeren
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, SessionModel session, FormCollection data)
{
// het actie-sjabloon aanmaken
IndexModel modèle = new IndexModel() { Application = application };
// we proberen de in het sjabloon ingevoerde waarden op te halen
TryUpdateModel(modèle, data);
// Is het model geldig?
if (!ModelState.IsValid)
{
// de foutpagina wordt weergegeven
return PartialView("Erreurs", Static.GetErreursForModel(ModelState));
}
// het salaris wordt berekend
FeuilleSalaire feuilleSalaire = null;
Exception exception = null;
try
{
// salarisberekening
feuilleSalaire = application.PamMetier.GetSalaire(modèle.SS, modèle.HeuresTravaillées, (int)modèle.JoursTravaillés);
}
catch (Exception ex)
{
exception = ex;
}
// fout?
if (exception != null)
{
// de foutpagina wordt weergegeven
return PartialView("Erreurs", Static.GetErreursForException(exception));
}
// er wordt een simulatie aangemaakt en in de sessie geplaatst
session.Simulation = ...
// de loonstrook wordt weergegeven
return PartialView("Simulation", feuilleSalaire);
}
- regel 3: de actie ontvangt het sessiemodel als parameter;
Opdracht 1: vul de code van de actie aan, regel 34
Opdracht 2: volg de procedure uit paragraaf 4.10 en zorg ervoor dat de parameter [SessionModel session] van de actie correct wordt geïnitialiseerd door het framework. Als er niets wordt gedaan, ontstaat er een pointer null voor deze parameter.
9.14. Stap 8: een simulatie opslaan
9.14.1. Het probleem
Wanneer we een simulatie hebben uitgevoerd, kunnen we deze opslaan:
![]() |

Het deeloverzicht [Simulations.cshtml] toont nu de lijst met simulaties die door de gebruiker zijn uitgevoerd. Ter herinnering: de berekende loonstrook is fictief.
9.14.2. Schrijven van de serveractie [EnregistrerSimulation]
De Ajax-link [Enregistrer la simulation] roept de serveractie [EnregistrerSimulation] aan, waarvan de code tot nu toe als volgt was:
[HttpPost]
public PartialViewResult EnregistrerSimulation()
{
return PartialView("Simulations");
}
Deze wordt als volgt aangepast:
// een simulatie opslaan
[HttpPost]
public PartialViewResult EnregistrerSimulation(SessionModel session)
{
// de laatst uitgevoerde simulatie wordt opgeslagen in de lijst met simulaties van de sessie
...
// het nummer van de volgende simulatie in de sessie wordt verhoogd
...
// de lijst met simulaties wordt weergegeven
...
}
- regel 1: de actie [EnregistrerSimulation] moet toegang hebben tot de sessie. Daarom heeft deze actie het sessiemodel als parameter.
Taak: de serveractie [EnregistrerSimulation] aanvullen.
9.14.3. Schrijven van de deelweergave [Simulations.cshtml]
De voorgaande actie [EnregistrerSimulation] zorgt ervoor dat de deelweergave [Simulations.cshtml] wordt weergegeven, met als sjabloon de lijst met simulaties die door de gebruiker zijn uitgevoerd. De code hiervoor is als volgt:
@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>
...
}
Opdracht 1: vul de code van de deelweergave [Simulations.cshtml] aan. Gebruik hiervoor de tabel HTML om de simulaties weer te geven. Je kunt daarbij gebruikmaken van de voorbeelden uit paragraaf 5.4.
Opmerking: de link [retirer] van elke simulatie in de tabel HTML zal een JavaScript-link zijn in de volgende vorm:
waarbij N het nummer van de simulatie is.
Opdracht 2: test je applicatie door simulaties uit te voeren. Om dit te doen, voer je de volgende stappen herhaaldelijk uit: 1) laad de applicatiepagina via [F5], 2) voer een simulatie uit, 3) sla deze op. De simulaties worden in de sessie opgeslagen, wat zichtbaar zou moeten zijn in de weergave [Simulations.cshtml].
Opdracht 3: verbeter de deelweergave [Simulations.cshtml] zodanig dat de kleuren van de rijen in de tabel HTML afwisselend worden weergegeven.

De rijen <tr> van de tabel HTML, de klassen CSS, [pair] en [impair] toegewezen, zoals gedefinieerd in het stylesheet [/Content/Site.css]:
.impair {
background-color: beige;
}
.pair {
background-color: lightsteelblue;
}
9.15. Stap 9: terugkeren naar het invoerformulier
9.15.1. Het probleem
Zodra we de lijst met simulaties hebben ontvangen, kunnen we terugkeren naar het invoerformulier, wat al een tijdje niet meer mogelijk was:


9.15.2. Serveractie [Formulaire] schrijven
De Ajax-link [Retour au formulaire de simulation] roept de serveractie [Formulaire] aan, waarvan de code tot nu toe als volgt was:
[HttpPost]
public PartialViewResult Formulaire()
{
return PartialView("Formulaire");
}
De deelweergave [Formulaire] die hierdoor wordt weergegeven, verwacht een sjabloon [IndexModel] (regel 1 hieronder):
@model Pam.Web.Models.IndexModel
@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
<div id="simulation" />
Daarom werkte de link [Retour au formulaire de simulation] niet meer.
Taak: de nieuwe versie van de serveractie [Formulaire] schrijven (2 regels herschrijven) en vervolgens testen.
9.15.3. Wijziging van de JavaScript-functie [retourFormulaire]
Met de eerder aangebrachte wijziging kunnen we nu terugkeren naar het formulier, maar dan treedt er een fout op:
![]() |
- in [1] keert men terug naar het invoerformulier;
- in [2] voeren we een simulatie uit met foutieve invoer. We ontdekken dan dat de validaties aan de clientzijde niet meer werken. Hier is de server aangeroepen en heeft deze een foutpagina teruggestuurd dankzij het werk dat in paragraaf 9.12.4 is verricht.
Deze afwijking is geïdentificeerd en verholpen in paragraaf 7.6.7.
Opdracht: volg de werkwijze uit paragraaf 7.6.7, corrigeer de JavaScript-functie [retourFormulaire] en voer vervolgens tests uit om te controleren of de client-side validators weer werken.
9.16. Stap 10: bekijk de lijst met simulaties
9.16.1. Het probleem
Wanneer je met het simulatieformulier werkt, kun je de lijst met uitgevoerde simulaties bekijken:
![]() | ![]() |
9.16.2. Code van de serveractie [VoirSimulations]
De Ajax-link [Voir les simulations] roept de serveractie [VoirSimulations] op, waarvan de code tot nu toe als volgt was:
// de simulaties bekijken
[HttpPost]
public PartialViewResult VoirSimulations()
{
return PartialView("Simulations");
}
De deelweergave [Simulations] die hier wordt weergegeven, verwacht een sjabloon [IEnumerable<Simulation>] (regel 1 hieronder):
@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>
...
}
Daarom werkte de link [Voir les simulations] niet meer.
Taak: schrijf de nieuwe versie van de serveractie [VoirSimulations] (2 regels herschrijven) en voer vervolgens de tests uit.
9.17. Stap 11: de sessie afsluiten
9.17.1. Het probleem
De sessie van de gebruiker kan op elk moment worden beëindigd met de link [Ajax] [Terminer la session]. Dit heeft tot gevolg dat de huidige sessie wordt beëindigd en er een nieuwe wordt gestart. Bovendien keert men terug naar het formulier:
![]() |
![]() |
- in [1] hebben we twee simulaties uitgevoerd en vervolgens de sessie beëindigd;
- in [2] zijn we teruggekeerd naar het invoerformulier. We willen de simulaties bekijken;
- in [3] is de lijst met simulaties nu leeg vanwege de sessiewisseling.
9.17.2. Code van de serveractie [TerminerSession]
De Ajax-link [Terminer la session] roept de serveractie [TerminerSession] aan, waarvan de code tot nu toe als volgt was:
// de sessie beëindigen
[HttpPost]
public PartialViewResult TerminerSession()
{
return PartialView("Formulaire");
}
De deelweergave [Formulaire] die hier wordt weergegeven, verwacht een sjabloon [IndexModel] (regel 1 hieronder):
@model Pam.Web.Models.IndexModel
@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
<div id="simulation" />
Daarom werkte de link [Terminer la session] niet meer.
Taak: schrijf de nieuwe versie van de serveractie [TerminerSession] (2 regels herschrijven) en voer vervolgens de tests uit.
Opmerking: om de sessie in de actie te beëindigen, schrijf je:
9.17.3. Wijziging van de JavaScript-functie [terminerSession]
Met de eerder aangebrachte wijziging kunnen we nu terugkeren naar het formulier, maar dan treedt er een fout op, die eerder is beschreven in paragraaf 9.15.3.
Opdracht: volg dezelfde werkwijze als in paragraaf 9.15.3, corrigeer de JavaScript-functie [terminerSession] en voer vervolgens tests uit om te controleren of de client-side validaties weer werken.
9.18. Stap 12: de simulatie wissen
9.18.1. Het probleem
Als je een simulatie hebt uitgevoerd, kun je deze verwijderen met de JavaScript-link [Effacer la simulation]:
![]() | ![]() |
9.18.2. Het schrijven van de klantactie [effacerSimulation]
De JavaScript-functie [effacerSimulation] heeft momenteel de volgende code:
function effacerSimulation() {
// de invoer in het formulier wordt gewist
// ...
// de simulatie wordt verborgen indien deze bestaat
$("#simulation").hide();
// menu
setMenu([lnkFaireSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
Opdracht: vul deze code aan. Je kunt je laten inspireren door het voorbeeld in paragraaf 7.6.6
9.19. Stap 13: een simulatie verwijderen
9.19.1. Het probleem
Op de simulatiepagina kun je bepaalde simulaties verwijderen met de JavaScript-link [retirer]:


9.19.2. Boeking van de klantactie [retirerSimulation]
De links [retirer] hebben de volgende vorm: HTML:
waarbij N het simulatienummer is.
Opdracht: volg de stappen uit paragraaf 9.9.3 en schrijf de functie JS [retirerSimulation]. Deze functie zal een Ajax-aanroep van het type POST naar de actie [/Pam/RetirerSimulation] verzenden. Daarbij worden de gegevens N in de vorm num=N verzonden.
Opmerking: de functie JS [retirerSimulation] is vergelijkbaar met de andere functies JS die u hebt geschreven en die een Ajax-aanroep naar de server doen. Het enige nieuwe hier is het verzenden van een waarde die niet in een formulier staat. We weten dat de verzonden waarden worden samengevoegd in een tekenreeks in de vorm:
de functie JS [retirerSimulation] zal dus de volgende vorm hebben:
function retirerSimulation(N) {
// handmatig een Ajax-verzoek uitvoeren
$.ajax({
url: '/Pam/RetirerSimulation',
...
data:"num="+N,
...
});
// menu
setMenu([lnkRetourFormulaire, lnkTerminerSession]);
}
- regel 6: de eigenschap [data] van een Ajax-aanroep JQuery vertegenwoordigt de tekenreeks die naar de server is verzonden.
9.19.3. Schrijven van de serveractie [RetirerSimulation]
De serveractie [RetirerSimulation]:
- ontvangt een verzonden parameter met de naam [num], het nummer van een simulatie;
- moet de simulatie met dit nummer verwijderen uit de lijst met simulaties die tijdens de sessie zijn opgeslagen;
- moet vervolgens de nieuwe lijst met simulaties weergeven.
Opdracht: schrijf de serveractie [RetirerSimulation]. Raadpleeg paragraaf 4.1 om te zien hoe je de via POST verzonden parameter met de naam [num] kunt ophalen.
9.20. Stap 14: verbetering van de initialisatiemethode van de applicatie
Onze webapplicatie is voltooid. Ze werkt met een gesimuleerde klasse [métier]. Laten we de architectuur die we hebben ontwikkeld nog eens op een rijtje zetten:
![]() |
Er moeten nog enkele details worden geregeld voordat we overgaan tot de daadwerkelijke implementatie van de laag [métier], en dat gebeurt in de initialisatiemethode van de applicatie: de methode [Application_Start] in [Global.asax]:
![]() |
De methode [Application_Start] in [Global.asax] wordt slechts één keer uitgevoerd bij het opstarten van de applicatie. Hier kan het configuratiebestand [Web.config] worden gebruikt. Op dit moment ziet onze methode [Application_Start] er als volgt uit:
// toepassing
protected void Application_Start()
{
// ----------Automatisch gegenereerd
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// -------------------------------------------------------------------
// ---------- specifieke configuratie
// -------------------------------------------------------------------
// gegevens van de toepassingsscope
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
// instantie van laag [métier]
application.PamMetier = new PamMetier();
...
// modelbinders
...
}
Op regel 17 wordt de businesslaag geïnstantieerd met een new-operator. Daarnaast wordt het applicatiemodel als volgt gedefinieerd:
public class ApplicationModel
{
// --- gegevens op applicatieniveau ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
}
In regel 5 hierboven zien we dat het type van de eigenschap [PamMetier] overeenkomt met dat van de interface [IPamMetier]. Dit betekent dat deze eigenschap kan worden geïnitialiseerd door elk object dat deze interface implementeert. Op regel 17 van [Application_Start] hebben we echter de naam van een klasse die [IPamMetier] implementeert, hard gecodeerd. Als de laag [métier] dus zou worden geïmplementeerd met een nieuwe klasse die [IPamMetier] implementeert, zou deze regel moeten worden aangepast. Dit is niet erg belangrijk, maar het kan worden voorkomen. De definitie van de implementatieklasse van de interface [IPamMetier] kan worden verplaatst naar een configuratiebestand. Om van implementatie te wisselen, wijzigt men vervolgens de inhoud van dit configuratiebestand. De code .NET hoeft niet te worden gewijzigd.
We gebruiken hier de dependency injection-container [Spring.net]. Er bestaan andere .NET-frameworks om hetzelfde te doen, misschien wel beter en eenvoudiger.
De architectuur van het project ontwikkelt zich als volgt:
![]() |
- naar [A]: de initialisatiemethode van de laag [ASP.NET MVC] vraagt bij [Spring.net] een verwijzing naar de gesimuleerde laag [métier];
- in [B] zal [Spring.net] de gesimuleerde laag [métier] aanmaken door zijn configuratiebestand te raadplegen om te bepalen welke klasse hij moet instantiëren;
- in [C] zal [Spring.net] de verwijzing van de gesimuleerde laag [métier] doorgeven aan de laag [ASP.NET MVC].
Merk op dat de objecten die door [Spring.net] worden beheerd, standaard singletons zijn: er bestaat slechts één exemplaar van. Als er later in ons voorbeeld code is die opnieuw een verwijzing opvraagt bij [Spring.net] naar de gesimuleerde laag [métier], geeft [Spring.net] dan gewoon de verwijzing naar het oorspronkelijk aangemaakte object terug.
9.20.1. Toevoegen van de [Spring]-referenties aan het webproject
We gaan [Spring.net] gebruiken. Dit framework wordt geleverd in de vorm van DLL, dat aan de verwijzingen van het project moet worden toegevoegd. Dit kan als volgt worden gedaan:
![]() |
In [1] klik je met de rechtermuisknop op de tak [References] van het project en kies je vervolgens de optie [Gérer les packages NuGet]. Er is een internetverbinding nodig. Vervolgens gaat men te werk zoals eerder is gedaan voor de bibliotheek JQuery [Globalize]. Zoek het trefwoord [Spring.core] en installeer dit pakket. De installatie levert twee DLL-pakketten op: [Spring.core] [2] en [Common.Logging] [3]. In de volgende voorbeelden is versie 1.3.2 van Spring gebruikt.
Opmerking: als u geen internetverbinding hebt, vindt u deze DLL in een map [lib] op de drager van deze casestudy.
9.20.2. Configuratie van [web.config]
De definitie van de implementatieklasse van de interface [IPamMetier] staat in het bestand [web.config].
<configuration>
<configSections>
...
<sectionGroup name="spring">
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
</sectionGroup>
</configSections>
<!-- Spring-configuratie -->
<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>
...
- regels 2-8: zoek de tag <configSections> in het bestand en voeg daarin de regels 4-7 in;
- regel 4: het attribuut [name="spring"] geeft informatie weer over het gedeelte [spring] van de regels 10-17;
- regel 5: definieert de klasse [Spring.Context.Support.DefaultSectionHandler], die zich bevindt in de DLL [Spring.Core], als de klasse die de sectie [objects] van de regels 14-16 kan verwerken;
- regel 6: definieert de klasse [Spring.Context.Support.ContextHandler], gelegen in de DLL [Spring.Core], als de klasse die de sectie [context] van de regels 11-13 kan verwerken;
- regels 11-13: dit gedeelte bevat de informatie [<resource uri="config://spring/objects" />], die aangeeft dat de Spring-objecten zich in het configuratiebestand bevinden in het gedeelte [/spring/objects], d.w.z. op de regels 14-16;
- regels 14-16: de tag [objects] introduceert de Spring-objecten;
- regel 15: definieert een object met de identificatie [id="pammetier"], dat een instantie is van de klasse [Pam.Metier.Service.PamMetier], gelegen in de DLL [pam-metier-simule]. Hier mag je je niet vergissen. Voor het attribuut [id] kunt u invullen wat u wilt. U gaat deze identificatiecode gebruiken in [Global.asax]. De klasse [Pam.Metier.Service.PamMetier] is die van onze gesimuleerde laag [métier]. Je moet teruggaan naar de definitie ervan om de volledige naam te weten te komen:
namespace Pam.Metier.Service
{
public class PamMetier : IPamMetier
{
...
Voor de DLL [pam-metier-simule] moet u de eigenschappen van het C#-project [pam-metier-simule] bekijken:
![]() |
Je moet de naam gebruiken die in [1] wordt vermeld.
9.20.3. Wijziging van [Application_Start]
De methode [Application_Start] wordt als volgt aangepast:
using Spring.Context.Support;
// toepassing
protected void Application_Start()
{
// ----------Automatisch gegenereerd
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// -------------------------------------------------------------------
// ---------- specifieke configuratie
// -------------------------------------------------------------------
// gegevens over het toepassingsbereik
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
// instantiëring van de [métier]-laag
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
...
// modelbinders
...
}
- regel 19: er wordt gebruikgemaakt van de Spring-klasse [ContextRegistry], een klasse die het bestand [web.config] kan verwerken. Hiervoor moet de naamruimte uit regel 1 worden geïmporteerd. Met de statische methode [GetContext] kan de inhoud worden opgehaald van de [context]-tags die aangeven waar de Spring-objecten zich bevinden. Met de statische methode [GetObject] kan vervolgens een specifiek object worden opgehaald dat wordt geïdentificeerd aan de hand van het id-attribuut. Merk op dat de naam van de klasse die de interface [IPamMetier] implementeert, nu niet meer hard gecodeerd is in de code. Deze staat nu in het bestand [web.config].
Test uw applicatie nadat u al deze wijzigingen hebt aangebracht. Deze zou nu moeten werken.
9.20.4. Een fout bij het initialiseren van de applicatie afhandelen
In de methode [Application_Start] hebben we geschreven:
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
De instructie rechts van het =-teken kan mislukken. Daar zijn verschillende redenen voor:
- de meest voor de hand liggende is dat de naam van het te instantiëren object verkeerd is;
- een andere reden is dat het instantiëren van de laag [métier] misgaat. Dit kan niet het geval zijn voor onze gesimuleerde laag [métier], maar het kan wel het geval zijn voor onze echte laag [métier] die met een database is verbonden. De SGBD wordt mogelijk niet gestart, de gegevens over de te beheren database kunnen onjuist zijn, enzovoort...
We gaan een eventuele uitzondering afhandelen in een try/catch-blok. De code ziet er als volgt uit:
// toepassing
protected void Application_Start()
{
// ----------Automatisch gegenereerd
...
// -------------------------------------------------------------------
// ---------- specifieke configuratie
// -------------------------------------------------------------------
// toepassingsbereikgegevens
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
application.InitException = null;
try
{
// instantiëring van de laag [métier]
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
catch (Exception ex)
{
application.InitException = ex;
}
//indien geen fout
if (application.InitException == null)
{
....
}
// modelbinders
...
}
- regel 12 voegen we een nieuwe eigenschap met de naam [InitException] toe aan het applicatiemodel:
public class ApplicationModel
{
// --- gegevens van het toepassingsbereik ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
public Exception InitException { get; set; }
}
- regel 7 hierboven: de uitzondering die mogelijk optreedt tijdens het initialiseren van de applicatie;
- regels 13-21 van [Application_Start]: het instantiëren van de laag [métier] gebeurt nu in een try/catch;
- regel 20: de uitzondering wordt opgeslagen;
- regels 23-26: als er geen fout is opgetreden, wordt de code uitgevoerd die we eerder hadden;
- regel 28: de [ModelBinders]-objecten worden aangemaakt, ongeacht of er een fout is opgetreden of niet. Dit is belangrijk. We willen er zeker van zijn dat het applicatiemodel [ApplicationModel] daadwerkelijk door het framework wordt gekoppeld.
We weten dat bij het opstarten van de applicatie de serveractie [Index] wordt uitgevoerd. Op dit moment is dat de volgende:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
}
Op regel 2 ontvangt de actie [Index] het applicatiemodel. Zo kan deze actie vaststellen of de initialisatie goed is verlopen en een foutpagina weergeven als de initialisatie op de een of andere manier is mislukt. We passen de code als volgt aan:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
// initialisatiefout?
if (application.InitException != null)
{
// foutpagina zonder menu
return View("InitFailed",Static.GetErreursForException(application.InitException));
}
// geen fout
return View(new IndexModel() { Application = application });
}
Regel 8: in geval van een initialisatiefout geven we de weergave [InitFailed.cshtml] weer, met als sjabloon de lijst met foutmeldingen van de uitzondering die tijdens de initialisatie is opgetreden. De methode [Static.GetErreursForException] is in paragraaf 9.12.4 beschreven en toegelicht. De weergave [InitFailed.cshtml] ziet er als volgt uit:
![]() |
De code ervan is als volgt:
@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>
- regel 1: het sjabloon van de weergave is een lijst met foutmeldingen. Deze worden weergegeven in een lijst HTML op de regels 24-29;
- regel 3: deze weergave maakt geen gebruik van de masterpagina [_Layout.cshtml]. We willen namelijk het menu dat door dit document wordt meegebracht niet. Daarom bouwen we een volledige pagina HTML (regels 5-23).
Om dit te testen, volstaat het om in [Application_Start] de instantiëring van de laag [métier] als volgt te wijzigen:
try
{
// instantiëring van de laag [métier]
application.PamMetier = ContextRegistry.GetContext().GetObject("xx") as IPamMetier;
}
catch (Exception ex)
{
application.InitException = ex;
}
Op regel 4 wordt gezocht naar een object dat niet bestaat in de Spring-objecten.
Wanneer je deze wijzigingen opslaat en de applicatie start, krijg je de volgende pagina te zien:
![]() |
Er verschijnt een foutpagina zonder menu. De gebruiker kan niets anders doen dan de fout constateren. Dit was de bedoeling.
9.21. Hoe ver zijn we nu?
We hebben nu een werkende webapplicatie die met een gesimuleerde bedrijfslaag werkt. De architectuur ziet er als volgt uit:
![]() |
De laag [ASP.NET MVC] werkt samen met de gesimuleerde bedrijfslaag via de interface [IPamMetier]. Als we deze gesimuleerde bedrijfslaag vervangen door een echte bedrijfslaag die aan deze interface voldoet, hoeven we de code van de weblaag niet aan te passen. Dankzij [Spring.net] hoeven we in [web.config] alleen de implementatieklasse van de interface [IPamMetier] te wijzigen. We gaan deze weg inslaan.
De nieuwe architectuur ziet er als volgt uit:
![]() |
We zullen achtereenvolgens het volgende beschrijven:
- de laag [EF5] die is gekoppeld aan SGBD. Deze wordt geïmplementeerd met Entity Framework 5 (EF5);
- de laag [DAO], die de toegang tot de gegevens beheert via de laag [EF5]. Hierdoor kan deze laag het bestaan van SGBD negeren. Deze laag beperkt zich tot het bewerken van de entiteiten van de applicatie [Employe, Cotisations, Indemnites];
- de laag [métier], die de salarisberekening implementeert.
De nieuwe architectuur is die welke helemaal aan het begin van dit document in paragraaf 1.1 is gepresenteerd en die we hier nogmaals weergeven:
![]() |
- de laag [Web] is de laag die in contact staat met de gebruiker van de webapplicatie. De gebruiker communiceert met de webapplicatie via webpagina’s die in een browser worden weergegeven. In deze laag bevinden zich ASP.NET en MVC, en uitsluitend in deze laag;
- de laag [métier] implementeert de bedrijfsregels van de applicatie, zoals de berekening van een salaris of een factuur. Deze laag maakt gebruik van gegevens die afkomstig zijn van de gebruiker via de laag [Web] en van SGBD via de laag [DAO];
- de laag [DAO] (Data Access Objects), de laag [ORM] (Object Relational Mapper) en de connector ADO.NET beheren de toegang tot de gegevens van de laag SGBD. De laag [ORM] vormt een brug tussen de objecten die door de laag [DAO] worden beheerd en de rijen en kolommen van de gegevens in een relationele database. Twee ORM-lagen worden wereldwijd veel gebruikt: NET, NHibernate (http://sourceforge.net/projects/nhibernate/) en Entity Framework (http://msdn.microsoft.com/en-us/data/ef.aspx);
- de integratie van de lagen kan worden gerealiseerd door middel van een dependency injection container zoals Spring (http://www.springframework.net/ );
De lagen [métier], [DAO] en [EF5] worden geïmplementeerd met behulp van C#-projecten. Vanaf nu werken we met Visual Studio Express 2012 voor desktop.
9.22. Stap 15: implementatie van de Entity Framework 5-laag
![]() |
Het aanmaken van de laag [EF5] is minder een kwestie van coderen dan van configureren. Om inzicht te krijgen in het schrijven van deze laag, raden we aan het document [Introduction à Entity Framework 5 Code First] te lezen, dat beschikbaar is op de URL [http://tahe.developpez.com/dotnet/ef5cf-02/]. Het is een vrij omvangrijk document. De basisprincipes staan in de eerste vier hoofdstukken. Welke paragrafen u in het bijzonder moet lezen, zal worden aangegeven. Wanneer we naar dit document verwijzen, gebruiken we de notatie [refEF5].
Daarnaast zullen we soms C#-concepten nodig hebben. We zullen dan verwijzen naar de cursus [Introduction au langage C#], beschikbaar op URL [http://tahe.developpez.com/dotnet/csharp/], met de notatie [refC#].
9.22.1. De database
De database van de applicatie is in paragraaf 9.4 beschreven. Het is een MySQL-database met de naam [dbpam_ef5] (pam=Paie Assistante Maternelle). Deze database heeft een beheerder met de naam root zonder wachtwoord.
Laten we het schema van de database nog eens bekijken. Deze bevat drie tabellen:

Er is een vreemde-sleutelrelatie tussen de kolom EMPLOYES (INDEMNITE_ID) en de kolom INDEMNITES (ID). Een deel van de structuur van deze database wordt bepaald door het gebruik ervan in combinatie met EF5.
Het script SQL voor het aanmaken van de database is als volgt:
-- phpMyAdmin SQL Dump
-- versie 3.5.1
-- http://www.phpmyadmin.net
--
-- Client: localhost
-- Gegenereerd op: ma 4 november 2013 om 09:34
-- Serverversie: 5.5.24-log
-- Versie van 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`
--
-- --------------------------------------------------------
--
-- Structuur van de tabel `cotisations`
--
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 ;
--
-- Inhoud van de tabel `cotisations`
--
INSERT INTO `cotisations` (`ID`, `SECU`, `RETRAITE`, `CSGD`, `CSGRDS`, `VERSIONING`) VALUES
(11, 9.39, 7.88, 6.15, 3.49, 1);
--
-- Triggers `cotisations`
--
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 ;
-- --------------------------------------------------------
--
-- Structuur van de tabel `employes`
--
CREATE TABLE IF NOT EXISTS `employes` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`PRENOM` varchar(20) CHARACTER SET latin1 NOT NULL,
`SS` varchar(15) CHARACTER SET latin1 NOT NULL,
`ADRESSE` varchar(50) CHARACTER SET latin1 NOT NULL,
`CP` varchar(5) CHARACTER SET latin1 NOT NULL,
`VILLE` varchar(30) CHARACTER SET latin1 NOT NULL,
`NOM` varchar(30) CHARACTER SET latin1 NOT NULL,
`VERSIONING` int(11) NOT NULL,
`INDEMNITE_ID` bigint(20) NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `SS` (`SS`),
KEY `FK_EMPLOYES_INDEMNITE_ID` (`INDEMNITE_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=26 ;
--
-- Inhoud van de tabel `employes`
--
INSERT INTO `employes` (`ID`, `PRENOM`, `SS`, `ADRESSE`, `CP`, `VILLE`, `NOM`, `VERSIONING`, `INDEMNITE_ID`) VALUES
(24, 'Marie', '254104940426058', '5 rue des oiseaux', '49203', 'St Corentin', 'Jouveinal', 1, 93),
(25, 'Justine', '260124402111742', 'La Brûlerie', '49014', 'St Marcel', 'Laverti', 1, 94);
--
-- Triggers `employes`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_EMPLOYES`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_EMPLOYES` BEFORE UPDATE ON `employes`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_EMPLOYES`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_EMPLOYES` BEFORE INSERT ON `employes`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=1;
END
//
DELIMITER ;
-- --------------------------------------------------------
--
-- Structuur van de tabel `vergoedingen`
--
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 ;
--
-- Inhoud van de tabel `indemnites`
--
INSERT INTO `indemnites` (`ID`, `ENTRETIEN_JOUR`, `REPAS_JOUR`, `INDICE`, `INDEMNITES_CP`, `BASE_HEURE`, `VERSIONING`) VALUES
(93, 2.1, 3.1, 2, 15, 2.1, 1),
(94, 2, 3, 1, 12, 1.93, 1);
--
-- Triggers `indemnites`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_INDEMNITES`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_INDEMNITES` BEFORE UPDATE ON `indemnites`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_INDEMNITES`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_INDEMNITES` BEFORE INSERT ON `indemnites`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=1;
END
//
DELIMITER ;
--
-- Beperkingen voor de geëxporteerde tabellen
--
--
-- Beperkingen voor de tabel `employes`
--
ALTER TABLE `employes`
ADD CONSTRAINT `FK_EMPLOYES_INDEMNITE_ID` FOREIGN KEY (`INDEMNITE_ID`) REFERENCES `indemnites` (`ID`);
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
Let op de volgende punten:
- regels 30, 73, 122: de primaire sleutels van de tabellen staan in de modus [AUTO_INCREMENT]. Deze worden beheerd door MySQL en niet door EF5;
- regel 83: het nummer SS heeft een uniekheidsbeperking;
- regel 130: het indexnummer van de werknemer heeft een uniekheidsbeperking;
- regels 168-169: de vreemde sleutel van de tabel [employes] naar de tabel [indemnites];
- regel 49: een trigger [Trigger] is een script SQL dat is ingebed in SGBD en dat op bepaalde momenten wordt uitgevoerd;
- regels 51-54: de trigger [INCR_VERSIONING_COTISATIONS] wordt geactiveerd voordat een regel in de tabel [cotisations] wordt gewijzigd. Vervolgens wordt de kolom [VERSIONING] met één verhoogd;
- regels 59-62: de trigger [START_VERSIONING_COTISATIONS] wordt geactiveerd voordat er een nieuwe regel in de tabel [cotisations] wordt ingevoegd. De kolom [VERSIONING] wordt dan op 1 gezet;
- uiteindelijk is de kolom [VERSIONING] gelijk aan 1 wanneer een rij wordt aangemaakt in de tabel [cotisations] en wordt deze vervolgens met 1 verhoogd bij elke wijziging die aan deze rij wordt aangebracht. Dankzij dit mechanisme kan EF5 de gelijktijdige toegang tot een rij in de tabel [cotisations] als volgt beheren:
- een proces P1 leest een rij L uit de tabel [cotisations] op tijdstip T1. De rij heeft de kolom [VERSIONING] V1;
- een proces P2 leest dezelfde rij L uit de tabel [cotisations] op tijdstip T2. De rij heeft de kolommen [VERSIONING] en V1 omdat het proces P1 zijn wijziging nog niet heeft gevalideerd;
- het proces P1 wijzigt rij L en bevestigt deze wijziging. De kolom [VERSIONING] van rij L verandert vervolgens in V1+1 als gevolg van de trigger [INCR_VERSIONING_COTISATIONS];
- het proces P2 doet vervolgens hetzelfde. EF5 genereert vervolgens een uitzondering omdat het proces P2 een regel heeft met een kolom [VERSIONING] waarvan de waarde V1 afwijkt van de waarde in de database, namelijk V1+1. Een rij kan alleen worden gewijzigd als de waarde van [VERSIONING] overeenkomt met die in de database.
Dit wordt optimistisch beheer van gelijktijdige toegang genoemd. Bij EF5 moet een veld dat deze rol vervult de annotatie [ConcurrencyCheck] hebben.
- Een soortgelijk mechanisme wordt gecreëerd voor de tabel [employes] (regels 98-113) en de tabel [indemnites] (regels 144-159).
Taak: maak de database MySQL [dbpam_ef5] aan met behulp van het voorgaande script SQL. De database [dbpam_ef5] moet eerst worden aangemaakt, omdat het script deze niet aanmaakt. Vervolgens wordt het script SQL op deze database uitgevoerd.
9.22.2. Het Visual Studio-project
Met Visual Studio Express 2012 voor desktop laden we de oplossing [pam-td] die is gebruikt bij het bouwen van de laag [web]:
![]() |
- in [1], VS slaagt Visual Studio Express 2012 voor Desktop er niet in het webproject [pam-web-01] te laden. Dit is normaal en vormt geen probleem;
- in [2] wordt een nieuw project toegevoegd aan de oplossing [pam-td];
![]() |
- in [3] is het project van het type [console] en heet het [4] [pam-ef5];
- in [5], het aangemaakte project. De naam staat niet vetgedrukt, dus dit is niet het startproject van de oplossing;
![]() |
- in [6] en [7] stellen we het nieuwe project in als startproject.
9.22.3. De benodigde referenties aan het project toevoegen
Laten we het project in zijn geheel bekijken:
![]() |
Ons project heeft een aantal DLL nodig:
- de DLL van Entity Framework 5;
- de DLL van de connector ADO.NET van de SGBD MySQL.
In paragraaf 4.2 van [refEF5] wordt uitgelegd hoe u deze DLL kunt installeren met behulp van de tool [NuGet]. Momenteel (nov. 2013) is versie 6 de beschikbare versie van Entity Framework (EF6). Helaas lijkt het erop dat de connector ADO.NET van de SGBD MySQL die beschikbaar is (nov. 2013) via [NuGet] niet compatibel is met EF6. Daarom hebben we in een map met [lib], [1] en DLL deEF5 en de andere DLL-bestanden die nodig zijn voor het project [pam-ef5]
![]() |
We hebben nog meer DLL-bestanden in de map [lib] geplaatst. Deze zullen we later gebruiken. In [2] voegen we deze nieuwe DLL-bestanden toe aan het project.
![]() |
- in [3] bladeren we door het bestandssysteem naar de map [lib];
- in [4] selecteren we de drie DLL en bevestigen we dit twee keer;
- in [5] zijn de drie DLL-bestanden toegevoegd aan de projectreferenties.
We hebben nog een DLL nodig. Deze is te vinden tussen de bestanden van het .NET-framework op de machine.
![]() |
- in [1], voeg een nieuwe referentie toe aan het project;
![]() |
- in [2], selecteer [Assemblys];
- in [3], typ [system.component];
- in [4], selecteer de assembly [System.ComponentModel.DataAnnotations];
- in [5] is de referentie toegevoegd.
We zijn nu klaar om te coderen en te configureren.
9.22.4. De Entity Framework-entiteiten
Entity Framework-entiteiten zijn klassen waarin de rijen van de verschillende tabellen in de database worden ingekapseld. Laten we deze nog eens op een rijtje zetten:

In de laag [web] hadden we de entiteiten [Employe, Cotisations, Indemnités] gebruikt (zie paragraaf 9.7.3, pagina 219). Deze waren geen exacte afspiegelingen van de tabellen. Zo waren de kolommen [ID, VERSIONING] genegeerd. Hier zal dat niet het geval zijn, omdat ze worden gebruikt door de entiteiten ORM en EF5. We gaan daarom de ontbrekende eigenschappen eraan toevoegen. We maken deze entiteiten aan in een map [Models] van het project:
![]() |
Hun nieuwe code is nu als volgt:
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; }
// handtekening
public override string ToString()
{
return string.Format("Cotisations[{0},{1},{2},{3}, {4}, {5}]", Id, Versioning, CsgRds, Csgd, Secu, Retraite);
}
}
}
- regel 3: de naamruimte is aangepast aan het nieuwe project;
- de eigenschappen van de regels 7 en 12 zijn toegevoegd om de structuur van de tabel [cotisations] weer te geven;
- regel 17: de methode [ToString] geeft nu de twee nieuwe velden weer.
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; }
// handtekening
public override string ToString()
{
return string.Format("Indemnités[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Versioning, Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
}
}
}
- regel 3: de naamruimte is aangepast aan het nieuwe project;
- de eigenschappen van de regels 7 en 13 zijn toegevoegd om de structuur van de tabel [indemnites] weer te geven;
- regel 18: de methode [ToString] geeft nu de twee nieuwe velden weer.
Klasse [Employe]
using System;
namespace Pam.EF5.Entites
{
public class Employe
{
public int Id { get; set; }
public string SS { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
public string Adresse { get; set; }
public string Ville { get; set; }
public string CodePostal { get; set; }
public Indemnites Indemnites { get; set; }
public int Versioning { get; set; }
// handtekening
public override string ToString()
{
return string.Format("Employé[{0},{1},{2},{3},{4},{5}, {6}, {7}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal);
}
}
}
- regel 3: de naamruimte is aangepast aan het nieuwe project;
- de eigenschappen van de regels 8 en 16 zijn toegevoegd om de structuur van de tabel [employes] weer te geven;
- regel 21: de methode [ToString] geeft nu de twee nieuwe velden weer.
Om bruikbaar te zijn voor ORM en EF5, moeten de eigenschappen van deze klassen worden voorzien van annotaties.
Opdracht: voeg, met behulp van paragraaf 3.4 van [Création de la base à partir des entités] van [refEF5], de nodige annotaties toe aan de entiteiten [Employe, Cotisations, Indemnites] voor EF5.
Tips:
- het gaat hier alleen om het aanmaken van annotaties. Volg het gedeelte [création de base] van de genoemde paragraaf niet;
- voor de annotatie [Table] volgt u het voorbeeld MySQL uit paragraaf 4.2 van [refEF5];
- voor de annotatie [ConcurrencyCheck] op de eigenschap [Versioning] volgt u het Oracle-voorbeeld uit paragraaf 5.2 van [refEF5];
- voor de vreemde sleutel die de tabel [employes] heeft op de tabel [indemnités], volgt u voorbeeld 3.4.2 uit [refEF5]. Zo voegt u een nieuwe eigenschap toe aan de entiteit [Employe]:
public int IndemniteId { get; set; }
waarvan de waarde overeenkomt met die van kolom [INDEMNITES_ID] in de tabel [employes]. U voegt de annotaties voor vreemde sleutels toe aan de eigenschappen [IndemniteId] en [Indemnites] van de entiteit [Employe]. Volg hiervoor voorbeeld 3.4.2 van [refEF5];
- u hoeft de omgekeerde relaties van de vreemde sleutels niet te beheren;
- voor deze taak moet je wat informatie uit [refEF5] doornemen.
9.22.5. Configuratie van ORM en EF5
Laten we het project in zijn geheel nog eens bekijken:
![]() |
De laag [EF5] zal toegang krijgen tot de database via de connector [ADO.NET] van de SGBD MySQL. Deze laag heeft een aantal gegevens nodig om toegang te krijgen tot deze database. Deze gegevens bevinden zich op verschillende plaatsen in het project.
We moeten eerst de databasecontext aanmaken. Deze context is een klasse die is afgeleid van de systeemklasse [System.Data.Entity.DbContext]. Deze dient om de objectmodellen van de tabellen in de database te definiëren. We plaatsen deze klasse in de map [Models] van het project, samen met de entiteiten EF5:
![]() |
De klasse [DbPamContext] ziet er als volgt uit:
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; }
}
}
- regel 6: de klasse [DbPamContext] is afgeleid van de systeemklasse [DbContext];
- regels 8-10: de afbeeldingsobjecten van de drie tabellen in de database. Hun type is [DbSet<Entity>], waarbij [Entity] een van de Entity Framework-entiteiten is die we zojuist hebben gedefinieerd. Het type [DbSet] kan worden gezien als een verzameling entiteiten. Deze kan worden opgevraagd met LINQ (Language INtegrated Query). Lezers die niet bekend zijn met LINQ, worden verzocht paragraaf 3.5.4 [Apprentissage de LINQ avec LINQPad] van [refEF5] te lezen.
We zullen de klasse [DbPamContext] hierna de persistentiecontext van de database [dbpam_ef5] noemen. Dit is gangbare terminologie in ORM (Object Relational Mapper). Deze persistentiecontext is een objectafbeelding van de database. Er wordt ook gesproken over synchronisatie van de persistentiecontext met de database: wijzigingen, toevoegingen en verwijderingen die in de persistentiecontext worden aangebracht, worden doorgevoerd in de database. Deze synchronisatie vindt plaats op specifieke momenten: bij het sluiten van de persistentiecontext, aan het einde van een transactie of vóór een SQL SELECT-query op de database.
De informatie over de SGBD en de database wordt opgeslagen in [App.config].
![]() |
De benodigde configuratie in [app.config] wordt uitgelegd in de volgende paragrafen van [refEF5]:
- 3.4 voor de SGBD SQL-server. Hier worden de belangrijkste principes van de configuratie van EF5 uiteengezet;
- 4.2 voor de SGBD MySQL.
We volgen deze laatste paragraaf en configureren het bestand [app.config] als volgt:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<!-- configuratie EF5 -->
<!-- verbindingsreeks met de database [dbam_ef5] -->
<connectionStrings>
<add name="DbPamContext"
connectionString="Server=localhost;Database=dbpam_ef5;Uid=root;Pwd=;"
providerName="MySql.Data.MySqlClient" />
</connectionStrings>
<!-- de factory provider van MySQL -->
<system.data>
<DbProviderFactories>
<remove invariant="MySql.Data.MySqlClient"/>
<add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL"
type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=C5687FC88969C44D"
/>
</DbProviderFactories>
</system.data>
</configuration>
- de regels 6-21 zijn toegevoegd. Deze moeten worden ingevoegd in de tag <configuration> op regel 2 en 22;
- regels 8-12: definiëren verbindingsstrings voor databases, een concept uit ADO.NET (zie paragraaf 7.3.5 in [refC#]);
- regels 9-11: hierin wordt de verbindingsstring voor de database MySQL [dbpam_ef5] gedefinieerd;
- regel 9: de naam van de verbindingsstring. Hier kun je niet zomaar iets invoeren. Standaard moet je de naam invoeren van de klasse die de databasecontext implementeert:
public class DbPamContext : DbContext
{
public DbSet<Employe> Employes { get; set; }
public DbSet<Cotisations> Cotisations { get; set; }
public DbSet<Indemnites> Indemnites { get; set; }
}
De klasse heet [DbPamContext]. Op regel 9 van [app.config] moet je dan [name="DbPamContext"] invoeren;
- regel 10: een verbindingsstring die specifiek is voor SGBD MySQL:
- [Server=localhost]: het adres van de machine waarop SGBD draait. In dit geval is dat de lokale machine [localhost];
- [Database=dbpam_ef5;]: naam van de database,
- [Uid=root;]: de gebruikersnaam waarmee we verbinding maken met de database,
- [Pwd=;]: wachtwoord van deze gebruikersnaam. Hier is geen wachtwoord;
- regel 10: [providerName="MySql.Data.MySqlClient"] is de naam van de te gebruiken connector ADO.NET. Deze naam is die van het attribuut [invariant] op regel 17. Je kunt elke gewenste naam gebruiken, zolang je je aan de vorige regel houdt en er nog geen provider met dezelfde invariant is geregistreerd;
- regels 15-20: definiëren een ‘factory’ voor ADO.NET-connectoren (providers). De [DbProviderFactory] is voor mij een wat vaag begrip. Als ik de naam goed begrijp, zou het een klasse zijn die de connector ADO.NET kan genereren, die toegang geeft tot de SGBD, in dit geval MySQL5. Meestal kopieer en plak je deze regels. Ze zijn noodzakelijk. Let goed op het attribuut [Version=6.5.4.0] in regel 16. Dit versienummer moet overeenkomen met het versienummer van de DLL [MySql.Data] die je aan de projectreferenties hebt toegevoegd:
![]() |
- Regel 16 is belangrijk. Omdat je niet twee providers met dezelfde naam kunt installeren, verwijder je eerst een eventueel reeds geïnstalleerde provider die dezelfde naam zou hebben als degene die je in regel 17 installeert;
Dat is alles. Het is ingewikkeld en onduidelijk wanneer je het voor het eerst doet, maar na verloop van tijd wordt het eenvoudig omdat je steeds hetzelfde herhaalt.
9.22.6. Testen van de laag [EF5]
We zijn klaar om onze laag [EF5] te testen. Dit doen we met behulp van het programma [Program.cs] dat al aanwezig is:
![]() |
We gaan de inhoud van de database weergeven. Als dat lukt, is dat een eerste aanwijzing dat onze configuratie correct is. Een voorbeeld van de code is te vinden in paragraaf 3.5.3 van [refEF5]. De code van [Program.cs] ziet er als volgt uit:
using Pam.EF5.Entites;
using Pam.Models;
using System;
namespace Pam
{
class Program
{
static void Main(string[] args)
{
try
{
using (var context = new DbPamContext())
{
// de inhoud van de tabellen wordt weergegeven
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;
}
}
}
}
- regel 13: elke bewerking op BD vindt plaats via de context van deze database. We hebben deze context geïmplementeerd met de klasse [DbPamContext]. We noemen dit ook wel de persistentiecontext van de database;
- regels 13, 31: bewerkingen op de persistentiecontext vinden plaats in een [using]-clausule. De persistentiecontext wordt geopend aan het begin van de clausule [using] en automatisch gesloten bij het verlaten van deze clausule. Dit houdt in dat elke wijziging die in de clausule [using] aan de persistentiecontext wordt aangebracht, bij het verlaten van de clausule in de database wordt doorgevoerd. Vervolgens wordt binnen een transactie een reeks opdrachten SQL naar de opdracht BD verzonden. Dit betekent dat als een opdracht SQL mislukt, alle eerder verzonden opdrachten SQL worden geannuleerd. Er wordt dan een uitzondering gegenereerd door EF5;
- regel 17: de uitdrukking [context.Employes] verwijst naar het objectbeeld van de tabel [employes]. Ter herinnering: [Employes] is een eigenschap van de persistentiecontext [DbPamContext]:
public class DbPamContext : DbContext
{
public DbSet<Employe> Employes { get; set; }
public DbSet<Cotisations> Cotisations { get; set; }
public DbSet<Indemnites> Indemnites { get; set; }
}
- regel 17: het feit dat [foreach] de collectie [context.Employes] doorloopt, zal alle medewerkers uit de database in de persistentiecontext ophalen. Er wordt dus een opdracht SQL SELECT gegenereerd door EF5;
- regels 17-20: de verzameling van werknemers wordt doorlopen en in regel 19 wordt de methode [ToString] van de klasse [Employe] gebruikt om de werknemers op de console weer te geven;
- regels 21-25: idem voor de verzameling vergoedingen;
- regels 27-30: idem voor de verzameling van de premies.
Laten we teruggaan naar de definitie van de entiteit [Employe]:
using System;
namespace Pam.EF5.Entites
{
public class Employe
{
public int Id { get; set; }
public string SS { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
public string Adresse { get; set; }
public string Ville { get; set; }
public string CodePostal { get; set; }
public Indemnites Indemnites { get; set; }
public int Versioning { get; set; }
// handtekening
public override string ToString()
{
return string.Format("Employé[{0},{1},{2},{3},{4},{5}, {6}, {7}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal);
}
}
}
- regel 15: een werknemer heeft een verwijzing naar een vergoeding.
Wanneer een werknemer naar de persistentie-context wordt teruggebracht, wordt zijn vergoeding dan mee teruggebracht? Het antwoord is standaard nee. Dit is het concept van [Lazy Loading]. Entiteiten waarnaar binnen een andere entiteit wordt verwezen, worden niet samen met die andere entiteit in de persistentiecontext opgenomen. Dit gebeurt alleen wanneer ze door de code worden opgevraagd binnen een geopende persistentiecontext. Als de persistentiecontext gesloten is, wordt er een uitzondering gegenereerd.
Als de methode [ToString] dus naar de eigenschap [Indemnites] had verwezen zoals hieronder:
// handtekening
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);
}
dan zou de volgende bewerking in [Program.cs]:
foreach (Employe employe in context.Employes)
{
Console.WriteLine(employe);
}
zou niet alleen de werknemers, maar ook hun vergoedingen in de persistentie-context hebben teruggebracht, omdat in regel 3 de methode [Employe.ToString] wordt aangeroepen en deze verwijst naar de entiteit [Indemnites].
De uitvoering van [Program.cs] levert de volgende resultaten op:
Wat te doen als het niet werkt? Dan zit u verkeerd... Er zijn tal van mogelijke foutbronnen:
- controleer de configuratie van EF5 (paragraaf 9.22.5);
- controleer je Entity Framework-entiteiten (paragraaf 9.22.4).
9.22.7. DLL van de laag [EF5]
We maken van ons project een klassenbibliotheek, zodat bij het genereren een .dll-assembly wordt gegenereerd in plaats van een .exe. Dit gebeurt in de projecteigenschappen, zoals besproken in paragraaf 9.7.6 voor de gesimuleerde bedrijfslaag.
Opdracht: wijzig het type van het project [pam-ef5] in een klassenbibliotheek en genereer het project vervolgens opnieuw.
9.23. Stap 16: het opzetten van de laag [DAO]
9.23.1. De interface van de laag [DAO]
![]() |
Net zoals we dat hebben gedaan voor de gesimuleerde laag [métier], zal de laag [DAO] toegankelijk zijn via een interface. Hoe zal die eruitzien?
Laten we eens kijken naar de interface [IPamMetier] van de gesimuleerde laag [métier] die we hebben gebouwd:
public interface IPamMetier {
// lijst met alle identiteitsgegevens van de werknemers
Employe[] GetAllIdentitesEmployes();
// ------- de salarisberekening
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
Regel 3: de methode [GetAllIdentitesEmployes] wordt gebruikt om de vervolgkeuzelijst op de startpagina te vullen:
![]() |
Deze medewerkers moeten uit de database worden opgehaald.
Regel 6: de methode [GetSalaire] maakt het mogelijk om de loonstrook te berekenen van een medewerker waarvan het nummer SS bekend is. Laten we de definitie van het type [FeuilleSalaire] nog eens bekijken:
public class FeuilleSalaire
{
// automatische eigenschappen
public Employe Employe { get; set; }
public Cotisations Cotisations { get; set; }
public ElementsSalaire ElementsSalaire { get; set; }
}
De informatie in de regels 5 en 6 wordt uit de database gehaald. Ter herinnering: een werknemer heeft een eigenschap [Indemnites]. Deze informatie moet ook worden opgehaald.
We zouden dus kunnen uitgaan van de volgende interface voor de laag [DAO]:
public interface IPamDao {
// lijst van alle identiteiten van de werknemers
Employe[] GetAllIdentitesEmployes();
// een specifieke werknemer met zijn vergoedingen
Employe GetEmploye(string ss);
// lijst van alle premies
Cotisations GetCotisations();
}
9.23.2. Het Visual Studio-project
Opdracht: voeg aan de oplossing [pam-td] een nieuw project van het type [console] toe met de naam [pam-dao]. Maak dit tot het startproject van de oplossing.
![]() |
9.23.3. De benodigde verwijzingen aan het project toevoegen
Laten we het project in zijn geheel bekijken:
![]() |
Voor het project [pam-dao] zijn een aantal DLL nodig:
- alle exemplaren die door het project [pam-ef5] worden vermeld;
- die van het project [pam-ef5] zelf.
Daarnaast gaan we [Spring.net] gebruiken om de laag [DAO] te instantiëren. Hiervoor hebben we de DLL, [Spring.core] en [Common.Logging] nodig. Deze DLL-bestanden bevinden zich in de map [lib] van het casusmateriaal.
Opdracht: voeg deze verschillende referenties toe aan het project [pam-dao].
![]() |
9.23.4. Implementatie van de laag [DAO]
![]() |
Hierboven is de klasse [PamException] de klasse die in paragraaf 9.7.4 is gedefinieerd. We wijzigen alleen de naamruimte ervan (regel 1 hieronder):
namespace Pam.Dao.Entites
{
// uitzonderingsklasse
public class PamException : Exception
{
....
}
}
De interface [IPamDao] is de interface die we zojuist in paragraaf 9.23.1 hebben gedefinieerd:
using Pam.EF5.Entites;
namespace Pam.Dao.Service
{
public interface IPamDao
{
// lijst van alle identiteiten van werknemers
Employe[] GetAllIdentitesEmployes();
// een specifieke werknemer met zijn vergoedingen
Employe GetEmploye(string ss);
// lijst van alle premies
Cotisations GetCotisations();
}
}
De klasse [PamDaoEF5] implementeert deze interface met behulp van ORM en EF5. De code ervan is als volgt:
using Pam.Dao.Entites;
using Pam.EF5.Entites;
using Pam.Models;
using System;
using System.Linq;
namespace Pam.Dao.Service
{
public class PamDaoEF5 : IPamDao
{
// privévelden
private Cotisations cotisations;
private Employe[] employes;
// Fabrikant
public PamDaoEF5()
{
// bijdrage
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);
}
}
}
}
Let op:
- regel 10: de klasse [PamDaoEF5] implementeert de interface [IPamDao];
- de tabellen [cotisations] en [employes] worden in de cache opgeslagen in de eigenschappen van de regels 13-14. De werknemers hebben geen toelagen;
- regels 17-28: de constructor initialiseert de regels 13-14;
- regels 43-52: de methode [GetEmploye] retourneert een werknemer met zijn vergoedingen. Deze methode ontvangt als parameter het socialezekerheidsnummer van deze werknemer. Als de werknemer niet in de database voorkomt, retourneert de methode de pointer null.
Opdracht: vul de code van de klasse [PamDaoEF5] aan.
Voor de constructor kunnen we ons laten inspireren door de testcode van de laag [EF5] die in paragraaf 9.22.6 wordt gepresenteerd. Voor de methode [GetEmploye] baseert u zich op het voorbeeld uit paragraaf 3.5.7 [Eager and Lazy loading] van [refEF5].
9.23.5. Configuratie van de laag [DAO]
Net als in paragraaf 9.22.5 moeten we EF5 configureren in het bestand [App.config] van het project:
![]() |
Opdracht 1: configureer EF5 in [App.config]. Hiervoor volstaat het om te herhalen wat er in het bestand [App.config] van de laag [EF5] is gedaan.
Ons testprogramma zal [Spring.net] gebruiken om een referentie op te halen uit de laag [DAO].
Opdracht 2: gebruikmakend van wat er in paragraaf 9.20.2 is gedaan, pas je het configuratiebestand [app.config] van het project [pam-dao] aan, zodat het een Spring-object met de naam [pamdao] definieert dat gekoppeld is aan de klasse [PamDaoEF5] die we zojuist hebben aangemaakt. De bestanden [app.config] en [web.config] hebben dezelfde structuur. Let erop dat de tag <configSections> de eerste tag is die volgt op de root-tag <configuration>.
9.23.6. Testen van de laag [DAO]
We zijn klaar om onze laag [DAO] te testen. Dit doen we met behulp van het reeds aanwezige programma [Program.cs]:
![]() |
We gaan de verschillende functies van de interface van de laag [DAO] testen. De code van [Program.cs] ziet er als volgt uit:
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
{
// instantie van laag [dao]
IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
// lijst met identiteiten van werknemers
foreach (Employe Employe in pamDao.GetAllIdentitesEmployes())
{
Console.WriteLine(Employe.ToString());
}
// een medewerker met zijn vergoedingen
Console.WriteLine("------------------------------------");
Employe e = pamDao.GetEmploye("254104940426058");
Console.WriteLine("employé= {0}, indemnités={1}", e, e.Indemnites);
Console.WriteLine("------------------------------------");
// een medewerker die niet bestaat
Employe employe = pamDao.GetEmploye("xx");
Console.WriteLine("Employé n° xx");
Console.WriteLine((employe == null ? "null" : employe.ToString()));
Console.WriteLine("------------------------------------");
// overzicht van de premies
Cotisations cotisations = pamDao.GetCotisations();
Console.WriteLine(cotisations.ToString());
}
catch (Exception ex)
{
// weergave van uitzondering
Console.WriteLine(ex.ToString());
}
//pauze
Console.ReadLine();
}
}
}
- regel 15: we verkrijgen een verwijzing naar de laag [DAO] via [Spring.net].
De resultaten van de uitvoering van dit programma zijn als volgt:
9.23.7. DLL van de laag [DAO]
Opdracht: wijzig het type van het project [pam-dao] in een klassenbibliotheek en genereer het project vervolgens opnieuw (herhaal wat in paragraaf 9.22.7 is gedaan).
9.24. Stap 17: het opzetten van de laag [métier]
9.24.1. De interface van de laag [métier]
![]() |
De interface van de laag [métier] is de interface [IPamMetier] van de gesimuleerde laag [métier] die we in paragraaf 9.7.2 hebben opgebouwd.
public interface IPamMetier {
// lijst met alle identiteiten van de werknemers
Employe[] GetAllIdentitesEmployes();
// ------- de salarisberekening
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
9.24.2. Het Visual Studio-project
Opdracht: voeg aan de oplossing [pam-td] een nieuw project van het type [console] toe met de naam [pam-metier]. Maak dit tot het startproject van de oplossing.
![]() |
9.24.3. De benodigde verwijzingen aan het project toevoegen
Laten we het project in zijn geheel bekijken:
![]() |
Het project [pam-metier] heeft een aantal DLL nodig:
- alle exemplaren waarnaar wordt verwezen door de projecten [pam-dao] en [pam-ef5];
- die van de projecten [pam-dao] en [pam-ef5] zelf.
Taak: voeg deze verschillende referenties toe aan het project [pam-metier].
![]() |
9.24.4. Implementatie van de laag [métier]
![]() |
Hierboven staan vier elementen die al zijn gebruikt in de gesimuleerde laag [métier] (zie paragraaf 9.7). Er kunnen wijzigingen zijn in de naamruimten die door deze verschillende klassen worden geïmporteerd. Beheer deze. De klasse [PamMetier] implementeert de interface [IPamMetier] als volgt:
using Pam.Dao.Service;
using Pam.EF5.Entites;
using Pam.Metier.Entites;
using System;
namespace Pam.Metier.Service
{
public class PamMetier : IPamMetier
{
// verwijzing naar de laag [DAO], geïnitialiseerd door Spring
public IPamDao PamDao { get; set; }
// lijst met alle identiteiten van de werknemers
public Employe[] GetAllIdentitesEmployes()
{
...
}
// een specifieke werknemer met zijn vergoedingen
public Employe GetEmploye(string ss)
{
...
}
// de premies
public Cotisations GetCotisations()
{
...
}
// loonberekening
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
{
// SS: SS-nummer van de werknemer
// HeuresTravaillées: het aantal gewerkte uren
// Gewerkte dagen: aantal gewerkte dagen
...
}
}
- regel 13: er is een verwijzing naar de laag [DAO]. Deze wordt door Spring geïnitialiseerd bij het instantiëren van de klasse [PamMetier]. Dus wanneer de verschillende methoden worden uitgevoerd, is regel 13 al geïnitialiseerd.
Opdracht: vul de code van de klasse [PamMetier] aan. Als in [GetSalaire] blijkt dat de werknemer met personeelsnummer ss niet bestaat, wordt een [PamException] gestart. De wijze van loonberekening wordt uitgelegd in paragraaf 9.5. Let erop dat alle tussenberekeningen worden afgerond op twee cijfers achter de komma.
9.24.5. Configuratie van de laag [métier]
Net zoals in paragraaf 9.22.5 moeten we EF5 configureren in het bestand [app.config] van het project:
![]() |
Opdracht 1: configureer EF5 in [app.config]. Hiervoor volstaat het om te herhalen wat er in het bestand [app.config] van de laag [EF5] is gedaan.
Ons testprogramma zal [Spring.net] gebruiken om een referentie op te halen uit de laag [métier].
Opdracht 2: gebruikmakend van wat u eerder in paragraaf 9.23.5 hebt gedaan, pas je het configuratiebestand [app.config] van het project [pam-metier] aan, zodat het een Spring-object met de naam [pammetier] definieert dat gekoppeld is aan de klasse [PamMetier] die we zojuist hebben gemaakt. Het eenvoudigste is om het bestand [app.config] uit het project [pam-dao] te kopiëren en de ontbrekende gegevens toe te voegen.
Hier zit echter een probleem. Niet alleen moet de laag [métier] worden geïnstantieerd met de klasse [PamMetier], maar ook de eigenschap [PamDao] moet worden geïnitialiseerd:
// verwijzing naar de laag [DAO], geïnitialiseerd door Spring
public IPamDao PamDao { get; set; }
De Spring-configuratie in [app.config] is dan als volgt:
<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>
- regel 6: definieert het object [pamdao] dat is gekoppeld aan de klasse [PamDaoEF5];
- regel 7: definieert het object [pammetier] dat is gekoppeld aan de klasse [PamMetier];
- regel 8: de tag [property] dient om een openbare eigenschap van de klasse [PamMetier] te initialiseren. Het attribuut [name="PamDao"] komt overeen met de naam van de eigenschap die in de klasse [PamMetier] moet worden geïnitialiseerd. Het attribuut [ref="pamdao"] geeft aan dat de eigenschap wordt geïnitialiseerd met een verwijzing, namelijk die naar het object [pamdao] uit regel 6, dus met de verwijzing naar de laag [DAO]. Dit is precies wat we wilden.
9.24.6. Testen van de laag [métier]
We zijn klaar om onze laag [métier] te testen. Dit doen we met behulp van het reeds aanwezige programma [Program.cs]:
![]() |
We gaan de verschillende functies van de interface van de laag [métier] testen. De code van [Program.cs] ziet er als volgt uit:
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
{
// instantiëring van de laag [métier]
IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// lijst met identiteitsgegevens van werknemers
Console.WriteLine("Employés -----------------------------");
foreach (Employe Employe in pamMetier.GetAllIdentitesEmployes())
{
Console.WriteLine(Employe);
}
// berekeningen van loonstroken
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));
}
// pauze
Console.ReadLine();
}
}
}
- regel 16: we verkrijgen een verwijzing naar de laag [métier] via [Spring.net].
De resultaten van de uitvoering van dit programma zijn als volgt:
9.24.7. DLL van de laag [métier]
Opdracht: wijzig het type van het project [pam-metier] in een klassenbibliotheek en genereer het project vervolgens opnieuw (herhaal wat in paragraaf 9.22.7 is gedaan).
9.25. Stap 18: het opzetten van de laag [web]
We zijn aangekomen bij de laatste laag van onze architectuur, de laag [web]:
![]() |
We gaan de laag [web] hergebruiken die we hadden ontwikkeld met behulp van een gesimuleerde laag [métier].
9.25.1. Het Visual Studio-project
We gaan terug naar Visual Studio Express 2012 voor het web om onze weblaag te koppelen aan de [métier, DAO, EF5]-lagen die we zojuist hebben ontwikkeld. Er moet vooral wat configuratiewerk worden gedaan en er zijn enkele wijzigingen in de naamruimten nodig.
Laad in Visual Studio Express 2012 voor het web de oplossing [pam-td]:
![]() |
- in [1], de oplossing [pam-td] in VS Studio voor het web. Het webproject [pam-web-01] wordt weer zichtbaar. We waren het kwijtgeraakt in VS Studio voor de desktop.
- De configuratie van het webproject [pam-web-01] moet worden aangepast. In plaats van een project te wijzigen dat goed werkt, voeren we de wijzigingen uit op een kopie van dit project. Allereerst verwijderen we in [2] het project uit de oplossing (hierdoor wordt er niets in het bestandssysteem verwijderd).
![]() |
- in [3] dupliceren we met Windows Verkenner de map [pam-web-01] naar [pam-web-02];
- in [4] laadt u het project [pam-web-02] in de oplossing [pam-td]. Het wordt dan opgeslagen onder de naam [pam-web-01];
- in [5], wijzig deze naam in [pam-web-02] en stel dit project in als het opstartproject;
![]() |
- in [6], laad het oude project [pam-web-01]. U beschikt nu over al uw projecten. Zorg ervoor dat u met [pam-web-02] werkt.
9.25.2. De benodigde verwijzingen aan het project toevoegen
Laten we het project in zijn geheel bekijken:
![]() |
Het project [pam-web-02] heeft een aantal DLL nodig:
- alle exemplaren waarnaar wordt verwezen door de projecten [pam-metier], [pam-dao] en [pam-ef5];
- die van de projecten [pam-metier], [pam-dao] en [pam-ef5] zelf.
Taak: voeg deze verschillende referenties toe aan het project [pam-web-02]. De referentie naar het project [pam-metier-simule] moet worden verwijderd. We wisselen van laag naar [métier]. Sommige DLL zijn al aanwezig in de referenties. Verwijder deze en voeg vervolgens uw toevoegingen toe.
![]() |
9.25.3. Implementatie van de laag [web]
Genereer het project [pam-web-02]. Er zullen fouten verschijnen, zoals de volgende:
![]() |
De klasse [ApplicationModel] gebruikt het type [Employe]. Bij de gesimuleerde laag [métier] was dit type gedefinieerd in de naamruimte [Pam.Metier.Entites]. Het bevindt zich nu in de naamruimte [Pam.EF5.Entites]. Corrigeer deze fouten zoals hierboven aangegeven.
9.25.4. Configuratie van de laag [web]
Net zoals in paragraaf 9.24.5 moeten we EF5 configureren in het bestand [web.config] van het project:
![]() |
Opdracht 1: vervang de volledige huidige inhoud van [web.config] door die van het bestand [app.config] uit het project [pam-metier].
Het bestand [Global.asax] van onze webapplicatie gebruikt [Spring.net] om een verwijzing op te halen uit de laag [métier]:
try
{
// instantie van laag [métier]
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
catch (Exception ex)
{
application.InitException = ex;
}
In regel 4 wordt een verwijzing opgevraagd naar het Spring-object met de naam [pammetier]. Dit is inderdaad de naam die is toegekend aan de laag [métier] (controleer dit in uw bestand [web.config]).
9.25.5. Test van de laag [web]
We zijn klaar om onze laag [web] te testen. We gaan eerst de werkpoort ervan wijzigen. Standaard heeft [pam-web-02] dezelfde configuratie als [pam-web-01] en werkt dus op dezelfde poort. De ervaring leert dat dit problemen oplevert: IIS blijft dan de codes van het project [pam-web-01] gebruiken. Ga als volgt te werk:
![]() |
![]() |
Wijzig in [4] het poortnummer, bijvoorbeeld door het cijfer in de eenhedenplaats te veranderen.
Voer het project [pam-web-02] uit via [Ctrl-F5]. U krijgt dan de volgende startpagina te zien:
![]() |
In [1] worden de medewerkers uit de database [dbpam_ef5] weergegeven. Merk op dat de medewerker [X X], die we hadden bij de gesimuleerde laag [métier], nu niet meer voorkomt. Laten we een simulatie uitvoeren:
![]() |
In [2] krijgen we inderdaad het werkelijke salaris te zien en niet langer een fictief salaris. Laten we nu SGBD en MySQL5 stoppen en nog een simulatie uitvoeren:
![]() |
In [3] hebben we een leesbare foutpagina gekregen, ook al zijn sommige meldingen in het Engels. Laten we nu MySQL opnieuw stoppen en de applicatie opnieuw uitvoeren in VS via [Ctrl-F5]:
![]() |
We krijgen de weergave [initFailed.cshtml] te zien die in paragraaf 9.20.4 is opgebouwd. Deze toont de foutmeldingen van de uitzonderingsstack. De lezer wordt uitgenodigd om nog meer tests uit te voeren.
9.26. Stap 19: een ASP.NET-toepassing op het internet beschikbaar maken
Wanneer men een toepassing ASP.NET ontwikkelt met Visual Studio, zorgt de standaardconfiguratie ervoor dat de ontwikkelde toepassing alleen toegankelijk is via het adres [localhost]. Elk ander adres wordt geweigerd door de ingebouwde server van Visual Studio, die vervolgens de foutmelding [400 Bad Request] retourneert.
Dit is als volgt te zien:
- noteer in een venster met het nummer DOS het adres IP van uw ontwikkelingsmachine:
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. . . :
Het adres IP wordt hier op regel 14 weergegeven. Als u een wifi-verbinding hebt, verschijnt het wifi-adres van de computer op regel 20 en de daaropvolgende regels.
- Controleer de eigenschappen van het project [clic droit sur projet / propriétés / onglet web]:
![]() |
De toepassing wordt uitgevoerd op poort [65010] van de machine [localhost].
- Voer uw project uit via [Ctrl-F5]

- Vervang [localhost] door het adres IP van de computer:

De server heeft een antwoord [400 Bad Request] teruggestuurd. De Express-server IIS die door Visual Studio wordt gebruikt, accepteert alleen de naam [localhost].
Om de ontwikkelde toepassing toegankelijk te maken voor een URL van het type [http://adresseIP/contexte/...], moet u een andere server gebruiken dan IIS Express, bijvoorbeeld een IIS-server (geen Express). Om te controleren of deze aanwezig is (normaal gesproken in de Pro-versies van Windows), moet u naar het configuratiescherm [Panneau de configuration\Système et sécurité\Outils d’administration] gaan:

Deze optie is niet altijd aanwezig. Ga in dat geval naar [ Panneau de configuration \ Programmes] en installeer de Webbeheerprogramma’s.
![]() |
Zodra de optie [Gestionnaire des services internet (IIS)] beschikbaar is, schakel je deze in:
![]() |
We starten de standaardwebsite. Hiervoor moet de service [Service de publication World Wide Web] eerst zijn gestart:
![]() |
Vervolgens roept u de URL [http://localhost] op met een browser. Controleer eerst of poort 80 niet al door een andere webserver wordt gebruikt. Zo ja, stop deze dan.
![]() |
De server IIS heeft ons geantwoord. Vervang nu [localhost] door het adres IP van uw computer:
![]() |
Het werkt. Laten we nu teruggaan naar Visual Studio:
- allereerst moet je Visual Studio starten in de modus [administrateur]
![]() |
Zodra dit is gebeurd, moet je de configuratie van het webproject dat je wilt implementeren aanpassen: [clic droit sur projet / propriétés / onglet web]:
![]() |
Je moet de lokale server IIS kiezen als implementatieserver. Visual Studio stelt de URL van de applicatie in. Je kunt dit wijzigen. Voer het project uit via [Ctrl-F5]:
![]() |
Vervang nu [localhost] door het adres IP van uw computer:
![]() |
Als de server IIS niet beschikbaar is, kunt u een gratis server ASP.NET gebruiken, zoals [Ultidev Web Server Pro], die beschikbaar is op URL en [http://ultidev.com/Download/ ]. Na de installatie zijn er twee manieren om een webapplicatie met deze server te starten:
De snelle manier
Open Windows Verkenner en selecteer de map van de ASP.NET-applicatie die je wilt implementeren:
![]() |
De webserver wordt dan gestart en de webapplicatie wordt in een browser weergegeven:
![]() |
- In [3] kunt u de webserver stoppen of starten;
- in [4] kan de servicepoort van de webapplicatie worden gewijzigd;
Voordat de server wordt gestart, moet de onderstaande service [UWS HiPriv Services] worden gestart:
![]() |
Zodra de server is gestart, ziet de interface er als volgt uit:
![]() |
Als je op de link [6] klikt, wordt de eerste pagina van de applicatie weergegeven:
![]() |
Vervolgens kun je het adres IP van de machine invoeren in plaats van [localhost]:
![]() |
Ook hier wordt dus alleen de naam [localhost] geaccepteerd.
De uitgebreide methode
Start de applicatie Ultidev Web Explorer
![]() |
en volg vervolgens de volgende stappen:
![]() |
![]() |
![]() |
- geef in [8] de map aan van de webapplicatie die moet worden geïmplementeerd;
![]() |
- vanwege [10-11] moet de webapplicatie worden aangevraagd met de URL [http://localhost:81/];
![]() |
![]() |
- start de webserver met [14];
![]() |
- vraag de URL [19] op;
![]() |
- in [20] hebben we de gewenste pagina verkregen door het lokale adres IP van de machine te gebruiken in plaats van de naam [localhost]. Dit is wat we zochten;
De Ultidev-server is geïnstalleerd als een Windows-service die automatisch wordt gestart. U kunt het automatisch opstarten van de Ultidev-server als volgt uitschakelen:
- kies de optie [Panneau de configuration\Système et sécurité\Outils d’administration];
![]() |
- [1, 2]: selecteer de eigenschappen van de service [Ultidev Web Server Pro];
- [3]: stel deze in op handmatig opstarten.
Om de server handmatig te starten, gebruikt u bijvoorbeeld de toepassing [Ultidev Web Explorer]:
![]() |
9.27. Stap 20: een native applicatie voor Android genereren
Wanneer je een webapplicatie van het type APU (applicatie met één pagina) hebt, is het mogelijk om een uitvoerbaar bestand voor mobiele apparaten (Android, IoS, Windows 8, ...) met de tool [Phonegap] [http://phonegap.com/]. Er zijn ook andere manieren om dit te doen, met name met het open-sourceproduct Apache Cordova [https://cordova.apache.org/]. De tool die online beschikbaar is op de website van Phonegap [http://build.phonegap.com/apps] ‘uploadt’ het zip-bestand van de te converteren website. De startpagina moet [index.html] heten en moet een statische pagina zijn, d.w.z. niet gegenereerd door een webframework (ASP.NET, JEE, PHP, ...). We gaan beginnen met het bouwen van deze pagina.
9.27.1. De architectuur van de applicatie
We moeten hier in gedachten houden dat we een Android-applicatie willen maken. Zo’n applicatie heeft vaak de volgende architectuur:
![]() |
- in [1] gebruikt de gebruiker een Android-tablet die communiceert met een of meer webservices [2];
Laten we teruggaan naar het model APU:
![]() |
- er wordt een startpagina in de browser geladen (het bovenstaande schema geeft niet aan waar deze vandaan komt);
- de volgende weergaven worden verkregen via Ajax-aanroepen [1-4]. Er wordt geen nieuwe pagina door de browser geladen;
De startpagina kan al dan niet door dezelfde server worden geleverd als de andere weergaven die via Ajax-aanroepen worden opgehaald. Als deze niet door dezelfde server wordt geleverd, moet het JavaScript van de startpagina de URL kennen van de webserver die de andere weergaven zal leveren. Dit zal het geval zijn in de Android-applicatie die we gaan bouwen:
![]() |
- de statische pagina [index.html] wordt ingekapseld in een native Android-app [1] die over de mogelijkheden van een browser beschikt en dus in staat is om de in de pagina [index.html] ingebedde JavaScript uit te voeren;
- deze pagina haalt de overige weergaven op via Ajax-verzoeken aan de server [2]. Hiervoor moet de pagina de URL van de webserver kennen;
We gaan de applicatie [pam-web-02] herstructureren zodat deze op deze manier werkt. De eerste pagina ziet er dan als volgt uit:
![]() |
- in [1], de URL van de startpagina van de applicatie. Deze wordt ons verstrekt door de Ultidev-server die in paragraaf 9.26 wordt besproken;
- in [2] moet de gebruiker de URL van de loonsimulator invoeren. We zouden deze waarde hard kunnen coderen in de JavaScript-code van de startpagina, maar dat zou het testen bemoeilijken: zodra we het adres van de simulator IP (of de poort) zouden wijzigen, zouden we deze waarde ook in de JavaScript-code moeten aanpassen;
- in [3], de link [Connexion] die de volgende weergave ophaalt:
![]() |
- Merk op dat bij [4] de URL van de browser niet is veranderd. Dit is nog steeds die van de startpagina en blijft zo gedurende de gehele levensduur van de applicatie.
Zodra deze weergave is opgehaald, werkt alles zoals voorheen: de verschillende weergaven worden opgehaald via Ajax-aanroepen. We zullen zien dat er maar heel weinig code hoeft te worden aangepast.
9.27.2. Herstructurering van het project [pam-web-02]
Binnen de map [Content] van het project [pam-web-02] maken we de volgende map [bootstrap] (de naam doet er niet toe):
![]() |
We hebben daarin de statische pagina [index.html] opgenomen, samen met alle benodigde bronbestanden (bestanden CSS en JS). De pagina [index.html] neemt de code over van de masterpagina [_Layout.cshtml] van het Visual Studio-project, waarbij alles wat niet statisch is, wordt verwijderd. Dit levert de volgende code op:
<!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>
We hebben de volgende punten toegevoegd:
- regels 27-29: we hebben de menuoptie [Connexion] toegevoegd om verbinding te kunnen maken met de simulatieservice;
- regels 55-56: het invoeren van de URL van de simulator;
- regels 59-63: een foutmelding als de verbinding mislukt;
De herstructurering van de code vindt uitsluitend plaats in de code [myScripts.js] op regel 14 hierboven. Verder verandert er niets. De code wordt als volgt aangepast:
// bij het laden van het document
$(document).ready(function () {
// de referenties van de verschillende componenten van de pagina worden opgehaald
loading = $("#loading");
content = $("#content");
erreur = $("#erreur");
erreur1 = $("#erreur1");
erreur2 = $("#erreur2");
// de links in het menu
lnkConnexion = $("#lnkConnexion");
lnkFaireSimulation = $("#lnkFaireSimulation");
lnkEffacerSimulation = $("#lnkEffacerSimulation");
lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
lnkVoirSimulations = $("#lnkVoirSimulations");
lnkTerminerSession = $("#lnkTerminerSession");
lnkRetourFormulaire = $("#lnkRetourFormulaire");
// deze worden in een array geplaatst
options = [lnkConnexion, lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
// bepaalde elementen van de pagina worden verborgen
loading.hide();
erreur.hide();
// het menu wordt vastgezet
setMenu([lnkConnexion]);
});
- regels 6-8: de ID's van het gebied waarin de verbindingsfouten worden weergegeven op de pagina [index.html];
- regel 10: de nieuwe link voor de verbinding met de simulator;
- regel 21: het foutvenster is aanvankelijk verborgen;
- regel 23: er wordt alleen de verbindingslink weergegeven;
Op de pagina [index.html] is de verbindingslink als volgt gedefinieerd:
<a id="lnkConnexion" href="javascript:connexion()">
| Connexion<br />
</a>
De functie JS [connexion] (regel 1) is als volgt:
var urlServiceWeb;
var erreur, erreur1, erreur2;
function connexion() {
// we halen de urlServiceWeb op uit de webservice
urlServiceWeb = $("#urlServiceWeb").val();
// het invoerformulier ophalen
$.ajax({
url: urlServiceWeb + '/Pam/Formulaire',
type: 'POST',
dataType: 'html',
beforeSend: function () {
// wachtindicator brandt
loading.show();
},
success: function (data) {
// weergave van de resultaten
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 () {
// wachtindicator uit
loading.hide();
}
});
}
- regel 7: de door de gebruiker ingevoerde URL wordt opgehaald. Deze wordt in de globale variabele van regel 1 geplaatst. Zo is deze bekend in de andere functies van het bestand;
- regel 10: er wordt een Ajax-aanroep gedaan naar de URL [/Pam/Formulaire] van de simulator. Deze URL geeft het gedeeltelijke overzicht weer van de ingevoerde simulatiegegevens (werknemers, gewerkte uren, gewerkte dagen). In de oorspronkelijke versie van [pam-web-02] was deze URL voldoende. Er werd automatisch het voorvoegsel URL aan toegevoegd, dat de startpagina had geladen. Nu gaan we ervan uit dat de startpagina kan worden geleverd door een andere server dan degene die de simulator ondersteunt. Dan moet aan de URL [/Pam/Formulaire] de variabele [urlServiceWeb] uit regel 1 worden toegevoegd, wat de URL van de simulator is (bijvoorbeeld http://172.19.81.34/pam-web-02). Dit moet voor alle Ajax-aanroepen in het bestand worden gedaan;
- regels 17-22: als de verbinding tot stand is gebracht, wordt de deelweergave [Formulaire.cshtml] weergegeven en verschijnt er een menu met alleen de link [Faire la simulation] (regel 21);
- regels 23-27: als de verbinding mislukt:
- in regel 24 wordt het antwoord HTML weergegeven dat door de webserver is verzonden (indien aanwezig);
- in regel 25 worden de headers HTTP weergegeven die door de webserver zijn verzonden (als deze heeft gereageerd);
Dat is alles. Als de verbinding tot stand is gebracht, krijg je de volgende pagina te zien:
![]() |
We bevinden ons nu in de vorige situatie, waarbij de weergaven voortaan via Ajax-aanroepen worden opgehaald. Zo zal hierboven het klikken op de link [Faire la simulation] worden uitgevoerd door de volgende code uit het bestand [myScripts.js]:
function faireSimulation() {
// referenties worden opgehaald
var simulation = $("#simulation");
var formulaire = $("#formulaire");
// formulier geldig?
var formValid = formulaire.validate().form();
if (!formValid) return;
// er wordt handmatig een Ajax-verzoek gedaan
$.ajax({
url: urlServiceWeb + '/Pam/FaireSimulation',
type: 'POST',
data: formulaire.serialize(),
dataType: 'html',
...
});
// menu
setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
- Er is slechts één wijziging aangebracht, namelijk in regel 10, waar de vorige URL nu wordt voorafgegaan door die van de simulator;
9.27.3. Test van het geherstructureerde project
In paragraaf 9.26 hebben we laten zien hoe de applicatie [pam-web-02] op de Ultidev-server geïnstalleerd kan worden. We gaan daar verder op in:
![]() |
- in [6] vragen we om de pagina [bootstrap/index.html] weer te geven. We krijgen het volgende scherm te zien:
![]() |
Laten we een foutieve URL invoeren:
![]() |
- in [10], de headers HTTP van het antwoord van de server;
- in [11], het document HTML uit het antwoord van de server;
Als je de juiste URL invoert:
![]() |
krijgt men het volgende antwoord:
![]() |
9.27.4. Het Android-binaire bestand maken
We gaan het Android-binaire bestand maken op basis van de statische website die we zojuist hebben gemaakt en getest: [1]:
![]() | ![]() |
We voegen in [2] een bestand [config.xml] toe dat zal dienen om de plug-in [Phonegap] te configureren, die het Android-binaire bestand zal genereren. De code ervan is als volgt:
<?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>
- regels 7-9: vul hier uw gegevens in;
- regels 11-13: deze regels zorgen ervoor dat de in de webapplicatie ingebedde JavaScript-code, die op het Android-apparaat wordt uitgevoerd, verzoeken kan sturen naar URL-bestanden buiten dit apparaat;
We zippen de inhoud van de map [Content/bootstrap]:
![]() |
Vervolgens gaan we naar de Phonegap-website [http://build.phonegap.com/apps]:
![]() |
- Vóór [1] moet u mogelijk een account aanmaken;
- in [1] gaan we aan de slag;
- in [2] kiest u een gratis abonnement waarmee slechts één Phonegap-app is toegestaan;
- in [3] download je de gecomprimeerde app [4];
![]() |
![]() |
- in [5], de naam van de app;
- klik op de link [6] om de binaire bestanden van OS en IoS voor Android en Windows te genereren. Dit kan enkele seconden duren;
![]() |
- in [7-9], download het Android-binaire bestand;
![]() |
Start een emulator [GenyMotion] voor een Android-tablet (zie paragraaf 11.1):
![]() |
Hierboven wordt een tabletemulator gestart met Android-versie API 21. Zodra de emulator is gestart,
- ontgrendel je deze door het grendelje (indien aanwezig) aan de zijkant naar de zijkant te schuiven en vervolgens los te laten;
- sleep met de muis het gedownloade bestand [Pam-debug.apk] naar de emulator. Het wordt dan geïnstalleerd en uitgevoerd;
![]() |
Stel [1] in, het URL van de simulator zoals beschreven in paragraaf 9.27.3. Maak vervolgens verbinding met de simulator via de link [2]:
![]() |
Test de applicatie op de emulator. Deze zou nu moeten werken.








































































































































































































