Skip to content

9. Caso di studio

9.1. Introduction

Presenteremo un caso di studio già pubblicato in un articolo disponibile su URL [http://tahe.developpez.com/dotnet/pam-aspnet/]. In questo articolo, il caso di studio è stato realizzato con il ASP.NET classico e il ORM NHibernate. Qui la realizzeremo con ASP.NET, MVC e ORM Entity Framework. Come nell’articolo esistente, il caso di studio è presentato come un progetto universitario. È quindi destinato agli studenti. Per ogni domanda, vengono forniti riferimenti ai capitoli che abbiamo appena illustrato per indicare le letture utili.

9.2. Il problema da risolvere

Vogliamo scrivere un’applicazione web che consenta a un utente di effettuare simulazioni di calcolo delle retribuzioni delle assistenti materne dell’associazione “Maison de la petite enfance” di un comune. Ci concentreremo sia sull’organizzazione del codice DotNet dell’applicazione sia sul codice stesso.

L’applicazione sarà di tipo APU [Application à Page Unique] e utilizzerà esclusivamente chiamate Ajax per comunicare con il server. Presenterà all’utente le seguenti viste:

  • la vista [VueSaisies] che presenta il modulo di simulazione

Image

  • la vista [VueSimulation] utilizzata per visualizzare il risultato dettagliato della simulazione:

Image

  • la vista [VueSimulations] che fornisce l’elenco delle simulazioni effettuate dal cliente

Image

  • la vista [VueSimulationsVides] che indica che il cliente non ha o non ha più simulazioni:

Image

  • la vista [VueErreurs] che segnala uno o più errori (in questo caso SGBD e MySQL sono stati interrotti):

Image

9.3. Architettura dell'applicazione

L'architettura dell'applicazione sarà la seguente:

Il livello [EF5] si riferisce all'Entity Framework 5 ORM. Il SGBD utilizzato sarà MySQL.

Realizzeremo questa applicazione inizialmente con un livello [métier] simulato:

Questo ci consentirà di concentrarci esclusivamente sul livello [web]. Il livello simulato [métier] rispetterà l’interfaccia del livello reale [métier]. Quando il livello [web] sarà operativo, verranno quindi creati i livelli [métier], [DAO] e [EF5].

9.4. Il database

I dati statici necessari per la creazione della busta paga sono inseriti in un database MySQL denominato [dbpam_ef5] (pam=Paie Assistante Maternelle). Questo database ha un amministratore denominato root senza password. Contiene tre tabelle:

Image

Esiste una relazione di chiave esterna tra la colonna EMPLOYES (INDEMNITE_ID) e la colonna INDEMNITES (ID). La struttura di questo database è determinata dal suo utilizzo con EF5. Torneremo su questo argomento quando svilupperemo i livelli inferiori dell'applicazione.

Tabella EMPLOYES: raccoglie informazioni sulle diverse assistenti materne

Struttura:

ID
chiave primaria incrementata automaticamente da SGBD
SS
numero di previdenza sociale del dipendente - univoco
NOM
nome del dipendente
PRENOM
il suo nome
ADRESSE
il suo indirizzo
VILLE
la sua città
CP
il suo codice postale
VERSIONING
un numero intero che si autoincrementa ogni volta che il record viene modificato
INDEMNITE_ID
chiave esterna sul campo [ID] della tabella [INDEMNITES]

Il suo contenuto potrebbe essere il seguente:

Image

Tabella COTISATIONS: raccoglie le aliquote dei contributi sociali trattenuti dallo stipendio

Struttura:

ID
chiave primaria incrementata automaticamente da SGBD
CSGRDS
percentuale: contributo sociale generalizzato + contributo al rimborso del debito sociale
CSGD
percentuale: contributo sociale generalizzato deducibile
SECU
percentuale: previdenza sociale
RETRAITE
percentuale: pensione integrativa + assicurazione contro la disoccupazione
VERSIONING
un numero intero autoincrementato ogni volta che il record viene modificato

Il suo contenuto potrebbe essere il seguente:

Image

Le aliquote dei contributi sociali sono indipendenti dal dipendente. La tabella precedente contiene una sola riga.

Tabella INDEMNITES: raccoglie le diverse indennità che dipendono dall’indice del dipendente
ID
chiave primaria incrementata automaticamente da SGBD
INDICE
indice di retribuzione - unico
BASE_HEURE
prezzo netto in euro per un'ora di guardia
ENTRETIEN_JOUR
indennità di vitto e alloggio in euro per giorno di servizio
REPAS_JOUR
indennità pasto in euro per giorno di assistenza
INDEMNITES_CP
indennità per ferie retribuite. Si tratta di una percentuale da applicare allo stipendio base.
VERSIONING
un numero intero che si autoincrementa ogni volta che il record viene modificato

Il suo contenuto potrebbe essere il seguente:

Image

9.5. Modalità di calcolo dello o stipendio di una tata

Presentiamo ora il metodo di calcolo dello stipendio mensile di un'assistente materna. Prendiamo come esempio lo stipendio della signora Marie Jouveinal, che ha lavorato 150 ore in 20 giorni durante il mese di riferimento.

Vengono presi in considerazione i seguenti elementi:
[TOTALHEURES]: totale delle ore lavorate nel mese
[TOTALJOURS]: totale dei giorni lavorati nel mese
[TOTALHEURES]=150
[TOTALJOURS]= 20
Lo stipendio base dell’assistente all’infanzia è dato dalla seguente formula:
[SALAIREBASE]=([TOTALHEURES]*[BASEHEURE])*(1+[INDEMNITESCP]/100)
[SALAIREBASE]=(150*[2.1])*(1+0,15)= 362,25
Da questo stipendio base devono essere prelevati alcuni contributi sociali:
Contributo sociale generalizzato e contributo al rimborso del debito sociale: [SALAIREBASE]*[CSGRDS/100]
Contributo sociale generalizzato deducibile: [SALAIREBASE]*[CSGD/100]
Previdenza sociale, vedovanza, vecchiaia: [SALAIREBASE]*[SECU/100]
Pensione integrativa + AGPF + Assicurazione contro la disoccupazione: [SALAIREBASE]*[RETRAITE/100]
CSGRDS: 12,64
CSGD: 22,28
Previdenza sociale: 34,02
Pensione: 28,55
Totale contributi sociali:
[COTISATIONSSOCIALES] = [SALAIREBASE] *(CSGRDS+CSGD+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
Inoltre, l’assistente all’infanzia ha diritto, per ogni giorno lavorato, a un’indennità di mantenimento e a un’indennità di pasto. A tal titolo riceve le seguenti indennità:
[Indemnités]=[TOTALJOURS]*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
In definitiva, lo stipendio netto da corrispondere alla tata è il seguente:
[SALAIREBASE] - [COTISATIONSSOCIALES] + [INDEMNITÉS]
[salaire NET]=368,77

9.6. Il progetto Visual Studio del livello [web]

Il progetto Visual Web Developer dell’applicazione sarà il seguente:

  • in [1], la struttura generale del progetto [pam-web-01];
  • in [2], la cartella [Content] è quella in cui vengono inserite le risorse statiche del progetto:
    • [indicator.gif]: l'immagine animata che indica l'attesa della conclusione di una richiesta Ajax,
    • [standard.jpg]: l'immagine di sfondo delle diverse viste,
    • [Site.css]: il foglio di stile dell'applicazione;
  • in [3], l’unico controller dell’applicazione [PamController];
  • in [4], le classi necessarie all’applicazione ma che non possono essere classificate come elementi di MVC:
    • [ApplicationModelBinder]: la classe che consente di includere i dati dell’ambito [Application] nel modello delle azioni,
    • [SessionModelBinder]: la classe che consente di includere i dati dell'ambito [Session] nel modello delle azioni,
    • [Static]: una classe di supporto con metodi statici;
  • in [5], i modelli dell'applicazione, siano essi modelli di azioni o di viste:
    • [ApplicationModel]: modello contenente i dati dell'ambito [Application],
    • [SessionModel]: modello contenente i dati dell'ambito [Session],
    • [Simulation]: classe che incapsula gli elementi di una simulazione di calcolo dello stipendio,
    • [IndexModel]: modello della prima vista [Index] visualizzata dall'applicazione;
  • in [6], gli script JS necessari per la globalizzazione dell’applicazione;
  • in [7], gli script JS della famiglia JQuery necessari per l’internazionalizzazione, la convalida lato client e l’implementazione di AJAX nell’applicazione;
  • in [8], [myScripts.js] è il file contenente i nostri script JS;
  • in [9], le viste dell'applicazione:
    • [Index]: la pagina iniziale,
    • [Formulaire]: il modulo per l'inserimento dei dati del dipendente, delle ore e dei giorni lavorati,
    • [Simulation]: la vista che presenta una simulazione,
    • [Simulations]: la vista che mostra l'elenco delle simulazioni effettuate,
    • [Erreurs]: la schermata che mostra l'elenco degli eventuali errori,
    • [InitFailed]: la vista che visualizza i messaggi di errore se l'inizializzazione dell'applicazione non va a buon fine;
  • in [10], la pagina principale dell’applicazione [_Layout];
  • in [11], i file [Web.config] e [Global.asax] utilizzati per configurare l'applicazione.

9.7. Fase 1 – Implementazione del livello simulato [métier]

Da questo punto in poi descriviamo i passaggi da seguire per realizzare il caso di studio. Quando necessario, riportiamo il numero del capitolo da rileggere eventualmente per svolgere il lavoro richiesto. Alcuni elementi del progetto sono forniti in una cartella [aspnetmvc-support.zip] disponibile sul sito di questo documento. Al suo interno si trova la cartella [étudedecas-support] con il seguente contenuto:

  

Il progetto riprende inoltre alcuni elementi presentati nei capitoli precedenti. È quindi sufficiente recuperarli tramite copia/incolla tra questa cartella PDF e Visual Studio.

9.7.1. La soluzione Visual Studio dell’applicazione completa

Per prima cosa creeremo una soluzione Visual Studio all’interno della quale creeremo due progetti:

  • un progetto per il livello simulato [métier];
  • un progetto per il livello web MVC.

Utilizzeremo due strumenti:

  • Visual Studio Express 2012 per desktop, che servirà a realizzare il livello [métier];
  • Visual Studio Express 2012 per il Web, che servirà a creare il livello [web].

Con Visual Studio Express per desktop, creiamo una soluzione [pam-td]:

  • in [1], selezionare un'applicazione C#;
  • in [2], selezionare [Application console];
  • in [3], assegnare un nome alla soluzione;
  • in [4], creare una cartella per questa soluzione;
  • in [5], assegnare un nome al livello [métier];
  • in [6], la soluzione generata.

9.7.2. L'interfaccia del livello [métier]

In un'architettura a livelli, è buona prassi che la comunicazione tra i livelli avvenga tramite interfacce:

Quale interfaccia deve presentare il livello [métier] al livello [web]? Quali sono le possibili interazioni tra questi due livelli? Ricordiamo l’interfaccia web che verrà presentata all’utente:

  1. alla visualizzazione iniziale del modulo, in [1] deve essere presente l'elenco dei dipendenti. È sufficiente un elenco semplificato (Cognome, Nome, SS). Il numero SS è necessario per accedere alle informazioni aggiuntive sul dipendente selezionato (informazioni da 6 a 11).
  2. Le informazioni da 12 a 15 indicano le diverse aliquote contributive.
  3. Le informazioni da 16 a 19 sono le indennità del dipendente
  4. le informazioni da 20 a 24 sono le componenti salariali calcolate in base ai dati inseriti dall'utente nei campi da 1 a 3.

L'interfaccia [IPamMetier] fornita al livello [web] dal livello [métier] deve soddisfare i requisiti sopra indicati. Esistono numerose interfacce possibili. Proponiamo la seguente:


using Pam.Metier.Entites;
namespace Pam.Metier.Service
{
  public interface IPamMetier
  {
    // elenco di tutte le identità dei dipendenti 
    Employe[] GetAllIdentitesEmployes();

    // ------- calcolo dello stipendio 
    FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
  }
}
  • riga 7: il metodo che consentirà il popolamento del menu a tendina [1]
  • riga 10: il metodo che consentirà di ottenere le informazioni da 6 a 24. Queste sono state raggruppate in un oggetto di tipo [FeuilleSalaire] che descriveremo a breve.

Inseriremo questa interfaccia in una cartella denominata [metier/service]:

9.7.3. Le entità del livello [métier]

L’interfaccia precedente utilizza due classi, [Employe] e [FeuilleSalaire], che dobbiamo definire:

  • [Employe] è l'immagine di una riga della tabella [employes] del database;
  • [FeuilleSalaire] è la busta paga di un dipendente.

Le entità saranno inserite in una cartella [metier / entites] del progetto:

Nell’architettura finale, il livello [métier] gestirà le entità immagine del database:

Image

Utilizzeremo le seguenti classi per rappresentare le righe delle tre tabelle del database. Si rimanda al paragrafo 9.4 per conoscere il significato dei diversi campi.

Classe [Employe]

Rappresenta una riga della tabella [employes]. Il suo codice è il seguente:


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; }

    // firma
    public override string ToString()
    {
      return string.Format("Employé[{0},{1},{2},{3},{4},{5}]", SS, Nom, Prenom, Adresse, Ville, CodePostal);
    }
  }
}

Classe [Indemnites]

Rappresenta una riga della tabella [indemnites]. Il suo codice è il seguente:


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; }
    // firma
    public override string ToString()
    {
      return string.Format("Indemnités[{0},{1},{2},{3},{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
    }
  }
}

Classe [Cotisations]

Rappresenta una riga della tabella [cotisations]. Il suo codice è il seguente:


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; }
    // firma
    public override string ToString()
    {
      return string.Format("Cotisations[{0},{1},{2},{3}]", CsgRds, Csgd, Secu, Retraite);
    }
  }
}

Si noti che le classi non riprendono le colonne [ID] e [VERSIONING] delle tabelle. Queste colonne, utili quando si utilizzano le tabelle ORM e EF5, non lo sono nel contesto del livello simulato [métier].

La classe [FeuilleSalaire] incapsula le informazioni da 6 a 24 del modulo già presentato:


namespace Pam.Metier.Entites
{
  public class FeuilleSalaire
  {

    // proprietà automatiche 
    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);
    }
  }
}
  • riga 7: le informazioni da 6 a 11 relative al dipendente di cui si calcola lo stipendio e le informazioni da 16 a 19 relative alle sue indennità. Non bisogna dimenticare che un oggetto [Employe] incapsula un oggetto [Indemnites] che rappresenta le sue indennità;
  • riga 8: le informazioni da 12 a 15;
  • riga 9: le informazioni da 20 a 24;
  • righe 12-14: il metodo [ToString].

La classe [ElementsSalaire] incapsula le informazioni da 20 a 24 del modulo:


namespace Pam.Metier.Entites
{
  public class ElementsSalaire
  {
    // proprietà automatiche 
    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);
    }
  }
}
  • righe da 6 a 10: le componenti dello stipendio come spiegato nelle regole di business descritte in precedenza;
  • riga 6: la retribuzione base del dipendente, in funzione del numero di ore lavorate;
  • riga 7: i contributi trattenuti su tale stipendio base;
  • righe 8 e 9: le indennità da aggiungere alla retribuzione base, in funzione dell’indice del dipendente e del numero di giorni lavorati;
  • riga 10: la retribuzione netta da corrispondere;
  • righe 14-17: il metodo [ToString] della classe.

9.7.4. La classe [PamException]

Creiamo un tipo di eccezioni specifico per la nostra applicazione. Si tratta del seguente tipo [PamException]:


using System;

namespace Pam.Metier.Entites
{
  // classe di eccezione
  public class PamException : Exception
  {

    // il codice di errore 
    public int Code { get; set; }

    // costruttori 
    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;
    }
  }
}
  • riga 6: la classe deriva dalla classe [Exception];
  • riga 10: ha una proprietà pubblica [Code] che è un codice di errore;
  • nella nostra applicazione useremo due tipi di costruttori:
    • quello delle righe 23-27, che può essere utilizzato come mostrato di seguito:
throw new PamException("Problème d'accès aux données",5);
  • (continua)
    • oppure quello delle righe 29-33, destinato a segnalare un'eccezione verificatasi incapsulandola in un'eccezione di tipo [PamException]:
try{
....
}catch (IOException ex){
     // si incapsula l'eccezione ex     
    throw new PamException("Problème d'accès aux données",ex,10);
}

Questo secondo metodo ha il vantaggio di non perdere le informazioni che può contenere la prima eccezione.

9.7.5. Implementazione del livello [métier]

L'interfaccia [IPamMetier] sarà implementata dalla seguente classe [PamMetier]:


using System;
using Pam.Metier.Entites;
using System.Collections.Generic;

namespace Pam.Metier.Service
{
  public class PamMetier : IPamMetier
  {
    // elenco dei dipendenti in cache
    public Employe[] Employes { get; set; }
    // dipendenti indicizzati in base al loro numero SS
    private IDictionary<string, Employe> dicEmployes = new Dictionary<string, Employe>();

    // elenco dei dipendenti
    public Employe[] GetAllIdentitesEmployes()
    {
...
      // elenco dei dipendenti
      return Employes;
    }

    // calcolo dello stipendio
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
    {
...
  }
}
  • riga 7: la classe [PamMetier] implementa l'interfaccia [IPamMetier];
  • riga 10: la classe [PamMetier] mantiene in cache l'elenco dei dipendenti;
  • riga 12: un dizionario che associa un dipendente al suo numero di previdenza sociale;
  • righe 15-20: il metodo che restituisce l'elenco dei dipendenti;
  • righe 23-26: il metodo che calcola lo stipendio di un dipendente.

Il metodo [GetAllIdentitesEmploye] è il seguente:


// elenco dei dipendenti
    public Employe[] GetAllIdentitesEmployes()
    {
      if (Employes == null)
      {
        // si crea una tabella con tre dipendenti
        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]);
        // un dipendente fittizio che non verrà inserito nel dizionario
        // per simulare un dipendente inesistente
        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 }
        };
      }
      // si restituisce l'elenco dei dipendenti
      return Employes;
    }
  • riga 4: si verifica se l’elenco dei dipendenti non sia già stato generato;
  • riga 7: se non è così, si crea un array di tre dipendenti;
  • righe 8-17: il primo dipendente;
  • riga 18: viene inserito nel dizionario;
  • righe 19-28: il secondo dipendente;
  • riga 29: viene inserito nel dizionario;
  • righe 32-42: il terzo dipendente. Quest'ultimo non viene inserito nel dizionario per un motivo che spiegheremo.

Il metodo [GetSalaire] sarà il seguente:


    // calcolo dello stipendio
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
    {
      // si recupera il dipendente con n. SS
      Employe e = dicEmployes.ContainsKey(ss) ? dicEmployes[ss] : null;
      // esiste?
      if (e == null)
      {
        throw new PamException(string.Format("L'employé de n° SS [{0}] n'existe pas", ss), 10);
      }
      // viene restituita una busta paga fittizia
      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 }
      };
}
  • riga 2: il metodo riceve il numero SS del dipendente di cui si vuole calcolare lo stipendio, il numero di ore lavorate e il numero di giorni lavorati;
  • riga 5: si cerca il dipendente nel dizionario. Ricordiamo che uno di essi non è presente;
  • righe 7-10: se il dipendente non viene trovato, viene generata un'eccezione [PamException];
  • righe 12-17: viene restituita una busta paga fittizia.

9.7.6. Il test da console del livello [métier]

Il progetto del livello [métier] è attualmente il seguente:

La classe [Program] sopra indicata testerà i metodi dell'interfaccia [IPamMetier]. Un esempio di base potrebbe essere il seguente:


using Pam.Metier.Entites;
using Pam.Metier.Service;
using System;

namespace Pam.Metier.Tests
{
  class Program
  {
    public static void Main()
    {
      // istanziazione del livello [métier]
      IPamMetier pamMetier = new PamMetier();
      // elenco dei dipendenti
      Employe[] employes = pamMetier.GetAllIdentitesEmployes();
      Console.WriteLine("Liste des employés--------------------");
      foreach (Employe e in employes)
      {
        Console.WriteLine(e);
      }
      // calcoli delle buste paga 
      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));
      }
    }
  }
}
  • riga 12: istanziazione del livello [métier];
  • righe 14-19: test del metodo [GetAllIdentitesEmploye] dell'interfaccia [IPamMetier];
  • righe 21-31: test del metodo [GetSalaire] dell'interfaccia [IPamMetier].

L'esecuzione di questo programma da console fornisce i seguenti risultati:

Liste des employés--------------------
Employé[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203]
Employé[260124402111742,Laverti,Justine,La brûlerie,St Marcel,49014]
Employé[XX,X,X,X,X,X]
Calculs de feuilles de salaire-----------------
[Employé[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203],Co
tisations[3,49,6,15,9,38,7,88],[100 : 100 : 100 : 100 : 100]]
[Employé[260124402111742,Laverti,Justine,La brûlerie,St Marcel,49014],Cotisation
s[3,49,6,15,9,38,7,88],[100 : 100 : 100 : 100 : 100]]
PamException : L'employé de n° SS [XX] n'existe pas

Si invita il lettore a collegare questi risultati al codice eseguito.

Per poter utilizzare questo progetto nel progetto web che realizzeremo, ne creiamo una libreria di classi:

  • in [1], nelle proprietà del file [Program.cs];
  • in [2], si specifica che il file non farà parte dell’assembly generato;
  • in [3, 4], nelle proprietà del progetto [pam-metier-simule], nell’opzione [Application] [3], si specifica [4] che la generazione deve fornire una libreria di classi (sotto forma di un DLL).
  • in [5], viene richiesto un assembly di tipo [Release]. L'altro tipo è [Debug]. L'assembly contiene quindi informazioni che facilitano il debug;
  • in [6], si genera il progetto [pam-metier-simule];
  • in [7], si visualizzano tutti i file della soluzione;
  • in [8], nella cartella [bin / Release], si trova il file DLL del nostro progetto.

9.8. Fase 2: configurazione dell'applicazione web

Nella soluzione Visual Studio precedente creeremo il progetto per il livello web MVC.

Con Visual Studio Express per il Web, apriamo la soluzione [pam-td] creata in precedenza con Visual Studio Express per il desktop.

  • In [1], la soluzione [pam-td] è stata caricata in Visual Studio Express per il Web;
  • in [2], la soluzione e il progetto per il livello [métier] simulato che abbiamo appena creato.

In questa nuova fase, creeremo la struttura di base dell’applicazione web.

  • in [1], aggiungiamo un nuovo progetto alla soluzione [pam-td];
  • in [2], si seleziona un progetto ASP.NET MVC 4;
  • denominato [pam-web-01] [3];
  • in [4], si sceglie il modello di base ASP.NET MVC;
  • in [5], il progetto creato;
  • in [6], si crea un nuovo progetto, il progetto di avvio della soluzione, quello che verrà eseguito quando si eseguirà [Ctrl-F5];
  • in [7], il nome del nuovo progetto è evidenziato in grassetto, a indicare che si tratta del progetto di avvio della soluzione.

Ora, tramite Esplora risorse di Windows, si sostituisce la cartella [Content] del progetto con la cartella [étudedecas-support / web / Content]. Fatto ciò, è necessario includere i nuovi file nel progetto [pam-web-01]. Si procederà come segue:

  • in [1], si aggiorna la soluzione;
  • in [2], visualizzare tutti i file della soluzione;
  • in [3], compare una cartella [Images];
  • che viene inclusa nel progetto in [4].

Nella cartella [Scripts], aggiungete gli script JQuery Globalization e [1] necessari per la convalida lato client.

La pagina master [_Layout.cshtml] [2] avrà il seguente contenuto:


<!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>

Nota: riga 8, adattare la versione di jQuery a quella della propria versione di Visual Studio.

  • riga 7: riferimento al foglio di stile dell'applicazione;
  • righe 8-10: riferimenti agli script necessari per la convalida lato client;
  • righe 11-12: riferimenti agli script necessari per l'inserimento dei numeri reali francesi con la virgola;
  • riga 13: riferimento agli script necessari per la modalità Ajax;
  • riga 14: gli script specifici dell'applicazione;
  • riga 24: l'immagine di attesa al termine delle chiamate Ajax;
  • righe 26-39: sei link JavaScript;
  • riga 43: la sezione in cui verranno visualizzate le diverse viste dell'applicazione;
  • riga 44: il corpo delle diverse viste dell'applicazione.

Successivamente, modificheremo il percorso predefinito dell'applicazione:

Il file [RouteConfig] avrà il seguente contenuto:


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" }
      );
    }
  }
}
  • riga 14: i URL assumeranno la forma [{controller}/{action}];
  • riga 15: in assenza di un'azione, verrà utilizzata l'azione [Index]. In assenza di un controller, verrà utilizzato il controller [Pam].

Da questa configurazione risulta che URL [/] è equivalente a URL [/Pam/Index]. Poiché la nostra applicazione è di tipo APU, URL e [/] saranno gli unici URL di quest'ultima.

Creare il controller [Pam]:

  

Modificate il controller [PamController] come segue:


using System.Web.Mvc;

namespace Pam.Web.Controllers
{
    public class PamController : Controller
    {
        [HttpGet]
        public ViewResult Index()
        {
            return View();
        }

    }
}
  • riga 3: inseriamo il controller nello spazio dei nomi [Pam.Web.Controllers];
  • riga 7: l'azione [Index] elaborerà solo il comando HTTP GET;
  • riga 8: si restituisce un tipo [ViewResult] anziché un tipo [ActionResult].

Ora create la vista [Index.cshtml] visualizzata dall'azione [Index] sopra indicata:

Modifica [Index.cshtml] come segue:


@{
  ViewBag.Title = "Pam";
}
<h2>Formulaire</h2>

Eseguire l'applicazione tramite [Ctrl-F5]. Dovrebbe apparire la seguente pagina:

 

Compito: Spiega cosa è successo.


L'applicazione utilizza un foglio di stile a cui si fa riferimento nella pagina master [_Layout.cshtml]:


  <link rel="stylesheet" href="~/Content/Site.css" />

Il foglio di stile [/Content/Site.css] definisce un'immagine di sfondo per le pagine dell'applicazione:


body {
  background-image: url("/Content/Images/standard.jpg");
}

9.9. Fase 3: configurazione del modello APU

Vogliamo scrivere un'applicazione seguendo il modello APU (Applicazione a pagina singola) descritto nel paragrafo 7.5 e nel paragrafo 7.6. La pagina singola è quella caricata dal browser all'avvio dell'applicazione:

  • la parte [1] sopra riportata è la parte fissa della pagina singola. Abbiamo visto che essa è fornita dalla pagina master [_Layout.cshtml];
  • la parte [2] è la parte variabile della pagina singola. Si inserisce nell’area con id [content] della pagina master [_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>

I vari frammenti di pagina dell'applicazione verranno visualizzati nell'area con ID [content] alla riga 13. Verranno visualizzati tramite chiamate Ajax. Gli script JavaScript che eseguono queste chiamate si trovano nel file [myScripts.js], a cui si fa riferimento alla riga 6. Creare questo file di cui avremo bisogno:

Ora seguiamo il modello APU descritto al paragrafo 7.6. Rileggete questo paragrafo se lo avete dimenticato. Ora implementeremo i diversi frammenti di pagina visualizzati dall’applicazione.

9.9.1. Gli strumenti di sviluppo JavaScript

Ricordiamo che con il browser Chrome avete a disposizione una serie di strumenti per il debug del codice JavaScript delle vostre pagine (HTML, CSS). Questi strumenti sono stati presentati in parte nel paragrafo 7.2. Nel modello APU, i browser mantengono nella cache gli script JavaScript a cui fa riferimento la prima pagina dell’applicazione. Pertanto, è necessario ricordarsi di svuotare questa cache quando si modificano gli script, altrimenti le modifiche potrebbero non essere prese in considerazione. Ecco come procedere con Chrome:

- eseguire [Ctrl-Maj-I] per visualizzare l’ambiente di sviluppo

  • fare clic sull'icona [1] in basso a destra nella finestra di sviluppo;
  • quindi selezionare l'opzione [2] che disattiva la cache in modalità di sviluppo.

9.9.2. Utilizzo di una vista parziale per visualizzare il modulo

Il modulo di immissione dati è uno dei frammenti visualizzati dall'applicazione. Al momento questo modulo viene visualizzato dalla vista [Index.cshtml], che è una vista completa:


@{
  ViewBag.Title = "Pam";
}
<h2>Formulaire</h2>

Questa vista viene visualizzata dall’azione [Index]:


    [HttpGet]
    public ViewResult Index()
    {
      return View();
}

Nella riga 4 sopra riportata, viene visualizzata effettivamente una vista [View] e non una vista parziale [PartialView]. Abbiamo bisogno di una vista parziale per il modulo, che costituirà un frammento di pagina. Modifichiamo la vista [Index.cshtml] nel modo seguente:


@{
  ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")

Riga 4: il modulo non fa più parte della pagina [Index.cshtml]. Ora è contenuto in una vista parziale [Formulaire.cshtml]:

Il codice di [Formulaire.cshtml] è semplicemente il seguente:


<h2>Formulaire</h2>

Apportate queste modifiche e verificate che all'avvio dell'applicazione venga visualizzata sempre la seguente schermata:

 

9.9.3. La chiamata Ajax [faireSimulation]

Ci interessa il frammento visualizzato quando l’utente clicca sul link [Faire la simulation]:

  • in [1], l’utente clicca sul link [Faire la simulation];
  • in [2], la simulazione appare sotto il modulo.

Modifichiamo come segue la vista parziale [Formulaire.cshtml] che visualizza il modulo:


<h2>Formulaire</h2>
<div id="simulation" />

Alla riga 3, creiamo una regione con ID [simulation] per ospitare il frammento della simulazione.

Creiamo la vista parziale [Simulation.cshtml] come segue:

Il contenuto della vista [Simulation.cshtml] è il seguente:


<hr />
<h2>Simulation</h2>

Ora dobbiamo scrivere il codice JavaScript che gestisce il clic sul link [Faire la simulation]. Seguiremo la procedura descritta nel paragrafo 7.6.5. Innanzitutto, esaminiamo il codice HTML del link in [_Layout.cshtml]:


<a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
</a>

Si nota che cliccando sul link [Faire la simulation] verrà avviata l’esecuzione della funzione JS [faireSimulation]. Questa funzione verrà scritta nel file [myScripts.js] insieme alle altre funzioni JS necessarie all’applicazione:


// variabili globali
var loading;
var content;

function faireSimulation() {
  // si effettua manualmente una chiamata Ajax
...
}

function effacerSimulation() {
  // si cancellano i dati inseriti nel modulo
...
}

function enregistrerSimulation() {
  // si effettua una chiamata Ajax manualmente
  ...
}

function voirSimulations() {
  // si effettua una chiamata Ajax manualmente
  ...
}

function retourFormulaire() {
  // si effettua una chiamata Ajax manualmente
...
}

function terminerSession() {
...
}

// al caricamento del documento
$(document).ready(function () {
  // si recuperano i riferimenti dei vari componenti della pagina
  loading = $("#loading");
  content = $("#content");
});
  • righe 35-39: la funzione JQuery viene eseguita all'avvio dell'applicazione;
  • righe 37-38: si inizializzano le variabili globali delle righe 2 e 3.

Si ricorda che gli elementi con ID [loading] e [content] sono definiti nella pagina master [_Layout.cshtml] (righe 14 e 21 riportate di seguito):


<!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>

Esercizio: seguendo la procedura illustrata al paragrafo 7.6.5, scrivere la funzione JS [faireSimulation]. Questa funzione invierà una richiesta Ajax di tipo POST all’azione [/Pam/FaireSimulation]. Per il momento non verranno inviati dati. L’azione [/Pam/FaireSimulation] restituirà la vista parziale [Simulation.cshtml] alla funzione JS [faireSimulation], che a sua volta inserirà questo flusso HTML nella regione con ID [simulation] del modulo.


Provate il link [Faire la simulation] della vostra applicazione.

9.9.4. La chiamata Ajax [enregistrerSimulation]

Il link [Enregistrer la simulation] è definito come segue in [_Layout.cshtml]:


<a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la simulation<br />
</a>

Esercizio: seguendo la procedura precedente, scrivi la funzione JS [enregistrerSimulation]. Questa invierà una chiamata Ajax di tipo POST all’azione [/Pam/EnregistrerSimulation]. Per il momento non verranno inviati dati. L’azione [/Pam/EnregistrerSimulation] restituirà la vista parziale [Simulations.cshtml] alla funzione JS [enregistrerSimulation], che a sua volta inserirà questo flusso HTML nella regione con ID [content] della pagina master.


La vista [Simulations.cshtml] è la seguente:

Il suo contenuto è il seguente:


<h2>Simulations</h2>

Ecco un esempio di esecuzione:

9.9.5. La chiamata Ajax [voirSimulations]

Il collegamento [Voir les simulations] è definito come segue in [_Layout.cshtml]:


<a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
</a>

Esercizio: seguendo la procedura precedente, scrivi la funzione JS [voirSimulations]. Questa invierà una chiamata Ajax di tipo POST all'azione [/Pam/VoirSimulations]. Per il momento non verranno inviati dati. L’azione [/Pam/VoirSimulations] restituirà la vista parziale [Simulations.cshtml] alla funzione JS [voirSimulations], che a sua volta inserirà questo flusso HTML nella regione con ID [content] della pagina master.


La vista [Simulations.cshtml] è quella già utilizzata nella domanda precedente.

Ecco un esempio di esecuzione:

9.9.6. La chiamata Ajax [retourFormulaire]

Il link [Retour au formulaire de simulation] è definito come segue in [_Layout.cshtml]:


<a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de simulation<br />
</a>

Esercizio: seguendo la procedura precedente, scrivi la funzione JS [retourFormulaire]. Questa invierà una chiamata Ajax di tipo POST all’azione [/Pam/Formulaire]. Per il momento non verranno inviati dati. L’azione [/Pam/Formulaire] restituirà la vista parziale [Formulaire.cshtml] alla funzione JS [retourFormulaire], che a sua volta inserirà questo flusso HTML nella regione con ID [content] della pagina master.


La vista [Formulaire .cshtml] è già stata definita. Ecco un esempio di esecuzione:

9.9.7. La chiamata Ajax [terminerSession]

Il collegamento [Terminer la session] è definito come segue in [_Layout.cshtml]:


<a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
</a>

Esercizio: seguendo la procedura descritta in precedenza, scrivete la funzione JS [terminerSession]. Questa invierà una chiamata Ajax di tipo POST all’azione [/Pam/TerminerSession]. Per il momento non verranno inviati dati. L’azione [/Pam/TerminerSession] restituirà la vista parziale [Formulaire.cshtml] alla funzione JS [terminerSession], che a sua volta inserirà questo flusso HTML nella regione con ID [content] della pagina master.


Ecco un esempio di esecuzione:

9.9.8. La funzione JS [effacerSimulation]

Il collegamento [Effacer la simulation] è definito come segue in [_Layout.cshtml]:


<a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
</a>

La funzione JS [effacerSimulation] ha lo scopo di:

  • nascondere il frammento [Simulation], se presente;
  • ripristinare i campi di immissione del modulo allo stato in cui si trovavano al momento del caricamento iniziale dell'applicazione (quando ci saranno campi di immissione - per ora non ce ne sono).

Compito: scrivere la funzione JS [effacerSimulation]. Qui non vi è alcuna chiamata Ajax. Ciò che avviene è interno al browser e non coinvolge il server.


Ecco un esempio di esecuzione:

9.9.9. Gestione della navigazione tra le schermate

Per il momento, i link vengono sempre visualizzati. Ora gestiremo la loro visualizzazione con una funzione JavaScript. Ricordiamo innanzitutto il codice dei sei link JavaScript in [_Layout.cshtml]:


<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>

Tutti i link hanno un attributo [id] che ci consentirà di gestirli in JavaScript. Modifichiamo il metodo JS eseguito al caricamento della pagina nel modo seguente:


// variabili globali
var loading;
var content;
var lnkFaireSimulation;
var lnkEffacerSimulation
var lnkEnregistrerSimulation;
var lnkTerminerSession;
var lnkVoirSimulations;
var lnkRetourFormulaire;
var options;

...
// al caricamento del documento
$(document).ready(function () {
  // si recuperano i riferimenti dei vari componenti della pagina
  loading = $("#loading");
  content = $("#content");
  // i link del menu
  lnkFaireSimulation = $("#lnkFaireSimulation");
  lnkEffacerSimulation = $("#lnkEffacerSimulation");
  lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
  lnkVoirSimulations = $("#lnkVoirSimulations");
  lnkTerminerSession = $("#lnkTerminerSession");
  lnkRetourFormulaire = $("#lnkRetourFormulaire");
  // li inseriamo in una tabella
  options = [lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
  // si nascondono alcuni elementi della pagina
  loading.hide();
  // si fissa il menu
  setMenu([lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]);
});

  • righe 19-24: si recuperano i riferimenti ai sei link. Questi riferimenti sono definiti come variabili globali alle righe 4-9;
  • riga 26: l'array [options] viene inizializzato con i sei riferimenti. Questo array è definito come variabile globale alla riga 10;
  • riga 28: si nasconde l'immagine animata che indica l'attesa del completamento delle chiamate Ajax;
  • riga 30: si visualizzano i link [lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]. Gli altri saranno nascosti.

La funzione JS [setMenu] è la seguente:


function setMenu(show) {
  // si visualizzano i link della tabella [show]
...
}

Compito: scrivere la funzione JS [setMenu].


Se T è un array di link:

  • T.length è il numero di link;
  • T[i] è il link n. i;
  • T[i].show() visualizza il link n. i;
  • T[i].hide() nasconde il link n. i.

Con queste nuove funzioni JS, la pagina visualizzata all'avvio è la seguente:

 

Modificate le funzioni JS e [faireSimulation, effacerSimulation, enregistrerSimulation, voirSimulations, retourFormulaire, terminerSession] in modo da ottenere le schermate seguenti:

Ora che il modello APU e i link di navigazione sono stati implementati, possiamo passare alla scrittura delle azioni e delle viste lato server. Man mano che procederete, scoprirete che alcuni dei link Ajax che ora funzionano smetteranno di funzionare, poiché modificherete le viste parziali inviate al client. Man mano che realizzerete le diverse azioni e viste lato server, i collegamenti Ajax lato client torneranno a funzionare come li avete impostati.

9.10. Fase 4: scrittura dell’azione lato server [Index]

Attualmente, all’avvio dell’applicazione, viene visualizzata la seguente schermata:

 

Invece di questa schermata, vorremmo visualizzare la seguente:

 

È l'azione [Index] che deve generare questa pagina. Facciamo alcune osservazioni:

  • la pagina presenta un modulo con tre campi di immissione:
    • il dipendente di cui si calcola lo stipendio,
    • il numero di ore lavorate,
    • il numero di giorni lavorati;
  • il modulo viene inviato tramite il link [Faire la simulation];
  • è necessario verificare la validità dei campi di immissione [Heures travaillées] e [Jours travaillés];
  • l'elenco dei dipendenti proviene dal livello [métier] che abbiamo creato in precedenza.

Ricordiamo il codice attuale dell’azione [Index]:


    [HttpGet]
    public ViewResult Index()
    {
      return View();
}

quello della vista [Index.cshtml] visualizzata da questa azione:


@{
  ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")

e quello della vista parziale [Formulaire.cshtml]:


<h2>Formulaire</h2>

Verranno apportate modifiche in questi tre punti.

9.10.1. Il modello del modulo

Torniamo alla catena di elaborazione di URL [/Pam/Index]:

  • la richiesta HTTP del cliente arriva come [1];
  • in [2], le informazioni contenute nella richiesta verranno trasformate nel modello di azione [3], che fungerà da input per l'azione [4];
  • in [4], l’azione, a partire da questo modello, genererà una risposta. Questa avrà due componenti: una vista V [6] e il modello M di tale vista [5];
  • la vista V [6] utilizzerà il proprio modello M [5] per generare la risposta HTTP destinata al cliente.

L'azione che ci interessa è l'azione [Index], che al momento è la seguente:


    [HttpGet]
    public ViewResult Index()
    {
      return View();
}

L’azione [Index] non passa alcun modello alla vista [Index.cshtml]. Quest’ultima non potrà quindi visualizzare l’elenco dei dipendenti. Tale elenco può essere richiesto al livello [métier]. A tal fine, è necessario che il progetto [pam-web-01] contenga un riferimento al progetto [pam-metier-simule]. Creiamo ora tale riferimento:

  • in [1], cliccare con il tasto destro su [References] del progetto [pam-web-01], quindi su [Ajouter une référence];
  • in [2], selezionare l’opzione [Solution], quindi il progetto [pam-metier-simule] in [3];
  • in [4], il progetto [pam-metier-simule] è stato aggiunto ai riferimenti del progetto [pam-web-01].

9.10.2. Il modello dell’applicazione

Abbiamo introdotto i concetti fondamentali di modello di applicazione e modello di sessione nel paragrafo 4.10, a pagina 78. Ora li utilizzeremo. Ricordiamo che nel modello si inseriscono:

  • nel modello dell’applicazione i dati in sola lettura per tutti gli utenti. Questo modello costituisce una memoria condivisa da tutte le richieste di tutti gli utenti;
  • nel modello di sessione, dati in lettura e scrittura per un determinato utente. Questo modello costituisce una memoria condivisa da tutte le richieste di tale utente.

Cosa inseriremo nel modello di applicazione? Torniamo alla sua architettura:

Il livello [web] contiene un riferimento al livello [métier]. Quest’ultimo può essere condiviso da tutti gli utenti. Possiamo quindi inserirlo nel modello dell’applicazione. Inoltre, ipotizziamo che l’elenco dei dipendenti non cambi. Può quindi essere letto una sola volta e poi condiviso tra tutti gli utenti. Proponiamo quindi il seguente modello di applicazione:

Il codice della classe [ApplicationModel] potrebbe essere il seguente:


using Pam.Metier.Entites;
using Pam.Metier.Service;
namespace PamWeb.Models
{
  public class ApplicationModel
  {
    // --- dati a livello di applicazione ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
  }
}

Per visualizzare un elenco a discesa in una vista, si scrive qualcosa del genere:


        <!-- il menu a tendina -->
        <tr>
          <td>Liste déroulante</td>
          <td>@Html.DropDownListFor(m => m.DropDownListField,
           new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
          </td>
</tr>

Il metodo [DropDownListFor] richiede come secondo parametro un tipo SelectListItem[], che era stato fornito in precedenza da un tipo [SelectList]. Dobbiamo costruire un array di questo tipo con l’elenco dei dipendenti. Poiché i dipendenti non cambiano, anche questo array può essere inserito nel modello dell’applicazione. Modifichiamo quest’ultimo come segue:


using Pam.Metier.Entites;
using Pam.Metier.Service;
using System.Web.Mvc;

namespace Pam.Web.Models
{
  public class ApplicationModel
  {
    // --- dati di ambito dell'applicazione ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
  }
}

Quando deve essere creato questo modello? Lo abbiamo illustrato nel paragrafo 4.10. Avviene durante l’esecuzione del metodo [Application_Start] del file [Global.asax]:

Il metodo [Application_Start] è attualmente il seguente:


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);
    }
  }
}

Lo modifichiamo come segue:


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()
    {
      // ----------Generato automaticamente
      AreaRegistration.RegisterAllAreas();
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // -------------------------------------------------------------------
      // ---------- configurazione specifica
      // -------------------------------------------------------------------
      // dati relativi all'ambito dell'applicazione
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
        // istanza del livello [métier]
        application.PamMetier = ...
        // tabella dei dipendenti 
        application.Employes = ...
        // elementi del menu a tendina dei dipendenti
        application.EmployesItems = ...
      // model binder per [ApplicationModel]
      ...
    }
  }
}

Compito: completare il codice del metodo [Application_Start]. Tutto ciò di cui avete bisogno si trova nel paragrafo 4.10. Prendetevi il tempo di rileggere questo paragrafo, lungo ma importante.


La riga 33 comprende in realtà diverse righe. Per creare un oggetto di tipo [SelectListItem] è possibile utilizzare il seguente metodo:


new SelectListItem() { Text = unTexte, Value = uneValeur };

Questo [SelectListItem] servirà a generare il seguente tag HTML <option>:

<option value='uneValeur'>unTexte</option>

dall'elenco a discesa. Si farà in modo che:

  • unTexte sia il nome seguito dal cognome del dipendente;
  • uneValeur sia il numero SS del dipendente.

Riga 35, sopra, sarà necessaria la classe [ApplicationModelBinder] descritta al paragrafo 4.10, pagina 82:

9.10.3. Il codice dell'azione [Index]

Ora che abbiamo definito un modello per l’applicazione, possiamo modificare il codice dell’azione [Index] nel modo seguente:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View();
}
  • riga 4: il modello dell'applicazione è ora un parametro dell'azione [Index]. Nel paragrafo 4.10 abbiamo spiegato come questo parametro venisse inizializzato dal framework.

9.10.4. Il modello della vista [Index.cshtml]

Ora l’azione [Index] ha accesso ai dipendenti registrati nel modello dell’applicazione. Deve ora passarli alla vista [Index.cshtml] che visualizzerà. Si potrebbe passare un tipo [ApplicationModel] come modello alla vista [Index.cshtml], ma vedremo subito che questa vista necessita di ulteriori informazioni che non sono presenti in [ApplicationModel]. Utilizzeremo il modello di vista [IndexModel] seguente:


namespace Pam.Web.Models
{
  public class IndexModel
  {
    // dati di ambito dell'applicazione
    public ApplicationModel Application { get; set; }
  }
}
  • riga 6: [IndexModel] carica il modello dell'applicazione.

L'azione [Index] diventa la seguente:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View(new IndexModel() { Application = application });
}
  • riga 4, viene visualizzata la vista predefinita [Index.cshtml] con come modello un tipo [IndexModel] inizializzato con i dati del modello dell'applicazione.

Sappiamo che la vista [Index.cshtml] deve visualizzare un modulo:

Image

Torniamo alla catena di elaborazione di una richiesta:

Per la richiesta [GET /Pam/Index]:

  • l'azione è [Index];
  • il modello di questa azione è [ApplicationModel];
  • la vista è [Index.cshtml];
  • il modello di questa vista è [IndexModel].

Quando il modulo verrà inviato, avremo una catena di elaborazione analoga:

  • l'azione è quella che elabora il POST;
  • il suo modello raccoglie i valori inviati, in questo caso:
    • il n. SS del dipendente selezionato;
    • il numero di ore lavorate;
    • il numero di giorni lavorati;

Si potrebbe creare un modello di azione che riunisca questi tre valori. È inoltre frequente riutilizzare il modello che è servito a visualizzare il modulo. È proprio ciò che faremo in questo caso. La classe [IndexModel] si evolve come segue:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Pam.Web.Models
{
  [Bind(Exclude = "Application")]
  public class IndexModel
  {
    // dati relativi all'ambito dell'applicazione
    public ApplicationModel Application { get; set; }

    // valori registrati
    [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; }
  }
}
  • righe 13, 16, 18: i tre valori inseriti. Si noti che [joursTravaillés] è stato dichiarato di tipo [double], mentre in realtà ci si aspetta un numero intero. Il tipo [double] è stato introdotto per facilitare la convalida di questo campo sul lato client, poiché la convalida di un tipo [int] aveva causato problemi;
  • righe 12, 14, 17: etichette per i metodi [Html.LabelFor] della vista associata al modello;
  • riga 15: un'annotazione per visualizzare il campo [HeuresTravaillées] con due cifre decimali;
  • riga 5: si indica che la proprietà denominata [Application] non fa parte dei valori inviati.

9.10.5. Le viste [Index.cshtml] e [Formulaire.cshtml]

La vista [Index.cshtml] viene visualizzata dall’azione [Index] seguente:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View(new IndexModel() { Application = application });
}

È interessante notare che la vista [Index.cshtml] rimane invariata:


@{
  ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")
  • la vista non dichiara alcun modello;
  • riga 4: integra la vista parziale [Formulaire.cshtml], anche in questo caso senza passare a un modello specifico. Durante i test, è stato riscontrato che il modello [IndexModel] passato alla vista [Index.cshtml] si propagava implicitamente alla vista parziale [Formulaire.cshtml]. Quest’ultima vista potrebbe ora avere la seguente forma:

@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" />
  • riga 1: la vista riceve un modello di tipo [IndexModel];
  • riga 3: il modulo;
  • righe 6-10: le intestazioni della tabella di immissione dati;
  • righe 12-14: la riga dei dati inseriti;
  • righe 15-17: eventuali messaggi di errore.

Compito: completare il codice della vista [Formulaire.cshtml]. Si utilizzeranno i metodi [DropDownListFor, EditorFor, LabelFor, ValidationMessageFor] descritti al paragrafo 5.7.


9.10.6. Test dell’azione [Index]

Abbiamo scritto tutti gli elementi della catena di elaborazione di URL e [/Pam/Index]:

Testiamo l'applicazione con [Ctrl-F5]:

È necessario verificare che il menu a tendina sia stato correttamente popolato con l’elenco dei dipendenti che avevamo definito nel livello simulato [métier].

9.11. Fase 5: implementazione della convalida dei dati inseriti

9.11.1. Il problema

Sebbene non abbiamo fatto nulla in tal senso, sono già in funzione alcune validazioni lato client:

 
 

La convalida lato client è attiva per impostazione predefinita a causa della riga 3 riportata di seguito nel file [Web.config] dell'applicazione.


  <appSettings>
    ...
    <add key="ClientValidationEnabled" value="true" />
</appSettings>

Tuttavia, poiché in [IndexModel] il campo [JoursTravaillés] è stato dichiarato di tipo [double]:


    public double JoursTravaillés { get; set; }

è possibile inserire un numero reale in questo campo:

 

Inoltre, è possibile inserire valori arbitrari in entrambi i campi:

 

Il modello [IndexModel] del modulo è attualmente il seguente:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Pam.Web.Models
{
  [Bind(Exclude = "Application")]
  public class IndexModel
  {
    // dati relativi all'ambito dell'applicazione
    public ApplicationModel Application { get; set; }

    // valori registrati
    [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; }
  }
}

Compito: migliorare questo modello per:

  • ottenere messaggi di errore personalizzati;
  • accettare solo valori reali nell'intervallo [0,400] per il campo [HeuresTravaillées];
  • accettare solo valori interi nell'intervallo [0,31] per il campo [JoursTravaillées];

Si può fare riferimento all'esempio riportato nel paragrafo 7.6.2. Per verificare che il numero di giorni lavorati sia un numero intero, è possibile utilizzare un'espressione regolare (cfr. esempi nel paragrafo 5.9.1).

Ecco alcuni esempi di ciò che ci si aspetta:

 
 
 

9.11.2. Inserimento di numeri reali nel formato francese

Nella versione attuale dell'applicazione, il numero di ore lavorate deve essere un numero decimale nel formato anglosassone (con il punto decimale). Il formato francese con la virgola non è accettato:

 

Questo problema è stato individuato e risolto nel paragrafo 6.1.


Compito: seguendo la procedura descritta nel paragrafo sopra citato, apportate le modifiche necessarie affinché sia possibile inserire i numeri reali nel formato decimale francese. Testate la vostra applicazione.


Ora, la schermata precedente diventa:

 

Attualmente è possibile inviare valori non validi, come mostra la seguente sequenza:

 

La presenza della simulazione in [1] e il cambiamento di menu in [2] dimostrano che cliccando sul link [Faire la simulation] il modulo è stato inviato nonostante i valori inseriti fossero non validi. Questo problema è stato individuato e risolto al paragrafo 7.6.5.


Compito: seguendo la procedura descritta nel paragrafo sopra citato, assicuratevi che il POST del link [Faire la simulation] non possa essere generato se i valori inseriti non sono validi. Ricordatevi di svuotare la cache del browser prima di testare le vostre modifiche.


Si ricorda che la vista parziale [Formulaire.cshtml] genera un modulo HTML con id [formulaire] (riga 1 qui sotto):


@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}

Ciò può essere verificato visualizzando il codice sorgente del modulo nel browser:


<div id="content">
    
    <form action="/Pam/FaireSimulation" id="formulaire" method="post">
    ...
    </form>
    <div id="simulation" />
</div>

9.12. Fase 6: eseguire una simulazione

9.12.1. Il problema

Quando si esegue una simulazione, si desidera ottenere il seguente risultato:

La vista parziale [Simulation.cshtml] ora mostra la busta paga di un dipendente.

9.12.2. Scrittura della vista [Simulation.cshtml]

La vista [Simulation.cshtml] si evolve come segue:


@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>
  • riga 1: la vista [Simulation.cshtml] ha come modello il tipo [FeuilleSalaire] definito al paragrafo 9.7.3;
  • la vista utilizza le classi [libellé, info, valeur] definite nel foglio di stile dell'applicazione [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;
}

Inoltre, sempre in [Site.css], si imposta l’altezza delle righe delle diverse tabelle HTML dell’area con ID [simulation], precisamente dove viene visualizzata la busta paga:


#simulazione tabella tr {
  height: 30px;
}

Compito: completare la vista [Simulation.cshtml].


Per visualizzare l’importo in euro di una somma di denaro, si utilizzerà il metodo [string.Format]:

string.Format("{0:C2}",somme)

L'istruzione sopra riportata visualizza [somme] come valore monetario [C] (Valuta) con due cifre decimali [C2].

Per testare questa vista, è necessario fornirle una busta paga. Questa deve essere fornita dall'azione [/Pam/FaireSimulation], che è la destinazione della chiamata Ajax del link [Faire la simulation]. Attualmente, questa azione è la seguente:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View(new IndexModel() { Application = application });
    }

    // eseguire una simulazione
    [HttpPost]
    public PartialViewResult FaireSimulation()
    {
      return PartialView("Simulation");
}

Come si vede sopra, l’azione [FaireSimulation] non passa alcun modello alla vista [Simulation.cshtml]. È necessario che le passi un foglio paga. Sappiamo che è il livello [métier] a occuparsi del calcolo dei fogli paga. Questo livello [métier] è accessibile tramite il modello dell’applicazione [ApplicationModel] che abbiamo definito nel paragrafo 9.10.2:


  public class ApplicationModel
  {
    // --- dati relativi all'ambito dell'applicazione ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
}

Il livello [métier] è accessibile tramite la proprietà della riga 5 sopra riportata. Affinché l’azione [FaireSimulation] possa accedere al livello [métier], le passeremo il modello dell’applicazione come abbiamo fatto per l’azione [Index]. Il codice diventa quindi il seguente:


    // eseguire una simulazione
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application)
    {
      return PartialView("Simulation");
}

Ora, all’interno dell’azione, siamo in grado di calcolare una busta paga fittizia. Il codice si evolve come segue:


// eseguire una simulazione
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application)
    {
      FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
      return PartialView("Simulation", feuilleSalaire);
    }
  • alla riga 5 si calcola una retribuzione fittizia. Il primo parametro è un numero SS esistente. È stato definito nella classe [métier] simulata al paragrafo 9.7.5. Il secondo parametro è il numero di ore lavorate e il terzo il numero di giorni lavorati;
  • riga 6: questa busta paga viene utilizzata come modello per la vista [Simulation.cshtml].

Ora siamo pronti a testare la vista [Simulation.cshtml]:

 

Non si effettua alcun inserimento e si richiede la simulazione. Si ottiene quindi il seguente risultato:

 

9.12.3. Calcolo della retribuzione effettiva

La nostra attuale azione [FaireSimulation] calcola sempre lo stesso foglio paga:


    // eseguire una simulazione
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application)
    {
      FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
      return PartialView("Simulation", feuilleSalaire);
}

Non tiene conto delle informazioni inserite:

  • il dipendente per il quale si calcola la retribuzione;
  • il numero di ore lavorate;
  • il numero di giorni lavorati.

I valori inseriti vengono trasmessi all'azione [FaireSimulation] nel modo seguente:

  1. l'utente clicca sul link [Faire la simulation]. Ciò avvia l'esecuzione della funzione JS [faireSimulation] che abbiamo già scritto;
  2. La funzione JS [faireSimulation] effettua quindi una chiamata Ajax all'azione server [/Pam/FaireSimulation] su cui stiamo attualmente lavorando. Per il momento, la funzione JS [faireSimulation] non trasmette alcuna informazione all’azione server. Dovrà trasmetterle i valori inseriti dall’utente;
  3. l’azione server [/Pam/FaireSimulation] recupererà i valori inseriti dai dati inviati tramite POST dalle funzioni JS e [faireSimulation].

Cominciamo dal punto 2: la funzione JS [faireSimulation] deve inviare i valori inseriti dall'utente all'azione server [/Pam/FaireSimulation].


Compito: completare la funzione JS [faireSimulation] in modo che invii i valori inseriti dall’utente. È possibile fare riferimento all’esempio riportato nel paragrafo 7.6.5, in cui è stato trattato questo problema.


Passiamo ora al punto 3 sopra indicato. L'azione server [/Pam/FaireSimulation] deve recuperare i valori inviati dalla funzione JS [faireSimulation].


Compito: completare il metodo server [FaireSimulation] in modo che calcoli lo stipendio utilizzando i valori inviati dalle funzioni JS e [faireSimulation]. Si potrà ricorrere nuovamente all’esempio del paragrafo 7.6.5, in cui è stato trattato questo problema. Per il momento, si supporrà che il modello derivato dai valori inviati sia ancora valido.


Suggerimento: l’azione server [FaireSimulation] si evolve come segue:


// eseguire una simulazione
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
    {
      // creazione del modello dell'azione
      ...
      // si tenta di recuperare i valori inseriti in questo modello
      ...
      // si calcola lo stipendio
      FeuilleSalaire feuilleSalaire = ...
      // visualizzazione della busta paga
      return PartialView("Simulation", feuilleSalaire);
    }

Ecco un esempio di esecuzione:

Si sceglie [Justine Laverti]. Si ottiene quindi il seguente risultato:

Abbiamo effettivamente ottenuto la busta paga fittizia di [Justine Laverti]. In precedenza, l’unica busta paga calcolata era quella di [Marie Jouveinal]. Pertanto, il valore registrato relativo alla scelta del dipendente è stato utilizzato. Per quanto riguarda il numero di ore e il numero di giorni, non è possibile fornire alcuna indicazione poiché il nostro livello simulato [métier] non ne tiene conto.

9.12.4. Gestione degli errori

Esaminiamo il seguente esempio:

  • in [1], si sceglie un dipendente che non esiste (vedere la definizione del livello [métier] simulato al paragrafo 9.7.5;
  • in [2], si esegue la simulazione;
  • in [3] qui sotto, viene visualizzata una pagina di errore.

Cosa è successo?

È stata eseguita la funzione JS [faireSimulation]. Il suo codice è simile al seguente:


function faireSimulation() {
...
  // si effettua manualmente una chiamata Ajax
  $.ajax({
    url: '/Pam/FaireSimulation',
...
    beforeSend: function () {
      // segnale di attesa acceso
      loading.show();
    },
    success: function (data) {
...
    },
    error: function (jqXHR) {
      // visualizzazione dell'errore
      simulation.html(jqXHR.responseText);
      simulation.show();
    },
    complete: function () {
      // segnale di attesa spento
      loading.hide();
    }
  });
  // menu
  setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}

La chiamata Ajax non è andata a buon fine ed è stata eseguita la funzione delle righe 14-18. È stata visualizzata la pagina di errore [jqXHR.responseText] restituita dal server. Questa è piuttosto precisa. Il livello simulato [métier] ha generato un'eccezione perché il numero SS fornito non corrisponde a quello di un dipendente esistente (vedere il codice del livello simulato [métier] al paragrafo 9.7.5). Dobbiamo gestire correttamente questo caso.

Creeremo una vista parziale [Erreurs.chtml] che verrà restituita al client JS ogni volta che verrà rilevato un errore lato server:

Il codice della vista parziale [Erreurs.chtml] è il seguente:


@model IEnumerable<string>

<hr />
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
  @foreach (string msg in Model)
  {
    <li>@msg</li>
  }
</ul>
  • riga 1: la vista riceve come modello un elenco di messaggi di errore;
  • righe 5-10: che vengono visualizzati in un elenco HTML;

Ora modifichiamo il codice dell'azione server [FaireSimulation] nel modo seguente:


    // effettuare una simulazione
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
    {
    ...
      // calcolo dello stipendio
      FeuilleSalaire feuilleSalaire = null;
      Exception exception=null;
      try
      {
        // calcolo dello stipendio
        feuilleSalaire = ...
      }
      catch (Exception ex)
      {
        exception = ex;
      }
      // errore?
      if (exception == null)
      {
        // visualizza la busta paga
        return PartialView("Simulation", feuilleSalaire);
      }
      else
      {
        // visualizza la pagina degli errori
        return PartialView("Erreurs", Static.GetErreursForException(exception));
      }
}
  • righe 9-17: il calcolo dello stipendio viene ora eseguito all’interno di un try/catch;
  • riga 27: in caso di errore, viene visualizzata la vista parziale [Erreurs.cshtml] utilizzando come modello l’elenco dei messaggi di errore fornito dal metodo statico [Static.GetErreursForException(exception)].

Nella classe [Static] sono raggruppate due funzioni di utilità statiche [1]:


using System;
using System.Collections.Generic;
using System.Web.Mvc;

namespace PamWeb.Infrastructure
{
  public class Static
  {
    // elenco dei messaggi di errore di un'eccezione
    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;
    }

    // elenco dei messaggi di errore relativi a un modello non valido
    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;
    }

    // il messaggio di errore relativo a un elemento del modello dell'azione
    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;
    }

  }
}
  • righe 10-19: la funzione statica [GetErreursForException] restituisce l'elenco degli errori di uno stack di eccezioni;
  • righe 22-36: la funzione statica [GetErreursForModel] restituisce l'elenco degli errori di un modello di azione non valido. Il codice di questa funzione, così come quello del metodo privato [getErrorMessageFor] (righe 39-54), è già stato visto in precedenza.

Fatto ciò, possiamo testare nuovamente il caso di errore:

  • in [1], si seleziona il dipendente che non esiste;
  • in [2], si esegue la simulazione;
  • in [3], si recupera la nuova pagina di errore.

Torniamo all’azione server [FaireSimulation]:


    // eseguire una simulazione
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
    {
      // creazione del modello dell'azione
      IndexModel modèle = new IndexModel() { Application = application};
      // si tenta di recuperare i valori inseriti nel modello
      TryUpdateModel(modèle, data);
      // si calcola lo stipendio
...
}

Alla riga 8, si aggiorna il modello della riga 6 con i valori inviati tramite la chiamata Ajax. Non si verifica la validità del modello. È necessario farlo perché non è possibile sapere da dove provengano i valori inviati. Qualcuno potrebbe aver modificato un POST e averci inviato dati non validi.


Compito: seguendo il modello che abbiamo sviluppato per il caso dell’eccezione, modificare l’azione server [FaireSimulation] in modo da inviare una pagina di errore quando i dati inviati tramite POST non sono validi. A tal fine, utilizzeremo il metodo statico [GetErreursForModel] della classe [Static].


Come testare questa modifica? Al paragrafo 9.11.3, avete fatto in modo che la funzione JS [faireSimulation] non esegua il POST sui valori inseriti se questi fossero non validi. Commentate le righe che eseguono questa operazione, quindi effettuate il seguente test:

  • in [1], si esegue la simulazione con valori non validi;
  • in [2], si ottiene correttamente la pagina di errore che abbiamo appena creato, a dimostrazione del fatto che i validatori lato server hanno funzionato correttamente.

Per il seguito, ricordatevi di rimuovere i commenti dalle righe che avete appena commentato nelle funzioni JS e [faireSimulation].

9.13. Fase 7: creazione di una sessione utente

L'applicazione [Simulateur de calcul de paie] consente all'utente di eseguire diverse simulazioni di busta paga tramite il link [Faire la simulation], di salvarle tramite il link [Enregistrer la simulation], visualizzarle tramite il link [Voir les simulations] e cancellarle tramite il link [Retirer la simulation]. Sappiamo che tra due richieste successive dell’utente non vi è alcuna memoria, a meno che non se ne crei una tramite il meccanismo della sessione (cfr. paragrafo 4.10). È abbastanza chiaro, in questo caso, che dobbiamo conservare nella sessione l’elenco delle simulazioni registrate nel corso del tempo dall’utente. Vi sono altri dati da memorizzare: quando l’utente esegue una simulazione, questa viene registrata nell’elenco delle simulazioni solo se l’utente lo richiede tramite il link [Enregistrer la simulation]. Quando lo fa, dobbiamo essere in grado di recuperare la simulazione calcolata nella richiesta precedente. A tal fine, anche questa verrà inserita nella sessione. Infine, numereremo le simulazioni a partire da 1. Per numerare correttamente una nuova simulazione, è necessario aver conservato il numero della simulazione precedente, sempre nella sessione.

Nel paragrafo 4.10 abbiamo introdotto il concetto di modello di sessione come parametro di input di un'azione, affinché quest'ultima abbia accesso alla sessione. Riprenderemo questo concetto. Vi invitiamo a rileggere il paragrafo in questione se questo concetto vi risulta poco chiaro.

Creiamo la seguente classe [SessionModel]:

Il suo codice è il seguente:


using Pam.Web.Models;
using System.Collections.Generic;

namespace Pam.Web.Models
{
  public class SessionModel
  {
    // l'elenco delle simulazioni
    public List<Simulation> Simulations { get; set; }
    // numero della prossima simulazione
    public int NumNextSimulation { get; set; }
    // l'ultima simulazione
    public Simulation Simulation { get; set; }

    // produttore
    public SessionModel()
    {
      // elenco delle simulazioni vuoto
      Simulations = new List<Simulation>();
      // numero della prossima simulazione
      NumNextSimulation = 1;
    }
  }
}

La classe [Simulation], alle righe 9 e 13, registrerà le informazioni relative a una simulazione. Cosa dobbiamo registrare? Il collegamento [Faire la simulation] calcola una busta paga di tipo [FeuilleSalaire]. Sembra logico inserirlo nella simulazione. Inoltre, dobbiamo memorizzare le informazioni che hanno portato a questo foglio paga:

  • il dipendente selezionato. Lo si trova nel campo [FeuilleSalaire.Employe]. È quindi superfluo memorizzarlo una seconda volta;
  • il numero di ore e di giorni lavorati. Queste informazioni non sono presenti nel tipo [FeuilleSalaire]. Dobbiamo quindi memorizzarle.

Infine, ogni simulazione è identificata da un numero. Si potrebbe quindi partire dalla seguente classe [Simulation]:


using Pam.Metier.Entites;

namespace Pam.Web.Models
{
  public class Simulation
  {
    // numero della simulazione
    public int Num { get; set; }
    // il numero di ore lavorate
    public double HeuresTravaillées { get; set; }
    // numero di giorni lavorati
    public int JoursTravaillés { get; set; }
    // la busta paga
    public FeuilleSalaire FeuilleSalaire { get; set; }
  }
}

L’azione server [FaireSimulation] deve, oltre a calcolare una busta paga, creare una simulazione e inserirla nella sessione. A tal fine, riceverà come parametro il modello della sessione:


// effettuare una simulazione
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, SessionModel session, FormCollection data)
    {
      // creazione del modello dell'azione
      IndexModel modèle = new IndexModel() { Application = application };
      // si tenta di recuperare i valori inseriti nel modello
      TryUpdateModel(modèle, data);
      // modello valido?
      if (!ModelState.IsValid)
      {
        // viene visualizzata la pagina degli errori
        return PartialView("Erreurs", Static.GetErreursForModel(ModelState));
      }
      // si calcola lo stipendio
      FeuilleSalaire feuilleSalaire = null;
      Exception exception = null;
      try
      {
        // calcolo dello stipendio
        feuilleSalaire = application.PamMetier.GetSalaire(modèle.SS, modèle.HeuresTravaillées, (int)modèle.JoursTravaillés);
      }
      catch (Exception ex)
      {
        exception = ex;
      }
      // errore?
      if (exception != null)
      {
        // viene visualizzata la pagina degli errori
        return PartialView("Erreurs", Static.GetErreursForException(exception));
      }
      // si crea una simulazione e la si inserisce nella sessione
      session.Simulation = ...
      // visualizza la busta paga
      return PartialView("Simulation", feuilleSalaire);
    }
  • riga 3: l'azione riceve come parametro il modello della sessione;

Compito 1: completare il codice dell’azione, riga 34



Compito 2: seguendo la procedura descritta nel paragrafo 4.10, fare il necessario affinché il parametro [SessionModel session] dell'azione sia correttamente inizializzato dal framework. Se non si interviene, per questo parametro si avrà un puntatore null.


9.14. Fase 8: salvare una simulazione

9.14.1. Il problema

Una volta eseguita una simulazione, è possibile salvarla:

Image

La vista parziale [Simulations.cshtml] mostra ora l'elenco delle simulazioni effettuate dall'utente. Si ricorda che la busta paga calcolata è fittizia.

9.14.2. Scrittura dell’azione server [EnregistrerSimulation]

Il collegamento Ajax [Enregistrer la simulation] richiama l'azione server [EnregistrerSimulation], il cui codice era finora il seguente:


    [HttpPost]
    public PartialViewResult EnregistrerSimulation()
    {
      return PartialView("Simulations");
}

Il codice viene modificato come segue:


    // salvare una simulazione
    [HttpPost]
    public PartialViewResult EnregistrerSimulation(SessionModel session)
    {
      // si salva l'ultima simulazione effettuata nell'elenco delle simulazioni della sessione
      ...
      // si incrementa nella sessione il numero della simulazione successiva
      ...
      // visualizza l'elenco delle simulazioni
      ...
}
  • riga 1: l'azione [EnregistrerSimulation] necessita dell'accesso alla sessione. Per questo motivo ha come parametro il modello della sessione.

Compito: completare l’azione server [EnregistrerSimulation].


9.14.3. Scrittura della vista parziale [Simulations.cshtml]

L'azione precedente [EnregistrerSimulation] visualizza la vista parziale [Simulations.cshtml] utilizzando come modello l'elenco delle simulazioni effettuate dall'utente. Il suo codice è il seguente:


@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>
...
}

Esercizio 1: completare il codice della vista parziale [Simulations.cshtml]. Si utilizzerà una tabella HTML per la visualizzazione delle simulazioni. È possibile avvalersi degli esempi riportati nel paragrafo 5.4.


Nota: il link [retirer] di ciascuna simulazione della tabella HTML sarà un link JavaScript della forma seguente:

<a href="javascript:retirerSimulation(N)">retirer</a>

dove N è il numero della simulazione.


Esercizio 2: testate la vostra applicazione effettuando delle simulazioni. Per eseguire le simulazioni, ripetete la seguente sequenza: 1) caricate la pagina dell’applicazione tramite [F5], 2) eseguite una simulazione, 3) salvatela. Le simulazioni si accumuleranno nella sessione, il che dovrebbe riflettersi nella vista [Simulations.cshtml].



Compito 3: migliorare la vista parziale [Simulations.cshtml] in modo tale che i colori delle righe della tabella HTML siano alternati.


Image

Si assegneranno in modo alternato alle righe <tr> della tabella HTML, le classi CSS, [pair] e [impair] definite nel foglio di stile [/Content/Site.css]:


.impair {
  background-color: beige;
}

.pair {
  background-color: lightsteelblue;
}

9.15. Passaggio 9: tornare al modulo di inserimento

9.15.1. Il problema

Una volta ottenuto l’elenco delle simulazioni, è possibile tornare al modulo di inserimento, cosa che non era più possibile fare da un po’ di tempo:

Image

Image

9.15.2. Codice dell'azione server [Formulaire]

Il collegamento Ajax [Retour au formulaire de simulation] richiama l'azione server [Formulaire], il cui codice era finora il seguente:


    [HttpPost]
    public PartialViewResult Formulaire()
    {
      return PartialView("Formulaire");
}

La vista parziale [Formulaire] che visualizza richiede un modello [IndexModel] (riga 1 qui sotto):


@model Pam.Web.Models.IndexModel

@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
<div id="simulation" />

È per questo motivo che il link [Retour au formulaire de simulation] non funzionava più.


Compito: scrivere la nuova versione dell'azione server [Formulaire] (2 righe da riscrivere) e poi eseguire i test.


9.15.3. Modifica della funzione JavaScript [retourFormulaire]

Con la modifica apportata in precedenza, ora è possibile tornare al modulo, ma si verifica un'anomalia:

  • in [1], si ritorna al modulo di inserimento;
  • in [2], si esegue una simulazione con dati errati. Si scopre quindi che i validatori lato client non funzionano più. In questo caso, il server è stato interpellato e ha restituito una pagina di errori grazie al lavoro svolto al paragrafo 9.12.4.

Questa anomalia è stata identificata e risolta nel paragrafo 7.6.7.


Compito: seguendo la procedura descritta nel paragrafo 7.6.7, correggete la funzione JavaScript [retourFormulaire], quindi eseguite dei test per verificare che i validatori lato client funzionino nuovamente.


9.16. Fase 10: consultare l’elenco delle simulazioni

9.16.1. Il problema

Quando si lavora con il modulo di simulazione, è possibile visualizzare l’elenco delle simulazioni effettuate:

9.16.2. Codice dell'azione server [VoirSimulations]

Il link Ajax [Voir les simulations] richiama l'azione server [VoirSimulations], il cui codice era finora il seguente:


    // visualizza le simulazioni
    [HttpPost]
    public PartialViewResult VoirSimulations()
    {
      return PartialView("Simulations");
}

La vista parziale [Simulations] che visualizza richiede un modello [IEnumerable<Simulation>] (riga 1 qui sotto):


@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>
...
}

È per questo motivo che il link [Voir les simulations] non funzionava più.


Compito: scrivere la nuova versione dell’azione server [VoirSimulations] (2 righe da riscrivere) e poi eseguire i test.


9.17. Fase 11: terminare la sessione

9.17.1. Il problema

È possibile terminare in qualsiasi momento la sessione dell’utente tramite il link [Ajax] [Terminer la session]. Ciò comporta l’abbandono della sessione corrente per avviarne una nuova. Inoltre, si ritorna alla visualizzazione del modulo:

  • in [1], sono state effettuate due simulazioni, quindi si chiude la sessione;
  • in [2], si è tornati al modulo di immissione dati. Si desidera visualizzare le simulazioni;
  • in [3], a causa del cambio di sessione, l’elenco delle simulazioni è ora vuoto.

9.17.2. Codice dell’azione server [TerminerSession]

Il link Ajax [Terminer la session] richiama l’azione server [TerminerSession], il cui codice era finora il seguente:


    // chiudere la sessione
    [HttpPost]
    public PartialViewResult TerminerSession()
    {
      return PartialView("Formulaire");
}

La vista parziale [Formulaire] che visualizza richiede un modello [IndexModel] (riga 1 qui sotto):


@model Pam.Web.Models.IndexModel

@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
<div id="simulation" />

È per questo motivo che il link [Terminer la session] non funzionava più.


Compito: scrivere la nuova versione dell'azione server [TerminerSession] (2 righe da riscrivere) e poi eseguire i test.


Nota: per terminare la sessione nell’azione, si scrive:

Session.Abandon() ;

9.17.3. Modifica della funzione JavaScript [terminerSession]

Con la modifica apportata in precedenza, ora è possibile tornare al modulo, ma si verifica un'anomalia, quella descritta in precedenza al paragrafo 9.15.3.


Compito: seguendo la procedura descritta nel paragrafo 9.15.3, correggere la funzione JavaScript [terminerSession], quindi eseguire dei test per verificare che i validatori lato client funzionino nuovamente.


9.18. Fase 12: cancellare la simulazione

9.18.1. Il problema

Una volta effettuata una simulazione, è possibile cancellarla tramite il link JavaScript [Effacer la simulation]:

9.18.2. Scrittura dell'azione cliente [effacerSimulation]

La funzione JavaScript [effacerSimulation] presenta attualmente il seguente codice:


function effacerSimulation() {
  // si cancellano i dati inseriti nel modulo
  // ...
  // si nasconde la simulazione, se presente
  $("#simulation").hide();
  // menu
  setMenu([lnkFaireSimulation, lnkTerminerSession, lnkVoirSimulations]);
}

Compito: completare questo codice. È possibile prendere spunto dall'esempio riportato nel paragrafo 7.6.6


9.19. Fase 13: rimuovere una simulazione

9.19.1. Il problema

Nella pagina delle simulazioni, è possibile eliminarne alcune utilizzando il link JavaScript [retirer]:

Image

Image

9.19.2. Registrazione dell'azione cliente [retirerSimulation]

I link [retirer] hanno la seguente forma: HTML

<a href="javascript:retirerSimulation(N)">retirer</a>

dove N è il numero della simulazione.


Compito: seguendo la procedura descritta nei paragrafi 9.9.3, scrivere la funzione JS [retirerSimulation]. Questa funzione invierà una richiesta Ajax di tipo POST all’azione [/Pam/RetirerSimulation]. Invierà il dato N nella forma num=N.


Nota: la funzione JS [retirerSimulation] è analoga alle altre funzioni JS che avete scritto e che effettuano una chiamata Ajax al server. L'unica novità in questo caso è l'invio di un valore che non si trova in un modulo. È noto che i valori inviati vengono raggruppati in una stringa di caratteri nella forma:

param1=val1&param2=val2&....

la funzione JS [retirerSimulation] avrà quindi la seguente forma:


function retirerSimulation(N) {
  // effettua manualmente una richiesta Ajax
  $.ajax({
    url: '/Pam/RetirerSimulation',
...
    data:"num="+N,
...
  });
  // menu
  setMenu([lnkRetourFormulaire, lnkTerminerSession]);
}
  • riga 6: la proprietà [data] di una chiamata Ajax JQuery rappresenta la stringa inviata al server.

9.19.3. Scrittura dell’azione server [RetirerSimulation]

L'azione server [RetirerSimulation]:

  • riceve un parametro inviato tramite POST denominato [num], che corrisponde al numero di una simulazione;
  • deve rimuovere dall'elenco delle simulazioni registrate nella sessione la simulazione con quel numero;
  • deve quindi visualizzare il nuovo elenco di simulazioni.

Compito: scrivere l’azione server [RetirerSimulation]. Rileggere il paragrafo 4.1 per sapere come recuperare il parametro POST denominato [num].


9.20. Fase 14: miglioramento del metodo di inizializzazione dell’applicazione

La nostra applicazione web è completa. È funzionante con una classe [métier] simulata. Ricordiamo l’architettura che abbiamo sviluppato:

Rimangono alcuni dettagli da sistemare prima di passare all’implementazione effettiva del livello [métier], e ciò avviene nel metodo di inizializzazione dell’applicazione: il metodo [Application_Start] in [Global.asax]:

Il metodo [Application_Start] in [Global.asax] viene eseguito una sola volta all’avvio dell’applicazione. È qui che è possibile utilizzare il file di configurazione [Web.config]. Per il momento, il nostro metodo [Application_Start] si presenta così:


// applicazione
    protected void Application_Start()
    {
      // ----------Generato automaticamente
      AreaRegistration.RegisterAllAreas();
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // -------------------------------------------------------------------
      // ---------- configurazione specifica
      // -------------------------------------------------------------------
      // dati di ambito dell'applicazione
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
      // istanza del livello [métier]
      application.PamMetier = new PamMetier();
...
      // model binders
...
}

Alla riga 17, il livello di business viene istanziato tramite l'operatore new. Inoltre, il modello dell'applicazione è definito come segue:


  public class ApplicationModel
  {
    // --- dati di ambito dell'applicazione ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
}

Alla riga 5 sopra riportata, si vede che il tipo della proprietà [PamMetier] è quello dell’interfaccia [IPamMetier]. Ciò significa che questa proprietà può essere inizializzata da qualsiasi oggetto che implementi tale interfaccia. Tuttavia, alla riga 17 di [Application_Start], abbiamo specificato in modo statico il nome di una classe di implementazione di [IPamMetier]. Se quindi il livello [métier] venisse implementato con una nuova classe che implementa [IPamMetier], sarebbe necessario modificare questa riga. Non è particolarmente importante, ma è possibile evitarlo. La definizione della classe di implementazione dell’interfaccia [IPamMetier] può essere spostata in un file di configurazione. Per cambiare l’implementazione, è sufficiente modificare il contenuto di questo file di configurazione. Il codice .NET non deve essere modificato.

In questo caso utilizzeremo il contenitore di iniezione delle dipendenze [Spring.net]. Esistono altri framework .NET che consentono di ottenere lo stesso risultato, forse in modo migliore e più semplice.

L’architettura del progetto si evolve come segue:

  • in [A], il metodo di inizializzazione del livello [ASP.NET MVC] richiederà a [Spring.net] un riferimento al livello [métier] simulato;
  • in [B], [Spring.net] creerà il livello simulato [métier] utilizzando il proprio file di configurazione per determinare quale classe debba istanziare;
  • in [C], [Spring.net] restituirà il riferimento del livello simulato [métier] al livello [ASP.NET MVC].

Si noti che, per impostazione predefinita, gli oggetti gestiti da [Spring.net] sono singleton: ne esiste un solo esemplare. Pertanto, se più avanti nel nostro esempio il codice richiede nuovamente a [Spring.net] un riferimento al livello simulato [métier], [Spring.net] si limiterà a restituire il riferimento all’oggetto creato inizialmente.

9.20.1. Aggiunta dei riferimenti [Spring] al progetto web

Utilizzeremo [Spring.net]. Questo framework è disponibile come DLL, che deve essere aggiunto ai riferimenti del progetto. Si può procedere come segue:

In [1], fare clic con il tasto destro del mouse sul ramo [References] del progetto, quindi selezionare l’opzione [Gérer les packages NuGet]. È necessaria una connessione a Internet. Successivamente si procederà come fatto in precedenza per la libreria JQuery [Globalize]. Si cercherà la parola chiave [Spring.core] e si installerà questo pacchetto. L'installazione comporta due DLL: [Spring.core] [2] e [Common.Logging] [3]. Negli esempi che seguono è stata utilizzata la versione 1.3.2 di Spring.

Nota: se non si dispone di una connessione a Internet, questi file DLL sono disponibili in una cartella [lib] contenuta nel materiale di supporto di questo caso di studio.

9.20.2. Configurazione di [web.config]

La definizione della classe di implementazione dell’interfaccia [IPamMetier] è contenuta nel file [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>
  <!-- configurazione Spring -->
  <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>
...
  • righe 2-8: individuare il tag <configSections> nel file e inserirvi le righe 4-7;
  • riga 4: l'attributo [name="spring"] fornisce informazioni relative alla sezione [spring] delle righe 10-17;
  • riga 5: definisce la classe [Spring.Context.Support.DefaultSectionHandler], situata all’interno della DLL [Spring.Core], come quella in grado di elaborare la sezione [objects] delle righe 14-16;
  • riga 6: definisce la classe [Spring.Context.Support.ContextHandler], situata all’interno di DLL e [Spring.Core], come quella in grado di elaborare la sezione [context] delle righe 11-13;
  • righe 11-13: questa sezione fornisce l’informazione [<resource uri="config://spring/objects" />] che indica che gli oggetti Spring si trovano nel file di configurazione nella sezione [/spring/objects], ovvero alle righe 14-16;
  • righe 14-16: il tag [objects] introduce gli oggetti Spring;
  • riga 15: definisce un oggetto identificato da [id="pammetier"], che è un'istanza della classe [Pam.Metier.Service.PamMetier] situata nella DLL [pam-metier-simule]. Qui è importante non commettere errori. Per l’attributo [id], potete inserire ciò che desiderate. Utilizzerete questo identificatore in [Global.asax]. La classe [Pam.Metier.Service.PamMetier] è quella del nostro livello simulato [métier]. È necessario tornare alla sua definizione per conoscere il suo nome completo:

namespace Pam.Metier.Service
{
  public class PamMetier : IPamMetier
  {
    ...

Per DLL e [pam-metier-simule], è necessario controllare le proprietà del progetto C# [pam-metier-simule]:

È necessario utilizzare il nome indicato in [1].

9.20.3. Modifica di [Application_Start]

Il metodo [Application_Start] viene modificato come segue:


using Spring.Context.Support;

// applicazione
    protected void Application_Start()
    {
      // ----------Generato automaticamente
      AreaRegistration.RegisterAllAreas();
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // -------------------------------------------------------------------
      // ---------- configurazione specifica
      // -------------------------------------------------------------------
      // dati relativi all'ambito dell'applicazione
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
      // istanziazione del livello [métier]
      application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
...
      // model binders
...
}
  • riga 19: si utilizza la classe Spring [ContextRegistry], che è in grado di elaborare il file [web.config]. A tal fine, è necessario importare lo spazio dei nomi della riga 1. Il metodo statico [GetContext] consente di ottenere il contenuto dei tag [context] che indicano dove si trovano gli oggetti Spring. Il metodo statico [GetObject] consente poi di ottenere un oggetto specifico identificato dal suo attributo id. Si noti che ora il nome della classe di implementazione dell’interfaccia [IPamMetier] non è più hardcoded nel codice. Si trova ora nel file [web.config].

Dopo aver apportato tutte queste modifiche, provate la vostra applicazione. Dovrebbe funzionare.

9.20.4. Gestione di un errore di inizializzazione dell’applicazione

Nel metodo [Application_Start] abbiamo scritto:


application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;

L'istruzione a destra del segno = potrebbe non andare a buon fine. Ci sono diverse ragioni per questo:

  • la più ovvia è che si sia commesso un errore nel nome dell’oggetto da istanziare;
  • un'altra è che l'istanziazione del livello [métier] non va a buon fine. Questo non può verificarsi per il nostro livello simulato [métier], ma potrebbe verificarsi per il nostro livello reale [métier] che sarà collegato a un database. Il SGBD potrebbe non essere avviato, le informazioni sul database da gestire potrebbero essere errate, ecc...

Gestiremo un’eventuale eccezione in un blocco try/catch. Il codice si evolve come segue:


// applicazione
    protected void Application_Start()
    {
      // ----------Generato automaticamente
...
      // -------------------------------------------------------------------
      // ---------- configurazione specifica
      // -------------------------------------------------------------------
      // dati di ambito dell'applicazione
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
      application.InitException = null;
      try
      {
        // istanza del livello [métier]
        application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
      }
      catch (Exception ex)
      {
        application.InitException = ex;
      }
      //se non ci sono errori
      if (application.InitException == null)
      {
....
      }
      // model binders
...
    }
  • alla riga 12, introduciamo una nuova proprietà denominata [InitException] nel modello dell’applicazione:

  public class ApplicationModel
  {
    // --- dati di ambito dell'applicazione ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
    public Exception InitException { get; set; }
}
  • riga 7 sopra, l’eccezione che potrebbe verificarsi durante l’inizializzazione dell’applicazione;
  • righe 13-21 di [Application_Start]: l'istanziazione del livello [métier] avviene ora all'interno di un try / catch;
  • riga 20: si memorizza l'eccezione;
  • righe 23-26: se non si è verificato alcun errore, si esegue il codice previsto in precedenza;
  • riga 28: i [ModelBinders] vengono creati indipendentemente dal fatto che si sia verificato un errore o meno. Questo è importante. Vogliamo assicurarci che il modello dell'applicazione [ApplicationModel] venga correttamente collegato dal framework.

Sappiamo che all’avvio dell’applicazione viene eseguita l’azione server [Index]. Per il momento è la seguente:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View(new IndexModel() { Application = application });
}

Alla riga 2, l’azione [Index] riceve il modello dell’applicazione. Può quindi verificare se l’inizializzazione è avvenuta correttamente o meno e visualizzare una pagina di errore se l’inizializzazione non è andata a buon fine per qualsiasi motivo. Modifichiamo il codice come segue:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      // errore di inizializzazione?
      if (application.InitException != null)
      {
        // pagina degli errori senza menu
        return View("InitFailed",Static.GetErreursForException(application.InitException));
      }
      // nessun errore
      return View(new IndexModel() { Application = application });
}

Riga 8: in caso di errore di inizializzazione, visualizziamo la vista [InitFailed.cshtml] utilizzando come modello l’elenco dei messaggi di errore dell’eccezione verificatasi durante l’inizializzazione. Il metodo [Static.GetErreursForException] è stato presentato e spiegato nel paragrafo 9.12.4. La vista [InitFailed.cshtml] sarà la seguente:

Il suo codice è il seguente:


@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>
  • riga 1: il modello della vista è un elenco di messaggi di errore. Questi vengono visualizzati in un elenco HTML alle righe 24-29;
  • riga 3: questa vista non utilizza la pagina master [_Layout.cshtml]. Infatti, non si desidera il menu fornito da tale documento. Si crea quindi una pagina HTML completa (righe 5-23).

Per effettuare un test, è sufficiente modificare in [Application_Start] l’istanza del livello [métier] come segue:


      try
      {
        // istanziazione del livello [métier]
        application.PamMetier = ContextRegistry.GetContext().GetObject("xx") as IPamMetier;
      }
      catch (Exception ex)
      {
        application.InitException = ex;
}

Alla riga 4, si cerca un oggetto che non esiste tra gli oggetti Spring.

Quando si salvano queste modifiche e si avvia l’applicazione, si ottiene la seguente pagina:

 

Viene visualizzata una pagina di errore senza menu. L’utente non può fare altro che prendere atto dell’errore. Questo era proprio l’obiettivo.

9.21. A che punto siamo?

Ora disponiamo di un’applicazione web operativa che funziona con un livello business simulato. La sua architettura è la seguente:

Il livello [ASP.NET MVC] interagisce con il livello business simulato tramite l'interfaccia [IPamMetier]. Se sostituiamo questo livello business simulato con un livello business reale che rispetta tale interfaccia, non dovremo modificare il codice del livello web. Grazie a [Spring.net], dovremo semplicemente modificare in [web.config] la classe di implementazione dell’interfaccia [IPamMetier]. Procediamo in questo modo.

La nuova architettura sarà la seguente:

Descriveremo di seguito:

  • il livello [EF5] collegato a SGBD. Sarà implementato con Entity Framework 5 (EF5);
  • il livello [DAO] che gestisce l’accesso ai dati tramite il livello [EF5]. Ciò gli consente di ignorare l’esistenza di SGBD. Questo livello si limita a gestire le entità dell’applicazione [Employe, Cotisations, Indemnites];
  • il livello [métier] che implementa il calcolo dello stipendio.

La nuova architettura è quella presentata all’inizio del presente documento al paragrafo 1.1, che riportiamo qui di seguito:

  • il livello [Web] è il livello a contatto con l’utente dell’applicazione web. Quest’ultimo interagisce con l’applicazione web attraverso pagine web visualizzate da un browser. È in questo livello che si trovano ASP.NET e MVC, ed esclusivamente in questo livello;
  • il livello [métier] implementa le regole di gestione dell’applicazione, come il calcolo di uno stipendio o di una fattura. Questo livello utilizza i dati provenienti dall’utente tramite il livello [Web] e quelli provenienti da SGBD tramite il livello [DAO];
  • il livello [DAO] (Data Access Objects), il livello [ORM] (Object Relational Mapper) e il connettore ADO.NET gestiscono l’accesso ai dati del livello SGBD. Il livello [ORM] funge da ponte tra gli oggetti gestiti dal livello [DAO] e le righe e le colonne dei dati di un database relazionale. Due ORM sono comunemente utilizzati nel mondo: NET, NHibernate (http://sourceforge.net/projects/nhibernate/) ed Entity Framework (http://msdn.microsoft.com/en-us/data/ef.aspx);
  • l’integrazione dei livelli può essere effettuata tramite un contenitore di iniezione delle dipendenze (Dependency Injection Container) come Spring (http://www.springframework.net/);

I livelli [métier], [DAO], [EF5] saranno implementati utilizzando progetti C#. Da questo momento in poi, lavoreremo con Visual Studio Express 2012 per desktop.

9.22. Fase 15: implementazione del livello Entity Framework 5

La creazione del livello [EF5] è più una questione di configurazione che di programmazione. Per comprendere come scrivere questo livello, si legga il documento [Introduction à Entity Framework 5 Code First] disponibile su URL [http://tahe.developpez.com/dotnet/ef5cf-02/]. Si tratta di un documento piuttosto corposo. I concetti fondamentali sono contenuti nei primi quattro capitoli. Verranno indicati con precisione i paragrafi da leggere con maggiore attenzione. Quando faremo riferimento a questo documento, useremo la notazione [refEF5].

Inoltre, a volte avremo bisogno di nozioni di C#. In tal caso faremo riferimento al corso [Introduction au langage C#], disponibile all’indirizzo URL [http://tahe.developpez.com/dotnet/csharp/], con la notazione [refC#].

9.22.1. Il database

Il database dell’applicazione è stato presentato nel paragrafo 9.4. Si tratta di un database MySQL denominato [dbpam_ef5] (pam=Paie Assistante Maternelle). Questo database ha un amministratore denominato root senza password.

Ricordiamo lo schema del database. Esso presenta tre tabelle:

Image

Esiste una relazione di chiave esterna tra la colonna EMPLOYES (INDEMNITE_ID) e la colonna INDEMNITES (ID). Parte della struttura di questo database è determinata dal suo utilizzo con EF5.

Lo script SQL per la creazione del database è il seguente:


-- phpMyAdmin SQL Dump
-- versione 3.5.1
-- http://www.phpmyadmin.net
--
-- Cliente: localhost
-- Generato il: lunedì 4 novembre 2013 alle 09:34
-- Versione del server: 5.5.24-log
-- Versione di 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`
--

-- --------------------------------------------------------

--
-- Struttura della tabella `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 ;

--
-- Contenuto della tabella `cotisations`
--

INSERT INTO `cotisations` (`ID`, `SECU`, `RETRAITE`, `CSGD`, `CSGRDS`, `VERSIONING`) VALUES
(11, 9.39, 7.88, 6.15, 3.49, 1);

--
-- Trigger `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 ;

-- --------------------------------------------------------

--
-- Struttura della tabella `dipendenti`
--

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 ;

--
-- Contenuto della tabella `dipendenti`
--

INSERT INTO `employes` (`ID`, `PRENOM`, `SS`, `ADRESSE`, `CP`, `VILLE`, `NOM`, `VERSIONING`, `INDEMNITE_ID`) VALUES
(24, 'Marie', '254104940426058', '5 rue des oiseaux', '49203', 'St Corentin', 'Jouveinal', 1, 93),
(25, 'Justine', '260124402111742', 'La Brûlerie', '49014', 'St Marcel', 'Laverti', 1, 94);

--
-- Trigger `dipendenti`
--
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 ;

-- --------------------------------------------------------

--
-- Struttura della tabella `indennità`
--

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 ;

--
-- Contenuto della tabella `indemnites`
--

INSERT INTO `indemnites` (`ID`, `ENTRETIEN_JOUR`, `REPAS_JOUR`, `INDICE`, `INDEMNITES_CP`, `BASE_HEURE`, `VERSIONING`) VALUES
(93, 2.1, 3.1, 2, 15, 2.1, 1),
(94, 2, 3, 1, 12, 1.93, 1);

--
-- Trigger `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 ;

--
-- Vincoli per le tabelle esportate
--

--
-- Vincoli per la tabella `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 */;

Si notino i seguenti punti:

  • righe 30, 73, 122: le chiavi primarie delle tabelle sono in modalità [AUTO_INCREMENT]. È MySQL a gestirle e non EF5;
  • riga 83: il n. SS ha un vincolo di unicità;
  • riga 130: l’indice del dipendente ha un vincolo di unicità;
  • righe 168-169: la chiave esterna dalla tabella [employes] alla tabella [indemnites];
  • riga 49: un trigger [Trigger] è uno script SQL incorporato nel trigger SGBD e viene eseguito in determinati momenti;
  • righe 51-54: il trigger [INCR_VERSIONING_COTISATIONS] si attiva prima di qualsiasi modifica a una riga della tabella [cotisations]. A quel punto incrementa di un'unità la colonna [VERSIONING];
  • righe 59-62: il trigger [START_VERSIONING_COTISATIONS] viene attivato prima di ogni inserimento di una nuova riga nella tabella [cotisations]. A quel punto, inizializza a 1 la colonna [VERSIONING];
  • alla fine, la colonna [VERSIONING] assume il valore 1 quando viene creata una riga nella tabella [cotisations] e viene poi incrementata di 1 ad ogni modifica apportata a tale riga. Questo meccanismo consente a EF5 di gestire la concorrenza di accesso a una riga della tabella [cotisations] nel modo seguente:
    • un processo P1 legge una riga L della tabella [cotisations] al tempo T1. La riga ha nella colonna [VERSIONING] il valore V1;
    • un processo P2 legge la stessa riga L della tabella [cotisations] al momento T2. La riga presenta le colonne [VERSIONING] e V1 poiché il processo P1 non ha ancora salvato la propria modifica;
    • il processo P1 modifica la riga L e convalida la modifica. La colonna [VERSIONING] della riga L passa quindi a V1+1 a causa del trigger [INCR_VERSIONING_COTISATIONS];
    • il processo P2 fa poi lo stesso. EF5 genera quindi un'eccezione poiché il processo P2 presenta una riga con una colonna [VERSIONING] il cui valore V1 è diverso da quello presente nel database, ovvero V1+1. È possibile modificare una riga solo se il valore di [VERSIONING] è uguale a quello presente nel database.

Questo approccio è noto come gestione ottimistica degli accessi concorrenti. Con EF5, un campo che svolge questa funzione deve avere l’annotazione [ConcurrencyCheck].

  • Un meccanismo analogo viene creato per la tabella [employes] (righe 98-113) e per la tabella [indemnites] (righe 144-159).

Operazione: creare il database MySQL [dbpam_ef5] utilizzando lo script SQL precedente. Il database [dbpam_ef5] deve essere creato in precedenza poiché lo script non lo crea. Successivamente verrà eseguito lo script SQL su questo database.


9.22.2. Il progetto Visual Studio

Con Visual Studio Express 2012 per desktop, carichiamo la soluzione [pam-td] utilizzata durante la creazione del livello [web]:

  • in [1], VS 2012 Express per desktop non riesce a caricare il progetto web [pam-web-01]. È normale e non è un problema;
  • in [2], si aggiunge un nuovo progetto alla soluzione [pam-td];
  • in [3], il progetto è di tipo [console] e si chiama [4] [pam-ef5];
  • in [5], il progetto creato. Il suo nome non è in grassetto, quindi non è il progetto di avvio della soluzione;
  • in [6] e [7], si definisce il nuovo progetto come progetto di avvio.

9.22.3. Aggiunta dei riferimenti necessari al progetto

Inquadriamo il progetto nel suo complesso:

Il nostro progetto necessita di una serie di DLL:

  • il DLL di Entity Framework 5;
  • il DLL del connettore ADO.NET del SGBD MySQL.

Il paragrafo 4.2 di [refEF5] spiega come installare questi DLL utilizzando lo strumento [NuGet]. Attualmente (novembre 2013), la versione disponibile di Entity Framework è la versione 6 (EF6). Purtroppo, sembra che il connettore ADO.NET del SGBD MySQL disponibile (novembre 2013) tramite [NuGet] non sia compatibile con EF6. Pertanto, in una cartella contenente [lib] e [1] è stato inserito il file DLL diEF5, insieme agli altri DLL necessari per il progetto [pam-ef5]

Abbiamo inserito altri DLL nella cartella [lib]. Li useremo in seguito. In [2], aggiungiamo questi nuovi DLL al progetto.

  • in [3], si naviga nel sistema di file fino alla cartella [lib];
  • in [4], si selezionano i tre DLL e poi si conferma due volte;
  • in [5], i tre DLL sono stati aggiunti ai riferimenti del progetto.

Ci serve un altro DLL. Questo si troverà tra quelli del framework .NET del computer.

  • in [1], aggiungete un nuovo riferimento al progetto;
  • in [2], selezionate [Assemblys];
  • in [3], digitare [system.component];
  • da [4], selezionare l'assembly [System.ComponentModel.DataAnnotations];
  • in [5], il riferimento è stato aggiunto.

Ora siamo pronti per la codifica e la configurazione.

9.22.4. Le entità di Entity Framework

Le entità di Entity Framework sono classi in cui vengono incapsulate le righe delle diverse tabelle del database. Ricordiamole:

Image

Nel livello [web] avevamo utilizzato le entità [Employe, Cotisations, Indemnités] (vedi paragrafo 9.7.3, pagina 219). Non si trattava di rappresentazioni fedeli delle tabelle. Pertanto, le colonne [ID, VERSIONING] erano state ignorate. In questo caso, però, non sarà così poiché sono utilizzate dalle entità ORM e EF5. Aggiungeremo quindi le proprietà mancanti. Creiamo queste entità in una cartella denominata [Models] all’interno del progetto:

Il loro nuovo codice è ora il seguente:

Classe [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; }

    // firma
    public override string ToString()
    {
      return string.Format("Cotisations[{0},{1},{2},{3}, {4}, {5}]", Id, Versioning, CsgRds, Csgd, Secu, Retraite);
    }
  }
}
  • riga 3: lo spazio dei nomi è stato adattato al nuovo progetto;
  • le proprietà delle righe 7 e 12 sono state aggiunte per rispecchiare la struttura della tabella [cotisations];
  • riga 17: il metodo [ToString] ora visualizza i due nuovi campi.

Classe [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; }

    // firma
    public override string ToString()
    {
      return string.Format("Indemnités[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Versioning, Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
    }
  }
}
  • riga 3: lo spazio dei nomi è stato adattato al nuovo progetto;
  • sono state aggiunte le proprietà delle righe 7 e 13 per rispecchiare la struttura della tabella [indemnites];
  • riga 18: il metodo [ToString] ora visualizza i due nuovi campi.

Classe [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; }

    // firma
    public override string ToString()
    {
      return string.Format("Employé[{0},{1},{2},{3},{4},{5}, {6}, {7}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal);
    }
  }
}
  • riga 3: lo spazio dei nomi è stato adattato al nuovo progetto;
  • sono state aggiunte le proprietà delle righe 8 e 16 per rispecchiare la struttura della tabella [employes];
  • riga 21: il metodo [ToString] ora visualizza i due nuovi campi.

Per poter essere utilizzate da ORM e EF5, le proprietà di queste classi devono essere contrassegnate da annotazioni.


Compito: avvalendovi del paragrafo 3.4 di [Création de la base à partir des entités], aggiungete alle entità [Employe, Cotisations, Indemnites] le annotazioni necessarie per EF5.


Suggerimenti:

  • si tratta semplicemente di creare delle annotazioni. Non seguire la parte [création de base] del paragrafo citato;
  • per l’annotazione [Table], seguire l’esempio MySQL del paragrafo 4.2 di [refEF5];
  • per l’annotazione [ConcurrencyCheck] relativa alla proprietà [Versioning], seguire l’esempio Oracle del paragrafo 5.2 di [refEF5];
  • per la chiave esterna che la tabella [employes] possiede sulla tabella [indemnités], seguire l'esempio 3.4.2 di [refEF5]. In questo modo aggiungerete una nuova proprietà all’entità [Employe]:

    public int IndemniteId { get; set; }

il cui valore sarà quello della colonna [INDEMNITES_ID] della tabella [employes]. Assegnate alle proprietà [IndemniteId] e [Indemnites] dell’entità [Employe] le annotazioni di chiave esterna. A tal fine, seguite l’esempio 3.4.2 di [refEF5];

  • non gestite le relazioni inverse delle chiavi esterne;
  • questo lavoro richiede una breve lettura di [refEF5].

9.22.5. Configurazione di ORM e EF5

Ricontextualizziamo il progetto nel suo insieme:

Il livello [EF5] accederà al database tramite il connettore [ADO.NET] del SGBD MySQL. Per accedere a tale database, sono necessarie alcune informazioni, che si trovano in varie parti del progetto.

Dobbiamo innanzitutto creare il contesto del database. Questo contesto è una classe derivata dalla classe di sistema [System.Data.Entity.DbContext]. Serve a definire le immagini oggetto delle tabelle del database. Inseriremo questa classe nella cartella [Models] del progetto insieme alle entità EF5:

La classe [DbPamContext] sarà la seguente:


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; }
  }
}
  • riga 6: la classe [DbPamContext] deriva dalla classe di sistema [DbContext];
  • righe 8-10: gli oggetti immagine delle tre tabelle del database. Il loro tipo è [DbSet<Entity>], dove [Entity] è una delle entità di Entity Framework che abbiamo appena definito. Il tipo [DbSet] può essere considerato come una collezione di entità. Può essere interrogato con LINQ (Language INtegrated Query). Il lettore che non conosca LINQ è invitato a leggere il paragrafo 3.5.4 [Apprentissage de LINQ avec LINQPad] di [refEF5].

D'ora in poi chiameremo la classe [DbPamContext] «contesto di persistenza» del database [dbpam_ef5]. Si tratta di una terminologia comunemente utilizzata negli ORM (Object Relational Mapper). Questo contesto di persistenza è un’immagine oggettuale del database. Si parla anche di sincronizzazione del contesto di persistenza con il database: le modifiche, le aggiunte e le eliminazioni effettuate sul contesto di persistenza vengono riportate nel database. Tale sincronizzazione avviene in momenti specifici: alla chiusura del contesto di persistenza, al termine di una transazione o prima di una query SQL SELECT sul database.

Le informazioni relative a SGBD e al database vengono inserite in [App.config].

La configurazione necessaria in [app.config] è illustrata nei seguenti paragrafi di [refEF5]:

  • 3.4 per il server SGBD SQL. È qui che vengono illustrati i principi fondamentali della configurazione di EF5;
  • 4.2 per il server SGBD MySQL.

Seguiamo le indicazioni di quest’ultimo paragrafo e configuriamo il file [app.config] nel modo seguente:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
    <!-- configurazione EF5 -->
    <!-- stringa di connessione al database [dbam_ef5] -->
    <connectionStrings>
        <add name="DbPamContext"
         connectionString="Server=localhost;Database=dbpam_ef5;Uid=root;Pwd=;"
         providerName="MySql.Data.MySqlClient" />
    </connectionStrings>
    <!-- il provider di fabbrica di 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>
  • sono state aggiunte le righe da 6 a 21. Devono essere inserite all’interno del tag <configuration> alle righe 2 e 22;
  • righe 8-12: definiscono le stringhe di connessione ai database, un concetto ADO.NET (vedere il paragrafo 7.3.5 in [refC#]);
  • righe 9-11: definiscono la stringa di connessione al database MySQL [dbpam_ef5];
  • riga 9: il nome della stringa di connessione. In questo caso, non è possibile inserire un valore qualsiasi. Per impostazione predefinita, è necessario inserire il nome della classe che implementa il contesto del database:

  public class DbPamContext : DbContext
  {
    public DbSet<Employe> Employes { get; set; }
    public DbSet<Cotisations> Cotisations { get; set; }
    public DbSet<Indemnites> Indemnites { get; set; }
}

La classe si chiama [DbPamContext]. Nella riga 9 di [app.config], occorre quindi inserire [name="DbPamContext"];

  • riga 10: una stringa di connessione specifica per SGBD MySQL:
    • [Server=localhost]: indirizzo IP del computer che ospita il SGBD. In questo caso si tratta del computer locale [localhost];
    • [Database=dbpam_ef5;]: nome del database,
    • [Uid=root;]: nome utente con cui ci si collegherà al database,
    • [Pwd=;]: password di tale login. In questo caso non è richiesta alcuna password;
  • riga 10: [providerName="MySql.Data.MySqlClient"] è il nome del connettore ADO.NET da utilizzare. Questo nome corrisponde all’attributo [invariant] della riga 17. È possibile inserire qualsiasi valore purché si rispetti la regola precedente e non sia già stato registrato un provider con lo stesso invariante;
  • righe 15-20: definiscono una factory di connettori (provider) ADO.NET. Il [DbProviderFactory] è un concetto un po’ nebuloso per me. A giudicare dal nome, si tratterebbe di una classe in grado di generare il connettore ADO.NET che dà accesso al SGBD, in questo caso MySQL5. In genere queste righe vengono copiate e incollate. Sono necessarie. Prestiamo attenzione all’attributo [Version=6.5.4.0] della riga 16. Questo numero di versione deve corrispondere al numero di versione del DLL [MySql.Data] che avete aggiunto ai riferimenti del progetto:
  • La riga 16 è importante. Poiché non è possibile installare due provider con lo stesso nome, si inizia eliminando un eventuale provider già installato che abbia lo stesso nome di quello che si sta installando alla riga 17;

Questo è tutto. All’inizio sembra complicato e oscuro, ma col passare del tempo diventa semplice perché si tratta sempre della stessa procedura che si ripete.

9.22.6. Test del livello [EF5]

Siamo pronti a testare il nostro livello [EF5]. Lo facciamo utilizzando il programma [Program.cs] già presente:

Visualizzeremo il contenuto del database. Se ci riusciremo, sarà un primo segnale che la nostra configurazione è corretta. Un esempio di codice è disponibile al paragrafo 3.5.3 di [refEF5]. Il codice di [Program.cs] sarà il seguente:


using Pam.EF5.Entites;
using Pam.Models;
using System;

namespace Pam
{
  class Program
  {
    static void Main(string[] args)
    {
      try
      {
        using (var context = new DbPamContext())
        {
          // viene visualizzato il contenuto delle tabelle
          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;
      }
    }
  }
}
  • riga 13: ogni operazione su BD viene eseguita attraverso il contesto di questo database. Abbiamo implementato questo contesto con la classe [DbPamContext]. Lo abbiamo chiamato anche contesto di persistenza del database;
  • righe 13, 31: le operazioni sul contesto di persistenza vengono eseguite all’interno di una clausola [using]. Il contesto di persistenza viene aperto all’inizio della clausola [using] e chiuso automaticamente all’uscita da tale clausola. Ciò implica che qualsiasi modifica apportata al contesto di persistenza nella clausola [using] verrà riportata nel database all’uscita dalla clausola. Viene quindi inviata una serie di comandi SQL al comando BD all’interno di una transazione. Ciò significa che se un comando SQL fallisce, tutti i comandi SQL emessi in precedenza vengono annullati. Viene quindi generata un’eccezione da EF5;
  • riga 17: l'espressione [context.Employes] indica l'immagine oggetto della tabella [employes]. Si ricorda che [Employes] è una proprietà del contesto di persistenza [DbPamContext]:

  public class DbPamContext : DbContext
  {
    public DbSet<Employe> Employes { get; set; }
    public DbSet<Cotisations> Cotisations { get; set; }
    public DbSet<Indemnites> Indemnites { get; set; }
}
  • riga 17: il fatto che [foreach] esegua un'iterazione sulla collezione [context.Employes] riporterà tutti i dipendenti dal database nel contesto di persistenza. Verrà quindi emesso un comando SQL SELECT da EF5;
  • righe 17-20: si percorre la collezione dei dipendenti e, alla riga 19, si utilizza il metodo [ToString] della classe [Employe] per visualizzare i dipendenti sulla console;
  • righe 21-25: lo stesso vale per la collezione delle indennità;
  • righe 27-30: lo stesso vale per la raccolta dei contributi.

Torniamo alla definizione dell’entità [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; }

    // firma
    public override string ToString()
    {
      return string.Format("Employé[{0},{1},{2},{3},{4},{5}, {6}, {7}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal);
    }
  }
}
  • riga 15: un dipendente ha un riferimento a un'indennità.

Quando si riporta un dipendente nel contesto di persistenza, si riporta anche la sua indennità? La risposta predefinita è no. È questo il concetto alla base di [Lazy Loading]. Le entità a cui si fa riferimento all’interno di un’altra entità non vengono trasferite nel contesto di persistenza insieme a quest’ultima. Vengono trasferite solo quando vengono richieste dal codice all’interno di un contesto di persistenza aperto. Se il contesto di persistenza è chiuso, viene generata un’eccezione.

Pertanto, se il metodo [ToString] avesse fatto riferimento alla proprietà [Indemnites] come di seguito:


    // firma
    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);
}

l’operazione seguente in [Program.cs]:


          foreach (Employe employe in context.Employes)
          {
            Console.WriteLine(employe);
}

avrebbe riportato nel contesto di persistenza non solo i dipendenti ma anche le loro indennità, poiché alla riga 3 viene chiamato il metodo [Employe.ToString], che fa riferimento all’entità [Indemnites].

L'esecuzione di [Program.cs] fornisce i seguenti risultati:

1
2
3
4
5
6
7
8
Liste des employés -----------------------------------------
Employé[24,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203]
Employé[25,1,260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014]
Liste des indemnités -----------------------------------------
Indemnités[93,1,2,2,1,2,1,3,1,15]
Indemnités[94,1,1,1,93,2,3,12]
Liste des cotisations -----------------------------------------
Cotisations[11,1,3,49,6,15,9,39,7,88]

Cosa fare se non funziona? Siete nei guai... Ci sono numerose possibili cause di errore:

  • verifica la configurazione di EF5 (paragrafo 9.22.5);
  • controllate le vostre entità Entity Framework (paragrafo 9.22.4).

9.22.7. DLL del livello [EF5]

Trasformiamo il nostro progetto in una libreria di classi in modo che, al momento della generazione, venga generato un assembly .dll anziché un .exe. Ciò avviene nelle proprietà del progetto, come illustrato nel paragrafo 9.7.6, per il livello business simulato.


Compito: convertire il tipo di progetto [pam-ef5] in una libreria di classi, quindi rigenerare il progetto.


9.23. Fase 16: implementazione del livello [DAO]

9.23.1. L'interfaccia del livello [DAO]

Come abbiamo fatto per il livello simulato [métier], il livello [DAO] sarà accessibile tramite un'interfaccia. Quale sarà?

Diamo un'occhiata all'interfaccia [IPamMetier] del livello simulato [métier] che abbiamo creato:


    public interface IPamMetier {
        // elenco di tutte le identità dei dipendenti 
        Employe[] GetAllIdentitesEmployes();

        // ------- calcolo dello stipendio 
        FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}

Riga 3: il metodo [GetAllIdentitesEmployes] serve ad alimentare l’elenco a discesa della pagina iniziale:

Questi dipendenti dovranno essere ricercati nel database.

Riga 6: il metodo [GetSalaire] consente di calcolare la busta paga di un dipendente di cui si conosce il numero SS. Ricordiamo la definizione del tipo [FeuilleSalaire]:


  public class FeuilleSalaire
  {

    // proprietà automatiche 
    public Employe Employe { get; set; }
    public Cotisations Cotisations { get; set; }
    public ElementsSalaire ElementsSalaire { get; set; }
}

Le informazioni delle righe 5 e 6 proverranno dal database. Ricordiamo che un dipendente ha una proprietà [Indemnites]. Anche questa informazione dovrà essere recuperata.

Si potrebbe quindi partire con la seguente interfaccia per il livello [DAO]:


    public interface IPamDao {
        // elenco di tutte le identità dei dipendenti 
        Employe[] GetAllIdentitesEmployes();
        // un dipendente specifico con le relative indennità 
        Employe GetEmploye(string ss);
        // elenco di tutti i contributi 
        Cotisations GetCotisations();
}

9.23.2. Il progetto Visual Studio


Compito: aggiungere alla soluzione [pam-td] un nuovo progetto di tipo [console] denominato [pam-dao]. Impostarlo come progetto iniziale della soluzione.


 

9.23.3. Aggiunta dei riferimenti necessari al progetto

Inquadriamo il progetto nel suo complesso:

Il progetto [pam-dao] necessita di un certo numero di DLL:

  • tutte quelle indicate nel progetto [pam-ef5];
  • quella del progetto [pam-ef5] stesso.

Inoltre, utilizzeremo [Spring.net] per istanziare il livello [DAO]. A tal fine, abbiamo bisogno di DLL, [Spring.core] e [Common.Logging]. Questi DLL si trovano nella cartella [lib] del supporto del caso di studio.


Compito: aggiungere questi diversi riferimenti al progetto [pam-dao].


9.23.4. Implementazione del livello [DAO]

Quella sopra riportata è la classe [PamException] definita al paragrafo 9.7.4. È sufficiente modificarne lo spazio dei nomi (riga 1 qui sotto):


namespace Pam.Dao.Entites
{
  // classe di eccezione
  public class PamException : Exception
  {
....
  }
}

L'interfaccia [IPamDao] è quella che abbiamo appena definito al paragrafo 9.23.1:


using Pam.EF5.Entites;

namespace Pam.Dao.Service
{
  public interface IPamDao
  {
    // elenco di tutte le identità dei dipendenti 
    Employe[] GetAllIdentitesEmployes();
    // un dipendente specifico con le relative indennità 
    Employe GetEmploye(string ss);
    // elenco di tutti i contributi 
    Cotisations GetCotisations();
  }
}

La classe [PamDaoEF5] implementa questa interfaccia utilizzando ORM e EF5. Il suo codice è il seguente:


using Pam.Dao.Entites;
using Pam.EF5.Entites;
using Pam.Models;
using System;
using System.Linq;

namespace Pam.Dao.Service
{

  public class PamDaoEF5 : IPamDao
  {
    // campi privati 
    private Cotisations cotisations;
    private Employe[] employes;

    // Costruttore
    public PamDaoEF5()
    {
      // contributo
      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);
      }
    }
  }
}

Da notare:

  • riga 10: la classe [PamDaoEF5] implementa l'interfaccia [IPamDao];
  • le tabelle [cotisations] e [employes] sono memorizzate nella cache nelle proprietà delle righe 13-14. I dipendenti non hanno le indennità;
  • righe 17-28: è il costruttore che inizializza le righe 13-14;
  • righe 43-52: il metodo [GetEmploye] restituisce un dipendente con le relative indennità. Riceve come parametro il numero di previdenza sociale di tale dipendente. Se il dipendente non esiste nel database, il metodo restituirà il puntatore null.

Compito: completare il codice della classe [PamDaoEF5].


Per il costruttore, ci si ispirerà al codice di test del livello [EF5] presentato al paragrafo 9.22.6. Per il metodo [GetEmploye] ci si ispirerà all’esempio del paragrafo 3.5.7 relativo a [Eager and Lazy loading] di [refEF5].

9.23.5. Configurazione del livello [DAO]

Come già fatto nel paragrafo 9.22.5, occorre configurare EF5 nel file [App.config] del progetto:


Esercizio 1: configurare EF5 in [App.config]. È sufficiente ripetere quanto fatto nel file [App.config] del livello [EF5].


Il nostro programma di test utilizzerà [Spring.net] per ottenere un riferimento al livello [DAO].


Esercizio 2: avvalendovi di quanto fatto al paragrafo 9.20.2, modificate il file di configurazione [app.config] del progetto [pam-dao] in modo che definisca un oggetto Spring denominato [pamdao] associato alla classe [PamDaoEF5] che abbiamo appena creato. I file [app.config] e [web.config] hanno la stessa struttura. È necessario assicurarsi che il tag <configSections> sia il primo tag che si incontra dopo il tag radice <configuration>.


9.23.6. Test del livello [DAO]

Siamo pronti a testare il nostro livello [DAO]. Lo facciamo utilizzando il programma [Program.cs] già presente:

Testeremo le diverse funzionalità dell’interfaccia del livello [DAO]. Il codice di [Program.cs] sarà il seguente:


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
      {
        // istanza del livello [dao]
        IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
        // elenco delle identità dei dipendenti 
        foreach (Employe Employe in pamDao.GetAllIdentitesEmployes())
        {
          Console.WriteLine(Employe.ToString());
        }
            // un dipendente con le relative indennità 
            Console.WriteLine("------------------------------------");
            Employe e = pamDao.GetEmploye("254104940426058");
            Console.WriteLine("employé= {0}, indemnités={1}", e, e.Indemnites);
            Console.WriteLine("------------------------------------");
        // un dipendente inesistente 
        Employe employe = pamDao.GetEmploye("xx");
        Console.WriteLine("Employé n° xx");
        Console.WriteLine((employe == null ? "null" : employe.ToString()));
        Console.WriteLine("------------------------------------");
        // elenco dei contributi 
        Cotisations cotisations = pamDao.GetCotisations();
        Console.WriteLine(cotisations.ToString());
      }
      catch (Exception ex)
      {
        // visualizzazione di un'eccezione 
        Console.WriteLine(ex.ToString());
      }
      //pausa 
      Console.ReadLine();
    }
  }
}
  • riga 15: si ottiene un riferimento al livello [DAO] tramite [Spring.net].

I risultati dell'esecuzione di questo programma sono i seguenti:

1
2
3
4
5
6
7
8
9
Employé[22,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203]
Employé[23,1,260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014]
------------------------------------
employé= Employé[22,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203], indemnités=Indemnités[91,1,2,2,1,2,1,3,1,15]
------------------------------------
Employé n° xx
null
------------------------------------
Cotisations[10,1,3,49,6,15,9,39,7,88]

9.23.7. DLL dal livello [DAO]


Compito: convertire il tipo del progetto [pam-dao] in una libreria di classi, quindi rigenerare il progetto (ripetere quanto fatto al paragrafo 9.22.7).


9.24. Fase 17: configurazione del livello [métier]

9.24.1. L'interfaccia del livello [métier]

L’interfaccia del livello [métier] sarà l’interfaccia [IPamMetier] del livello [métier] simulato che abbiamo costruito al paragrafo 9.7.2.


    public interface IPamMetier {
        // elenco di tutte le identità dei dipendenti 
        Employe[] GetAllIdentitesEmployes();

        // ------- calcolo dello stipendio 
        FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}

9.24.2. Il progetto Visual Studio


Compito: aggiungere alla soluzione [pam-td] un nuovo progetto di tipo [console] denominato [pam-metier]. Impostarlo come progetto iniziale della soluzione.


 

9.24.3. Aggiunta dei riferimenti necessari al progetto

Inquadriamo il progetto nel suo complesso:

Il progetto [pam-metier] richiede un certo numero di DLL:

  • tutte quelle a cui fanno riferimento i progetti [pam-dao] e [pam-ef5];
  • quelle dei progetti [pam-dao] e [pam-ef5] stessi.

Compito: aggiungere questi diversi riferimenti al progetto [pam-metier].


 

9.24.4. Implementazione del livello [métier]

Quanto sopra riporta quattro elementi già utilizzati nel livello [métier] simulato (vedere paragrafo 9.7). Potrebbero esserci delle modifiche per gli spazi dei nomi importati da queste diverse classi. Gestirli. La classe [PamMetier] implementa l'interfaccia [IPamMetier] nel modo seguente:


using Pam.Dao.Service;
using Pam.EF5.Entites;
using Pam.Metier.Entites;
using System;

namespace Pam.Metier.Service
{

  public class PamMetier : IPamMetier
  {

    // riferimento al livello [DAO] inizializzato da Spring
    public IPamDao PamDao { get; set; }

    // elenco di tutte le identità dei dipendenti 
    public Employe[] GetAllIdentitesEmployes()
    {
      ...
    }

    // un dipendente specifico con le relative indennità 
    public Employe GetEmploye(string ss)
    {
      ...
    }

    // i contributi 
    public Cotisations GetCotisations()
    {
      ...
    }

    // calcolo dello stipendio 
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
    {
      // SS: n. SS del dipendente 
      // HeuresTravaillées: numero di ore lavorate 
      // Giorni lavorati: numero di giorni lavorati 
...
  }
}
  • riga 13: è presente un riferimento al livello [DAO]. Questo verrà inizializzato da Spring al momento dell'istanziazione della classe [PamMetier]. Pertanto, quando vengono eseguiti i vari metodi, la riga 13 è già stata inizializzata.

Compito: completare il codice della classe [PamMetier]. Se in [GetSalaire] si riscontra che il dipendente con numero ss non esiste, verrà lanciata una [PamException]. La modalità di calcolo dello stipendio è spiegata al paragrafo 9.5. Si dovrà prestare attenzione ad arrotondare tutti i calcoli intermedi a due cifre dopo la virgola.


9.24.5. Configurazione del livello [métier]

Come già fatto al paragrafo 9.22.5, è necessario configurare EF5 nel file [app.config] del progetto:


Esercizio 1: configurare EF5 in [app.config]. È sufficiente ripetere quanto fatto nel file [app.config] del livello [EF5].


Il nostro programma di test utilizzerà [Spring.net] per ottenere un riferimento sul livello [métier].


Compito 2: avvalendovi di quanto fatto in precedenza al paragrafo 9.23.5, modificate il file di configurazione [app.config] del progetto [pam-metier] in modo che definisca un oggetto Spring denominato [pammetier] associato alla classe [PamMetier] che abbiamo appena creato. Il modo più semplice è copiare il file [app.config] dal progetto [pam-dao] e aggiungere ciò che manca.


Qui sorge una difficoltà. Non solo è necessario istanziare il livello [métier] con la classe [PamMetier], ma occorre anche inizializzarne la proprietà [PamDao]:


    // riferimento al livello [DAO] inizializzato da Spring
    public IPamDao PamDao { get; set; }

La configurazione di Spring in [app.config] è quindi la seguente:


  <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>
  • riga 6: definisce l'oggetto [pamdao] associato alla classe [PamDaoEF5];
  • riga 7: definisce l'oggetto [pammetier] associato alla classe [PamMetier];
  • riga 8: il tag [property] serve a inizializzare una proprietà pubblica della classe [PamMetier]. L'attributo [name="PamDao"] corrisponde al nome della proprietà da inizializzare nella classe [PamMetier]. L'attributo [ref="pamdao"] indica che la proprietà viene inizializzata con un riferimento, ovvero quello dell'oggetto [pamdao] della riga 6, quindi con il riferimento del livello [DAO]. È proprio quello che volevamo.

9.24.6. Test del livello [métier]

Siamo pronti a testare il nostro livello [métier]. Lo facciamo utilizzando il programma [Program.cs] già presente:

Testeremo le diverse funzionalità dell’interfaccia del livello [métier]. Il codice di [Program.cs] sarà il seguente:


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
      {
        // istanza del livello [métier]
        IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        // elenco delle identità dei dipendenti
        Console.WriteLine("Employés -----------------------------");
        foreach (Employe Employe in pamMetier.GetAllIdentitesEmployes())
        {
          Console.WriteLine(Employe);
        }

        // calcoli delle buste paga 
        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));
      }
      // pausa 
      Console.ReadLine();
    }
  }
}
  • riga 16: si ottiene un riferimento al livello [métier] tramite [Spring.net].

I risultati dell'esecuzione di questo programma sono i seguenti:

1
2
3
4
5
6
7
Employés -----------------------------
Employé[24,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203]
Employé[25,1,260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014]
salaires -----------------------------
[Employé[25,1,260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014],Cotisations[11,1,3,49,6,15,9,39,7,88],[64,85 : 17,45 : 10 : 15 : 72,4]]
[Employé[24,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203],Cotisations[11,1,3,49,6,15,9,39,7,88],[362,25 : 97,48 : 42 : 62 : 368,77]]
PamException : L'employé de n° [xx] n'existe pas

9.24.7. DLL del livello [métier]


Compito: convertire il tipo del progetto [pam-metier] in una libreria di classi, quindi rigenerare il progetto (ripetere quanto fatto al paragrafo 9.22.7).


9.25. Fase 18: implementazione del livello [web]

Arriviamo all’ultimo livello della nostra architettura, il livello [web]:

Riutilizzeremo il livello [web] che avevamo sviluppato con l’aiuto di un livello [métier] simulato.

9.25.1. Il progetto Visual Studio

Torniamo a Visual Studio Express 2012 per il Web per collegare il nostro livello Web ai livelli [métier, DAO, EF5] che abbiamo appena sviluppato. Si tratta principalmente di configurazioni da effettuare e di alcune modifiche agli spazi dei nomi.

Con Visual Studio Express 2012 per il Web, caricate la soluzione [pam-td]:

  • in [1], la soluzione [pam-td] in VS Studio per il Web. Il progetto web [pam-web-01] torna visibile. Lo avevamo perso in VS Studio per desktop.
  • La configurazione del progetto web [pam-web-01] dovrà essere modificata. Anziché modificare un progetto funzionante, apporteremo le modifiche su una copia di tale progetto. Innanzitutto, in [2], eliminiamo il progetto dalla soluzione (questo non elimina nulla dal file system).
  • in [3], con Esplora risorse di Windows, si duplica la cartella [pam-web-01] in [pam-web-02];
  • in [4], si carica il progetto [pam-web-02] nella soluzione [pam-td]. Viene visualizzato con il nome [pam-web-01];
  • in [5], modificare il nome in [pam-web-02] e impostare questo progetto come progetto di avvio;
  • in [6], caricate il vecchio progetto [pam-web-01]. Ora avete tutti i vostri progetti. Assicuratevi di lavorare con [pam-web-02].

9.25.2. Aggiunta dei riferimenti necessari al progetto

Inquadriamo il progetto nel suo insieme:

Il progetto [pam-web-02] necessita di un certo numero di DLL:

  • tutti quelli a cui fanno riferimento i progetti [pam-metier], [pam-dao] e [pam-ef5];
  • quelle dei progetti [pam-metier], [pam-dao] e [pam-ef5] stessi.

Compito: aggiungere questi diversi riferimenti al progetto [pam-web-02]. Il riferimento al progetto [pam-metier-simule] deve essere rimosso. Si passa al livello [métier]. Alcuni DLL sono già presenti nei riferimenti. Eliminateli e poi effettuate le vostre aggiunte.


 

9.25.3. Implementazione del livello [web]

Generare il progetto [pam-web-02]. Verranno visualizzati errori come il seguente:

La classe [ApplicationModel] utilizza il tipo [Employe]. Con il livello [métier] simulato, questo tipo era definito nello spazio dei nomi [Pam.Metier.Entites]. Ora si trova nello spazio dei nomi [Pam.EF5.Entites]. Correggere questi errori come indicato sopra.

9.25.4. Configurazione del livello [web]

Come già fatto nel paragrafo 9.24.5, è necessario configurare EF5 nel file [web.config] del progetto:


Compito 1: sostituire l’intero contenuto attuale di [web.config] con quello del file [app.config] del progetto [pam-metier].


Il file [Global.asax] della nostra applicazione web utilizza [Spring.net] per recuperare un riferimento sul livello [métier]:


      try
      {
        // istanza del livello [métier]
        application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
      }
      catch (Exception ex)
      {
        application.InitException = ex;
}

Alla riga 4, viene richiesto un riferimento all’oggetto Spring denominato [pammetier]. Questo è proprio il nome assegnato al livello [métier] (verificatelo nel vostro file [web.config]).

9.25.5. Test del livello [web]

Siamo pronti a testare il nostro livello [web]. Per prima cosa modificheremo la sua porta di lavoro. Per impostazione predefinita, [pam-web-02] ha la configurazione di [pam-web-01] e quindi opera sulla stessa porta. L’esperienza dimostra che ciò crea problemi: IIS continua infatti a utilizzare i codici del progetto [pam-web-01]. Procedete come segue:

In [4], modificare il numero della porta, ad esempio cambiando la cifra delle unità.

Si esegue il progetto [pam-web-02] tramite [Ctrl-F5]. Si ottiene quindi la seguente pagina iniziale:

In [1] si ottengono i dipendenti presenti nel database [dbpam_ef5]. Si noti che non è più presente il dipendente [X X] che avevamo con il livello [métier] simulato. Facciamo una simulazione:

In [2] si ottiene effettivamente lo stipendio reale e non più uno stipendio fittizio. Ora fermiamo SGBD e MySQL5 ed eseguiamo un’altra simulazione:

Con [3], abbiamo ottenuto una pagina di errori leggibile, anche se alcuni messaggi sono in inglese. Ora fermiamo nuovamente MySQL e rieseguiamo l’applicazione in VS passando da [Ctrl-F5]:

 

Si ottiene la vista [initFailed.cshtml] creata al paragrafo 9.20.4. Essa visualizza i messaggi di errore della pila delle eccezioni. Il lettore è invitato a effettuare ulteriori test.

9.26. Fase 19: rendere accessibile su Internet un'applicazione ASP.NET

Quando si sviluppa un'applicazione ASP.NET con Visual Studio, la configurazione predefinita fa sì che l'applicazione sviluppata sia accessibile solo all'indirizzo [localhost]. Qualsiasi altro indirizzo viene rifiutato dal server integrato di Visual Studio, che restituisce quindi l’errore [400 Bad Request].

È possibile verificarlo come segue:

  • in una finestra DOS, prendere nota dell’indirizzo IP del proprio computer di sviluppo:

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. . . :

L’indirizzo IP è indicato qui alla riga 14. Se si dispone di una connessione Wi-Fi, l’indirizzo Wi-Fi del computer apparirà alle righe 20 e seguenti.

  • Verificate le proprietà del progetto [clic droit sur projet / propriétés / onglet web]:
 

L'applicazione verrà eseguita sulla porta [65010] del computer [localhost].

  • Eseguite il vostro progetto tramite [Ctrl-F5]

Image

  • sostituire [localhost] con l'indirizzo IP del computer:

Image

Il server ha restituito una risposta [400 Bad Request]. Il server IIS Express utilizzato da Visual Studio accetta solo il nome [localhost].

Per rendere accessibile l'applicazione sviluppata a un URL di tipo [http://adresseIP/contexte/...], è necessario utilizzare un server diverso da IIS Express, ad esempio un server IIS (non Express). Per verificare la presenza di quest’ultimo (normalmente nelle versioni Pro di Windows), occorre accedere al Pannello di controllo [Panneau de configuration\Système et sécurité\Outils d’administration]:

Image

Questa opzione non è sempre presente. In tal caso, occorre accedere a [ Panneau de configuration \ Programmes] e installare gli Strumenti di amministrazione web.

Una volta che l’opzione [Gestionnaire des services internet (IIS)] è presente, la si attiva:

Si avvia il sito web predefinito. A tal fine, è necessario che il servizio [Service de publication World Wide Web] sia stato precedentemente avviato:

Fatto ciò, accedere a URL [http://localhost] tramite un browser. Verificare prima che nessun altro server web stia già occupando la porta 80. In caso affermativo, arrestarlo.

Il server IIS ci ha risposto. Ora sostituisci [localhost] con l'indirizzo IP del tuo computer:

Funziona. Torniamo ora a Visual Studio:

  • innanzitutto, è necessario avviare Visual Studio in modalità [administrateur]

Fatto ciò, è necessario modificare la configurazione del progetto web che si desidera distribuire [clic droit sur projet / propriétés / onglet web]:

È necessario selezionare il server locale IIS come server di distribuzione. Visual Studio imposta il percorso URL dell’applicazione. È possibile modificarlo. Eseguire il progetto tramite [Ctrl-F5]:

Ora sostituisci [localhost] con l'indirizzo IP del tuo computer:

Se non si dispone del server IIS, è possibile utilizzare un server ASP.NET gratuito, come ad esempio [Ultidev Web Server Pro], disponibile su URL [http://ultidev.com/Download/ ]. Una volta installato, esistono due metodi per avviare un'applicazione web con questo server:

Il metodo veloce

Aprire un browser di Windows e selezionare la cartella dell’applicazione ASP.NET da distribuire:

Il server web verrà quindi avviato e l’applicazione web visualizzata in un browser:

  • in [3] è possibile arrestare o avviare il server web;
  • in [4] è possibile modificare la porta di servizio dell'applicazione web;

Prima di avviare il server, è necessario che sia in esecuzione il servizio [UWS HiPriv Services] riportato di seguito:

Una volta avviato il server, l'interfaccia si presenta come segue:

Cliccando sul link [6] viene visualizzata la prima pagina dell’applicazione:

È quindi possibile sostituire [localhost] con l'indirizzo IP del computer:

Quindi anche in questo caso viene accettato solo il nome [localhost].

Il metodo lungo

Avviare l’applicazione Ultidev Web Explorer

e seguire i passaggi seguenti:

  • in [8], specificare la cartella dell'applicazione web da distribuire;
  • a causa di [10-11], l'applicazione web dovrà essere richiesta con URL e [http://localhost:81/];
  • avviare il server web con [14];
  • richiedere URL [19] ;
  • in [20], abbiamo ottenuto la pagina desiderata utilizzando l'indirizzo locale IP del computer anziché il nome [localhost]. È proprio quello che cercavamo;

Il server Ultidev si è installato come servizio Windows che si avvia automaticamente. È possibile disabilitare l’avvio automatico del server Ultidev nel modo seguente:

  • selezionare l’opzione [Panneau de configuration\Système et sécurité\Outils d’administration];
  • [1, 2]: selezionare le proprietà del servizio [Ultidev Web Server Pro];
  • [3]: impostarlo su avvio manuale.

Per avviare manualmente il server, utilizzare ad esempio l'applicazione [Ultidev Web Explorer]:

9.27. Passaggio 20: generazione di un'applicazione nativa per Android

Quando si dispone di un'applicazione web di tipo APU (Applicazione a Pagina Singola), è possibile generare un eseguibile per dispositivi mobili (Android, IoS, Windows 8, ...) utilizzando lo strumento [Phonegap] [http://phonegap.com/]. Esistono altri modi per farlo, in particolare con il prodotto open source Apache Cordova [https://cordova.apache.org/]. Lo strumento disponibile online sul sito di Phonegap [http://build.phonegap.com/apps] «carica» il file zip del sito da convertire. La pagina iniziale deve chiamarsi [index.html] e deve essere una pagina statica, ovvero non generata da un framework web (ASP.NET, JEE, PHP, ...). Inizieremo quindi a crearne una.

9.27.1. L’architettura dell’applicazione

È importante ricordare che vogliamo creare un’applicazione Android. Un’applicazione di questo tipo presenta spesso la seguente architettura:

  • in [1], l’utente utilizza un tablet Android che comunica con uno o più servizi web [2];

Torniamo al modello APU:

  • una pagina iniziale viene caricata nel browser (lo schema sopra non specifica da dove provenga);
  • le viste successive vengono ottenute tramite richieste Ajax [1-4]. Il browser non caricherà alcuna nuova pagina;

La vista iniziale può essere fornita o meno dallo stesso server delle altre viste ottenute tramite chiamate Ajax. Se non è fornita dallo stesso server, il JavaScript della pagina iniziale deve conoscere l'URL del server web che fornirà le altre viste. Questo sarà il caso nell'applicazione Android che realizzeremo:

  • la pagina statica [index.html] verrà incapsulata in un’applicazione nativa Android [1] dotata delle funzionalità di un browser, quindi in grado di eseguire il JavaScript incorporato nella pagina [index.html];
  • questa pagina recupererà le altre viste tramite chiamate Ajax al server [2]. A tal fine, deve conoscere l'ID URL del server web;

Rifattorizzeremo l’applicazione [pam-web-02] affinché funzioni in questa modalità. Pertanto, la prima pagina sarà la seguente:

  • in [1], l'URL della pagina iniziale dell'applicazione. Ci verrà fornito dal server Ultidev descritto nel paragrafo 9.26;
  • in [2], l’utente dovrà inserire l’URL del simulatore di busta paga. Si potrebbe inserirlo in modo statico nel codice JavaScript della pagina iniziale, ma ciò complicherebbe i test: non appena si modificasse l’indirizzo (o la porta) del simulatore IP, sarebbe necessario modificarlo anche nel codice JavaScript;
  • in [3], il link [Connexion] che richiamerà la vista successiva:
  • si noti che in [4], l'URL del browser non è cambiato. È sempre quello della pagina iniziale e rimarrà tale per tutta la durata dell'applicazione.

Una volta ottenuta questa vista, tutto funziona come in precedenza: le diverse viste vengono recuperate tramite chiamate Ajax. Vedremo che è necessario modificare pochissimo codice.

9.27.2. Rifattorizzazione del progetto [pam-web-02]

All’interno della cartella [Content] del progetto [pam-web-02], creiamo la cartella [bootstrap] (il nome non ha importanza) come segue:

Al suo interno abbiamo incluso la pagina statica [index.html] e tutte le risorse di cui ha bisogno (i file CSS e JS). La pagina [index.html] riprende il codice della pagina master [_Layout.cshtml] del progetto Visual Studio, eliminando tutto ciò che non è statico. Il risultato è il seguente codice:


<!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>

Abbiamo aggiunto i seguenti elementi:

  • righe 27-29: è stata aggiunta l’opzione di menu [Connexion] per consentire la connessione al servizio di simulazione;
  • righe 55-56: l'inserimento dell'ID URL del simulatore;
  • righe 59-63: un messaggio di errore in caso di connessione non riuscita;

La rifattorizzazione del codice riguarda esclusivamente il codice [myScripts.js] alla riga 14 sopra riportata. Nient’altro cambia. Il codice viene modificato come segue:


// al caricamento del documento
$(document).ready(function () {
    // si recuperano i riferimenti dei diversi componenti della pagina
    loading = $("#loading");
    content = $("#content");
    erreur = $("#erreur");
    erreur1 = $("#erreur1");
    erreur2 = $("#erreur2");
    // i link del menu
    lnkConnexion = $("#lnkConnexion");
    lnkFaireSimulation = $("#lnkFaireSimulation");
    lnkEffacerSimulation = $("#lnkEffacerSimulation");
    lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
    lnkVoirSimulations = $("#lnkVoirSimulations");
    lnkTerminerSession = $("#lnkTerminerSession");
    lnkRetourFormulaire = $("#lnkRetourFormulaire");
    // vengono inseriti in una tabella
    options = [lnkConnexion, lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
    // si nascondono alcuni elementi della pagina
    loading.hide();
    erreur.hide();
    // si fissa il menu
    setMenu([lnkConnexion]);
});
  • righe 6-8: gli identificatori dell'area che visualizza gli errori di connessione nella pagina [index.html];
  • riga 10: il nuovo link per la connessione al simulatore;
  • riga 21: l'area degli errori è inizialmente nascosta;
  • riga 23: viene visualizzato solo il link di connessione;

Nella pagina [index.html], il link di connessione è definito come segue:


<a id="lnkConnexion" href="javascript:connexion()">
| Connexion<br />
</a>

La funzione JS [connexion] (riga 1) è la seguente:


var urlServiceWeb;
var erreur, erreur1, erreur2;


function connexion() {
    // si recupera l'urlServiceWeb dal servizio web
    urlServiceWeb = $("#urlServiceWeb").val();
    // si recupera il modulo di inserimento dati
    $.ajax({
        url: urlServiceWeb + '/Pam/Formulaire',
        type: 'POST',
        dataType: 'html',
        beforeSend: function () {
            // segnale di attesa acceso
            loading.show();
        },
        success: function (data) {
            // visualizzazione dei risultati
            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 () {
            // segnale di attesa spento
            loading.hide();
        }
    });
}
  • riga 7: si recupera il valore URL inserito dall’utente. Viene inserito nella variabile globale della riga 1. In questo modo sarà disponibile nelle altre funzioni del file;
  • riga 10: si effettua una chiamata Ajax a URL [/Pam/Formulaire] del simulatore. Questo URL restituisce la vista parziale dell’inserimento delle informazioni relative alla simulazione (dipendenti, ore lavorate, giorni lavorati). Nella versione iniziale di [pam-web-02], questa URL era sufficiente. Le veniva automaticamente anteposto il prefisso URL, che aveva caricato la pagina iniziale. Ora si ipotizza che la pagina iniziale possa essere fornita da un server diverso da quello che ospita il simulatore. È quindi necessario anteporre a URL e [/Pam/Formulaire] la variabile [urlServiceWeb] della riga 1, che corrisponde a URL del simulatore (ad esempio, http://172.19.81.34/pam-web-02). Questa operazione dovrà essere eseguita per tutte le chiamate Ajax del file;
  • righe 17-22: in caso di connessione riuscita, viene visualizzata la vista parziale [Formulaire.cshtml] e viene mostrato un menu contenente il solo link [Faire la simulation] (riga 21);
  • righe 23-27: in caso di connessione non riuscita:
    • alla riga 24, viene visualizzata la risposta HTML inviata dal server web (se presente);
    • alla riga 25, vengono visualizzate le intestazioni HTTP inviate dal server web (se ha risposto);

Questo è tutto. In caso di esito positivo, si ottiene la pagina seguente:

Ci si trova quindi nella situazione precedente, in cui ora le visualizzazioni vengono ottenute tramite chiamate Ajax. Pertanto, come sopra, il clic sul link [Faire la simulation] verrà eseguito dal seguente codice contenuto nel file [myScripts.js]:


function faireSimulation() {
    // si recuperano i riferimenti
    var simulation = $("#simulation");
    var formulaire = $("#formulaire");
    // modulo valido?
    var formValid = formulaire.validate().form();
    if (!formValid) return;
    // si esegue manualmente una chiamata Ajax
    $.ajax({
        url: urlServiceWeb + '/Pam/FaireSimulation',
        type: 'POST',
        data: formulaire.serialize(),
        dataType: 'html',
        ...
    });
    // menu
    setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
  • È stata apportata un'unica modifica, quella alla riga 10, dove il precedente URL è ora preceduto dal prefisso del simulatore;

9.27.3. Test del progetto rifattorizzato

Nel paragrafo 9.26 abbiamo illustrato come installare l’applicazione [pam-web-02] sul server Ultidev. Partiremo da lì:

  • in [6], richiediamo la visualizzazione della pagina [bootstrap/index.html]. Si ottiene la seguente visualizzazione:

Digitiamo un URL errato:

  • in [10], le intestazioni HTTP della risposta del server;
  • in [11], il documento HTML della risposta del server;

Se si digita il codice corretto URL:

si ottiene la seguente risposta:

9.27.4. Creazione del file binario Android

Creeremo il file binario Android a partire dal sito statico che abbiamo appena creato e testeremo [1]:

Aggiungiamo in [2] un file [config.xml] che servirà a configurare il plugin [Phonegap] che genererà il file binario Android. Il suo codice è il seguente:


<?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>
  • righe 7-9: inserite qui i vostri dati;
  • righe 11-13: queste righe consentono al JavaScript incorporato nell’applicazione web, che verrà eseguito all’interno del dispositivo Android, di richiedere file URL esterni a tale dispositivo;

Comprimiamo il contenuto della cartella [Content/bootstrap]:

 

Successivamente accediamo al sito di Phonegap [http://build.phonegap.com/apps]:

  • prima di [1], potrebbe essere necessario creare un account;
  • in [1], si inizia;
  • in [2], si sceglie un piano gratuito che consente solo un'applicazione Phonegap;
  • in [3], si scarica l'applicazione compressa in formato ZIP [4];
  • in [5], si assegna un nome all’applicazione;
  • fare clic sul link [6] per generare i file binari di OS e IoS, per Android e Windows. L'operazione potrebbe richiedere alcuni secondi;
  • in [7-9], scaricate il file binario per Android;

Avviare un emulatore [GenyMotion] per un tablet Android (vedere paragrafo 11.1):

 

Nell'esempio sopra riportato, si avvia un emulatore di tablet con la versione 21 di Android (API). Una volta avviato l'emulatore,

  • sbloccarlo trascinando il cursore di sblocco (se presente) sul lato e poi rilasciandolo;
  • con il mouse, trascinate il file [Pam-debug.apk] che avete scaricato e rilasciatelo sull'emulatore. Verrà quindi installato ed eseguito;

Impostate [1] come URL del simulatore, come descritto al paragrafo 9.27.3. Fatto ciò, collegatevi al simulatore tramite il link [2]:

Provare l'applicazione sull'emulatore. Dovrebbe funzionare.