Skip to content

4. A aplicação [SimuPaie] – versão 1 – ASP.NET

4.1. Introduction

Gostaríamos de desenvolver um aplicativo .NET que permita ao usuário realizar simulações de cálculo da folha de pagamento das babás da associação “Maison de la petite enfance” de um município.

O formulário ASP.NET para cálculo do salário terá a seguinte aparência:

Image

O aplicativo ASP.NET terá a seguinte arquitetura:

  • na primeira solicitação feita ao aplicativo, um objeto do tipo [Global], derivado do tipo [System.Web.HttpApplication], é instanciado. É ele que irá utilizar o arquivo de configuração [web.config] do aplicativo web e armazenar em cache determinados dados do banco de dados (operação 0 acima).
  • Na primeira solicitação (operação 1) feita à página [Default.aspx], que é a única página do aplicativo, o evento [Load] é processado. Nele, é tratado o preenchimento da lista suspensa de funcionários. Os dados necessários para a lista suspensa são solicitados ao objeto [Global], que os armazenou em cache. Essa é a operação 2 acima. A operação 4 envia a página assim inicializada ao usuário.
  • Ao solicitar o cálculo do salário (operação 1) na página [Default.aspx], o evento [Load] é processado novamente. Nada é feito, pois trata-se de uma solicitação do tipo POST, e o gerenciador [Pam_Load] foi programado para não realizar nenhuma ação nesse caso (uso do booleano IsPostBack). Após o evento [Load] ser processado, o evento [Click] no botão [Salaire] é processado em seguida. Este último precisa de dados que não foram armazenados em cache no [Global]. Por isso, o manipulador do evento os solicita ao banco de dados. Essa é a operação 3 acima. O cálculo do salário é então realizado e a operação 4 envia os resultados ao usuário.

4.2. O projeto Visual Web Developer 2008

  • em [1], cria-se um novo projeto
  • em [2], do tipo [Web / Application Web ASP.NET]
  • em [3], atribui-se um nome ao projeto
  • em [4], especifica-se o nome e, em [5], a localização. Será criada uma pasta [c:\temp\pam-aspnet\pam-v1-adonet] para o projeto.
  • em [5], o projeto do Visual Web Developer
  • em [6], as propriedades do projeto (clique com o botão direito do mouse no projeto / Propriedades / Aplicativo).
  • em [7], o nome do assembly que será gerado pelo projeto
  • em [8], o namespace padrão que desejamos para as classes do projeto. A classe [_Default], definida nos arquivos [Default.aspx.cs] e [Default.aspx.designer.cs], foi criada no namespace [pam_v1_adonet], derivado do nome do projeto. É possível alterar esse espaço de nomes diretamente no código desses dois arquivos:

[Default.aspx.cs]


using System;
....

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

[Default.aspx.designer.cs]


//------------------------------------------------------------------------------
// <gerado automaticamente>
//      Este código foi gerado por uma ferramenta.
//      Versão do runtime: 2.0.50727.3603
//
//      As alterações feitas neste arquivo podem causar um comportamento incorreto e serão perdidas se
//      o código for regenerado.
// </auto-generated>
//------------------------------------------------------------------------------

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

A marcação do arquivo [Default.aspx] também deve ser alterada:

  

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

O atributo Inherits acima refere-se à classe definida nos arquivos [Default.aspx.cs] e [Default.aspx.designer.cs]. Neles, utiliza-se o espaço de nomes pam_v1.

4.2.1. O formulário [Default.aspx]

A aparência do formulário [Default.aspx] é a seguinte:

Os componentes são os seguintes:

N.º
Tipo
Nome
Função
1
DropDownList
ComboBoxEmployes
Contém a lista dos nomes dos funcionários
2
TextBox
TextBoxHeures
Número de horas trabalhadas – número real
3
TextBox
TextBoxJours
Número de dias trabalhados – número inteiro
4
Botão
ButtonSalaire
Solicita o cálculo do salário
5
TextBox
TextBoxErreur
Mensagem informativa destinada ao usuário
ReadOnly=true, TextMode=MultiLine
6
Rótulo
LabelNom
Nome do funcionário selecionado em (1)
7
Rótulo
LabelPrénom
Nome do funcionário selecionado em (1)
8
Rótulo
LabelAdresse
Endereço
9
Rótulo
LabelVille
Cidade
10
Rótulo
LabelCP
Código postal
11
Rótulo
LabelIndice
Índice
12
Rótulo
LabelCSGRDS
Alíquota de contribuição CSGRDS
13
Rótulo
LabelCSGD
Taxa de contribuição CSGD
14
Rótulo
LabelRetraite
Taxa de contribuição para a aposentadoria
15
Rótulo
LabelSS
Taxa de contribuição para a Previdência Social
16
Rótulo
LabelSH
Salário-base por hora para o índice indicado em (11)
17
Rótulo
LabelEJ
Subsídio diário de manutenção para o índice indicado em (11)
18
Rótulo
LabelRJ
Subsídio diário de alimentação para o índice indicado em (11)
19
Rótulo
LabelCongés
Taxa de subsídio de férias remuneradas a ser aplicada ao salário-base
20
Rótulo
LabelSB
Valor do salário-base
21
Rótulo
LabelCS
Valor das contribuições sociais a serem pagas
22
Rótulo
LabelIE
Valor das pensões de pensão alimentícia para o filho sob guarda
23
Rótulo
LabelIR
Valor das ajudas de custo para alimentação da criança sob cuidados
24
Rótulo
LabelSN
Salário líquido a ser pago ao(à) funcionário(a)

O formulário contém, além disso, dois campos do tipo [Panel]:

PanelErreurs
contém o componente (5) TextBoxErreur
PanelSalaire
contém os componentes (6) a (24)

Um componente do tipo [Panel] pode ser tornado visível ou não por meio de programação, graças à sua propriedade booleana [Panel].Visible.

4.2.2. Verificação das entradas

Para calcular um salário, o usuário:

  • seleciona um funcionário em [1]
  • insere o número de horas trabalhadas em [2]. Esse número pode ser decimal, como 2,5 para 2 h 30 min.
  • insere o número de dias trabalhados em [3]. Esse número é inteiro.
  • solicita o salário com o botão [4]

Quando o usuário insere dados incorretos em [2] e [3], esses dados são verificados assim que o usuário muda de campo de entrada. Assim, a captura de tela abaixo foi obtida antes mesmo de o usuário clicar no botão [Salaire]:

São necessárias duas condições para que ocorra o comportamento descrito acima:

  • os componentes de validação devem ter a propriedade EnableClientScript definida como verdadeiro:
  
  • o navegador que exibe a página deve ser capaz de executar o código JavaScript incorporado na página HTML.

Se o navegador do cliente não verificar por conta própria a validade dos dados, estes só serão verificados quando o navegador enviar os dados preenchidos no formulário para o servidor. Nesse momento, será o código localizado no servidor, responsável por processar a solicitação do navegador, que verificará a validade dos dados. Vale lembrar que essa verificação deve ser sempre realizada, mesmo que a página exibida no navegador do cliente contenha código JavaScript que faça essa mesma verificação. De fato, o servidor não pode ter certeza de que a solicitação POST que lhe é enviada provém realmente dessa página e que, portanto, a verificação dos dados já foi feita.

A lista de validadores é a seguinte:

N.º
Tipo
Nome
Função
25
RequiredFieldValidator
RequiredFieldValidatorHeures
verifica se o campo [2] [TextBoxHeures] não está vazio
26
RangeValidator
RangeValidatorHeures
verifica se o campo [2] [TextBoxHeures] é um número real no intervalo [0, 200]
27
RequiredFieldValidator
RequiredFieldValidatorJours
verifica se o campo [3] [TextBoxJours] não está vazio
28
RangeValidator
RangeValidatorJours
verifica se o campo [3] [TextBoxJours] é um número inteiro no intervalo [0,31]

Tarefa a ser realizada: construir a página [Default.aspx]. Primeiro, colocaremos os dois contêineres [PanelErreurs] e [PanelSalaire] para, em seguida, poder colocar neles os componentes que devem conter.


4.2.3. As entidades do aplicativo

Após serem lidas, as linhas das tabelas [cotisations], [employes] e [indemnites] serão inseridas em objetos do tipo [Cotisations], [Employe] e [Indemnites], definidos da seguinte forma:


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

    // construtores
    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
  {
    // propriedades 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; }

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

    // propriedades 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;
    }

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

  }
}

4.2.4. Configuração do aplicativo

O arquivo [Web.config], que configura o aplicativo, será o seguinte:


<?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>
  • linha 9: define a string de conexão com o servidor SQL anterior
  • linhas 13-16: definem as consultas SQL utilizadas pelo aplicativo para evitar que sejam inseridas diretamente no código.
  • linha 13: a consulta SQL é configurada. A notação do parâmetro é específica do SQL Server.

Tarefa a ser realizada: inserir esses parâmetros no arquivo [Web.config]. A linha 9 deverá ser adaptada ao caminho real do banco de dados [dbpam.mdf].


4.2.5. Inicialização do aplicativo

A inicialização de uma aplicação ASP.NET é feita pelo arquivo [Global.asax.cs]. Este arquivo é estruturado da seguinte forma:

  • no [1], adiciona-se um novo elemento ao projeto
  • em [2], adiciona-se a classe de aplicativo global, que por padrão se chama [Global.asax] [3]
  • em [4], o arquivo [Global.asax] e a classe associada [Global.asax.cs]
  • em [5], exibe-se a marcação de [Global.asax]

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

A classe [pam_v1.Global] está definida no arquivo [Global.asax.cs]. Para o nosso problema, ela será definida da seguinte forma:


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
  {
    // --- dados estáticos do aplicativo ---
    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;

    // inicialização do aplicativo
    public void Application_Start(object sender, EventArgs e)
    {
...
      try
      {
        // conectividade
        ConnectionString = ...
        using (SqlConnection connexion = new SqlConnection(ConnectionString))
        {
          connexion.Open();
          // recupera-se a lista de funcionários, que é inserida na matriz estática [Employes]
...
          // recupera-se as alíquotas de contribuição na variável estática [Cotisations]
...
          // recupera-se os valores das indenizações no dicionário estático [Indemnites]
...
          // concluído com sucesso
          Msg = "Base chargée...";
        }
      }
      catch (Exception ex)
      {
        // registramos o erro
        Msg = string.Format("L'erreur suivante s'est produite lors de l'accès à la base de données : {0}", ex);
        Erreur = true;
      }
    }
  }
}
  • linhas 20: o método [Application_Start] é executado ao iniciar o aplicativo web. Ele é executado apenas uma vez.
  • linhas 12-17: campos públicos e estáticos da classe. Um campo estático é compartilhado por todas as instâncias da classe. Assim, se forem criadas várias instâncias da classe [Global], todas elas compartilham o mesmo campo estático [Employes], acessível por meio das referências [Global.Employes] e c.a.d. [NomDeClasse].ChampStatique. [Global] é o nome de uma classe. Portanto, trata-se de um tipo de dado. Esse nome é arbitrário. A classe sempre deriva de [System.Web.HttpApplication].

Voltemos à nossa classe [Global]. Podemos nos perguntar se é necessário declarar seus campos como estáticos. Na verdade, parece que, em alguns casos, podemos ter várias instâncias da classe [Global], o que justifica tornar estáticos os campos que precisam ser compartilhados por todas essas instâncias.

Existe outra maneira de compartilhar dados com escopo “aplicação” entre as diferentes páginas de uma aplicação web. Assim, a matriz Employes dos funcionários poderia ser armazenada no procedimento Application_Start da seguinte maneira:


            Application.Add("employes",Employes);

[Application] é, por padrão em qualquer aplicação, uma referência a uma instância da classe definida por ASP.NET. [Global.asax.cs] é um contêiner capaz de armazenar objetos de qualquer tipo. A matriz Employes poderia então ser recuperada no código de qualquer página do aplicativo web da seguinte maneira:


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

Como o contêiner Application armazena objetos de qualquer tipo, obtemos um tipo Object que precisa ser convertido posteriormente. Esse método ainda pode ser utilizado, mas compartilhar dados por meio de campos estáticos tipados do objeto [Global] evita as conversões de tipo e permite que o compilador realize verificações de tipo que auxiliam o desenvolvedor. É esse método que será utilizado aqui.

Os dados compartilhados por todos os usuários são os seguintes:

  • linha 12: a matriz de objetos do tipo [Employe] que armazenará a lista simplificada (SS, NOM, PRENOM) de todos os funcionários
  • linha 13: o objeto do tipo [Cotisations], que armazenará as alíquotas de contribuição
  • linha 14: o dicionário que armazenará os valores de indenização vinculados aos diferentes índices dos funcionários. Ele será indexado pelo índice do funcionário e seus valores serão do tipo [Indemnites]
  • linha 15: uma mensagem indicando como a inicialização foi concluída (com sucesso ou com erro)
  • linha 16: um valor booleano indicando se a inicialização terminou com erro ou não.
  • linha 17: a string de conexão com o banco de dados.

Pergunta: complete o código da classe [Application_Start].


4.3. Os eventos do formulário [Default.aspx]

4.3.1. O procedimento [Page_Load]

Quando o formulário [Default.aspx] é carregado, ele busca na tabela [Global.Employes] (linha 12) os nomes dos funcionários para inseri-los na lista suspensa [1]:

  • a lista suspensa [1] foi preenchida
  • o TextBox [5] indica que o banco de dados foi lido corretamente

Se ocorreram erros de inicialização ao iniciar o aplicativo, o TextBox [5] indicará isso:

Image


Pergunta: escreva o procedimento [Page_Load] da página da web [Default.aspx] que, quando executado na inicialização do aplicativo, garante o funcionamento descrito acima.


4.3.2. O cálculo do salário

Ao clicar no botão [4], o gerenciador é executado:


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

Esse gerenciador começa verificando a validade das entradas feitas em [2] e [3]. Se uma das duas estiver incorreta, o erro é sinalizado conforme mostrado anteriormente. Uma vez que as entradas em [2] e [3] tenham sido verificadas e consideradas válidas, o aplicativo deve exibir dados complementares sobre o usuário selecionado em [1], bem como seu salário (ver captura de tela no parágrafo 4.1).


Questão: escreva o código do procedimento [ButtonSalaire_Click].