Skip to content

4. L'applicazione [SimuPaie] – versione 1 – ASP.NET

4.1. Introduzione

Vogliamo scrivere un'applicazione .NET che consenta a un utente di simulare i calcoli delle buste paga per gli operatori dell'associazione "Maison de la petite enfance" in un comune.

Il modulo di calcolo delle retribuzioni ASP.NET avrà questo aspetto:

Image

L'applicazione ASP.NET avrà la seguente architettura:

  • Quando viene effettuata la prima richiesta all'applicazione, viene istanziato un oggetto di tipo [Global] derivato dal tipo [System.Web.HttpApplication]. Questo oggetto elaborerà il file di configurazione [web.config] dell'applicazione web e memorizzerà nella cache alcuni dati provenienti dal database (operazione 0 sopra).
  • Quando viene effettuata la prima richiesta (operazione 1) alla pagina [Default.aspx], che è l'unica pagina dell'applicazione, viene gestito l'evento [Load]. È qui che viene popolata la casella combinata dei dipendenti. I dati richiesti per la casella combinata vengono recuperati dall'oggetto [Global], che li ha memorizzati nella cache. Questa è l'operazione 2 sopra. L'operazione 4 invia la pagina inizializzata all'utente.
  • Quando viene effettuata la richiesta di calcolo dello stipendio (operazione 1) alla pagina [Default.aspx], l'evento [Load] viene elaborato nuovamente. Non viene eseguita alcuna operazione poiché si tratta di una richiesta POST e il gestore [Pam_Load] è stato scritto per non eseguire alcuna operazione in questo caso (utilizzando il valore booleano IsPostBack). Una volta gestito l'evento [Load], viene gestito l'evento [Click] sul pulsante [Salary]. Questo evento richiede dati che non sono stati memorizzati nella cache in [Global]. Pertanto, il gestore di eventi li recupera dal database. Questa è l'operazione 3 sopra. Lo stipendio viene quindi calcolato e l'operazione 4 invia i risultati all'utente.

4.2. Visual Web Developer 2008

  • In [1], creare un nuovo progetto
  • in [2], di tipo [Web / Applicazione Web ASP.NET]
  • In [3], assegnare un nome al progetto
  • in [4], specificarne il nome e in [5] la posizione. Verrà creata una cartella [c:\temp\pam-aspnet\pam-v1-adonet] per il progetto.
  • In [5], il progetto Visual Web Developer
  • In [6], le proprietà del progetto (clic con il tasto destro del mouse sul progetto / Proprietà / Applicazione).
  • in [7], il nome dell'assembly che verrà generato al momento della compilazione del progetto
  • in [8], lo spazio dei nomi predefinito che vogliamo utilizzare per le classi del progetto. La classe [_Default] definita nei file [Default.aspx.cs] e [Default.aspx.designer.cs] è stata creata nello spazio dei nomi [pam_v1_adonet], derivato dal nome del progetto. È possibile modificare questo spazio dei nomi direttamente nel codice di questi due file:

[Default.aspx.cs]


using System;
....
 
namespace pam_v1
{
  public partial class _Default : System.Web.UI.Page
  {
 

[Default.aspx.designer.cs]


//------------------------------------------------------------------------------
// <auto-generated>
//      This code was generated by a tool.
//      Runtime version :2.0.50727.3603
//
//      Changes made to this file may result in incorrect behavior and will be lost if
//      the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
 
namespace pam_v1 {
 
 
    public partial class _Default {
 
.........

È necessario modificare anche il markup del file [Default.aspx]:

  

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="pam_v1._Default" %>
...

L'attributo Inherits sopra riportato fa riferimento alla classe definita nei file [Default.aspx.cs] e [Default.aspx.designer.cs]. In essi viene utilizzato lo spazio dei nomi pam_v1.

4.2.1. Il modulo [ Default.aspx]

L'aspetto visivo del modulo [ Default.aspx] è il seguente:

I componenti sono i seguenti:

N.
Tipo
Nome
Ruolo
1
Elenco a discesa
CasellaCombinataDipendenti
Contiene l'elenco dei nomi dei dipendenti
2
Casella di testo
Casella di testo Ore
Numero di ore lavorate – numero effettivo
3
Casella di testo
Casella di testo
Numero di giorni lavorati – numero intero
4
Pulsante
PulsanteStipendio
Calcola lo stipendio
5
Casella di testo
Casella di testo di errore
Messaggio informativo per l'utente
ReadOnly=true, TextMode=MultiLine
6
Etichetta
NomeEtichetta
Nome del dipendente selezionato in (1)
7
Etichetta
EtichettaNome
Nome del dipendente selezionato al punto (1)
8
Etichetta
EtichettaIndirizzo
Indirizzo
9
Etichetta
Etichetta Città
Città
10
Etichetta
Codice postale
Codice postale
11
Etichetta
Indice delle etichette
Indice
12
Etichetta
EtichettaCSGRDS
Tasso di contribuzione CSGRDS
13
Etichetta
Etichetta CSGD
Tasso di contribuzione CSGD
14
Etichetta
Etichetta "Pensionamento"
Aliquota contributiva per la pensione
15
Etichetta
EtichettaSS
Aliquota contributiva previdenziale
16
Etichetta
EtichettaSH
Salario orario base per l'indice indicato al punto (11)
17
Etichetta
EtichettaEJ
Indennità giornaliera di soggiorno per l'indice indicato al punto (11)
18
Etichetta
EtichettaRJ
Indennità giornaliera per i pasti per l'indice indicato al punto (11)
19
Etichetta
EtichettaVacanze
Tasso dell'indennità per ferie pagate da applicare allo stipendio base
20
Etichetta
EtichettaSB
Importo dello stipendio base
21
Etichetta
EtichettaCS
Importo dei contributi previdenziali da versare
22
Etichetta
EtichettaIE
Importo dell'assegno per la cura dei figli
23
Etichetta
EtichettaIR
Importo delle indennità pasto per il bambino in affidamento
24
Etichetta
EtichettaSN
Stipendio netto da corrispondere al dipendente

Il modulo contiene anche due contenitori [Panel]:

PanelErrors
contiene i (5) componenti TextBoxError
PanelSalary
contiene i componenti da (6) a (24)

Un componente [Panel] può essere reso visibile o nascosto a livello di programmazione utilizzando la sua proprietà booleana [Panel].Visible.

4.2.2. Convalida degli input

Per calcolare uno stipendio, l'utente:

  • seleziona un dipendente in [1]
  • inserisce il numero di ore lavorate in [2]. Questo numero può essere decimale, ad esempio 2,5 per 2 ore e 30 minuti.
  • inserisce il numero di giorni lavorati in [3]. Questo numero è un numero intero.
  • calcola lo stipendio utilizzando il pulsante [4]

Quando l'utente inserisce dati errati nei campi [2] e [3], questi vengono convalidati non appena l'utente passa a un altro campo di immissione. Pertanto, la schermata qui sotto è stata catturata prima ancora che l'utente facesse clic sul pulsante [Stipendio]:

Per il comportamento sopra descritto sono necessarie due condizioni:

  • i componenti di convalida devono avere la proprietà EnableClientScript impostata su true:
  
  • Il browser che visualizza la pagina deve essere in grado di eseguire il codice JavaScript incorporato in una pagina HTML.

Se il browser client non verifica la validità dei dati autonomamente, questi saranno verificati solo quando il browser invierà i dati inseriti nel modulo al server. Sarà quindi il codice sul server che elabora la richiesta del browser a verificare la validità dei dati. Si noti che questa convalida deve essere sempre eseguita, anche se la pagina visualizzata nel browser client contiene codice JavaScript che esegue la stessa convalida. Questo perché il server non può essere certo che la richiesta POST che riceve provenga effettivamente da quella pagina e che i dati siano stati quindi convalidati.

L'elenco dei validatori è il seguente:

N.
Tipo
Nome
Ruolo
25
Validatore dei campi obbligatori
ValidatoreCampoObbligatorioOre
verifica che il campo [2] [TextBoxHeures] non sia vuoto
26
RangeValidator
RangeValidatorHours
verifica che il campo [2] [TextBoxHours] sia un numero reale compreso nell'intervallo [0, 200]
27
ValidatoreCampoObbligatorio
RequiredFieldValidatorDays
verifica che il campo [3] [TextBoxDays] non sia vuoto
28
RangeValidator
RangeValidatorDays
verifica che il campo [3] [TextBoxDays] sia un numero intero compreso nell'intervallo [0,31]

Compito: Creare la pagina [Default.aspx]. Per prima cosa, posizionare i due contenitori [PanelErrors] e [PanelSalary] in modo da poter poi inserire i componenti che devono contenere.


4.2.3. Entità dell'applicazione

Una volta lette, le righe delle tabelle [contributions], [employees] e [allowances] verranno memorizzate in oggetti di tipo [Contributions], [Employee] e [Allowances] definiti come segue:


namespace Pam.Entites
{
  public class Cotisations
  {
    // automatic properties
    public double CsgRds { get; set; }
    public double Csgd { get; set; }
    public double Secu { get; set; }
    public double Retraite { get; set; }
 
    // manufacturers
    public Cotisations()
    {
    }
 
    public Cotisations(double csgRds, double csgd, double secu, double retraite)
    {
      CsgRds = csgRds;
      Csgd = csgd;
      Secu = secu;
      Retraite = retraite;
    }
 
    // ToString
    public override string ToString()
    {
      return string.Format("[{0},{1},{2},{3}]", CsgRds, Csgd, Secu, Retraite);
    }
  }
}

namespace Pam.Entites
{
  public class Employe
  {
    // automatic properties
    public string SS { get; set; }
    public string Nom { get; set; }
    public string Prenom { get; set; }
    public string Adresse { get; set; }
    private string Ville { get; set; }
    private string CodePostal { get; set; }
    private int Indice { get; set; }
 
    // manufacturers
    public Employe()
    {
 
    }
 
    public Employe(string ss, string nom, string prenom, string adresse, string codePostal, string ville, int indice)
    {
      SS = ss;
      Nom = nom;
      Prenom = prenom;
      Adresse = adresse;
      CodePostal = codePostal;
      Ville = ville;
      Indice = indice;
    }
 
    // ToString
    public override string ToString()
    {
      return string.Format("[{0},{1},{2},{3},{4},{5},{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indice);
    }
  }
}

namespace Pam.Entites
{
  public class Indemnites
  {
 
    // automatic properties
    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; }
 
    // manufacturers
    public Indemnites()
    {
 
    }
 
    public Indemnites(int indice, double baseHeure, double entretienJour, double repasJour, double indemnitesCP)
    {
      Indice = indice;
      BaseHeure = baseHeure;
      EntretienJour = entretienJour;
      RepasJour = repasJour;
      IndemnitesCP = indemnitesCP;
    }
 
    // identity
    public override string ToString()
    {
      return string.Format("[{0}, {1}, {2}, {3}, {4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCP);
    }
 
  }
}

4.2.4. Configurazione dell'applicazione

Il file [Web.config] che configura l'applicazione sarà il seguente:


<?xml version="1.0" encoding="utf-8"?>
 
<configuration>
    <configSections>
...
    </configSections>
 
  <connectionStrings>
    <add name="dbpamSqlServer2005" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=C:\data\...\dbpam.mdf;User Id=sa;Password=msde;Connect Timeout=30;" providerName="System.Data.SqlClient"/>
  </connectionStrings>
 
  <appSettings>
    <add key="selectEmploye" value="select NOM,PRENOM,ADRESSE,VILLE,CODEPOSTAL,INDICE from EMPLOYES where SS=@SS"/>
    <add key="selectEmployes" value="select PRENOM, NOM, SS from EMPLOYES"/>
    <add key="selectCotisations" value="select CSGRDS,CSGD,SECU,RETRAITE from COTISATIONS"/>
    <add key="SelectIndemnites" value="select INDICE,BASEHEURE,ENTRETIENJOUR,REPASJOUR,INDEMNITESCP from INDEMNITES"/>
  </appSettings>
 
  <system.web>
        <!-- 
            Définissez compilation debug="true" pour insérer des symboles 
            de débogage dans la page compilée. Comme ceci 
            affecte les performances, définissez cette valeur à true uniquement 
            lors du développement.
        -->
        <compilation debug="false">
...
</configuration>
  • riga 9: definisce la stringa di connessione al database SQL Server
  • righe 13–16: definiscono le query SQL utilizzate dall'applicazione per evitare di codificarle in modo rigido nel codice.
  • riga 13: la query SQL è parametrizzata. La notazione dei parametri è specifica di SQL Server.

Compito: inserisci questi parametri nel file [Web.config]. La riga 9 verrà adattata al percorso effettivo del database [dbpam.mdf].


4.2.5. Inizializzazione dell'applicazione

Un'applicazione ASP.NET viene inizializzata dal file [Global.asax.cs]. È strutturata come segue:

  • In [1], aggiungi un nuovo elemento al progetto
  • In [2], aggiungi la classe dell'applicazione globale, denominata [Global.asax] per impostazione predefinita [3]
  • in [4], il file [Global.asax] e la classe associata [Global.asax.cs]
  • In [5], visualizza il markup per [Global.asax]

<%@ Application Codebehind="Global.asax.cs" Inherits="pam_v1.Global" Language="C#" %>

La classe [pam_v1.Global] è definita nel file [Global.asax.cs]. Ai fini del nostro esempio, sarà definita come segue:


using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using Pam.Entites;
 
namespace pam_v1
{
  public class Global : System.Web.HttpApplication
  {
    // --- static application data ---
    public static Employe[] Employes;
    public static Cotisations Cotisations;
    public static Dictionary<int, Indemnites> Indemnites = new Dictionary<int, Indemnites>();
    public static string Msg = string.Empty;
    public static bool Erreur = false;
    public static string ConnectionString = null;
 
    // application startup
    public void Application_Start(object sender, EventArgs e)
    {
...
      try
      {
        // connection
        ConnectionString = ...
        using (SqlConnection connexion = new SqlConnection(ConnectionString))
        {
          connexion.Open();
          // retrieve the list of employees and place it in the static array [Employees]
...
          // contribution rates are retrieved from the static variable [Contributions]
...
          // retrieve allowances from the static dictionary [Allowances]
...
          // we succeeded
          Msg = "Base chargée...";
        }
      }
      catch (Exception ex)
      {
        // we note the error
        Msg = string.Format("L'erreur suivante s'est produite lors de l'accès à la base de données : {0}", ex);
        Erreur = true;
      }
    }
  }
}
  • Riga 20: il metodo [Application_Start] viene eseguito all'avvio dell'applicazione web. Viene eseguito una sola volta.
  • Righe 12–17: campi pubblici e statici della classe. Un campo statico è condiviso da tutte le istanze della classe. Pertanto, se vengono create più istanze della classe [Global], tutte condividono lo stesso campo statico [Employees], accessibile tramite il riferimento [Global.Employees], ovvero [ClassName].StaticField. [Global] è il nome di una classe. Si tratta quindi di un tipo di dati. Questo nome è arbitrario. La classe deriva sempre da [System.Web.HttpApplication].

Torniamo alla nostra classe [Global]. Ci si potrebbe chiedere se sia necessario dichiarare i suoi campi come statici. In effetti, sembra che in alcuni casi possano esserci più istanze della classe [Global], il che giustifica la scelta di rendere statici i campi che devono essere condivisi da tutte queste istanze.

Esiste un altro modo per condividere i dati nell'ambito "applicazione" tra le diverse pagine di un'applicazione web. Pertanto, l'array Employees potrebbe essere memorizzato nella procedura Application_Start come segue:


            Application.Add("employes",Employes);

Per impostazione predefinita, [Application] in qualsiasi applicazione ASP.NET è un riferimento a un'istanza della classe definita in [Global.asax.cs]. Application è un contenitore in grado di memorizzare oggetti di qualsiasi tipo. L'array Employees potrebbe quindi essere recuperato nel codice di qualsiasi pagina dell'applicazione web come segue:


            Employe[] employes=Application.Get["employes"] as Employe[];

Poiché il contenitore Application memorizza oggetti di qualsiasi tipo, recuperiamo un tipo Object che deve quindi essere convertito. Questo metodo è sempre utilizzabile, ma la condivisione dei dati tramite campi statici tipizzati dell'oggetto [Global] evita la conversione e consente al compilatore di eseguire controlli di tipo che assistono lo sviluppatore. Questo è il metodo che verrà utilizzato in questo caso.

I dati condivisi da tutti gli utenti sono i seguenti:

  • riga 12: l'array di oggetti [Employee] che memorizzerà l'elenco semplificato (SSN, LAST_NAME, FIRST_NAME) di tutti i dipendenti
  • riga 13: l'oggetto di tipo [Contributions] che memorizzerà le aliquote contributive
  • riga 14: il dizionario che memorizzerà le indennità collegate ai vari indici dei dipendenti. Sarà indicizzato in base all'indice del dipendente e i suoi valori saranno di tipo [Indennità]
  • riga 15: un messaggio che indica se l'inizializzazione è stata completata con successo o con un errore
  • riga 16: un valore booleano che indica se l'inizializzazione si è conclusa con un errore o meno
  • riga 17: la stringa di connessione al database.

Domanda: Completa il codice per la classe [Application_Start].


4.3. Eventi del modulo [Default.aspx]

4.3.1. La procedura [P age_Load]

Quando il modulo [Default.aspx] viene caricato, recupera i nomi dei dipendenti dall'array [Global.Employees] (riga 12) e li inserisce nell'elenco a discesa [1]:

  • L'elenco a discesa [1] è stato popolato
  • La casella di testo [5] indica che il database è stato letto correttamente

Se si sono verificati errori di inizializzazione all'avvio dell'applicazione, la casella di testo [5] lo segnala:

Image


Domanda: Scrivere la procedura [Page_Load] per la pagina web [Default.aspx] che, una volta eseguita all'avvio dell'applicazione, garantisca il comportamento sopra descritto.


4.3.2. Calcolo dello stipendio

Facendo clic sul pulsante [4] si attiva il gestore:


    protected void ButtonSalaire_Click(object sender, System.EventArgs e)

Questo gestore inizia verificando la validità dei dati inseriti in [2] e [3]. Se uno dei due è errato, l'errore viene segnalato come mostrato in precedenza. Una volta che i dati inseriti in [2] e [3] sono stati verificati e ritenuti validi, l'applicazione deve visualizzare ulteriori informazioni sull'utente selezionato in [1] e sul suo stipendio (vedere la schermata nella sezione 4.1).


Domanda: Scrivi il codice per la procedura [ButtonSalaire_Click].