Skip to content

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

4.1. Introducción

Queremos crear una aplicación .NET que permita al usuario simular los cálculos de nóminas para los cuidadores infantiles de la asociación «Maison de la petite enfance» de un municipio.

El formulario de cálculo de salarios de ASP.NET tendrá este aspecto:

Image

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

  • Cuando se realiza la primera solicitud a la aplicación, se crea una instancia de un objeto de tipo [Global] derivado del tipo [System.Web.HttpApplication]. Este objeto 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).
  • Cuando se realiza la primera solicitud (operación 1) a la página [Default.aspx], que es la única página de la aplicación, se gestiona el evento [Load]. Aquí es donde se rellena el cuadro combinado de empleados. Los datos necesarios para el cuadro combinado se recuperan del objeto [Global], que los ha almacenado en caché. Esta es la operación 2 anterior. La operación 4 envía la página inicializada al usuario.
  • Cuando se realiza la solicitud para calcular el salario (operación 1) a la página [Default.aspx], se procesa de nuevo el evento [Load]. No se hace nada porque se trata de una solicitud POST, y el controlador [Pam_Load] se escribió para no hacer nada en este caso (utilizando el booleano IsPostBack). Una vez gestionado el evento [Load], se gestiona a continuación el evento [Click] del botón [Salary]. Este evento requiere datos que no se han almacenado en caché en [Global]. Por lo tanto, el controlador de eventos los recupera de la base de datos. Esta es la operación 3 anterior. A continuación, se calcula el salario y la operación 4 envía los resultados al usuario.

4.2. Visual Web Developer 2008

  • En [1], cree un nuevo proyecto
  • en [2], de tipo [Web / Aplicación web ASP.NET]
  • En [3], asigne un nombre al proyecto
  • en [4], especifique 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 de 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 utilizar 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. Puedes cambiar este espacio de nombres 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>
//      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 {
 
.........

También hay que modificar el marcado del archivo [Default.aspx]:

  

<%@ 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
Lista desplegable
Combinado de empleados
Contiene la lista de nombres de empleados
2
Cuadro de texto
Cuadro de texto Horas
Número de horas trabajadas: número real
3
Cuadro de texto
TextBoxDays
Número de días trabajados – entero
4
Botón
BotónSalario
Calcular salario
5
Cuadro de texto
Cuadro de texto de error
Mensaje informativo para el usuario
ReadOnly=true, TextMode=MultiLine
6
Etiqueta
LabelName
Nombre del empleado seleccionado en (1)
7
Etiqueta
EtiquetaNombre
Nombre del empleado seleccionado en (1)
8
Etiqueta
EtiquetaDirección
Dirección
9
Etiqueta
EtiquetaCiudad
Ciudad
10
Etiqueta
Código postal
Código postal
11
Etiqueta
Índice de etiquetas
Índice
12
Etiqueta
EtiquetaCSGRDS
Tasa de contribución CSGRDS
13
Etiqueta
Etiqueta CSGD
Tasa de contribución CSGD
14
Etiqueta
Etiqueta de jubilación
Tasa de contribución para la jubilación
15
Etiqueta
EtiquetaSS
Tasa de cotización a la Seguridad Social
16
Etiqueta
EtiquetaSH
Salario base por hora para el índice indicado en (11)
17
Etiqueta
EtiquetaEJ
Subsidio de manutención diario para el índice indicado en (11)
18
Etiqueta
EtiquetaRJ
Subsidio diario para comidas correspondiente al índice indicado en (11)
19
Etiqueta
EtiquetaVacaciones
Porcentaje de la asignación por vacaciones pagadas que se aplicará al salario base
20
Etiqueta
EtiquetaSB
Importe del salario base
21
Etiqueta
EtiquetaCS
Importe de las cotizaciones a la Seguridad Social a pagar
22
Etiqueta
EtiquetaIE
Importe de la prestación por hijos a cargo
23
Etiqueta
EtiquetaIR
Importe de las ayudas para comidas del niño en acogida
24
Etiqueta
EtiquetaSN
Salario neto a pagar al empleado

El formulario también contiene dos contenedores [Panel]:

PanelErrors
contiene el componente TextBoxError (5)
PanelSalary
contiene los componentes del (6) al (24)

Un componente [Panel] se puede mostrar u ocultar mediante programación utilizando su propiedad booleana [Panel].Visible.

4.2.2. Validación de entradas

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 horas y 30 minutos.
  • introduce el número de días trabajados en [3]. Este número es un entero.
  • calcula el salario utilizando el botón [4]

Cuando el usuario introduce datos incorrectos en [2] y [3], estos se validan en cuanto el usuario cambia de campo de entrada. Por lo tanto, la captura de pantalla que se muestra a continuación se tomó incluso antes de que el usuario hiciera clic en el botón [Salario]:

Para que se produzca el comportamiento descrito anteriormente, se requieren dos condiciones:

  • los componentes de validación deben tener la propiedad EnableClientScript establecida en true:
  
  • 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 la validez de los datos por sí mismo, estos solo se verificarán cuando el navegador envíe los datos del formulario al servidor. Será entonces el código del servidor que procesa la solicitud del navegador el que verifique la validez de los datos. Tenga en cuenta que esta validación debe realizarse siempre, incluso si la página mostrada en el navegador del cliente contiene código JavaScript que realiza la misma validación. Esto se debe a que el servidor no puede estar seguro de que la solicitud POST que recibe provenga realmente de esa página y de que, por lo tanto, los datos hayan sido validados.

La lista de validadores es la siguiente:

N.º
Tipo
Nombre
Función
25
Validador de campos obligatorios
Validador de campo obligatorio Horas
comprueba que el campo [2] [TextBoxHeures] no esté vacío
26
Validador de rango
RangeValidatorHours
comprueba que el campo [2] [TextBoxHours] sea un número real comprendido entre [0, 200]
27
Validador de campo obligatorio
RequiredFieldValidatorDays
comprueba que el campo [3] [TextBoxDays] no esté vacío
28
RangeValidator
RangeValidatorDays
comprueba que el campo [3] [TextBoxDays] sea un número entero comprendido entre [0,31]

Tarea: Crea la página [Default.aspx]. En primer lugar, coloca los dos contenedores [PanelErrors] y [PanelSalary] para poder colocar después los componentes que deben contener.


4.2.3. Entidades de la aplicación

Una vez leídas, las filas de las tablas [contributions], [employees] y [allowances] se almacenarán en objetos de tipo [Contributions], [Employee] y [Allowances] definidos de la siguiente manera:


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. Configuración de la aplicación

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


<?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
  • líneas 13–16: definen las consultas SQL utilizadas por la aplicación para evitar codificarlas de forma rígida en el código.
  • línea 13: la consulta SQL está parametrizada. La notación de los parámetros es específica de SQL Server.

Tarea: Introduzca 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

Una aplicación ASP.NET se inicializa mediante el archivo [Global.asax.cs]. Su estructura es la siguiente:

  • En [1], añade un nuevo elemento al proyecto
  • En [2], añade la clase de aplicación global, que se denomina [Global.asax] de forma predeterminada [3]
  • en [4], el archivo [Global.asax] y la clase asociada [Global.asax.cs]
  • En [5], 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 nuestros fines, 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
  {
    // --- 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;
      }
    }
  }
}
  • Línea 20: El método [Application_Start] se ejecuta cuando se inicia 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 [Employees], accesible a través de la referencia [Global.Employees], es decir, [ClassName].StaticField. [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]. Uno podría preguntarse si es necesario declarar sus campos como estáticos. De hecho, parece que, en algunos casos, puede haber varias instancias de la clase [Global], lo que justifica que los campos que deben ser compartidos por todas estas instancias sean estáticos.

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


            Application.Add("employes",Employes);

Por defecto, [Application] en cualquier aplicación ASP.NET es una referencia a una instancia de la clase definida en [Global.asax.cs]. Application es un contenedor capaz de almacenar objetos de cualquier tipo. La matriz Employees podría entonces recuperarse 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, recuperamos un tipo Object que luego debe ser convertido. Este método siempre es utilizable, pero compartir datos a través de campos estáticos tipados del objeto [Global] evita la conversión 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 [Employee] que almacenará la lista simplificada (SSN, LAST_NAME, FIRST_NAME) de todos los empleados
  • línea 13: el objeto de tipo [Contributions] que almacenará las tasas de cotización
  • línea 14: el diccionario que almacenará las prestaciones vinculadas a los distintos índices de los empleados. Estará indexado por el índice del empleado y sus valores serán de tipo [Allowances]
  • línea 15: un mensaje que indica si la inicialización se completó correctamente o con un 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: Completa el código de la clase [Application_Start].


4.3. Eventos del formulario [Default.aspx]

4.3.1. El procedimiento [P age_Load]

Cuando se carga el formulario [Default.aspx], recupera los nombres de los empleados de la matriz [Global.Employees] (línea 12) y rellena la lista desplegable [1] con ellos:

  • La lista desplegable [1] se ha rellenado
  • El cuadro de texto [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 cuadro de texto [5] lo indica:

Image


Pregunta: Escribe el procedimiento [Page_Load] para la página web [Default.aspx], que, al ejecutarse al iniciar la aplicación, garantiza el comportamiento descrito anteriormente.


4.3.2. Cálculo del salario

Al hacer clic en el botón [4] se activa el controlador:


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

Este controlador comienza verificando la validez de los datos introducidos en [2] y [3]. Si alguno de ellos es incorrecto, se muestra el error tal y como se ha indicado anteriormente. Una vez que se han verificado los datos de [2] y [3] y se ha comprobado que son válidos, la aplicación debe mostrar información adicional sobre el usuario seleccionado en [1], así como su salario (véase la captura de pantalla de la sección 4.1).


Pregunta: Escribe el código para el procedimiento [ButtonSalaire_Click].