Skip to content

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

4.1. Introducción

Queremos escribir 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 nóminas « » ASP.NET tendrá el siguiente aspecto:

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]. Es este el que utilizará 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).
  • Durante la primera solicitud (operación 1) realizada a la página [Default.aspx], que es la única página de la aplicación, se procesa el evento [Load]. En él se gestiona el rellenado del cuadro combinado de empleados. Los datos necesarios para el cuadro combinado se solicitan al objeto [Global], que los ha almacenado en caché. Esta es la operación 2 anterior. 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 vuelve a procesar el evento [Load]. No se realiza ninguna acción, ya que se trata de una solicitud de tipo POST, y el gestor [Pam_Load] se ha programado 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 necesita datos que no se han almacenado en caché en [Global]. Por lo tanto, el gestor del evento los solicita a la base de datos. Esta es la operación 3 anterior. 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], de 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 con el botón derecho del ratón sobre 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 ha creado en el espacio de nombres [pam_v1_adonet] derivado del nombre del proyecto. Este espacio de nombres se puede cambiar 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]


//------------------------------------------------------------------------------
// <auto-generated>
//      Este código ha sido generado por una herramienta.
//      Version 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 anterior 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 de jubilación
15
Etiqueta
LabelSS
Tasa de cotización a 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 comidas para el índice indicado en (11)
19
Etiqueta
LabelCongés
Porcentaje de las indemnizaciones por vacaciones pagadas que se aplicará al salario base
20
Etiqueta
LabelSB
Importe del salario base
21
Etiqueta
LabelCS
Importe de las cotizaciones sociales a pagar
22
Etiqueta
LabelIE
Importe de las prestaciones por manutención del hijo a cargo
23
Etiqueta
LabelIR
Importe de las prestaciones por comidas del niño acogido
24
Etiqueta
LabelSN
Salario net a pagar al empleado

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

PanelErreurs
contiene el componente (5) TextBoxErreur
PanelSalaire
contiene los componentes (6) a (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 introducidos

Para calcular un salario, el usuario:

  • selecciona un empleado en [1]
  • introduce el número de horas trabajadas en [2]. Este número puede ser decimal, como 2,5 para 2 h 30 min.
  • introduce 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 introduce datos erróneos en [2] y [3], estos se comprueban en cuanto el usuario cambia de campo de entrada. 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]:

Para que se produzca el comportamiento anterior, deben cumplirse dos condiciones:

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

Si el navegador del cliente no comprueba por sí mismo la validez de los datos, estos solo se verificarán cuando el navegador envíe los datos introducidos en el formulario al servidor. Será entonces el código ubicado en este último, que procesa la solicitud del navegador, el que compruebe la validez de los datos. Cabe recordar que esta verificación debe realizarse siempre, incluso si la página mostrada en el navegador del cliente incluye el 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
comprueba que el campo [2] [TextBoxHeures] no esté vacío
26
RangeValidator
RangeValidatorHeures
comprueba que el campo [2] [TextBoxHeures] sea un número real dentro del intervalo [0, 200]
27
RequiredFieldValidator
RequiredFieldValidatorJours
comprueba que el campo [3] [TextBoxJours] no está vacío
28
RangeValidator
RangeValidatorJours
comprueba que el campo [3] [TextBoxJours] sea un número entero en el intervalo [0,31]

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


4.2.3. Las entidades de la aplicación

Una vez leídas, las líneas de las tablas [cotisations], [employes], [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; }

    // constructores
    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 utilizadas por 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 añade un nuevo elemento al proyecto
  • en [2], se añade la clase de aplicación global que se llama por defecto [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
      {
        // conexión
        ConnectionString = ...
        using (SqlConnection connexion = new SqlConnection(ConnectionString))
        {
          connexion.Open();
          // se recupera la lista de empleados y se coloca en la matriz estática [Employes]
...
          // se recuperan las tasas de cotización en la variable estática [Cotisations]
...
          // se recuperan las indemnizaciones en el diccionario estático [Indemnites]
...
          // se ha realizado correctamente
          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. Así, si se crean varias instancias de la clase [Global], todas comparten el mismo campo estático [Employes], accesible a través de la referencia [Global.Employes], 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 se hagan estáticos los campos que deben ser compartidos por todas estas instancias.

Existe otra forma de compartir datos de ámbito «aplicación» entre las diferentes páginas de una aplicación web. Así, la tabla 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 [Global.asax.cs]. La aplicación es un contenedor capaz de almacenar objetos de cualquier tipo. La tabla Empleados 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 todo tipo, se obtiene 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 al compilador realizar comprobaciones 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 cotisations
  • línea 14: el diccionario que almacenará las indemnizaciones vinculadas a los diferentes índices de los empleados. Estará indexado por el índice del empleado y sus valores serán de tipo [Indemnites]
  • línea 15: un mensaje que indica cómo ha finalizado la inicialización (correctamente o con error)
  • línea 16: un valor booleano que indica si la inicialización ha finalizado con un error o no.
  • línea 17: la cadena de conexión a la base de datos.

Pregunta: complete 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 rellenado
  • el TextBox [5] indica que la base de datos se ha leído correctamente

Si se han producido errores de inicialización al iniciar la aplicación, TextBox [5] lo indica:


Pregunta: escriba el procedimiento [Page_Load] de la página web [Default.aspx] que, 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 las entradas realizadas en [2] y [3]. Si alguna de las dos es incorrecta, se señala el error tal y como se ha mostrado anteriormente. Una vez verificadas y consideradas válidas las entradas de [2] y [3], la aplicación debe mostrar datos adicionales sobre el usuario seleccionado en [1], así como su salario (véase la captura de pantalla del apartado 4.1).


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