Skip to content

4. L'application [SimuPaie] – version 1 – ASP.NET

4.1. Introduction

Nous souhaitons écrire une application .NET permettant à un utilisateur de faire des simulations de calcul de la paie des assistantes maternelles de l'association " Maison de la petite enfance " d'une commune.

Le formulaire ASP.NET de calcul du salaire aura l'allure suivante :

Image

L'application ASP.NET aura l'architecture suivante :

  • lors de la première requête faite à l'application, un objet de type [Global] dérivé du type [System.Web.HttpApplication] est instancié. C'est lui qui va exploiter le fichier de configuration [web.config] de l'application web et mettre en cache certaines données de la base de données (opération 0 ci-dessus).
  • lors de la première requête (opération 1) faite à la page [Default.aspx] qui est l'unique page de l'application, l'événement [Load] est traité. On y traite le remplissage du combo des employés. Les données nécessaires au combo sont demandées à l'objet [Global] qui les a mises en cache. C'est l'opération 2 ci-dessus. L'opération 4 envoie la page ainsi initialisée à l'utilisateur.
  • lors de la demande du calcul du salaire (opération 1) faite à la page [Default.aspx], l'événement [Load] est de nouveau traité. Rien n'est fait car il s'agit d'une demande de type POST, et le gestionnaire [Pam_Load] a été écrit pour ne rien faire dans ce cas (utilisation du booléen IsPostBack). L'événement [Load] traité, c'est l'événement [Click] sur le bouton [Salaire] qui l'est ensuite. Celui-ci a besoin de données qui n'ont pas été mises en cache dans [Global]. Aussi le gestionnaire de l'événement les demande-t-il à la base de données. C'est l'opération 3 ci-dessus. Le calcul du salaire est ensuite fait et l'opération 4 envoie les résultats à l'utilisateur.

4.2. Le projet Visual Web Developer 2008

  • en [1], on crée un nouveau projet
  • en [2], de type [Web / Application Web ASP.NET]
  • en [3], on donne un nom au projet
  • en [4], on précise son nom et en [5] son emplacement. Un dossier [c:\temp\pam-aspnet\pam-v1-adonet] va être créé pour le projet.
  • en [5], le projet Visual Web Developer
  • en [6], les propriétés du projet (clic droit sur projet / Propriétés / Application).
  • en [7], le nom de l'assembly qui sera produit par la génération du projet
  • en [8], l'espace de noms par défaut que nous souhaitons avoir pour les classes du projet. La classe [_Default] définie dans les fichiers [Default.aspx.cs] et [Default.aspx.designer.cs] a elle été créée dans l'espace de noms [pam_v1_adonet] dérivé du nom du projet. On peut changer cet espace de noms directement dans le code de ces deux fichiers :

[Default.aspx.cs]


using System;
....

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

[Default.aspx.designer.cs]


//------------------------------------------------------------------------------
// <auto-generated>
//     Ce code a été généré par un outil.
//     Version du runtime :2.0.50727.3603
//
//     Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si
//     le code est régénéré.
// </auto-generated>
//------------------------------------------------------------------------------

namespace pam_v1 {
    
    
    public partial class _Default {
        
.........

Le balisage du fichier [Default.aspx] doit être également modifié :

  

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

L'attribut Inherits ci-dessus désigne la classe définie dans les fichiers [Default.aspx.cs] et [Default.aspx.designer.cs]. On y utilise l'espace de noms pam_v1.

4.2.1. Le formulaire [Default.aspx]

L'aspect visuel du formulaire [Default.aspx] est le suivant :

Les composants sont les suivants :

Type
Nom
Rôle
1
DropDownList
ComboBoxEmployes
Contient la liste des noms des employés
2
TextBox
TextBoxHeures
Nombre d'heures travaillées – nombre réel
3
TextBox
TextBoxJours
Nombre de jours travaillés – nombre entier
4
Button
ButtonSalaire
Demande le calcul du salaire
5
TextBox
TextBoxErreur
Message d'information à destination de l'utilisateur
ReadOnly=true, TextMode=MultiLine
6
Label
LabelNom
Nom de l'employé sélectionné en (1)
7
Label
LabelPrénom
Prénom de l'employé sélectionné en (1)
8
Label
LabelAdresse
Adresse
9
Label
LabelVille
Ville
10
Label
LabelCP
Code postal
11
Label
LabelIndice
Indice
12
Label
LabelCSGRDS
Taux de cotisation CSGRDS
13
Label
LabelCSGD
Taux de cotisation CSGD
14
Label
LabelRetraite
Taux de cotisation Retraite
15
Label
LabelSS
Taux de cotisation Sécurité Sociale
16
Label
LabelSH
Salaire horaire de base pour l'indice indiqué en (11)
17
Label
LabelEJ
Indemnité journalière d'entretien pour l'indice indiqué en (11)
18
Label
LabelRJ
Indemnité journalière de repas pour l'indice indiqué en (11)
19
Label
LabelCongés
Taux d'indemnités de congés payés à appliquer au salaire de base
20
Label
LabelSB
Montant du salaire de base
21
Label
LabelCS
Montant des cotisations sociales à verser
22
Label
LabelIE
Montant des indemnités d'entretien de l'enfant gardé
23
Label
LabelIR
Montant des indemnités de repas de l'enfant gardé
24
Label
LabelSN
Salaire net à payer à l'employé(e)

Le formulaire contient en outre deux conteneurs de type [Panel] :

PanelErreurs
contient le composant (5) TextBoxErreur
PanelSalaire
contient les composants (6) à (24)

Un composant de type [Panel] peut être rendu visible ou non par programmation grâce à sa propriété booléenne [Panel].Visible.

4.2.2. La vérification des saisies

Pour calculer un salaire, l'utilisateur :

  • sélectionne un employé en [1]
  • saisit le nombre d'heures travaillées en [2]. Ce nombre peut être décimal, comme 2,5 pour 2 h 30 mn.
  • saisit le nombre de jours travaillés en [3]. Ce nombre est entier.
  • demande le salaire avec le bouton [4]

Lorsque l'utilisateur entre des données erronées dans [2] et [3], celles-ci sont vérifiées dès que l'utilisateur change de champ de saisie. Ainsi, la copie d'écran ci-dessous a été obtenue avant même que l'utilisateur ne clique sur le bouton [Salaire] :

Il faut deux conditions pour avoir le comportement précédent :

  • les composants de validation doivent avoir leur propriété EnableClientScript à vrai :
  
  • le navigateur affichant la page doit être capable d'exécuter le code Javascript embarqué dans une page HTML.

Si le navigateur client ne vérifie pas lui-même la validité des données, celles-ci ne seront vérifiées que lorsque le navigateur postera les saisies du formulaire au serveur. C'est alors le code situé sur ce dernier et qui traite la demande du navigateur qui vérifiera la validité des données. On rappelle que cette vérification doit être toujours faite même si la page affichée dans le navigateur client embarque du code javascript faisant cette même vérification. En effet, le serveur ne peut être assuré que la demande POST qui lui est faite vient réellement de cette page et que donc la vérification des données a été faite.

La liste des validateurs est la suivante :

Type
Nom
Rôle
25
RequiredFieldValidator
RequiredFieldValidatorHeures
vérifie que le champ [2] [TextBoxHeures] n'est pas vide
26
RangeValidator
RangeValidatorHeures
vérifie que le champ [2] [TextBoxHeures] est un nombre réel dans l'intervalle [0, 200]
27
RequiredFieldValidator
RequiredFieldValidatorJours
vérifie que le champ [3] [TextBoxJours] n'est pas vide
28
RangeValidator
RangeValidatorJours
vérifie que le champ [3] [TextBoxJours] est un nombre entier dans l'intervalle [0,31]

Travail à faire : construire la page [Default.aspx]. On placera d'abord les deux conteneurs [PanelErreurs] et [PanelSalaire] pour pouvoir ensuite y placer les composants qu'ils doivent contenir.


4.2.3. Les entités de l'application

Une fois lues, les lignes des tables [cotisations], [employes], [indemnites] seront mises dans des objets de type [Cotisations], [Employe] et [Indemnites] définis comme suit :


namespace Pam.Entites
{
  public class Cotisations
  {
    // propriétés automatiques
    public double CsgRds { get; set; }
    public double Csgd { get; set; }
    public double Secu { get; set; }
    public double Retraite { get; set; }

    // constructeurs
    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
  {
    // propriétés automatiques
    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; }

    // constructeurs
    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
  {

    // propriétés automatiques
    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; }

    // constructeurs
    public Indemnites()
    {

    }

    public Indemnites(int indice, double baseHeure, double entretienJour, double repasJour, double indemnitesCP)
    {
      Indice = indice;
      BaseHeure = baseHeure;
      EntretienJour = entretienJour;
      RepasJour = repasJour;
      IndemnitesCP = indemnitesCP;
    }

    // identité
    public override string ToString()
    {
      return string.Format("[{0}, {1}, {2}, {3}, {4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCP);
    }

  }
}

4.2.4. Configuration de l'application

Le fichier [Web.config] qui configure l'application sera le suivant :


<?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>
  • ligne 9 : définit la chaîne de connexion à la base SQL Server précédente
  • lignes 13-16 : définissent des requêtes SQL utilisées par l'application afin d'éviter de les mettre en dur dans le code.
  • ligne 13 : la requête SQL est paramétrée. La notation du paramètre est propre à SQL Server.

Travail à faire : introduire ces paramètres dans le fichier [Web.config]. La ligne 9 sera adaptée au chemin réel de la base de données [dbpam.mdf].


4.2.5. Initialisation de l'application

L'initialisation d'une application ASP.NET est faite par le fichier [Global.asax.cs]. Celui-ci est construit de la façon suivante :

  • en [1], on ajoute un nouvel élément au projet
  • en [2], on ajoute la classe d'application globale qui s'appelle par défaut [Global.asax] [3]
  • en [4], le fichier [Global.asax] et la classe associée [Global.asax.cs]
  • en [5], on fait afficher le balisage de [Global.asax]

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

La classe [pam_v1.Global] est définie dans le fichier [Global.asax.cs]. Pour notre problème, elle sera définie comme suit :


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
  {
    // --- données statiques de l'application ---
    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;

    // démarrage de l'application
    public void Application_Start(object sender, EventArgs e)
    {
...
      try
      {
        // connexion
        ConnectionString = ...
        using (SqlConnection connexion = new SqlConnection(ConnectionString))
        {
          connexion.Open();
          // on récupère la liste des employés qu'on met dans le tableau statique [Employes]
...
          // on récupère les taux de cotisation dans la variable statique [Cotisations]
...
          // on récupère les indemnités dans le dictionnaire statique [Indemnites]
...
          // on a réussi
          Msg = "Base chargée...";
        }
      }
      catch (Exception ex)
      {
        // on note l'erreur
        Msg = string.Format("L'erreur suivante s'est produite lors de l'accès à la base de données : {0}", ex);
        Erreur = true;
      }
    }
  }
}
  • lignes 20: la méthode [Application_Start] est exécutée au démarrage de l'application web. Elle n'est exécutée qu'une fois.
  • lignes 12-17 : champs publics et statiques de la classe. Un champ statique est partagé par toutes les instances de la classe. Ainsi, si plusieurs instances de la classe [Global] sont créées, elles partagent toutes le même champ statique [Employes], accessible via la référence [Global.Employes], c.a.d. [NomDeClasse].ChampStatique. [Global] est le nom d'une classe. C'est donc un type de donnée. Ce nom est arbitraire. La classe dérive toujours de [System.Web.HttpApplication].

Revenons à notre classe [Global]. On peut se demander s'il est nécessaire de déclarer statiques ses champs. En fait, il semble que dans certains cas, on puisse avoir plusieurs exemplaires de la classe [Global], ce qui justifie le fait de rendre statiques les champs qui ont à être partagés par toutes ces instances.

Il existe une autre façon de partager des données de portée " application " entre les différentes pages d'une application web. Ainsi le tableau Employes des employés pourrait être mémorisé dans la procédure Application_Start de la façon suivante :


            Application.Add("employes",Employes);

[Application] est par défaut dans toute application ASP.NET une référence sur une instance de la classe définie par [Global.asax.cs]. Application est un conteneur pouvant mémoriser des objets de tout type. Le tableau Employes pourrait ensuite être récupéré dans le code d'une page quelconque de l'application web de la façon suivante :


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

Parce que le conteneur Application mémorise des objets de tout type, on récupère un type Object qu'il faut ensuite transtyper. Cette méthode est toujours utilisable mais partager des données via des champs statiques typés de l'objet [Global] évite les transtypages et permet au compilateur de faire des vérifications de type qui aident le développeur. C'est la méthode qui sera utilisée ici.

Les données partagées par tous les utilisateurs sont les suivantes :

  • ligne 12 : le tableau d'objets de type [Employe] qui mémorisera la liste simplifiée (SS, NOM, PRENOM) de tous les employés
  • ligne 13 : l'objet de type [Cotisations] qui mémorisera les taux de cotisations
  • ligne 14 : le dictionnaire qui mémorisera les indemnités liés aux différents indices des employés. Il sera indexé par l'indice de l'employé et ses valeurs seront de type [Indemnites]
  • ligne 15 : un message indiquant comment s'est terminée l'initialisation (bien ou avec erreur)
  • ligne 16 : un booléen indiquant si l'initialisation s'est terminée par une erreur ou non.
  • ligne 17 : la chaîne de connexion à la base de données.

Question : compléter le code de la classe [Application_Start].


4.3. Les événements du formulaire [Default.aspx]

4.3.1. La procédure [Page_Load]

Lorsque le formulaire [Default.aspx] est chargé, il va chercher dans le tableau [Global.Employes] (ligne 12) les noms des employés pour les mettre dans la liste déroulante [1] :

  • la liste déroulante [1] a été remplie
  • le TextBox [5] indique que la base des données a été lue correctement

S'il s'est produit des erreurs d'initialisation lors du démarrage de l'application, le TextBox [5] l'indique :

Image


Question : écrire la procédure [Page_Load] de la page web [Default.aspx] qui, exécutée au démarrage de l'application, garantit le fonctionnement précédent.


4.3.2. Le calcul du salaire

Le clic sur le bouton [4] provoque l'exécution du gestionnaire :


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

Ce gestionnaire commence par vérifier la validité des saisies faites en [2] et [3]. Si l'une des deux est incorrecte, l'erreur est signalée comme il a été montré précédemment. Une fois les saisies [2] et [3] vérifiées et trouvées valides, l'application doit afficher des données complémentaires sur l'utilisateur sélectionné en [1] ainsi que son salaire (cf copie d'écran paragraphe 4.1).


Question : écrire le code de la procédure [ButtonSalaire_Click].