Skip to content

4. La aplicación [SimuPaie] – versión 1 – ASP.NET

4.1. Introduction

Queremos desarrollar una aplicación .NET que permita a un usuario realizar simulaciones del cálculo de la nómina de las cuidadoras infantiles de la asociación «Maison de la petite enfance» de un municipio.

El formulario de cálculo de salario ASP.NET tendrá el siguiente aspecto:

Image

La aplicación ASP.NET tendrá la siguiente arquitectura:

  • Al realizar la primera solicitud a la aplicación, se instancia un objeto de tipo [Global] derivado del tipo [System.Web.HttpApplication]. Este objeto es el que procesará el archivo de configuración [web.config] de la aplicación web y almacenará en caché ciertos datos de la base de datos (operación 0 anterior).
  • Al realizar la primera solicitud (operación 1) a la página [Default.aspx], que es la única página de la aplicación, se procesa el evento [Load]. En él se procesa el llenado del menú desplegable de empleados. Los datos necesarios para el menú desplegable se solicitan al objeto [Global], que los ha almacenado en caché. Esta es la operación 2 mencionada anteriormente. La operación 4 envía la página así inicializada al usuario.
  • Al solicitar el cálculo del salario (operación 1) en la página [Default.aspx], se procesa nuevamente el evento [Load]. No se realiza ninguna acción, ya que se trata de una solicitud de tipo POST, y el controlador [Pam_Load] fue diseñado para no hacer nada en este caso (uso del valor booleano IsPostBack). Una vez procesado el evento [Load], se procesa a continuación el evento [Click] en el botón [Salaire]. Este último necesita datos que no se han almacenado en caché en [Global]. Por lo tanto, el controlador del evento los solicita a la base de datos. Esta es la operación 3 mencionada anteriormente. A continuación, se realiza el cálculo del salario y la operación 4 envía los resultados al usuario.

4.2. El proyecto Visual Web Developer 2008

  • en [1], se crea un nuevo proyecto
  • en [2], del tipo [Web / Application Web ASP.NET]
  • en [3], se le da un nombre al proyecto
  • en [4], se especifica su nombre y en [5] su ubicación. Se creará una carpeta [c:\temp\pam-aspnet\pam-v1-adonet] para el proyecto.
  • en [5], el proyecto Visual Web Developer
  • en [6], las propiedades del proyecto (clic derecho en el proyecto / Propiedades / Aplicación).
  • en [7], el nombre del ensamblado que se generará al compilar el proyecto
  • en [8], el espacio de nombres predeterminado que queremos para las clases del proyecto. La clase [_Default], definida en los archivos [Default.aspx.cs] y [Default.aspx.designer.cs], se creó en el espacio de nombres [pam_v1_adonet], derivado del nombre del proyecto. Este espacio de nombres se puede modificar directamente en el código de estos dos archivos:

[Default.aspx.cs]


using System;
....

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

[Default.aspx.designer.cs]


//------------------------------------------------------------------------------
// <generado automáticamente>
//      Este código fue generado por una herramienta.
//      Versión del tiempo de ejecución: 2.0.50727.3603
//
//      Los cambios realizados en este archivo pueden provocar un comportamiento incorrecto y se perderán si
//      se vuelve a generar el código.
// </auto-generated>
//------------------------------------------------------------------------------

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

El marcado del archivo [Default.aspx] también debe modificarse:

  

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

El atributo Inherits mencionado anteriormente hace referencia a la clase definida en los archivos [Default.aspx.cs] y [Default.aspx.designer.cs]. En ellos se utiliza el espacio de nombres pam_v1.

4.2.1. El formulario [Default.aspx]

El aspecto visual del formulario [Default.aspx] es el siguiente:

Los componentes son los siguientes:

N.º
Tipo
Nombre
Función
1
DropDownList
ComboBoxEmployes
Contiene la lista de nombres de los empleados
2
TextBox
TextBoxHeures
Número de horas trabajadas – número real
3
TextBox
TextBoxJours
Número de días trabajados – número entero
4
Botón
ButtonSalaire
Solicita el cálculo del salario
5
TextBox
TextBoxErreur
Mensaje informativo para el usuario
ReadOnly=true, TextMode=MultiLine
6
Etiqueta
LabelNom
Nombre del empleado seleccionado en (1)
7
Etiqueta
LabelPrénom
Nombre del empleado seleccionado en (1)
8
Etiqueta
LabelAdresse
Dirección
9
Etiqueta
LabelVille
Ciudad
10
Etiqueta
LabelCP
Código postal
11
Etiqueta
LabelIndice
Índice
12
Etiqueta
LabelCSGRDS
Tasa de cotización CSGRDS
13
Etiqueta
LabelCSGD
Tasa de cotización CSGD
14
Etiqueta
LabelRetraite
Tasa de cotización para la jubilación
15
Etiqueta
LabelSS
Tasa de cotización de la Seguridad Social
16
Etiqueta
LabelSH
Salario base por hora para el índice indicado en (11)
17
Etiqueta
LabelEJ
Indemnización diaria de manutención para el índice indicado en (11)
18
Etiqueta
LabelRJ
Indemnización diaria por comida para el índice indicado en (11)
19
Etiqueta
LabelCongés
Tasa de indemnizaciones por vacaciones pagadas que se aplicará al salario base
20
Etiqueta
LabelSB
Monto del salario base
21
Etiqueta
LabelCS
Monto de las contribuciones sociales a pagar
22
Etiqueta
LabelIE
Importe de las indemnizaciones por manutención del niño a cargo
23
Etiqueta
LabelIR
Importe de las prestaciones por comida del niño en cuidado
24
Etiqueta
LabelSN
Salario neto a pagar al empleado(a)

El formulario contiene además dos contenedores de tipo [Panel]:

PanelErreurs
contiene el componente (5) TextBoxErreur
PanelSalaire
contiene los componentes del (6) al (24)

Un componente de tipo [Panel] puede hacerse visible o no mediante programación gracias a su propiedad booleana [Panel].Visible.

4.2.2. Verificación de los datos ingresados

Para calcular un salario, el usuario:

  • selecciona a un empleado en [1]
  • ingresa el número de horas trabajadas en [2]. Este número puede ser decimal, como 2,5 para 2 h 30 min.
  • ingresa el número de días trabajados en [3]. Este número es entero.
  • solicita el salario con el botón [4]

Cuando el usuario ingresa datos erróneos en [2] y [3], estos se verifican tan pronto como el usuario cambia de campo de ingreso. Así, la captura de pantalla que se muestra a continuación se obtuvo incluso antes de que el usuario hiciera clic en el botón [Salaire]:

Se deben cumplir dos condiciones para que se produzca el comportamiento anterior:

  • los componentes de validación deben tener su propiedad EnableClientScript establecida en verdadero:
  
  • el navegador que muestra la página debe poder ejecutar el código JavaScript incrustado en una página HTML.

Si el navegador del cliente no verifica por sí mismo la validez de los datos, estos solo se verificarán cuando el navegador envíe los datos ingresados en el formulario al servidor. En ese momento, será el código ubicado en el servidor, que procesa la solicitud del navegador, el que verifique la validez de los datos. Cabe recordar que esta verificación debe realizarse siempre, incluso si la página que se muestra en el navegador del cliente incluye código JavaScript que realiza esa misma verificación. De hecho, el servidor no puede estar seguro de que la solicitud POST que se le envía provenga realmente de esa página y de que, por lo tanto, se haya realizado la verificación de los datos.

La lista de validadores es la siguiente:

N.º
Tipo
Nombre
Función
25
RequiredFieldValidator
RequiredFieldValidatorHeures
verifica que el campo [2] [TextBoxHeures] no esté vacío
26
RangeValidator
RangeValidatorHeures
verifica que el campo [2] [TextBoxHeures] sea un número real dentro del intervalo [0, 200]
27
RequiredFieldValidator
RequiredFieldValidatorJours
verifica que el campo [3] [TextBoxJours] no esté vacío
28
RangeValidator
RangeValidatorJours
verifica que el campo [3] [TextBoxJours] sea un número entero dentro del intervalo [0,31]

Tarea a realizar: construir la página [Default.aspx]. Primero se colocarán los dos contenedores [PanelErreurs] y [PanelSalaire] para luego poder colocar en ellos los componentes que deben contener.


4.2.3. Las entidades de la aplicación

Una vez leídas, las filas de las tablas [cotisations], [employes] y [indemnites] se colocarán en objetos de tipo [Cotisations], [Employe] y [Indemnites], definidos de la siguiente manera:


namespace Pam.Entites
{
  public class Cotisations
  {
    // propiedades automáticas
    public double CsgRds { get; set; }
    public double Csgd { get; set; }
    public double Secu { get; set; }
    public double Retraite { get; set; }

    // constructores
    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
  {
    // propiedades automáticas
    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; }

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

    // propiedades automáticas
    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; }

    // fabricantes
    public Indemnites()
    {

    }

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

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

  }
}

4.2.4. Configuración de la aplicación

El archivo [Web.config] que configura la aplicación será el siguiente:


<?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>
  • línea 9: define la cadena de conexión a la base de datos SQL Server anterior
  • líneas 13-16: definen las consultas SQL que utiliza la aplicación para evitar que se incluyan de forma estática en el código.
  • línea 13: se configura la consulta SQL. La notación del parámetro es específica de SQL Server.

Tarea pendiente: introducir estos parámetros en el archivo [Web.config]. La línea 9 se adaptará a la ruta real de la base de datos [dbpam.mdf].


4.2.5. Inicialización de la aplicación

La inicialización de una aplicación ASP.NET se realiza mediante el archivo [Global.asax.cs]. Este se estructura de la siguiente manera:

  • en [1], se agrega un nuevo elemento al proyecto
  • en [2], se agrega la clase de aplicación global, que por defecto se llama [Global.asax] [3]
  • en [4], el archivo [Global.asax] y la clase asociada [Global.asax.cs]
  • en [5], se muestra el marcado de [Global.asax]

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

La clase [pam_v1.Global] se define en el archivo [Global.asax.cs]. Para nuestro problema, se definirá de la siguiente manera:


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
  {
    // --- datos estáticos de la aplicación ---
    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;

    // inicio de la aplicación
    public void Application_Start(object sender, EventArgs e)
    {
...
      try
      {
        // inicio de sesión
        ConnectionString = ...
        using (SqlConnection connexion = new SqlConnection(ConnectionString))
        {
          connexion.Open();
          // se recupera la lista de empleados y se almacena en el array estático [Employes]
...
          // se recuperan las tasas de cotización en la variable estática [Cotisations]
...
          //: se recuperan las indemnizaciones del diccionario estático [Indemnites]
...
          // se ha realizado con éxito
          Msg = "Base chargée...";
        }
      }
      catch (Exception ex)
      {
        // se registra el error
        Msg = string.Format("L'erreur suivante s'est produite lors de l'accès à la base de données : {0}", ex);
        Erreur = true;
      }
    }
  }
}
  • líneas 20: el método [Application_Start] se ejecuta al iniciar la aplicación web. Solo se ejecuta una vez.
  • líneas 12-17: campos públicos y estáticos de la clase. Un campo estático es compartido por todas las instancias de la clase. Por lo tanto, si se crean varias instancias de la clase [Global], todas comparten el mismo campo estático [Employes], al que se puede acceder a través de las referencias [Global.Employes] y c.a.d. [NomDeClasse].ChampStatique. [Global] es el nombre de una clase. Por lo tanto, es un tipo de datos. Este nombre es arbitrario. La clase siempre deriva de [System.Web.HttpApplication].

Volvamos a nuestra clase [Global]. Cabe preguntarse si es necesario declarar estáticos sus campos. De hecho, parece que en algunos casos se pueden tener varias instancias de la clase [Global], lo que justifica que los campos que deben ser compartidos por todas estas instancias se declaren estáticos.

Existe otra forma de compartir datos de ámbito «aplicación» entre las diferentes páginas de una aplicación web. Así, la tabla Employes de empleados podría almacenarse en el procedimiento Application_Start de la siguiente manera:


            Application.Add("employes",Employes);

[Application] es, por defecto en cualquier aplicación, una referencia a una instancia de la clase definida por ASP.NET. [Global.asax.cs] es un contenedor capaz de almacenar objetos de cualquier tipo. La matriz Employes podría recuperarse posteriormente en el código de cualquier página de la aplicación web de la siguiente manera:


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

Dado que el contenedor Application almacena objetos de cualquier tipo, se recupera un tipo Object que luego hay que convertir. Este método sigue siendo válido, pero compartir datos a través de campos estáticos tipados del objeto [Global] evita las conversiones de tipo y permite que el compilador realice verificaciones de tipo que ayudan al desarrollador. Este es el método que se utilizará aquí.

Los datos compartidos por todos los usuarios son los siguientes:

  • línea 12: la matriz de objetos de tipo [Employe] que almacenará la lista simplificada (SS, NOM, PRENOM) de todos los empleados
  • línea 13: el objeto de tipo [Cotisations], que almacenará las tasas de cotización
  • línea 14: el diccionario que almacenará las indemnizaciones relacionadas con los distintos índices de los empleados. Estará indexado por el índice del empleado y sus valores serán del tipo [Indemnites]
  • línea 15: un mensaje que indica cómo terminó la inicialización (correctamente o con error)
  • línea 16: un valor booleano que indica si la inicialización terminó con un error o no.
  • línea 17: la cadena de conexión a la base de datos.

Pregunta: completa el código de la clase [Application_Start].


4.3. Los eventos del formulario [Default.aspx]

4.3.1. El procedimiento [Page_Load]

Cuando se carga el formulario [Default.aspx], busca en la tabla [Global.Employes] (línea 12) los nombres de los empleados para incluirlos en la lista desplegable [1]:

  • La lista desplegable [1] se ha completado
  • el TextBox [5] indica que la base de datos se ha leído correctamente

Si se produjeron errores de inicialización al iniciar la aplicación, el código TextBox [5] lo indica:

Image


Pregunta: escriba el procedimiento [Page_Load] de la página web [Default.aspx], el cual, al ejecutarse al iniciar la aplicación, garantiza el funcionamiento anterior.


4.3.2. El cálculo del salario

Al hacer clic en el botón [4] se ejecuta el gestor:


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

Este gestor comienza por verificar la validez de los datos ingresados en [2] y [3]. Si alguno de los dos es incorrecto, se reporta el error tal como se mostró anteriormente. Una vez que se han verificado las entradas de [2] y [3] y se han considerado válidas, la aplicación debe mostrar datos adicionales sobre el usuario seleccionado en [1], así como su salario (véase la captura de pantalla del párrafo 4.1).


Pregunta: escriba el código del procedimiento [ButtonSalaire_Click].