Skip to content

9. Estudo de caso

9.1. Introduction

Apresentaremos um estudo de caso já publicado em um artigo disponível no URL [http://tahe.developpez.com/dotnet/pam-aspnet/]. Nesse artigo, o estudo de caso é realizado com o ASP.NET clássico e o ORM NHibernate. Aqui, vamos realizá-lo com o ASP.NET, o MVC e o ORM do Entity Framework. Assim como no artigo existente, o estudo de caso é apresentado como um projeto universitário. Portanto, ele é destinado a estudantes. Para todas as questões, são feitas referências aos capítulos que acabamos de detalhar para indicar leituras úteis.

9.2. O problema a ser resolvido

Desejamos desenvolver um aplicativo web que permita a um usuário realizar simulações de cálculo da remuneração das babás da associação “Maison de la petite enfance” de um município. Nos interessaremos tanto pela organização do código DotNet do aplicativo quanto pelo código em si.

A aplicação será do tipo APU [Application à Page Unique] e utilizará exclusivamente chamadas Ajax para se comunicar com o servidor. Ela apresentará ao usuário as seguintes visualizações:

  • a visualização [VueSaisies], que apresenta o formulário de simulação

Image

  • a vista [VueSimulation], utilizada para exibir o resultado detalhado da simulação:

Image

  • a visualização [VueSimulations], que exibe a lista das simulações realizadas pelo cliente

Image

  • a visualização [VueSimulationsVides], que indica que o cliente não possui ou não possui mais simulações:

Image

  • a visualização [VueErreurs], que indica um ou mais erros (neste caso, o SGBD e o MySQL foram interrompidos):

Image

9.3. Arquitetura do aplicativo

A arquitetura da aplicação será a seguinte:

A camada [EF5] refere-se ao Entity Framework 5 ORM. O SGBD utilizado será o MySQL.

Construiremos essa aplicação inicialmente com uma camada [métier] simulada:

Isso nos permitirá concentrar-nos exclusivamente na camada [web]. A camada simulada [métier] respeitará a interface da camada real [métier]. Quando a camada [web] estiver operacional, construiremos então as camadas [métier], [DAO] e [EF5].

9.4. O banco de dados

Os dados estáticos necessários para gerar a folha de pagamento são armazenados em um banco de dados MySQL denominado [dbpam_ef5] (pam=Folha de Pagamento de Assistente Maternal). Esse banco de dados possui um administrador chamado root, sem senha. Ele contém três tabelas:

Image

Existe uma relação de chave estrangeira entre a coluna EMPLOYES (INDEMNITE_ID) e a coluna INDEMNITES (ID). A estrutura desse banco de dados é determinada por sua utilização com a tabela EF5. Voltaremos a esse assunto quando estivermos construindo as camadas inferiores da aplicação.

Tabela EMPLOYES: reúne informações sobre as diferentes babás

Estrutura:

ID
chave primária incrementada automaticamente pelo SGBD
SS
número de previdência social do funcionário — único
NOM
nome do funcionário
PRENOM
nome próprio
ADRESSE
seu endereço
VILLE
sua cidade
CP
seu CEP
VERSIONING
um número inteiro autoincrementado sempre que o registro for alterado
INDEMNITE_ID
chave estrangeira no campo [ID] da tabela [INDEMNITES]

Seu conteúdo poderia ser o seguinte:

Image

Tabela COTISATIONS: reúne as alíquotas das contribuições sociais deduzidas do salário

Estrutura:

ID
chave primária incrementada automaticamente pelo SGBD
CSGRDS
porcentagem: contribuição social generalizada + contribuição para o pagamento da dívida social
CSGD
porcentagem: contribuição social generalizada dedutível
SECU
porcentagem: previdência social
RETRAITE
porcentagem: aposentadoria complementar + seguro-desemprego
VERSIONING
um número inteiro autoincrementado sempre que o registro for alterado

Seu conteúdo poderia ser o seguinte:

Image

As alíquotas das contribuições sociais são independentes do empregado. A tabela anterior possui apenas uma linha.

Tabela INDEMNITES: reúne os diversos subsídios que dependem do índice do funcionário
ID
chave primária incrementada automaticamente pela SGBD
INDICE
índice salarial — único
BASE_HEURE
preço líquido em euros por hora de plantão
ENTRETIEN_JOUR
subsídio de manutenção em euros por dia de plantão
REPAS_JOUR
subsídio de alimentação em euros por dia de plantão
INDEMNITES_CP
Subsídio de férias remuneradas. Trata-se de uma porcentagem a ser aplicada ao salário-base.
VERSIONING
um número inteiro autoincrementado sempre que o registro for alterado

Seu conteúdo poderia ser o seguinte:

Image

9.5. Modo de cálculo do salário de uma babá

Apresentamos agora o método de cálculo do salário mensal de uma babá. Tomamos como exemplo o salário da Sra. Marie Jouveinal, que trabalhou 150 horas em 20 dias durante o mês a ser pago.

Os seguintes elementos são levados em consideração:
[TOTALHEURES]: total de horas trabalhadas no mês
[TOTALJOURS]: total de dias trabalhados no mês
[TOTALHEURES]=150
[TOTALJOURS]= 20
O salário-base da babá é calculado pela seguinte fórmula:
[SALAIREBASE]=([TOTALHEURES]*[BASEHEURE])*(1+[INDEMNITESCP]/100)
[SALAIREBASE]=(150*[2.1])*(1+0,15)= 362,25
É necessário deduzir uma série de contribuições sociais desse salário-base:
Contribuição social generalizada e contribuição para o pagamento da dívida social: [SALAIREBASE]*[CSGRDS/100]
Contribuição social generalizada dedutível: [SALAIREBASE]*[CSGD/100]
Seguridade social, pensão de viuvez, aposentadoria: [SALAIREBASE]*[SECU/100]
Aposentadoria complementar + AGPF + Seguro-desemprego: [SALAIREBASE]*[RETRAITE/100]
CSGRDS: 12,64
CSGD: 22,28
Seguridade Social: 34,02
Previdência: 28,55
Total das contribuições sociais:
[COTISATIONSSOCIALES] = [SALAIREBASE] *(CSGRDS + CSGD + SECU + RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
Além disso, a babá tem direito, por cada dia trabalhado, a um auxílio de subsistência e a um auxílio-alimentação. A esse título, ela recebe os seguintes auxílios:
[Indemnités]=[TOTALJOURS]*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
No final, o salário líquido a ser pago à babá é o seguinte:
[SALAIREBASE] – [COTISATIONSSOCIALES] + [INDEMNITÉS]
[salaire NET]=368,77

9.6. O projeto do Visual Studio da camada [web]

O projeto do Visual Web Developer da aplicação será o seguinte:

  • em [1], a estrutura geral do projeto [pam-web-01];
  • em [2], a pasta [Content] é a pasta onde se colocam os recursos estáticos do projeto:
    • [indicator.gif]: a imagem animada que indica a espera pelo término de uma solicitação Ajax,
    • [standard.jpg]: a imagem de fundo das diferentes visualizações,
    • [Site.css]: a folha de estilo do aplicativo;
  • em [3], o único controlador do aplicativo [PamController];
  • em [4], as classes necessárias para o aplicativo, mas que não podem ser classificadas como elementos do MVC:
    • [ApplicationModelBinder]: a classe que permite incluir os dados do escopo [Application] no modelo de ações,
    • [SessionModelBinder]: a classe que permite incluir os dados do escopo [Session] no modelo de ações,
    • [Static]: uma classe auxiliar com métodos estáticos;
  • em [5], os modelos do aplicativo, sejam eles modelos de ações ou de visualizações:
    • [ApplicationModel]: modelo que contém os dados do escopo [Application],
    • [SessionModel]: modelo que contém os dados do escopo [Session],
    • [Simulation]: classe que encapsula os elementos de uma simulação de cálculo de salário,
    • [IndexModel]: modelo da primeira visualização [Index] exibida pelo aplicativo;
  • em [6], os scripts JS necessários para a globalização do aplicativo;
  • em [7], os scripts JS da família JQuery necessários para a internacionalização, a validação do lado do cliente e a implementação de Ajax na aplicação;
  • em [8], [myScripts.js] é o arquivo que contém nossos próprios scripts JS;
  • em [9], as visualizações do aplicativo:
    • [Index]: a página inicial,
    • [Formulaire]: formulário para inserção de dados do funcionário e de suas horas e dias trabalhados,
    • [Simulation]: a visualização que apresenta uma simulação,
    • [Simulations]: a tela que apresenta a lista das simulações realizadas,
    • [Erreurs]: a tela que exibe a lista de possíveis erros,
    • [InitFailed]: a tela que exibe mensagens de erro caso a inicialização do aplicativo falhe;
  • em [10], a página principal do aplicativo [_Layout];
  • em [11], os arquivos [Web.config] e [Global.asax] utilizados para configurar o aplicativo.

9.7. Etapa 1 – implementação da camada simulada [métier]

A partir de agora, descreveremos as etapas a serem seguidas para realizar o estudo de caso. Quando for necessário, indicaremos o número do capítulo que deve ser relido, se for o caso, para realizar o trabalho solicitado. Alguns elementos do projeto são fornecidos em uma pasta [aspnetmvc-support.zip], disponível no site deste documento. Nela, você encontrará a pasta [étudedecas-support] com o seguinte conteúdo:

  

Além disso, o projeto retoma elementos apresentados nos capítulos anteriores. Basta, portanto, recuperá-los por meio de copiar/colar entre este PDF e o Visual Studio.

9.7.1. A solução do Visual Studio para o aplicativo completo

Primeiramente, vamos criar uma solução do Visual Studio na qual criaremos dois projetos:

  • um projeto para a camada simulada [métier];
  • um projeto para a camada web MVC.

Utilizaremos duas ferramentas:

  • o Visual Studio Express 2012 para desktop, que servirá para construir a camada [métier];
  • Visual Studio Express 2012 para a Web, que servirá para construir a camada [web].

Com o Visual Studio Express para desktop, criamos uma solução [pam-td]:

  • em [1], selecione um aplicativo C#;
  • em [2], selecione [Application console];
  • em [3], nomeie a solução;
  • em [4], crie uma pasta para essa solução;
  • em [5], nomeie a camada [métier];
  • em [6], a solução gerada.

9.7.2. A interface da camada [métier]

Em uma arquitetura em camadas, é recomendável que a comunicação entre as camadas ocorra por meio de interfaces:

Que interface a camada [métier] deve apresentar à camada [web]? Quais são as interações possíveis entre essas duas camadas? Lembremo-nos da interface web que será apresentada ao usuário:

  1. na exibição inicial do formulário, deve constar na camada [1] a lista de funcionários. Basta uma lista simplificada (Sobrenome, Nome, SS). O número SS é necessário para acessar as informações complementares sobre o funcionário selecionado (informações 6 a 11).
  2. As informações 12 a 15 correspondem às diferentes alíquotas de contribuição.
  3. As informações de 16 a 19 são os subsídios do funcionário
  4. as informações 20 a 24 são os componentes do salário calculados a partir dos dados inseridos pelo usuário nos campos 1 a 3.

A interface [IPamMetier] fornecida à camada [web] pela camada [métier] deve atender aos requisitos acima. Existem várias interfaces possíveis. Propomos a seguinte:


using Pam.Metier.Entites;
namespace Pam.Metier.Service
{
  public interface IPamMetier
  {
    // lista de todas as identidades dos funcionários 
    Employe[] GetAllIdentitesEmployes();

    // ------- cálculo do salário 
    FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
  }
}
  • linha 7: o método que permitirá o preenchimento do combo [1]
  • linha 10: o método que permitirá obter as informações de 6 a 24. Essas informações foram reunidas em um objeto do tipo [FeuilleSalaire], que descreveremos em breve.

Colocaremos essa interface em uma pasta [metier/service]:

9.7.3. As entidades da camada [métier]

A interface anterior utiliza duas classes, [Employe] e [FeuilleSalaire], que precisamos definir:

  • [Employe] é a imagem de uma linha da tabela [employes] do banco de dados;
  • [FeuilleSalaire] é a folha de pagamento de um funcionário.

As entidades serão colocadas em uma pasta [metier / entites] do projeto:

Na arquitetura final, a camada [métier] irá manipular entidades de imagem do banco de dados:

Image

Utilizaremos as seguintes classes para representar as linhas das três tabelas do banco de dados. Consulte o parágrafo 9.4 para conhecer o significado dos diferentes campos.

Classe [Employe]

Ela representa uma linha da tabela [employes]. Seu código é o seguinte:


using System;

namespace Pam.Metier.Entites
{

  public class Employe
  {
    public string SS { get; set; }
    public string Nom { get; set; }
    public string Prenom { get; set; }
    public string Adresse { get; set; }
    public string Ville { get; set; }
    public string CodePostal { get; set; }
    public Indemnites Indemnites { get; set; }

    // assinatura
    public override string ToString()
    {
      return string.Format("Employé[{0},{1},{2},{3},{4},{5}]", SS, Nom, Prenom, Adresse, Ville, CodePostal);
    }
  }
}

Classe [Indemnites]

Ela representa uma linha da tabela [indemnites]. Seu código é o seguinte:


using System;

namespace Pam.Metier.Entites
{
  public class Indemnites
  {
    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; }
    // assinatura
    public override string ToString()
    {
      return string.Format("Indemnités[{0},{1},{2},{3},{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
    }
  }
}

Classe [Cotisations]

Ela representa uma linha da tabela [cotisations]. Seu código é o seguinte:


using System;

namespace Pam.Metier.Entites
{

  public class Cotisations
  {
    public double CsgRds { get; set; }
    public double Csgd { get; set; }
    public double Secu { get; set; }
    public double Retraite { get; set; }
    // assinatura
    public override string ToString()
    {
      return string.Format("Cotisations[{0},{1},{2},{3}]", CsgRds, Csgd, Secu, Retraite);
    }
  }
}

Observe-se que as classes não incluem as colunas [ID] e [VERSIONING] das tabelas. Essas colunas, úteis quando se utiliza a ORM e a EF5, não são necessárias no contexto da camada simulada [métier].

A classe [FeuilleSalaire] encapsula as informações de 6 a 24 do formulário já apresentado:


namespace Pam.Metier.Entites
{
  public class FeuilleSalaire
  {

    // propriedades automáticas 
    public Employe Employe { get; set; }
    public Cotisations Cotisations { get; set; }
    public ElementsSalaire ElementsSalaire { get; set; }

    // ToString 
    public override string ToString()
    {
      return string.Format("[{0},{1},{2}]", Employe, Cotisations, ElementsSalaire);
    }
  }
}
  • linha 7: as informações de 6 a 11 sobre o funcionário cujo salário está sendo calculado e as informações de 16 a 19 sobre seus subsídios. Não se deve esquecer aqui que um objeto [Employe] encapsula um objeto [Indemnites] que representa seus subsídios;
  • linha 8: as informações 12 a 15;
  • linha 9: as informações 20 a 24;
  • linhas 12-14: o método [ToString].

A classe [ElementsSalaire] encapsula as informações 20 a 24 do formulário:


namespace Pam.Metier.Entites
{
  public class ElementsSalaire
  {
    // propriedades automáticas 
    public double SalaireBase { get; set; }
    public double CotisationsSociales { get; set; }
    public double IndemnitesEntretien { get; set; }
    public double IndemnitesRepas { get; set; }
    public double SalaireNet { get; set; }


    // ToString 
    public override string ToString()
    {
      return string.Format("[{0} : {1} : {2} : {3} : {4} ]", SalaireBase, CotisationsSociales, IndemnitesEntretien, IndemnitesRepas, SalaireNet);
    }
  }
}
  • linhas 6-10: os elementos do salário, conforme explicado nas regras de negócio descritas anteriormente;
  • linha 6: o salário-base do funcionário, em função do número de horas trabalhadas;
  • linha 7: as contribuições deduzidas desse salário-base;
  • linhas 8 e 9: os adicional a serem somados ao salário-base, em função do índice do funcionário e do número de dias trabalhados;
  • linha 10: o salário líquido a ser pago;
  • linhas 14-17: o método [ToString] da classe.

9.7.4. A classe [PamException]

Criamos um tipo de exceção específico para nossa aplicação. Trata-se do tipo [PamException] a seguir:


using System;

namespace Pam.Metier.Entites
{
  // classe de exceção
  public class PamException : Exception
  {

    // o código do erro 
    public int Code { get; set; }

    // construtores 
    public PamException()
    {
    }

    public PamException(int Code)
      : base()
    {
      this.Code = Code;
    }

    public PamException(string message, int Code)
      : base(message)
    {
      this.Code = Code;
    }

    public PamException(string message, Exception ex, int Code)
      : base(message, ex)
    {
      this.Code = Code;
    }
  }
}
  • linha 6: a classe deriva da classe [Exception];
  • linha 10: ela possui uma propriedade pública [Code], que é um código de erro;
  • utilizaremos em nosso aplicativo dois tipos de construtor:
    • o das linhas 23-27, que pode ser usado conforme mostrado abaixo:
throw new PamException("Problème d'accès aux données",5);
  • (continuação)
    • ou o das linhas 29-33, destinado a reportar uma exceção ocorrida, encapsulando-a em uma exceção do tipo [PamException]:
try{
....
}catch (IOException ex){
     // encapsulamos a exceção ex     
    throw new PamException("Problème d'accès aux données",ex,10);
}

Esse segundo método tem a vantagem de não perder as informações que a primeira exceção possa conter.

9.7.5. Implementação da camada [métier]

A interface [IPamMetier] será implementada pela seguinte classe [PamMetier]:


using System;
using Pam.Metier.Entites;
using System.Collections.Generic;

namespace Pam.Metier.Service
{
  public class PamMetier : IPamMetier
  {
    // lista de funcionários em cache
    public Employe[] Employes { get; set; }
    // funcionários indexados por seu número SS
    private IDictionary<string, Employe> dicEmployes = new Dictionary<string, Employe>();

    // lista de funcionários
    public Employe[] GetAllIdentitesEmployes()
    {
...
      // gerar a lista de funcionários
      return Employes;
    }

    // cálculo do salário
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
    {
...
  }
}
  • linha 7: a classe [PamMetier] implementa a interface [IPamMetier];
  • linha 10: a classe [PamMetier] mantém a lista de funcionários em cache;
  • linha 12: um dicionário que associa um funcionário ao seu número de previdência social;
  • linhas 15-20: o método que retorna a lista de funcionários;
  • linhas 23-26: o método que calcula o salário de um funcionário.

O método [GetAllIdentitesEmploye] é o seguinte:


// lista de funcionários
    public Employe[] GetAllIdentitesEmployes()
    {
      if (Employes == null)
      {
        // cria-se uma tabela com três funcionários
        Employes = new Employe[3];
        Employes[0] = new Employe()
        {
          SS = "254104940426058",
          Nom = "Jouveinal",
          Prenom = "Marie",
          Adresse = "5 rue des oiseaux",
          Ville = "St Corentin",
          CodePostal = "49203",
          Indemnites = new Indemnites() { Indice = 2, BaseHeure = 2.1, EntretienJour = 2.1, RepasJour = 3.1, IndemnitesCp = 15 }
        };
        dicEmployes.Add(Employes[0].SS, Employes[0]);
        Employes[1] = new Employe()
        {
          SS = "260124402111742",
          Nom = "Laverti",
          Prenom = "Justine",
          Adresse = "La brûlerie",
          Ville = "St Marcel",
          CodePostal = "49014",
          Indemnites = new Indemnites() { Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 }
        };
        dicEmployes.Add(Employes[1].SS, Employes[1]);
        // um funcionário fictício que não será incluído no dicionário
        // para simular um funcionário inexistente
        Employes[2] = new Employe()
        {
          SS = "XX",
          Nom = "X",
          Prenom = "X",
          Adresse = "X",
          Ville = "X",
          CodePostal = "X",
          Indemnites = new Indemnites() { Indice = 0, BaseHeure = 0, EntretienJour = 0, RepasJour = 0, IndemnitesCp = 0 }
        };
      }
      // retornamos a lista de funcionários
      return Employes;
    }
  • linha 4: verifica-se se a lista de funcionários já não foi gerada;
  • linha 7: se não for o caso, cria-se uma tabela com três funcionários;
  • linhas 8-17: o primeiro funcionário;
  • linha 18: ele é inserido no dicionário;
  • linhas 19-28: o segundo funcionário;
  • linha 29: ele é inserido no dicionário;
  • linhas 32-42: o terceiro funcionário. Este não é inserido no dicionário por um motivo que explicaremos a seguir.

O método [GetSalaire] será o seguinte:


    // cálculo do salário
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
    {
      // recuperamos o funcionário com o nº SS
      Employe e = dicEmployes.ContainsKey(ss) ? dicEmployes[ss] : null;
      // existe?
      if (e == null)
      {
        throw new PamException(string.Format("L'employé de n° SS [{0}] n'existe pas", ss), 10);
      }
      // retorna uma folha de pagamento fictícia
      return new FeuilleSalaire()
      {
        Employe = e,
        Cotisations = new Cotisations() { CsgRds = 3.49, Csgd = 6.15, Secu = 9.38, Retraite = 7.88 },
        ElementsSalaire = new ElementsSalaire() { CotisationsSociales = 100, IndemnitesEntretien = 100, IndemnitesRepas = 100, SalaireBase = 100, SalaireNet = 100 }
      };
}
  • linha 2: o método recebe o número SS do funcionário cujo salário se deseja calcular, bem como o número de horas trabalhadas e o número de dias trabalhados;
  • linha 5: busca-se o funcionário no dicionário. Lembramos que um deles não está lá;
  • linhas 7-10: se o funcionário não for encontrado, é lançada uma exceção [PamException];
  • linhas 12-17: é gerada uma folha de pagamento fictícia.

9.7.6. O teste de console da camada [métier]

O projeto da camada [métier] é atualmente o seguinte:

A classe [Program] acima testará os métodos da interface [IPamMetier]. Um exemplo básico poderia ser o seguinte:


using Pam.Metier.Entites;
using Pam.Metier.Service;
using System;

namespace Pam.Metier.Tests
{
  class Program
  {
    public static void Main()
    {
      // instanciação da camada [métier]
      IPamMetier pamMetier = new PamMetier();
      // lista de funcionários
      Employe[] employes = pamMetier.GetAllIdentitesEmployes();
      Console.WriteLine("Liste des employés--------------------");
      foreach (Employe e in employes)
      {
        Console.WriteLine(e);
      }
      // cálculos de folhas de pagamento 
      Console.WriteLine("Calculs de feuilles de salaire-----------------");
      Console.WriteLine(pamMetier.GetSalaire(employes[0].SS, 30, 5));
      Console.WriteLine(pamMetier.GetSalaire(employes[1].SS, 150, 20));
      try
      {
        Console.WriteLine(pamMetier.GetSalaire(employes[2].SS, 150, 20));
      }
      catch (PamException ex)
      {
        Console.WriteLine(string.Format("PamException : {0}", ex.Message));
      }
    }
  }
}
  • linha 12: instanciação da camada [métier];
  • linhas 14-19: teste do método [GetAllIdentitesEmploye] da interface [IPamMetier];
  • linhas 21-31: teste do método [GetSalaire] da interface [IPamMetier].

A execução deste programa de console produz os seguintes resultados:

Liste des employés--------------------
Employé[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203]
Employé[260124402111742,Laverti,Justine,La brûlerie,St Marcel,49014]
Employé[XX,X,X,X,X,X]
Calculs de feuilles de salaire-----------------
[Employé[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203],Co
tisations[3,49,6,15,9,38,7,88],[100 : 100 : 100 : 100 : 100]]
[Employé[260124402111742,Laverti,Justine,La brûlerie,St Marcel,49014],Cotisation
s[3,49,6,15,9,38,7,88],[100 : 100 : 100 : 100 : 100]]
PamException : L'employé de n° SS [XX] n'existe pas

Sugere-se ao leitor que relacione esses resultados com o código executado.

Para podermos utilizar este projeto no projeto web que vamos construir, transformamos ele em uma biblioteca de classes:

  • em [1], nas propriedades do arquivo [Program.cs];
  • em [2], indicamos que o arquivo não fará parte do assembly gerado;
  • no [3, 4], nas propriedades do projeto [pam-metier-simule], na opção [Application] [3], indica-se em [4] que a geração deve fornecer uma biblioteca de classes (na forma de um DLL).
  • em [5], solicita-se um assembly do tipo [Release]. O outro tipo é [Debug]. O assembly contém, então, informações que facilitam a depuração;
  • em [6], gera-se o projeto [pam-metier-simule];
  • no [7], exibem-se todos os arquivos da solução;
  • no [8], na pasta [bin / Release], o DLL do nosso projeto.

9.8. Etapa 2: configuração do aplicativo web

Na solução do Visual Studio anterior, vamos criar o projeto para a camada web MVC.

Com o Visual Studio Express para a Web, abrimos a solução [pam-td] criada anteriormente com o Visual Studio Express para desktop.

  • em [1], a solução [pam-td] foi carregada no Visual Studio Express para a Web;
  • em [2], a solução e o projeto para a camada simulada [métier] que acabamos de criar.

Nesta nova etapa, vamos criar a estrutura básica do aplicativo web.

  • em [1], adicionamos um novo projeto à solução [pam-td];
  • em [2], selecionamos um projeto ASP.NET MVC 4;
  • denominado [pam-web-01] [3];
  • em [4], seleciona-se o modelo básico ASP.NET MVC;
  • em [5], o projeto criado;
  • em [6], cria-se um novo projeto, o projeto inicial da solução, que será executado quando se executar [Ctrl-F5];
  • em [7], o nome do novo projeto aparece em negrito, indicando que ele é o projeto inicial da solução.

Agora, substituímos, usando o Explorador do Windows, a pasta [Content] do projeto pela pasta [étudedecas-support / web / Content]. Feito isso, é preciso incluir os novos arquivos no projeto [pam-web-01]. Procederemos da seguinte forma:

  • em [1], atualize a solução;
  • em [2], exibe-se todos os arquivos da solução;
  • em [3], aparece uma pasta [Images];
  • que é incluída no projeto em [4].

Na pasta [Scripts], adicione os scripts JQuery Globalization e [1] necessários para a validação no lado do cliente.

A página mestre [_Layout.cshtml] [2] terá o seguinte conteúdo:


<!DOCTYPE html>
<html>
<head>
  <title>@ViewBag.Title</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <link rel="stylesheet" href="~/Content/Site.css" />
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
  <script type="text/javascript" src="~/Scripts/myScripts.js"></script>
</head>
<body>
  <table>
    <tbody>
      <tr>
        <td>
          <h2>Simulateur de calcul de paie</h2>
        </td>
        <td style="width: 20px">
          <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
        </td>
        <td>
          <a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
          </a>
          <a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
          </a>
          <a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
          </a>
          <a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de simulation<br />
          </a>
          <a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la simulation<br />
          </a>
          <a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
          </a>
        </td>
    </tbody>
  </table>
  <hr />
  <div id="content">
    @RenderBody()
  </div>
</body>
</html>

Observação: linha 8, adapte a versão de jQuery à versão do seu Visual Studio.

  • linha 7: referência à folha de estilo do aplicativo;
  • linhas 8-10: referências aos scripts necessários para a validação do lado do cliente;
  • linhas 11-12: referências aos scripts necessários para a inserção de números reais franceses com vírgula;
  • linha 13: referência aos scripts necessários para o modo Ajax;
  • linha 14: os scripts específicos do aplicativo;
  • linha 24: a imagem de espera pelo término das chamadas Ajax;
  • linhas 26-39: seis links em JavaScript;
  • linha 43: a seção onde serão exibidas as diferentes visualizações do aplicativo;
  • linha 44: o corpo das diferentes visualizações do aplicativo.

Em seguida, vamos alterar a rota padrão do aplicativo:

O arquivo [RouteConfig] terá o seguinte conteúdo:


using System.Web.Mvc;
using System.Web.Routing;

namespace pam_web_01
{
  public class RouteConfig
  {
    public static void RegisterRoutes(RouteCollection routes)
    {
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}",
          defaults: new { controller = "Pam", action = "Index" }
      );
    }
  }
}
  • linha 14: os URL terão o formato [{controller}/{action}];
  • linha 15: na ausência de ação, será utilizada a ação [Index]. Na ausência de controlador, será utilizado o controlador [Pam].

A partir dessa configuração, conclui-se que URL [/] é equivalente a URL [/Pam/Index]. Como nosso aplicativo é do tipo APU, o URL e o [/] serão os únicos URL desse aplicativo.

Crie o controlador [Pam]:

  

Altere o controlador [PamController] da seguinte maneira:


using System.Web.Mvc;

namespace Pam.Web.Controllers
{
    public class PamController : Controller
    {
        [HttpGet]
        public ViewResult Index()
        {
            return View();
        }

    }
}
  • linha 3: colocamos o controlador no espaço de nomes [Pam.Web.Controllers];
  • linha 7: a ação [Index] processará apenas o comando HTTP GET;
  • linha 8: retornamos um tipo [ViewResult] em vez de um tipo [ActionResult].

Crie agora a visualização [Index.cshtml] exibida pela ação [Index] acima:

Altere [Index.cshtml] da seguinte maneira:


@{
  ViewBag.Title = "Pam";
}
<h2>Formulaire</h2>

Execute o aplicativo por meio de [Ctrl-F5]. Você deverá obter a seguinte página:

 

Tarefa: Explique o que aconteceu.


O aplicativo utiliza uma folha de estilo referenciada na página mestre [_Layout.cshtml]:


  <link rel="stylesheet" href="~/Content/Site.css" />

A folha de estilo [/Content/Site.css] define uma imagem de fundo para as páginas do aplicativo:


body {
  background-image: url("/Content/Images/standard.jpg");
}

9.9. Etapa 3: configuração do modelo APU

Queremos criar um aplicativo seguindo o modelo APU (Aplicativo de Página Única) descrito no parágrafo 7.5, bem como no parágrafo 7.6. A página única é aquela carregada pelo navegador ao iniciar o aplicativo:

  • a parte [1] acima é a parte fixa da página única. Vimos que ela é fornecida pela página mestre [_Layout.cshtml];
  • a parte [2] é a parte variável da página única. Ela se insere na região de id [content] da página mestre [_Layout.cshtml]:

<!DOCTYPE html>
<html>
<head>
  <title>@ViewBag.Title</title>
  ...
  <script type="text/javascript" src="~/Scripts/myScripts.js"></script>
</head>
<body>
  <table>
...
  </table>
  <hr />
  <div id="content">
    @RenderBody()
  </div>
</body>
</html>

Os diferentes fragmentos de página do aplicativo serão exibidos na região com o ID [content], na linha 13. Eles serão exibidos por meio de chamadas Ajax. Os scripts JavaScript que executam essas chamadas estão no arquivo [myScripts.js], referenciado na linha 6. Crie este arquivo, do qual precisaremos:

Agora, seguiremos o modelo APU descrito no parágrafo 7.6. Releia esse parágrafo caso tenha esquecido. Vamos agora implementar os diferentes fragmentos de página exibidos pelo aplicativo.

9.9.1. As ferramentas do desenvolvedor JavaScript

Lembramos que, com o navegador Chrome, você dispõe de um conjunto de ferramentas para depurar o JavaScript (HTML, CSS) das suas páginas. Essas ferramentas foram apresentadas parcialmente no parágrafo 7.2. No modelo APU, os navegadores mantêm em cache os scripts de JavaScript referenciados pela primeira página do aplicativo. Portanto, lembre-se de esvaziar esse cache ao modificar seus scripts; caso contrário, as alterações podem não ser aplicadas. Veja como fazer isso no Chrome:

- execute o [Ctrl-Maj-I] para exibir o ambiente de desenvolvimento

  • clique no ícone [1] no canto inferior direito da janela de desenvolvimento;
  • em seguida, marque a opção [2], que desativa o cache no modo de desenvolvimento.

9.9.2. Uso de uma visualização parcial para exibir o formulário

O formulário de entrada é um dos fragmentos exibidos pelo aplicativo. No momento, esse formulário é exibido pela visualização [Index.cshtml], que é uma visualização completa:


@{
  ViewBag.Title = "Pam";
}
<h2>Formulaire</h2>

Essa visualização é exibida pela ação [Index]:


    [HttpGet]
    public ViewResult Index()
    {
      return View();
}

Na linha 4 acima, é exibida, de fato, uma visualização [View] e não uma visualização parcial [PartialView]. Precisamos de uma visualização parcial para o formulário, que será um fragmento de página. Alteramos a visualização [Index.cshtml] da seguinte maneira:


@{
  ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")

Na linha 4, o formulário não faz mais parte da página [Index.cshtml]. Agora, ele está alojado em uma visualização parcial [Formulaire.cshtml]:

O código de [Formulaire.cshtml] é simplesmente o seguinte:


<h2>Formulaire</h2>

Faça essas alterações e verifique se você ainda obtém a seguinte tela ao iniciar o aplicativo:

 

9.9.3. A chamada Ajax [faireSimulation]

Estamos interessados no fragmento exibido quando o usuário clica no link [Faire la simulation]:

  • em [1], o usuário clica no link [Faire la simulation];
  • em [2], a simulação aparece abaixo do formulário.

Atualizamos da seguinte forma a visualização parcial [Formulaire.cshtml] que exibe o formulário:


<h2>Formulaire</h2>
<div id="simulation" />

Na linha 3, criamos uma região com o ID [simulation] para acomodar o fragmento da simulação.

Criamos a seguinte visão parcial [Simulation.cshtml]:

O conteúdo da visualização [Simulation.cshtml] é o seguinte:


<hr />
<h2>Simulation</h2>

Agora precisamos escrever o código JavaScript que gerencia o clique no link [Faire la simulation]. Seguiremos o procedimento descrito no parágrafo 7.6.5. Primeiro, vamos examinar o código HTML do link em [_Layout.cshtml]:


<a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
</a>

Vemos que um clique no link [Faire la simulation] iniciará a execução da função JS [faireSimulation]. Essa função será gravada no arquivo [myScripts.js], assim como as outras funções JS necessárias para a aplicação:


// variáveis globais
var loading;
var content;

function faireSimulation() {
  // faz-se uma chamada Ajax manualmente
...
}

function effacerSimulation() {
  // apagamos os dados inseridos no formulário
...
}

function enregistrerSimulation() {
  // faz-se uma chamada Ajax manualmente
  ...
}

function voirSimulations() {
  // faz-se uma chamada Ajax manualmente
  ...
}

function retourFormulaire() {
  // é feita uma chamada Ajax manualmente
...
}

function terminerSession() {
...
}

// ao carregar o documento
$(document).ready(function () {
  // recuperam-se as referências dos diferentes componentes da página
  loading = $("#loading");
  content = $("#content");
});
  • linhas 35-39: a função JQuery executada ao iniciar o aplicativo;
  • linhas 37-38: inicializam-se as variáveis globais das linhas 2 e 3.

Lembre-se de que os elementos de ID [loading] e [content] são definidos na página mestre [_Layout.cshtml] (linhas 14 e 21 abaixo):


<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
  <table>
    <tbody>
      <tr>
        <td>
          <h2>Simulateur de calcul de paie</h2>
        </td>
        <td style="width: 20px">
          <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
        </td>
...
        </td>
    </tbody>
  </table>
  <hr />
  <div id="content">
    @RenderBody()
  </div>
</body>
</html>

Tarefa: seguindo o procedimento descrito no parágrafo 7.6.5, escreva a função JS [faireSimulation]. Essa função emitirá uma chamada Ajax do tipo POST para a ação [/Pam/FaireSimulation]. Não haverá dados enviados por enquanto. Aação [/Pam/FaireSimulation] retornará a visão parcial [Simulation.cshtml] para a função JS [faireSimulation], que, por sua vez, colocará esse fluxo HTML na região com o ID [simulation] do formulário.


Teste o link [Faire la simulation] do seu aplicativo.

9.9.4. A chamada Ajax [enregistrerSimulation]

O link [Enregistrer la simulation] é definido da seguinte forma em [_Layout.cshtml]:


<a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la simulation<br />
</a>

Tarefa: seguindo o procedimento anterior, escreva a função JS [enregistrerSimulation]. Ela enviará uma chamada Ajax do tipo POST para a ação [/Pam/EnregistrerSimulation]. Não haverá dados enviados por enquanto. Aação [/Pam/EnregistrerSimulation] retornará a visão parcial [Simulations.cshtml] para a função JS [enregistrerSimulation], que, por sua vez, colocará esse fluxo HTML na região com o ID [content] da página mestre.


A visualização [Simulations.cshtml] é a seguinte:

Seu conteúdo é o seguinte:


<h2>Simulations</h2>

Aqui está um exemplo de execução:

9.9.5. A chamada Ajax [voirSimulations]

O link [Voir les simulations] está definido da seguinte forma em [_Layout.cshtml]:


<a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
</a>

Tarefa: seguindo o procedimento anterior, escreva a função JS [voirSimulations]. Ela enviará uma chamada Ajax do tipo POST para a ação [/Pam/VoirSimulations]. Não haverá dados enviados por enquanto. Aação [/Pam/VoirSimulations] retornará a visão parcial [Simulations.cshtml] para a função JS [voirSimulations], que, por sua vez, colocará esse fluxo HTML na região com o ID [content] da página mestre.


A visualização [Simulations.cshtml] é a mesma já utilizada na pergunta anterior.

Aqui está um exemplo de execução:

9.9.6. A chamada Ajax [retourFormulaire]

O link [Retour au formulaire de simulation] é definido da seguinte forma em [_Layout.cshtml]:


<a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de simulation<br />
</a>

Tarefa: seguindo o procedimento anterior, escreva a função JS [retourFormulaire]. Ela enviará uma chamada Ajax do tipo POST para a ação [/Pam/Formulaire]. Não haverá dados enviados por enquanto. Aação [/Pam/Formulaire] retornará a visão parcial [Formulaire.cshtml] para a função JS [retourFormulaire], que, por sua vez, colocará esse fluxo HTML na região com o ID [content] da página mestre.


A visualização [Formulaire .cshtml] já foi definida. Aqui está um exemplo de execução:

9.9.7. A chamada Ajax [terminerSession]

O link [Terminer la session] está definido da seguinte forma em [_Layout.cshtml]:


<a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
</a>

Exercício: seguindo o procedimento anterior, escreva a função JS [terminerSession]. Ela enviará uma chamada Ajax do tipo POST para a ação [/Pam/TerminerSession]. Não haverá dados enviados por enquanto. Aação [/Pam/TerminerSession] retornará a visão parcial [Formulaire.cshtml] para a função JS [terminerSession], que, por sua vez, colocará esse fluxo HTML na região com o ID [content] da página mestre.


Veja um exemplo de execução:

9.9.8. A função JS [effacerSimulation]

O link [Effacer la simulation] é definido da seguinte forma em [_Layout.cshtml]:


<a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
</a>

A função JS [effacerSimulation] tem como objetivo:

  • ocultar o fragmento [Simulation], caso ele exista;
  • restaurar os campos de preenchimento do formulário ao estado em que se encontravam no carregamento inicial do aplicativo (quando houver campos de preenchimento — por enquanto, não há nenhum).

Tarefa: escreva a função JS [effacerSimulation]. Não há nenhuma chamada Ajax aqui. O que ocorre é interno ao navegador e não envolve o servidor.


Aqui está um exemplo de execução:

9.9.9. Gerenciamento da navegação entre telas

Por enquanto, os links continuam sendo exibidos. Agora, vamos gerenciar sua exibição com uma função JavaScript. Primeiro, vamos relembrar o código dos seis links JavaScript em [_Layout.cshtml]:


<a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
</a>
<a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
</a>
<a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
</a>
<a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de simulation<br />
</a>
<a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la simulation<br />
</a>
<a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
</a>

Todos os links possuem um atributo [id] que nos permitirá gerenciá-los em JavaScript. Modificamos o método JS executado ao carregar a página da seguinte maneira:


// variáveis globais
var loading;
var content;
var lnkFaireSimulation;
var lnkEffacerSimulation
var lnkEnregistrerSimulation;
var lnkTerminerSession;
var lnkVoirSimulations;
var lnkRetourFormulaire;
var options;

...
// ao carregar o documento
$(document).ready(function () {
  // recuperam-se as referências dos diferentes componentes da página
  loading = $("#loading");
  content = $("#content");
  // os links do menu
  lnkFaireSimulation = $("#lnkFaireSimulation");
  lnkEffacerSimulation = $("#lnkEffacerSimulation");
  lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
  lnkVoirSimulations = $("#lnkVoirSimulations");
  lnkTerminerSession = $("#lnkTerminerSession");
  lnkRetourFormulaire = $("#lnkRetourFormulaire");
  // colocamos em uma tabela
  options = [lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
  // ocultamos alguns elementos da página
  loading.hide();
  // fixamos o menu
  setMenu([lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]);
});

  • linhas 19-24: recuperamos as referências dos seis links. Essas referências são definidas como variáveis globais nas linhas 4-9;
  • linha 26: o array [options] é inicializado com as seis referências. Esse array é definido como variável global na linha 10;
  • linha 28: oculta-se a imagem animada que indica a espera pelo término das chamadas Ajax;
  • linha 30: exibimos os links [lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]. Os demais serão ocultados.

A função JS [setMenu] é a seguinte:


function setMenu(show) {
  // exibimos os links da tabela [show]
...
}

Tarefa: escreva a função JS [setMenu].


Se T for uma matriz de links:

  • T.length é o número de links;
  • T[i] é o link nº i;
  • T[i].show() exibe o link nº i;
  • T[i].hide() oculta o link nº i.

Com essas novas funções JS, a página exibida ao iniciar é a seguinte:

 

Adapte as funções JS e [faireSimulation, effacerSimulation, enregistrerSimulation, voirSimulations, retourFormulaire, terminerSession] para obter as seguintes telas:

Agora que o modelo APU e os links de navegação estão configurados, podemos passar à criação das ações e visualizações do lado do servidor. Ao longo das etapas, você perceberá que alguns dos links Ajax que funcionavam até agora deixarão de funcionar, pois você modificará as visualizações parciais enviadas ao cliente. À medida que você for criando as diferentes ações e visualizações do lado do servidor, os links Ajax do lado do cliente voltarão a funcionar da maneira que você definiu.

9.10. Etapa 4: criação da ação do servidor [Index]

Atualmente, ao iniciar o aplicativo, temos a seguinte tela:

 

Em vez dessa tela, gostaríamos de ter a seguinte:

 

É a ação [Index] que deve gerar essa página. Vamos fazer algumas observações:

  • a página apresenta um formulário com três campos de preenchimento:
    • o funcionário cujo salário está sendo calculado,
    • o número de horas trabalhadas por ele,
    • o número de dias trabalhados;
  • o formulário é enviado pelo link [Faire la simulation];
  • a validade dos campos de entrada [Heures travaillées] e [Jours travaillés] deve ser verificada;
  • a lista de funcionários provém da camada [métier] que criamos anteriormente.

Lembramos o código atual da ação [Index]:


    [HttpGet]
    public ViewResult Index()
    {
      return View();
}

e o da visualização [Index.cshtml] que essa ação exibe:


@{
  ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")

e o da visualização parcial [Formulaire.cshtml]:


<h2>Formulaire</h2>

Serão feitas alterações nesses três locais.

9.10.1. O modelo do formulário

Voltemos à cadeia de processamento do URL [/Pam/Index]:

  • a solicitação HTTP do cliente chega como [1];
  • em [2], as informações contidas na solicitação serão transformadas no modelo de ação [3], que servirá como entrada para a ação [4];
  • em [4], a ação, a partir desse modelo, irá gerar uma resposta. Esta terá dois componentes: uma vista V [6] e o modelo M dessa vista [5];
  • a visualização V [6] utilizará seu modelo M [5] para gerar a resposta HTTP destinada ao cliente.

A ação que nos interessa é a ação [Index], que, no momento, é a seguinte:


    [HttpGet]
    public ViewResult Index()
    {
      return View();
}

A ação [Index] não passa nenhum modelo para a visualização [Index.cshtml]. Portanto, esta não poderá exibir a lista de funcionários. Essa lista pode ser solicitada à camada [métier]. Para isso, é necessário que o projeto [pam-web-01] tenha uma referência ao projeto [pam-metier-simule]. Vamos criar essa referência agora:

  • em [1], clique com o botão direito do mouse em [References] do projeto [pam-web-01] e, em seguida, em [Ajouter une référence];
  • em [2], selecione a opção [Solution] e, em seguida, o projeto [pam-metier-simule] em [3];
  • em [4], o projeto [pam-metier-simule] foi adicionado às referências do projeto [pam-web-01].

9.10.2. O modelo da aplicação

Apresentamos os conceitos importantes de modelo de aplicação e modelo de sessão no parágrafo 4.10, página 78. Vamos utilizá-los agora. Vale lembrar que no modelo se insere:

  • no modelo de aplicação, dados somente para leitura para todos os usuários. Esse modelo constitui uma memória compartilhada por todas as consultas de todos os usuários;
  • no modelo de sessão, dados em modo de leitura e gravação para um determinado usuário. Esse modelo constitui uma memória compartilhada por todas as solicitações desse usuário.

O que vamos colocar no modelo de aplicação? Voltemos à arquitetura dela:

A camada [web] possui uma referência à camada [métier]. Esta pode ser compartilhada por todos os usuários. Portanto, podemos incluí-la no modelo da aplicação. Além disso, vamos partir do pressuposto de que a lista de funcionários não muda. Assim, ela pode ser lida uma única vez e, em seguida, compartilhada entre todos os usuários. Propomos, então, o seguinte modelo de aplicação:

O código da classe [ApplicationModel] poderia ser o seguinte:


using Pam.Metier.Entites;
using Pam.Metier.Service;
namespace PamWeb.Models
{
  public class ApplicationModel
  {
    // --- dados do escopo da aplicação ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
  }
}

Para exibir uma lista suspensa em uma visualização, escreve-se algo como o seguinte:


        <!-- a lista suspensa -->
        <tr>
          <td>Liste déroulante</td>
          <td>@Html.DropDownListFor(m => m.DropDownListField,
           new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
          </td>
</tr>

O método [DropDownListFor] espera, como segundo parâmetro, um tipo SelectListItem[], que havia sido fornecido acima por um tipo [SelectList]. Precisamos construir essa matriz com a lista de funcionários. Como os funcionários não mudam, essa matriz também pode ser colocada no modelo do aplicativo. Atualizamos o modelo da seguinte forma:


using Pam.Metier.Entites;
using Pam.Metier.Service;
using System.Web.Mvc;

namespace Pam.Web.Models
{
  public class ApplicationModel
  {
    // --- dados do escopo do aplicativo ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
  }
}

Em que momento esse modelo deve ser construído? Mostramos isso no parágrafo 4.10. É durante a execução do método [Application_Start] do arquivo [Global.asax]:

O método [Application_Start] é, por enquanto, o seguinte:


using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace pam_web_01
{
  public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
      AreaRegistration.RegisterAllAreas();

      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
  }
}

Estamos atualizando-o da seguinte forma:


using Pam.Metier.Entites;
using Pam.Metier.Service;
using PamWeb.Infrastructure;
using PamWeb.Models;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace pam_web_01
{
  public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
      // ----------Gerado automaticamente
      AreaRegistration.RegisterAllAreas();
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // -------------------------------------------------------------------
      // ---------- configuração específica
      // -------------------------------------------------------------------
      // dados de escopo da aplicação
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
        // instanciação da camada [métier]
        application.PamMetier = ...
        // tabela de funcionários 
        application.Employes = ...
        // elementos da lista suspensa de funcionários
        application.EmployesItems = ...
      // model binder para [ApplicationModel]
      ...
    }
  }
}

Tarefa: completar o código do método [Application_Start]. Tudo o que você precisa está no parágrafo 4.10. Reserve um tempo para reler esse parágrafo, que é longo, mas importante.


A linha 33, na verdade, contém várias linhas. Para criar um objeto do tipo [SelectListItem], você pode usar o seguinte método:


new SelectListItem() { Text = unTexte, Value = uneValeur };

Esse [SelectListItem] servirá para gerar a seguinte tag HTML <option>:

<option value='uneValeur'>unTexte</option>

da lista suspensa. Deve-se garantir que:

  • unTexte seja o nome seguido do sobrenome do funcionário;
  • uneValeur seja o número SS do funcionário.

Na linha 35, acima, você precisará da classe [ApplicationModelBinder] descrita no parágrafo 4.10, página 82:

9.10.3. O código da ação [Index]

Agora que definimos um modelo para o aplicativo, podemos adaptar o código da ação [Index] da seguinte maneira:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View();
}
  • linha 4: o modelo do aplicativo agora é um parâmetro da ação [Index]. Explicamos no parágrafo 4.10 como esse parâmetro era inicializado pelo framework.

9.10.4. O modelo da visualização [Index.cshtml]

Agora, a ação [Index] tem acesso aos funcionários registrados no modelo da aplicação. Ela precisa agora passar esses dados para a vista [Index.cshtml], que será exibida. Seria possível passar um tipo [ApplicationModel] como modelo para a vista [Index.cshtml], mas veremos rapidamente que essa vista precisa de outras informações que não estão em [ApplicationModel]. Vamos utilizar o modelo de visualização [IndexModel] a seguir:


namespace Pam.Web.Models
{
  public class IndexModel
  {
    // dados de escopo do aplicativo
    public ApplicationModel Application { get; set; }
  }
}
  • linha 6: [IndexModel] carrega o modelo do aplicativo.

A ação [Index] passa a ser a seguinte:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View(new IndexModel() { Application = application });
}
  • na linha 4, a visualização padrão [Index.cshtml] é exibida com um modelo do tipo [IndexModel], inicializado com os dados do modelo da aplicação.

Sabemos que a visualização [Index.cshtml] deve exibir um formulário:

Image

Voltemos à cadeia de processamento de uma solicitação:

Para a solicitação [GET /Pam/Index]:

  • a ação é [Index];
  • o modelo dessa ação é [ApplicationModel];
  • a visualização é [Index.cshtml];
  • o modelo dessa visualização é [IndexModel].

Quando o formulário for enviado, teremos uma cadeia de processamento semelhante:

  • a ação é a que processa o POST;
  • seu modelo reúne os valores enviados, neste caso:
    • o nº SS do funcionário selecionado;
    • o número de horas trabalhadas;
    • o número de dias trabalhados;

Seria possível criar um modelo de ação que reunisse esses três valores. Também é comum reutilizar o modelo que serviu para exibir o formulário. É isso que faremos aqui. A classe [IndexModel] é alterada da seguinte forma:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Pam.Web.Models
{
  [Bind(Exclude = "Application")]
  public class IndexModel
  {
    // dados de escopo da aplicação
    public ApplicationModel Application { get; set; }

    // valores lançados
    [Display(Name = "Employé")]
    public string SS { get; set; }
    [Display(Name = "Heures travaillées")]
    [UIHint("Decimal")]
    public double HeuresTravaillées { get; set; }
    [Display(Name = "Jours travaillés")]
    public double JoursTravaillés { get; set; }
  }
}
  • linhas 13, 16, 18: os três valores lançados. Observe-se que [joursTravaillés] foi declarado como do tipo [double], embora, na realidade, seja esperado um inteiro. O tipo [double] foi introduzido para facilitar a validação desse campo no lado do cliente, uma vez que a validação do tipo [int] apresentou problemas;
  • linhas 12, 14, 17: descrições para os métodos [Html.LabelFor] da visualização associada ao modelo;
  • linha 15: uma anotação para que o campo [HeuresTravaillées] seja exibido com duas casas decimais;
  • linha 5: indica-se que a propriedade denominada [Application] não faz parte dos valores lançados.

9.10.5. As visualizações [Index.cshtml] e [Formulaire.cshtml]

A visualização [Index.cshtml] é exibida pela ação [Index] a seguir:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View(new IndexModel() { Application = application });
}

Curiosamente, a visualização [Index.cshtml] permanece inalterada:


@{
  ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")
  • a visualização não declara nenhum modelo;
  • linha 4: ela incorpora a visão parcial [Formulaire.cshtml], mais uma vez sem passar para o modelo correspondente. Durante os testes, constatou-se que o modelo [IndexModel], passado para a visão [Index.cshtml], propagava-se implicitamente para a visão parcial [Formulaire.cshtml]. Essa última visão poderia agora ter a seguinte forma:

@model Pam.Web.Models.IndexModel

@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
  <table>
    <thead>
      <tr>
...
      </tr>
    </thead>
    <tbody>
      <tr>
...
      </tr>
      <tr>
...
      </tr>
    </tbody>
  </table>
}
<div id="simulation" />
  • linha 1: a visualização recebe um modelo do tipo [IndexModel];
  • linha 3: o formulário;
  • linhas 6-10: os cabeçalhos da tabela de entradas;
  • linhas 12-14: a linha de entradas;
  • linhas 15-17: eventuais mensagens de erro.

Tarefa: completar o código da visualização [Formulaire.cshtml]. Utilizar-se-ão os métodos [DropDownListFor, EditorFor, LabelFor, ValidationMessageFor] descritos no parágrafo 5.7.


9.10.6. Teste da ação [Index]

Escrevemos todos os elementos da cadeia de processamento da ação URL [/Pam/Index]:

Estamos testando o aplicativo com o [Ctrl-F5]:

Você deve verificar se sua lista suspensa foi preenchida corretamente com a lista de funcionários que definimos na camada simulada [métier].

9.11. Etapa 5: implementação da validação das entradas

9.11.1. O problema

Embora não tenhamos feito nada para isso, as validações do lado do cliente já estão em funcionamento:

 
 

A validação do lado do cliente está ativa por padrão devido à linha 3 abaixo, no arquivo [Web.config] do aplicativo.


  <appSettings>
    ...
    <add key="ClientValidationEnabled" value="true" />
</appSettings>

No entanto, como no arquivo [IndexModel] o campo [JoursTravaillés] foi declarado como sendo do tipo [double]:


    public double JoursTravaillés { get; set; }

é possível inserir um número real nesse campo:

 

Além disso, é possível inserir valores aleatórios nos dois campos:

 

O modelo [IndexModel] do formulário é atualmente o seguinte:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Pam.Web.Models
{
  [Bind(Exclude = "Application")]
  public class IndexModel
  {
    // dados de escopo da aplicação
    public ApplicationModel Application { get; set; }

    // valores lançados
    [Display(Name = "Employé")]
    public string SS { get; set; }
    [Display(Name = "Heures travaillées")]
    [UIHint("Decimal")]
    public double HeuresTravaillées { get; set; }
    [Display(Name = "Jours travaillés")]
    public double JoursTravaillés { get; set; }
  }
}

Tarefa: aprimore esse modelo para:

  • exibir mensagens de erro personalizadas;
  • aceitar apenas valores reais no intervalo [0,400] para o campo [HeuresTravaillées];
  • aceitar apenas valores inteiros no intervalo [0,31] para o campo [JoursTravaillées];

Pode-se utilizar o exemplo do parágrafo 7.6.2. Para verificar se o número de dias trabalhados é um número inteiro, pode-se usar uma expressão regular (ver exemplos do parágrafo 5.9.1).

Aqui estão alguns exemplos do que é esperado:

 
 
 

9.11.2. Entrada de números reais no formato francês

Na versão atual do aplicativo, o número de horas trabalhadas deve ser um número decimal no formato anglo-saxão (com ponto decimal). O formato francês com vírgula não é aceito:

 

Esse problema foi identificado e resolvido no parágrafo 6.1.


Tarefa: seguindo a abordagem do parágrafo mencionado acima, faça as alterações necessárias para que seja possível inserir números reais no formato decimal francês. Teste seu aplicativo.


Agora, a tela anterior fica assim:

 

Atualmente, é possível enviar valores inválidos, conforme mostra a sequência a seguir:

 

A presença da simulação em [1] e a mudança no menu em [2] mostram que o clique no link [Faire la simulation] enviou o formulário, mesmo que os valores inseridos fossem inválidos. Esse problema foi identificado e tratado no parágrafo 7.6.5.


Tarefa: seguindo o procedimento descrito no parágrafo acima mencionado, certifique-se de que o POST do link [Faire la simulation] não seja executado se os valores inseridos forem inválidos. Lembre-se de limpar o cache do navegador antes de testar suas alterações.


Lembramos que a visualização parcial [Formulaire.cshtml] gera um formulário HTML com o ID [formulaire] (linha 1 abaixo):


@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}

Isso pode ser verificado exibindo o código-fonte do formulário no navegador:


<div id="content">
    
    <form action="/Pam/FaireSimulation" id="formulaire" method="post">
    ...
    </form>
    <div id="simulation" />
</div>

9.12. Etapa 6: realizar uma simulação

9.12.1. O problema

Quando fazemos uma simulação, queremos obter o seguinte resultado:

A visão parcial [Simulation.cshtml] agora exibe a folha de pagamento de um funcionário.

9.12.2. Criação da visualização [Simulation.cshtml]

A visão [Simulation.cshtml] passa a ter a seguinte forma:


@model Pam.Metier.Entites.FeuilleSalaire
<hr />
<p><span class="info">Informations Employé</span></p>
<table>
  <tbody>
    <tr>
      <td><span class="libellé">Nom</span>
      </td>
      <td><span class="libellé">Prénom</span>
      </td>
      <td><span class="libellé">Adresse</span>
      </td>
    </tr>
    <tr>
      <td>
        <span class="valeur">@Model.Employe.Nom</span>
      </td>
...
    </tr>
    <tr>
      <td><span class="libellé">Ville</span>
      </td>
      <td><span class="libellé">Code Postal</span>
      </td>
      <td><span class="libellé">Indice</span>
      </td>
    </tr>
    <tr>
...
    </tr>
  </tbody>
</table>
<br />
<p><span class="info">Informations Cotisations</span></p>
<table>
...
  </tbody>
</table>
<br />
<p><span class="info">Informations Indemnités</span></p>
<table>
...
</table>
<br />
<p><span class="info">Informations Salaire</span></p>
<table>
...
</table>
<br />
<table>
...
</table>
  • linha 1: a visualização [Simulation.cshtml] tem como modelo o tipo [FeuilleSalaire] definido no parágrafo 9.7.3;
  • a visualização utiliza as classes [libellé, info, valeur] definidas na folha de estilo do aplicativo [Content / Site.css]:

.libellé {
  background-color: azure;
  margin: 5px;
  padding: 5px;
}

.info {
  background-color: antiquewhite;
  margin: 5px;
  padding: 5px;
}

.valeur {
  background-color: beige;
  padding: 5px;
  margin: 5px;
}

Além disso, ainda no [Site.css], define-se a altura das linhas das diferentes tabelas HTML da região com ID [simulation], exatamente onde é exibida a folha de pagamento:


#simulação da tabela tr {
  height: 30px;
}

Tarefa: preencha a visualização [Simulation.cshtml].


Para exibir o valor em euros de uma quantia, utilizar-se-á o método [string.Format]:

string.Format("{0:C2}",somme)

A instrução acima exibe [somme] como valor monetário [C] (Moeda) com duas casas decimais [C2].

Para testar essa visualização, é necessário fornecer-lhe uma folha de pagamento. Ela deve ser fornecida pela ação [/Pam/FaireSimulation], que é o destino da chamada Ajax do link [Faire la simulation]. Atualmente, essa ação é a seguinte:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View(new IndexModel() { Application = application });
    }

    // fazer uma simulação
    [HttpPost]
    public PartialViewResult FaireSimulation()
    {
      return PartialView("Simulation");
}

No exemplo acima, a ação [FaireSimulation] não passa nenhum modelo para a visualização [Simulation.cshtml]. É necessário que ela passe uma folha de pagamento para ela. Sabemos que é a camada [métier] que realiza o cálculo das folhas de pagamento. Essa camada [métier] é acessível por meio do modelo da aplicação [ApplicationModel] que definimos no parágrafo 9.10.2:


  public class ApplicationModel
  {
    // --- dados de escopo da aplicação ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
}

A camada [métier] é acessível por meio da propriedade da linha 5 acima. Para que a ação [FaireSimulation] tenha acesso à camada [métier], vamos passar a ela o modelo do aplicativo, assim como fizemos para a ação [Index]. O código passa a ser o seguinte:


    // fazer uma simulação
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application)
    {
      return PartialView("Simulation");
}

Agora, dentro da ação, podemos calcular uma folha de pagamento fictícia. O código passa a ser o seguinte:


// fazer uma simulação
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application)
    {
      FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
      return PartialView("Simulation", feuilleSalaire);
    }
  • Na linha 5, calcula-se um salário fictício. O primeiro parâmetro é um número SS existente. Ele foi definido na classe [métier] simulada no parágrafo 9.7.5. O segundo parâmetro é o número de horas trabalhadas e o terceiro, o número de dias trabalhados;
  • linha 6: essa folha de pagamento é passada como modelo para a visualização [Simulation.cshtml].

Agora estamos prontos para testar a visualização [Simulation.cshtml]:

 

Não fazemos nenhuma entrada e solicitamos a simulação. Obtemos então o seguinte resultado:

 

9.12.3. Cálculo do salário real

Nossa ação atual [FaireSimulation] sempre calcula a mesma folha de pagamento:


    // fazer uma simulação
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application)
    {
      FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
      return PartialView("Simulation", feuilleSalaire);
}

Ela não leva em conta as informações inseridas:

  • o funcionário cujo salário está sendo calculado;
  • o número de horas trabalhadas por ele;
  • o número de dias trabalhados.

Os valores inseridos chegam à ação [FaireSimulation] da seguinte maneira:

  1. o usuário clica no link [Faire la simulation]. Isso aciona a execução da função JS [faireSimulation] que já escrevemos;
  2. A função JS [faireSimulation], em seguida, faz uma chamada Ajax para a ação do servidor [/Pam/FaireSimulation], na qual estamos trabalhando atualmente. Por enquanto, a função JS [faireSimulation] não transmite nenhuma informação para a ação do servidor. Ela precisará transmitir os valores inseridos pelo usuário;
  3. a ação do servidor [/Pam/FaireSimulation] irá recuperar os valores inseridos a partir dos valores enviados pela função JS e [faireSimulation].

Vamos começar pelo ponto 2: a função JS [faireSimulation] deve enviar os valores inseridos pelo usuário para a ação do servidor [/Pam/FaireSimulation].


Tarefa: complete a função JS [faireSimulation] para que ela envie os valores inseridos pelo usuário. É possível usar como referência o exemplo do parágrafo 7.6.5, onde esse problema foi abordado.


Vamos agora abordar o ponto 3 acima. A ação de servidor [/Pam/FaireSimulation] deve recuperar os valores enviados pela função JS [faireSimulation].


Tarefa: complete o método do servidor [FaireSimulation] para que ele calcule o salário com os valores lançados pelas funções JS e [faireSimulation]. Podemos recorrer novamente ao exemplo do parágrafo 7.6.5, onde esse problema foi abordado. Por enquanto, vamos supor que o modelo derivado dos valores lançados ainda seja válido.


Dica: a ação do servidor [FaireSimulation] evolui da seguinte forma:


// fazer uma simulação
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
    {
      // criação do modelo da ação
      ...
      // tenta-se recuperar os valores lançados nesse modelo
      ...
      // calcula-se o salário
      FeuilleSalaire feuilleSalaire = ...
      // exibimos a folha de pagamento
      return PartialView("Simulation", feuilleSalaire);
    }

Aqui está um exemplo de execução:

Escolhe-se [Justine Laverti]. Obtém-se, então, o seguinte resultado:

De fato, obtivemos a folha de pagamento fictícia de [Justine Laverti]. Anteriormente, a única folha de pagamento calculada era a de [Marie Jouveinal]. Portanto, o valor lançado para a opção do funcionário foi utilizado. Quanto ao número de horas e ao número de dias, não é possível afirmar nada, uma vez que nossa camada simulada [métier] não leva esses dados em consideração.

9.12.4. Gerenciamento de erros

Vejamos o exemplo a seguir:

  • em [1], escolhe-se um funcionário que não existe (veja a definição da camada [métier] simulada no parágrafo 9.7.5;
  • em [2], realiza-se a simulação;
  • em [3] abaixo, é exibida uma página de erro.

O que aconteceu?

A função JS [faireSimulation] foi executada. Seu código é semelhante a este:


function faireSimulation() {
...
  // é feita uma chamada Ajax manualmente
  $.ajax({
    url: '/Pam/FaireSimulation',
...
    beforeSend: function () {
      // luz de espera acesa
      loading.show();
    },
    success: function (data) {
...
    },
    error: function (jqXHR) {
      // exibição de erro
      simulation.html(jqXHR.responseText);
      simulation.show();
    },
    complete: function () {
      // luz de espera apagada
      loading.hide();
    }
  });
  // menu
  setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}

A chamada Ajax falhou e foi executada a função das linhas 14 a 18. A página de erro [jqXHR.responseText] retornada pelo servidor foi exibida. Ela é bastante precisa. A camada simulada [métier] gerou uma exceção porque o número SS fornecido a ela não corresponde ao de um funcionário existente (veja o código da camada simulada [métier] no parágrafo 9.7.5). Precisamos lidar com esse caso de maneira adequada.

Vamos criar uma visão parcial [Erreurs.chtml] que será retornada ao cliente JS sempre que um erro for detectado no lado do servidor:

O código da vista parcial [Erreurs.chtml] é o seguinte:


@model IEnumerable<string>

<hr />
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
  @foreach (string msg in Model)
  {
    <li>@msg</li>
  }
</ul>
  • linha 1: a visualização recebe como modelo uma lista de mensagens de erro;
  • linhas 5-10: que são exibidas em uma lista HTML;

Agora, vamos modificar o código da ação do servidor [FaireSimulation] da seguinte maneira:


    // fazer uma simulação
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
    {
    ...
      // calculando o salário
      FeuilleSalaire feuilleSalaire = null;
      Exception exception=null;
      try
      {
        // cálculo do salário
        feuilleSalaire = ...
      }
      catch (Exception ex)
      {
        exception = ex;
      }
      // erro?
      if (exception == null)
      {
        // exibir a folha de pagamento
        return PartialView("Simulation", feuilleSalaire);
      }
      else
      {
        // exibir a página de erros
        return PartialView("Erreurs", Static.GetErreursForException(exception));
      }
}
  • linhas 9-17: o cálculo do salário agora é feito em um try/catch;
  • linha 27: se ocorrer um erro, exibe-se a visão parcial [Erreurs.cshtml] com base na lista de mensagens de erro fornecida pelo método estático [Static.GetErreursForException(exception)].

Agrupamos na classe [Static] duas funções utilitárias estáticas [1]:


using System;
using System.Collections.Generic;
using System.Web.Mvc;

namespace PamWeb.Infrastructure
{
  public class Static
  {
    // lista de mensagens de erro de uma exceção
    public static List<string> GetErreursForException(Exception ex)
    {
      List<string> erreurs = new List<string>();
      while (ex != null)
      {
        erreurs.Add(ex.Message);
        ex = ex.InnerException;
      }
      return erreurs;
    }

    // lista de mensagens de erro relacionadas a um modelo inválido
    public static List<string> GetErreursForModel(ModelStateDictionary état)
    {
      List<string> erreurs = new List<string>();
      if (!état.IsValid)
      {
        foreach (ModelState modelState in état.Values)
        {
          foreach (ModelError error in modelState.Errors)
          {
            erreurs.Add(getErrorMessageFor(error));
          }
        }
      }
      return erreurs;
    }

    // a mensagem de erro relacionada a um elemento do modelo da ação
    static private string getErrorMessageFor(ModelError error)
    {
      if (error.ErrorMessage != null && error.ErrorMessage.Trim() != string.Empty)
      {
        return error.ErrorMessage;
      }
      if (error.Exception != null && error.Exception.InnerException == null && error.Exception.Message != string.Empty)
      {
        return error.Exception.Message;
      }
      if (error.Exception != null && error.Exception.InnerException != null && error.Exception.InnerException.Message != string.Empty)
      {
        return error.Exception.InnerException.Message;
      }
      return string.Empty;
    }

  }
}
  • linhas 10-19: a função estática [GetErreursForException] retorna a lista de erros de uma pilha de exceções;
  • linhas 22-36: a função estática [GetErreursForModel] retorna a lista de erros de um modelo de ação inválido. O código dessa função, bem como o do método privado [getErrorMessageFor] (linhas 39-54), já foi abordado anteriormente.

Feito isso, podemos testar novamente o caso de erro:

  • em [1], selecionamos o funcionário que não existe;
  • em [2], realizamos a simulação;
  • em [3], recuperamos a nova página de erros.

Voltemos à ação do servidor [FaireSimulation]:


    // fazer uma simulação
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
    {
      // criação do modelo da ação
      IndexModel modèle = new IndexModel() { Application = application};
      // tentativa de recuperar os valores lançados no modelo
      TryUpdateModel(modèle, data);
      // cálculo do salário
...
}

Na linha 8, atualizamos o modelo da linha 6 com os valores enviados pela chamada Ajax. Não verificamos a validade do modelo. É preciso fazer isso, pois não podemos saber de onde vêm os valores enviados. Alguém poderia ter manipulado um POST e nos enviado dados inválidos.


Tarefa: seguindo o modelo que desenvolvemos para o caso de exceção, modifique a ação do servidor [FaireSimulation] para enviar uma página de erros quando os dados enviados forem inválidos. Para isso, utilizaremos o método estático [GetErreursForModel] da classe [Static].


Como testar essa modificação? No parágrafo 9.11.3, você fez com que a função JS [faireSimulation] não processasse os valores inseridos (POST) caso estes fossem inválidos. Coloque as linhas que realizam isso em comentários e, em seguida, faça o seguinte teste:

  • em [1], fazemos a simulação com valores inválidos;
  • em [2], recuperamos corretamente a página de erros que acabamos de criar, prova de que os validadores do lado do servidor funcionaram corretamente.

Em seguida, lembre-se de descomentar as linhas que você acabou de colocar como comentários nas funções JS e [faireSimulation].

9.13. Etapa 7: configuração de uma sessão de usuário

O aplicativo [Simulateur de calcul de paie] permite que o usuário realize diversas simulações de folha de pagamento com o link [Faire la simulation], as salve com o link [Enregistrer la simulation], exibi-las por meio do link [Voir les simulations] e excluí-las por meio do link [Retirer la simulation]. Sabemos que, entre duas solicitações sucessivas do usuário, não há memória, a menos que seja criada por meio do mecanismo de sessão (ver parágrafo 4.10). Fica bastante claro aqui que precisamos manter na sessão a lista das simulações registradas ao longo do tempo pelo usuário. Há outros dados a serem memorizados: quando o usuário realiza uma simulação, esta só é registrada na lista de simulações se o usuário solicitar isso por meio do link [Enregistrer la simulation]. Quando isso ocorre, devemos ser capazes de recuperar a simulação calculada na solicitação anterior. Para isso, ela também será armazenada na sessão. Por fim, vamos numerar as simulações a partir de 1. Para numerar corretamente uma nova simulação, é necessário ter mantido o número da simulação anterior, mais uma vez na sessão.

No parágrafo 4.10, introduzimos o conceito de modelo de sessão como parâmetro de entrada de uma ação, para que esta tenha acesso à sessão. Vamos retomar esse conceito. Recomendamos que você releia o parágrafo em questão caso essa noção não esteja clara para você.

Criamos a seguinte classe [SessionModel]:

Seu código é o seguinte:


using Pam.Web.Models;
using System.Collections.Generic;

namespace Pam.Web.Models
{
  public class SessionModel
  {
    // a lista de simulações
    public List<Simulation> Simulations { get; set; }
    // nº da próxima simulação
    public int NumNextSimulation { get; set; }
    // a última simulação
    public Simulation Simulation { get; set; }

    // fabricante
    public SessionModel()
    {
      // lista de simulações vazia
      Simulations = new List<Simulation>();
      // nº da próxima simulação
      NumNextSimulation = 1;
    }
  }
}

A classe [Simulation], nas linhas 9 e 13, registrará informações sobre uma simulação. O que precisamos registrar? O link [Faire la simulation] calcula uma folha de pagamento do tipo [FeuilleSalaire]. Parece natural incluir essa folha na simulação. Além disso, precisamos registrar as informações que levaram à geração dessa folha de pagamento:

  • o funcionário selecionado. Ele pode ser encontrado no campo [FeuilleSalaire.Employe]. Portanto, não há necessidade de registrá-lo uma segunda vez;
  • o número de horas e dias trabalhados. Essas informações não constam no tipo [FeuilleSalaire]. Portanto, precisamos memorizá-las.

Por fim, cada simulação é identificada por um número. Portanto, poderíamos partir da seguinte classe [Simulation]:


using Pam.Metier.Entites;

namespace Pam.Web.Models
{
  public class Simulation
  {
    // nº da simulação
    public int Num { get; set; }
    // o número de horas trabalhadas
    public double HeuresTravaillées { get; set; }
    // número de dias trabalhados
    public int JoursTravaillés { get; set; }
    // folha de pagamento
    public FeuilleSalaire FeuilleSalaire { get; set; }
  }
}

A ação do servidor [FaireSimulation] deve, além de calcular uma folha de pagamento, criar uma simulação e inseri-la na sessão. Para isso, ela receberá como parâmetro o modelo da sessão:


// fazer uma simulação
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, SessionModel session, FormCollection data)
    {
      // criação do modelo da ação
      IndexModel modèle = new IndexModel() { Application = application };
      // tentando recuperar os valores lançados no modelo
      TryUpdateModel(modèle, data);
      // modelo válido?
      if (!ModelState.IsValid)
      {
        // exibindo a página de erros
        return PartialView("Erreurs", Static.GetErreursForModel(ModelState));
      }
      // cálculo do salário
      FeuilleSalaire feuilleSalaire = null;
      Exception exception = null;
      try
      {
        // cálculo do salário
        feuilleSalaire = application.PamMetier.GetSalaire(modèle.SS, modèle.HeuresTravaillées, (int)modèle.JoursTravaillés);
      }
      catch (Exception ex)
      {
        exception = ex;
      }
      // erro?
      if (exception != null)
      {
        // é exibida a página de erros
        return PartialView("Erreurs", Static.GetErreursForException(exception));
      }
      // cria-se uma simulação e ela é inserida na sessão
      session.Simulation = ...
      // exibe-se a folha de pagamento
      return PartialView("Simulation", feuilleSalaire);
    }
  • linha 3: a ação recebe como parâmetro o modelo da sessão;

Tarefa 1: complete o código da ação, linha 34



Tarefa 2: seguindo a abordagem do parágrafo 4.10, faça o necessário para que o parâmetro [SessionModel session] da ação seja corretamente inicializado pelo framework. Se nada for feito, teremos um ponteiro null para esse parâmetro.


9.14. Etapa 8: gravar uma simulação

9.14.1. O problema

Quando realizamos uma simulação, podemos gravá-la:

Image

A visualização parcial [Simulations.cshtml] agora exibe a lista das simulações realizadas pelo usuário. Vale lembrar que a folha de pagamento calculada é fictícia.

9.14.2. Gravação da ação do servidor [EnregistrerSimulation]

O link Ajax [Enregistrer la simulation] chama a ação do servidor [EnregistrerSimulation], cujo código era, até agora, o seguinte:


    [HttpPost]
    public PartialViewResult EnregistrerSimulation()
    {
      return PartialView("Simulations");
}

Ele passa a ser o seguinte:


    // gravar uma simulação
    [HttpPost]
    public PartialViewResult EnregistrerSimulation(SessionModel session)
    {
      // a última simulação realizada é registrada na lista de simulações da sessão
      ...
      // incrementa-se na sessão o número da próxima simulação
      ...
      // exibe a lista de simulações
      ...
}
  • linha 1: a ação [EnregistrerSimulation] precisa ter acesso à sessão. É por isso que ela tem como parâmetro o modelo da sessão.

Tarefa: completar a ação de servidor [EnregistrerSimulation].


9.14.3. Gravação da visão parcial [Simulations.cshtml]

A ação anterior [EnregistrerSimulation] exibe a vista parcial [Simulations.cshtml], tendo como modelo a lista de simulações realizadas pelo usuário. Seu código é o seguinte:


@model IEnumerable<Simulation>

@using Pam.Web.Models

@if (Model.Count() == 0)
{
  <h2>Votre liste de simulations est vide</h2>
}
@if (Model.Count() != 0)
{
  <h2>Liste des simulations</h2>
...
}

Tarefa 1: completar o código da visão parcial [Simulations.cshtml]. Utilizar-se-á uma tabela HTML para a exibição das simulações. É possível recorrer aos exemplos do parágrafo 5.4.


Observação: o link [retirer] de cada simulação da tabela HTML será um link em JavaScript com o seguinte formato:

<a href="javascript:retirerSimulation(N)">retirer</a>

onde N é o número da simulação.


Tarefa 2: teste sua aplicação realizando simulações. Para realizá-las, execute a seguinte sequência repetidamente: 1) carregue a página do aplicativo por meio de [F5], 2) faça uma simulação, 3) salve-a. As simulações serão acumuladas na sessão, o que deverá ser refletido na visualização [Simulations.cshtml].



Tarefa 3: aprimore a visualização parcial [Simulations.cshtml] de forma que as cores das linhas da tabela HTML sejam alternadas.


Image

Serão atribuídas cores alternadas às linhas <tr> da tabela HTML, as classes CSS, [pair] e [impair] definidas na folha de estilo [/Content/Site.css]:


.impair {
  background-color: beige;
}

.pair {
  background-color: lightsteelblue;
}

9.15. Etapa 9: retornar ao formulário de preenchimento

9.15.1. O problema

Depois de obtermos a lista de simulações, podemos voltar ao formulário de preenchimento, o que já não era possível há algum tempo:

Image

Image

9.15.2. Criação da ação do servidor [Formulaire]

O link Ajax [Retour au formulaire de simulation] chama a ação do servidor [Formulaire], cujo código era, até agora, o seguinte:


    [HttpPost]
    public PartialViewResult Formulaire()
    {
      return PartialView("Formulaire");
}

A visualização parcial [Formulaire] que ela exibe espera um modelo [IndexModel] (linha 1 abaixo):


@model Pam.Web.Models.IndexModel

@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
<div id="simulation" />

É por esse motivo que o link [Retour au formulaire de simulation] não funcionava mais.


Tarefa: escrever a nova versão da ação de servidor [Formulaire] (2 linhas a serem reescritas) e, em seguida, realizar os testes.


9.15.3. Modificação da função JavaScript [retourFormulaire]

Com a modificação feita anteriormente, agora é possível voltar ao formulário, mas surge então uma anomalia:

  • em [1], retorna-se ao formulário de preenchimento;
  • em [2], faz-se uma simulação com entradas incorretas. Descobre-se então que os validadores do lado do cliente não funcionam mais. Aqui, o servidor foi chamado e retornou uma página de erros graças ao trabalho realizado no parágrafo 9.12.4.

Essa anomalia foi identificada e tratada no parágrafo 7.6.7.


Tarefa: seguindo a abordagem do parágrafo 7.6.7, corrija a função JavaScript [retourFormulaire] e, em seguida, realize testes para verificar se os validadores do lado do cliente estão funcionando novamente.


9.16. Etapa 10: consulte a lista de simulações

9.16.1. O problema

Ao trabalhar com o formulário de simulação, é possível visualizar a lista das simulações realizadas:

9.16.2. Gravação da ação do servidor [VoirSimulations]

O link Ajax [Voir les simulations] chama a ação do servidor [VoirSimulations], cujo código era, até agora, o seguinte:


    // visualizar as simulações
    [HttpPost]
    public PartialViewResult VoirSimulations()
    {
      return PartialView("Simulations");
}

A visualização parcial [Simulations] que ela exibe espera um modelo [IEnumerable<Simulation>] (linha 1 abaixo):


@model IEnumerable<Simulation>

@using Pam.Web.Models

@if (Model.Count() == 0)
{
  <h2>Votre liste de simulations est vide</h2>
}
@if (Model.Count() != 0)
{
  <h2>Liste des simulations</h2>
...
}

É por esse motivo que o link [Voir les simulations] não funcionava mais.


Tarefa: escrever a nova versão da ação do servidor [VoirSimulations] (2 linhas a serem reescritas) e, em seguida, realizar os testes.


9.17. Etapa 11: encerrar a sessão

9.17.1. O problema

É possível encerrar a sessão do usuário a qualquer momento usando o link [Ajax] [Terminer la session]. Isso faz com que a sessão atual seja encerrada para iniciar uma nova. Além disso, retorna-se à visualização do formulário:

  • em [1], realizamos duas simulações e, em seguida, encerramos a sessão;
  • em [2], voltamos ao formulário de preenchimento. Queremos ver as simulações;
  • em [3], devido à mudança de sessão, a lista de simulações agora está vazia.

9.17.2. Gravação da ação do servidor [TerminerSession]

O link Ajax [Terminer la session] chama a ação do servidor [TerminerSession], cujo código era, até agora, o seguinte:


    // encerrar a sessão
    [HttpPost]
    public PartialViewResult TerminerSession()
    {
      return PartialView("Formulaire");
}

A visualização parcial [Formulaire] que ela exibe espera um modelo [IndexModel] (linha 1 abaixo):


@model Pam.Web.Models.IndexModel

@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
<div id="simulation" />

É por esse motivo que o link [Terminer la session] não funcionava mais.


Tarefa: escrever a nova versão da ação do servidor [TerminerSession] (2 linhas a serem reescritas) e, em seguida, realizar os testes.


Observação: para encerrar a sessão na ação, escreve-se:

Session.Abandon() ;

9.17.3. Modificação da função JavaScript [terminerSession]

Com a modificação feita anteriormente, agora é possível retornar ao formulário, mas surge então uma anomalia, a mesma descrita anteriormente no parágrafo 9.15.3.


Tarefa: seguindo o procedimento que você realizou no parágrafo 9.15.3, corrija a função JavaScript [terminerSession] e, em seguida, faça testes para verificar se os validadores do lado do cliente estão funcionando novamente.


9.18. Etapa 12: apagar a simulação

9.18.1. O problema

Depois de realizar uma simulação, é possível apagá-la usando o link JavaScript [Effacer la simulation]:

9.18.2. Registro da ação do cliente [effacerSimulation]

A função JavaScript [effacerSimulation] possui, por enquanto, o seguinte código:


function effacerSimulation() {
  // apaga-se o conteúdo preenchido no formulário
  // ...
  // oculta a simulação, caso exista
  $("#simulation").hide();
  // menu
  setMenu([lnkFaireSimulation, lnkTerminerSession, lnkVoirSimulations]);
}

Tarefa: complete este código. Pode-se se inspirar no exemplo do parágrafo 7.6.6


9.19. Etapa 13: remover uma simulação

9.19.1. O problema

Quando se está na página de simulações, é possível excluir algumas delas usando o link JavaScript [retirer]:

Image

Image

9.19.2. Registro da ação do cliente [retirerSimulation]

Os links [retirer] têm o seguinte formato: HTML:

<a href="javascript:retirerSimulation(N)">retirer</a>

onde N é o número da simulação.


Tarefa: seguindo o procedimento descrito nos parágrafos 9.9.3, escreva a função JS [retirerSimulation]. Essa função enviará uma chamada Ajax do tipo POST para a ação [/Pam/RetirerSimulation]. Ela enviará o dado N no formato num=N.


Observação: a função JS [retirerSimulation] é semelhante às outras funções JS que você escreveu e que fazem uma chamada Ajax ao servidor. A única novidade aqui é o envio de um valor que não está em um formulário. Sabe-se que os valores enviados são reunidos em uma sequência de caracteres no formato:

param1=val1&param2=val2&....

portanto, a função JS [retirerSimulation] terá o seguinte formato:


function retirerSimulation(N) {
  // faz-se uma chamada Ajax manualmente
  $.ajax({
    url: '/Pam/RetirerSimulation',
...
    data:"num="+N,
...
  });
  // menu
  setMenu([lnkRetourFormulaire, lnkTerminerSession]);
}
  • linha 6: a propriedade [data] de uma chamada Ajax JQuery representa a string enviada ao servidor.

9.19.3. Codificação da ação do servidor [RetirerSimulation]

A ação do servidor [RetirerSimulation]:

  • recebe um parâmetro enviado chamado [num], que é o número de uma simulação;
  • deve remover da lista de simulações registradas na sessão a simulação com esse número;
  • deve, em seguida, exibir a nova lista de simulações.

Tarefa: escreva a ação de servidor [RetirerSimulation]. Consulte o parágrafo 4.1 para saber como recuperar o parâmetro enviado por POST chamado [num].


9.20. Etapa 14: aprimoramento do método de inicialização do aplicativo

Nosso aplicativo web está concluído. Ele está funcional com uma classe [métier] simulada. Vamos relembrar a arquitetura que desenvolvemos:

Ainda há alguns detalhes a serem ajustados antes de passarmos para a implementação real da camada [métier], e isso ocorre no método de inicialização do aplicativo: o método [Application_Start] em [Global.asax]:

O método [Application_Start] em [Global.asax] é executado uma única vez ao iniciar o aplicativo. É nesse momento que o arquivo de configuração [Web.config] pode ser utilizado. Por enquanto, nosso método [Application_Start] tem a seguinte aparência:


// aplicativo
    protected void Application_Start()
    {
      // ----------Gerado automaticamente
      AreaRegistration.RegisterAllAreas();
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // -------------------------------------------------------------------
      // ---------- configuração específica
      // -------------------------------------------------------------------
      // dados do escopo da aplicação
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
      // instanciação da camada [métier]
      application.PamMetier = new PamMetier();
...
      // ligadores de modelo
...
}

Na linha 17, a camada de negócios é instanciada por um operador new. Além disso, o modelo do aplicativo é definido da seguinte forma:


  public class ApplicationModel
  {
    // --- dados de escopo da aplicação ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
}

Na linha 5 acima, vemos que o tipo da propriedade [PamMetier] é o da interface [IPamMetier]. Isso significa que essa propriedade pode ser inicializada por qualquer objeto que implemente essa interface. No entanto, na linha 17 de [Application_Start], definimos explicitamente o nome de uma classe de implementação de [IPamMetier]. Portanto, se a camada [métier] fosse implementada com uma nova classe que implementasse [IPamMetier], seria necessário alterar essa linha. Isso não é muito importante, mas pode ser evitado. A definição da classe de implementação da interface [IPamMetier] pode ser transferida para um arquivo de configuração. Para alterar a implementação, basta modificar o conteúdo desse arquivo de configuração. O código .NET não precisa ser alterado.

Usaremos aqui o contêiner de injeção de dependências [Spring.net]. Existem outras estruturas .NET para fazer a mesma coisa, talvez de maneira melhor e mais simples.

A arquitetura do projeto evolui da seguinte forma:

  • em [A], o método de inicialização da camada [ASP.NET MVC] solicitará ao [Spring.net] uma referência à camada [métier] simulada;
  • em [B], [Spring.net] criará a camada simulada [métier] utilizando seu arquivo de configuração para determinar qual classe deve instanciar;
  • em [C], [Spring.net] passará a referência da camada simulada [métier] para a camada [ASP.NET MVC].

Observe-se que, por padrão, os objetos gerenciados por [Spring.net] são singletons: existem apenas em uma única instância. Assim, se mais adiante em nosso exemplo, algum código solicitar novamente ao [Spring.net] uma referência à camada [métier] simulada, o [Spring.net] se limitará a retornar a referência ao objeto criado inicialmente.

9.20.1. Adicionando as referências [Spring] ao projeto web

Vamos utilizar o [Spring.net]. Esse framework vem na forma de DLL, que deve ser adicionado às referências do projeto. Podemos proceder da seguinte forma:

No [1], clique com o botão direito do mouse no ramo [References] do projeto e selecione a opção [Gérer les packages NuGet]. É necessária uma conexão com a internet. Em seguida, proceda da mesma forma que foi feito anteriormente para a biblioteca JQuery [Globalize]. Procure a palavra-chave [Spring.core] e instale esse pacote. A instalação gera dois pacotes DLL: [Spring.core] e [2], e [Common.Logging] e [3]. Nos exemplos a seguir, foi utilizada a versão 1.3.2 do Spring.

Observação: caso não tenha conexão com a Internet, você encontrará esses arquivos DLL na pasta [lib] do material de apoio deste estudo de caso.

9.20.2. Configuração do [web.config]

A definição da classe de implementação da interface [IPamMetier] é feita no arquivo [web.config].


<configuration>
  <configSections>
...
    <sectionGroup name="spring">
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
    </sectionGroup>
  </configSections>
  <!-- configuração do Spring -->
  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier-simule"/>
    </objects>
  </spring>
...
  • linhas 2-8: localize a tag <configSections> no arquivo e insira nela as linhas 4-7;
  • linha 4: o atributo [name="spring"] fornece informações relativas à seção [spring] das linhas 10 a 17;
  • linha 5: define a classe [Spring.Context.Support.DefaultSectionHandler], localizada na DLL [Spring.Core], como aquela capaz de processar a seção [objects] das linhas 14 a 16;
  • linha 6: define a classe [Spring.Context.Support.ContextHandler], localizada na DLL [Spring.Core], como aquela capaz de processar a seção [context] das linhas 11 a 13;
  • linhas 11-13: esta seção fornece a informação [<resource uri="config://spring/objects" />], que indica que os objetos Spring estão no arquivo de configuração na seção [/spring/objects], ou seja, nas linhas 14-16;
  • linhas 14-16: a tag [objects] introduz os objetos Spring;
  • linha 15: define um objeto identificado por [id="pammetier"], que é uma instância da classe [Pam.Metier.Service.PamMetier] localizada na DLL [pam-metier-simule]. Aqui, é preciso ter cuidado para não cometer erros. Para o atributo [id], você pode inserir o que quiser. Você utilizará esse identificador em [Global.asax]. A classe [Pam.Metier.Service.PamMetier] é a da nossa camada simulada [métier]. É preciso voltar à sua definição para saber seu nome completo:

namespace Pam.Metier.Service
{
  public class PamMetier : IPamMetier
  {
    ...

Para o DLL e o [pam-metier-simule], é necessário verificar as propriedades do projeto C# [pam-metier-simule]:

É preciso usar o nome indicado em [1].

9.20.3. Alteração em [Application_Start]

O método [Application_Start] passa a ter a seguinte forma:


using Spring.Context.Support;

// aplicação
    protected void Application_Start()
    {
      // ----------Gerado automaticamente
      AreaRegistration.RegisterAllAreas();
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // -------------------------------------------------------------------
      // ---------- configuração específica
      // -------------------------------------------------------------------
      // dados de escopo da aplicação
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
      // instanciação da camada [métier]
      application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
...
      // ligadores de modelo
...
}
  • linha 19: utiliza-se a classe Spring [ContextRegistry], que é capaz de processar o arquivo [web.config]. Para isso, é necessário importar o espaço de nomes da linha 1. O método estático [GetContext] permite obter o conteúdo das tags [context], que indicam onde se encontram os objetos Spring. O método estático [GetObject] permite, em seguida, obter um objeto específico identificado por seu atributo id. Observe que, agora, o nome da classe de implementação da interface [IPamMetier] não está mais definido de forma rígida no código. Ele agora está no arquivo [web.config].

Depois de fazer todas essas alterações, teste seu aplicativo. Ele deve funcionar.

9.20.4. Tratamento de um erro de inicialização do aplicativo

No método [Application_Start], escrevemos:


application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;

A instrução à direita do sinal = pode falhar. Há várias razões para isso:

  • a mais óbvia é que cometemos um erro no nome do objeto a ser instanciado;
  • ou que a instanciação da camada [métier] não ocorra corretamente. Isso não pode ser o caso para nossa camada simulada [métier], mas pode ocorrer com nossa camada real [métier], que estará conectada a um banco de dados. A camada SGBD pode não ser iniciada, as informações sobre o banco de dados a ser gerenciado podem estar incorretas, etc...

Vamos lidar com uma possível exceção em um try/catch. O código fica da seguinte forma:


// aplicativo
    protected void Application_Start()
    {
      // ----------Gerado automaticamente
...
      // -------------------------------------------------------------------
      // ---------- configuração específica
      // -------------------------------------------------------------------
      // dados de escopo do aplicativo
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
      application.InitException = null;
      try
      {
        // instanciação da camada [métier]
        application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
      }
      catch (Exception ex)
      {
        application.InitException = ex;
      }
      //se não houver erro
      if (application.InitException == null)
      {
....
      }
      // ligadores de modelo
...
    }
  • na linha 12, introduzimos uma nova propriedade chamada [InitException] no modelo do aplicativo:

  public class ApplicationModel
  {
    // --- dados de escopo da aplicação ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
    public Exception InitException { get; set; }
}
  • linha 7 acima, a exceção que pode ocorrer durante a inicialização do aplicativo;
  • linhas 13-21 de [Application_Start]: a instanciação da camada [métier] agora é feita em um try/catch;
  • linha 20: a exceção é registrada;
  • linhas 23-26: se não houve erro, executa-se o código que tínhamos anteriormente;
  • linha 28: os [ModelBinders] são criados, independentemente de ter ocorrido erro ou não. Isso é importante. Queremos garantir que o modelo da aplicação [ApplicationModel] seja corretamente vinculado pelo framework.

Sabemos que, ao iniciar a aplicação, a ação de servidor [Index] é executada. Por enquanto, ela é a seguinte:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View(new IndexModel() { Application = application });
}

Na linha 2, a ação [Index] recebe o modelo do aplicativo. Assim, ela pode verificar se a inicialização ocorreu corretamente ou não e exibir uma página de erros caso a inicialização tenha falhado de alguma forma. Modificamos o código da seguinte maneira:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      // erro de inicialização?
      if (application.InitException != null)
      {
        // página de erros sem menu
        return View("InitFailed",Static.GetErreursForException(application.InitException));
      }
      // sem erro
      return View(new IndexModel() { Application = application });
}

Na linha 8, em caso de erro de inicialização, exibimos a visualização [InitFailed.cshtml] com o modelo da lista de mensagens de erro da exceção que ocorreu durante a inicialização. O método [Static.GetErreursForException] foi apresentado e explicado no parágrafo 9.12.4. A visualização [InitFailed.cshtml] será a seguinte:

Seu código é o seguinte:


@model IEnumerable<string>
@{
  Layout = null;
}
<!DOCTYPE html>
<html>
<head>
  <title>@ViewBag.Title</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <link rel="stylesheet" href="~/Content/Site.css" />
</head>
<body>
  <table>
    <tbody>
      <tr>
        <td>
          <h2>Simulateur de calcul de paie</h2>
        </td>
    </tbody>
  </table>
  <hr />
  <h2>Les erreurs suivantes se sont produites à l'initialisation de l'application : </h2>
  <ul>
    @foreach (string msg in Model)
    {
      <li>@msg</li>
    }
  </ul>
</body>
</html>
  • linha 1: o modelo da visualização é uma lista de mensagens de erro. Estas são exibidas em uma lista HTML nas linhas 24 a 29;
  • linha 3: esta visualização não utiliza a página mestre [_Layout.cshtml]. De fato, não se deseja o menu fornecido por esse documento. Portanto, cria-se uma página HTML completa (linhas 5 a 23).

Para testar, basta modificar, em [Application_Start], a instanciação da camada [métier] da seguinte forma:


      try
      {
        // instanciação da camada [métier]
        application.PamMetier = ContextRegistry.GetContext().GetObject("xx") as IPamMetier;
      }
      catch (Exception ex)
      {
        application.InitException = ex;
}

Na linha 4, procura-se um objeto que não existe entre os objetos do Spring.

Ao validar essas alterações e iniciar a aplicação, obtém-se a seguinte página:

 

É exibida uma página de erros sem menu. O usuário não pode fazer nada além de constatar o erro. Era exatamente isso que se desejava.

9.21. Em que ponto estamos?

Agora temos uma aplicação web operacional que funciona com uma camada de negócios simulada. Sua arquitetura é a seguinte:

A camada [ASP.NET MVC] interage com a camada de negócios simulada por meio da interface [IPamMetier]. Se substituirmos essa camada de negócios simulada por uma camada de negócios real que respeite essa interface, não precisaremos modificar o código da camada web. Graças à [Spring.net], bastará alterar, na [web.config], a classe de implementação da interface [IPamMetier]. Seguiremos por esse caminho.

A nova arquitetura será a seguinte:

Descreveremos sucessivamente:

  • a camada [EF5] conectada à SGBD. Ela será implementada com o Entity Framework 5 (EF5);
  • a camada [DAO], que gerencia o acesso aos dados por meio da camada [EF5]. Isso permite que ela ignore a existência da camada SGBD. Essa camada se limita a manipular as entidades do aplicativo [Employe, Cotisations, Indemnites];
  • a camada [métier], que implementa o cálculo do salário.

A nova arquitetura é a apresentada logo no início deste documento, no parágrafo 1.1, e que relembramos agora:

  • a camada [Web] é a camada que interage com o usuário do aplicativo web. O usuário interage com o aplicativo web por meio de páginas da web visualizadas em um navegador. É nessa camada que se situam ASP.NET e MVC, e exclusivamente nessa camada;
  • a camada [métier] implementa as regras de gestão do aplicativo, como o cálculo de um salário ou de uma fatura. Essa camada utiliza dados provenientes do usuário por meio da camada [Web] e da camada SGBD por meio da camada [DAO];
  • a camada [DAO] (Data Access Objects), a camada [ORM] (Object Relational Mapper) e o conector ADO.NET gerenciam o acesso aos dados da camada SGBD. A camada [ORM] faz a ponte entre os objetos manipulados pela camada [DAO] e as linhas e colunas dos dados de um banco de dados relacional. Dois ORM são comumente utilizados no mundo: o NET, NHibernate (http://sourceforge.net/projects/nhibernate/) e o Entity Framework (http://msdn.microsoft.com/en-us/data/ef.aspx);
  • a integração das camadas pode ser realizada por meio de um contêiner de injeção de dependências (Dependency Injection Container), como o Spring (http://www.springframework.net/ );

As camadas [métier], [DAO] e [EF5] serão implementadas por meio de projetos em C#. A partir de agora, trabalharemos com o Visual Studio Express 2012 para desktop.

9.22. Etapa 15: implementação da camada Entity Framework 5

A criação da camada [EF5] envolve menos programação e mais configuração. Para compreender a criação dessa camada, consulte o documento [Introduction à Entity Framework 5 Code First], disponível no URL [http://tahe.developpez.com/dotnet/ef5cf-02/]. Trata-se de um documento bastante extenso. Os conceitos básicos estão nos quatro primeiros capítulos. Os parágrafos que devem ser lidos com mais atenção serão indicados. Quando fizermos referência a este documento, utilizaremos a notação [refEF5].

Além disso, por vezes precisaremos de conceitos de C#. Nesse caso, faremos referência ao curso [Introduction au langage C#], disponível em URL [http://tahe.developpez.com/dotnet/csharp/], utilizando a notação [refC#].

9.22.1. O banco de dados

O banco de dados do aplicativo foi apresentado no parágrafo 9.4. Trata-se de um banco de dados MySQL denominado [dbpam_ef5] (pam=Paie Assistante Maternelle). Esse banco de dados possui um administrador chamado root, sem senha.

Vamos relembrar o esquema do banco de dados. Ele possui três tabelas:

Image

Existe uma relação de chave estrangeira entre a coluna EMPLOYES (INDEMNITE_ID) e a coluna INDEMNITES (ID). Parte da estrutura desse banco de dados é determinada por sua utilização com EF5.

O script SQL para a criação do banco de dados é o seguinte:


-- phpMyAdmin SQL Dump
-- versão 3.5.1
-- http://www.phpmyadmin.net
--
-- Cliente: localhost
-- Gerado em: Seg, 4 de novembro de 2013, às 09h34
-- Versão do servidor: 5.5.24-log
-- Versão do PHP: 5.4.3

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;

--
-- Banco de dados: `dbpam_ef5`
--

-- --------------------------------------------------------

--
-- Estrutura da tabela `cotizações`
--

CREATE TABLE IF NOT EXISTS `cotisations` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `SECU` double NOT NULL,
  `RETRAITE` double NOT NULL,
  `CSGD` double NOT NULL,
  `CSGRDS` double NOT NULL,
  `VERSIONING` int(11) NOT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=12 ;

--
-- Conteúdo da tabela `cotisations`
--

INSERT INTO `cotisations` (`ID`, `SECU`, `RETRAITE`, `CSGD`, `CSGRDS`, `VERSIONING`) VALUES
(11, 9.39, 7.88, 6.15, 3.49, 1);

--
-- Triggers `cotisations`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_COTISATIONS`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_COTISATIONS` BEFORE UPDATE ON `cotisations`
 FOR EACH ROW BEGIN
  SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_COTISATIONS`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_COTISATIONS` BEFORE INSERT ON `cotisations`
 FOR EACH ROW BEGIN
  SET NEW.VERSIONING:=1;
END
//
DELIMITER ;

-- --------------------------------------------------------

--
-- Estrutura da tabela `employes`
--

CREATE TABLE IF NOT EXISTS `employes` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `PRENOM` varchar(20) CHARACTER SET latin1 NOT NULL,
  `SS` varchar(15) CHARACTER SET latin1 NOT NULL,
  `ADRESSE` varchar(50) CHARACTER SET latin1 NOT NULL,
  `CP` varchar(5) CHARACTER SET latin1 NOT NULL,
  `VILLE` varchar(30) CHARACTER SET latin1 NOT NULL,
  `NOM` varchar(30) CHARACTER SET latin1 NOT NULL,
  `VERSIONING` int(11) NOT NULL,
  `INDEMNITE_ID` bigint(20) NOT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `SS` (`SS`),
  KEY `FK_EMPLOYES_INDEMNITE_ID` (`INDEMNITE_ID`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=26 ;

--
-- Conteúdo da tabela `employes`
--

INSERT INTO `employes` (`ID`, `PRENOM`, `SS`, `ADRESSE`, `CP`, `VILLE`, `NOM`, `VERSIONING`, `INDEMNITE_ID`) VALUES
(24, 'Marie', '254104940426058', '5 rue des oiseaux', '49203', 'St Corentin', 'Jouveinal', 1, 93),
(25, 'Justine', '260124402111742', 'La Brûlerie', '49014', 'St Marcel', 'Laverti', 1, 94);

--
-- Triggers `employes`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_EMPLOYES`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_EMPLOYES` BEFORE UPDATE ON `employes`
 FOR EACH ROW BEGIN
  SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_EMPLOYES`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_EMPLOYES` BEFORE INSERT ON `employes`
 FOR EACH ROW BEGIN
  SET NEW.VERSIONING:=1;
END
//
DELIMITER ;

-- --------------------------------------------------------

--
-- Estrutura da tabela `indenizações`
--

CREATE TABLE IF NOT EXISTS `indemnites` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `ENTRETIEN_JOUR` double NOT NULL,
  `REPAS_JOUR` double NOT NULL,
  `INDICE` int(11) NOT NULL,
  `INDEMNITES_CP` double NOT NULL,
  `BASE_HEURE` double NOT NULL,
  `VERSIONING` int(11) NOT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `INDICE` (`INDICE`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=95 ;

--
-- Conteúdo da tabela `indemnites`
--

INSERT INTO `indemnites` (`ID`, `ENTRETIEN_JOUR`, `REPAS_JOUR`, `INDICE`, `INDEMNITES_CP`, `BASE_HEURE`, `VERSIONING`) VALUES
(93, 2.1, 3.1, 2, 15, 2.1, 1),
(94, 2, 3, 1, 12, 1.93, 1);

--
-- Triggers `indemnites`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_INDEMNITES`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_INDEMNITES` BEFORE UPDATE ON `indemnites`
 FOR EACH ROW BEGIN
  SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_INDEMNITES`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_INDEMNITES` BEFORE INSERT ON `indemnites`
 FOR EACH ROW BEGIN
  SET NEW.VERSIONING:=1;
END
//
DELIMITER ;

--
-- Restrições para as tabelas exportadas
--

--
-- Restrições para a tabela `employes`
--
ALTER TABLE `employes`
  ADD CONSTRAINT `FK_EMPLOYES_INDEMNITE_ID` FOREIGN KEY (`INDEMNITE_ID`) REFERENCES `indemnites` (`ID`);

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

Observe os seguintes pontos:

  • linhas 30, 73, 122: as chaves primárias das tabelas estão no modo [AUTO_INCREMENT]. É o MySQL que as gerencia, e não o EF5;
  • linha 83: o número SS possui uma restrição de exclusividade;
  • linha 130: o índice do funcionário possui uma restrição de exclusividade;
  • linhas 168-169: a chave estrangeira da tabela [employes] para a tabela [indemnites];
  • linha 49: um disparador [Trigger] é um script SQL incorporado pelo SGBD e que é executado em determinados momentos;
  • linhas 51-54: o disparador [INCR_VERSIONING_COTISATIONS] é acionado antes de qualquer modificação em uma linha da tabela [cotisations]. Ele então incrementa em uma unidade a coluna [VERSIONING];
  • linhas 59-62: o gatilho [START_VERSIONING_COTISATIONS] é acionado antes de qualquer inserção de uma nova linha na tabela [cotisations]. Ele então inicializa a coluna [VERSIONING] com o valor 1;
  • por fim, a coluna [VERSIONING] assume o valor 1 quando uma linha é criada na tabela [cotisations] e, em seguida, é incrementada em 1 a cada modificação feita nessa linha. Esse mecanismo permite que EF5 gerencie a concorrência de acesso a uma linha da tabela [cotisations] da seguinte maneira:
    • um processo P1 lê uma linha L da tabela [cotisations] no momento T1. A linha possui, na coluna [VERSIONING], o valor V1;
    • um processo P2 lê a mesma linha L da tabela [cotisations] no momento T2. A linha possui as colunas [VERSIONING] e V1 porque o processo P1 ainda não validou sua modificação;
    • O processo P1 altera a linha L e valida essa alteração. A coluna [VERSIONING] da linha L passa então para V1+1 devido ao gatilho [INCR_VERSIONING_COTISATIONS];
    • o processo P2 faz o mesmo em seguida. EF5 lança então uma exceção, pois o processo P2 possui uma linha com a coluna [VERSIONING] cujo valor V1 difere daquele encontrado no banco de dados, que é V1+1. Só é possível modificar uma linha se o valor de [VERSIONING] for o mesmo que o do banco de dados.

Isso é chamado de gerenciamento otimista de acessos concorrentes. Com EF5, um campo que desempenha essa função deve ter a anotação [ConcurrencyCheck].

  • Um mecanismo análogo é criado para a tabela [employes] (linhas 98-113) e para a tabela [indemnites] (linhas 144-159).

Tarefa: crie o banco de dados MySQL [dbpam_ef5] usando o script SQL anterior. O banco de dados [dbpam_ef5] deve ser criado previamente, pois o script não o cria. Em seguida, executaremos o script SQL nesse banco de dados.


9.22.2. O projeto do Visual Studio

Com o Visual Studio Express 2012 para desktop, carregamos a solução [pam-td] utilizada durante a construção da camada [web]:

  • no [1], o Visual Studio Express 2012 para desktop não consegue carregar o projeto web [pam-web-01]. Isso é normal e não representa nenhum problema;
  • em [2], adiciona-se um novo projeto à solução [pam-td];
  • no [3], o projeto é do tipo [console] e se chama [4] [pam-ef5];
  • em [5], o projeto criado. Seu nome não está em negrito, portanto não é o projeto inicial da solução;
  • em [6] e [7], define-se o novo projeto como projeto inicial.

9.22.3. Adicionar as referências necessárias ao projeto

Vamos contextualizar o projeto como um todo:

Nosso projeto precisa de alguns DLL:

  • o DLL do Entity Framework 5;
  • o DLL do conector ADO.NET do SGBD MySQL.

O parágrafo 4.2 do [refEF5] explica como instalar esses DLL usando a ferramenta [NuGet]. Atualmente (novembro de 2013), a versão disponível do Entity Framework é a versão 6 (EF6). Infelizmente, parece que o conector ADO.NET do SGBD MySQL disponível (novembro de 2013) por meio do [NuGet] não seja compatível com o EF6. Por isso, colocamos em uma pasta o [lib], o [1] e o DLL deEF5, bem como os outros DLL necessários para o projeto [pam-ef5]

Colocamos outros DLL na pasta [lib]. Vamos usá-los posteriormente. No [2], adicionamos esses novos DLL ao projeto.

  • no [3], navegamos pelo sistema de arquivos até a pasta [lib];
  • em [4], selecionamos os três DLL e, em seguida, confirmamos duas vezes;
  • em [5], os três DLL foram adicionados às referências do projeto.

Precisamos de outro DLL. Esse será encontrado entre os do framework .NET da máquina.

  • em [1], adicione uma nova referência ao projeto;
  • em [2], selecione [Assemblys];
  • em [3], digite [system.component];
  • em [4], selecione o assembly [System.ComponentModel.DataAnnotations];
  • em [5], a referência foi adicionada.

Agora estamos prontos para programar e configurar.

9.22.4. As entidades do Entity Framework

As entidades do Entity Framework são classes nas quais se encapsulam as linhas das diferentes tabelas do banco de dados. Vamos relembrá-las:

Image

Na camada [web], utilizamos as entidades [Employe, Cotisations, Indemnités] (ver parágrafo 9.7.3, página 219). Elas não eram representações fiéis das tabelas. Assim, as colunas [ID, VERSIONING] foram ignoradas. Aqui, isso não vai acontecer, pois elas são utilizadas pelas entidades ORM e EF5. Portanto, vamos adicionar as propriedades que faltam a elas. Criamos essas entidades em uma pasta [Models] do projeto:

Seu novo código agora é o seguinte:

Classe [Cotisations]


using System;

namespace Pam.EF5.Entites
{
  public class Cotisations
  {
    public int Id { get; set; }
    public double CsgRds { get; set; }
    public double Csgd { get; set; }
    public double Secu { get; set; }
    public double Retraite { get; set; }
    public int Versioning { get; set; }

    // assinatura
    public override string ToString()
    {
      return string.Format("Cotisations[{0},{1},{2},{3}, {4}, {5}]", Id, Versioning, CsgRds, Csgd, Secu, Retraite);
    }
  }
}
  • linha 3: o namespace foi adaptado ao novo projeto;
  • as propriedades das linhas 7 e 12 foram adicionadas para refletir a estrutura da tabela [cotisations];
  • linha 17: o método [ToString] agora exibe os dois novos campos.

Classe [Indemnites]


using System;

namespace Pam.EF5.Entites
{
  public class Indemnites
  {
    public int Id { get; set; }
    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; }
    public int Versioning { get; set; }

    // assinatura
    public override string ToString()
    {
      return string.Format("Indemnités[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Versioning, Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
    }
  }
}
  • linha 3: o namespace foi adaptado ao novo projeto;
  • as propriedades das linhas 7 e 13 foram adicionadas para refletir a estrutura da tabela [indemnites];
  • linha 18: o método [ToString] agora exibe os dois novos campos.

Classe [Employe]


using System;

namespace Pam.EF5.Entites
{

  public class Employe
  {
    public int Id { get; set; }
    public string SS { get; set; }
    public string Nom { get; set; }
    public string Prenom { get; set; }
    public string Adresse { get; set; }
    public string Ville { get; set; }
    public string CodePostal { get; set; }
    public Indemnites Indemnites { get; set; }
    public int Versioning { get; set; }

    // assinatura
    public override string ToString()
    {
      return string.Format("Employé[{0},{1},{2},{3},{4},{5}, {6}, {7}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal);
    }
  }
}
  • linha 3: o namespace foi adaptado ao novo projeto;
  • as propriedades das linhas 8 e 16 foram adicionadas para refletir a estrutura da tabela [employes];
  • linha 21: o método [ToString] agora exibe os dois novos campos.

Para que possam ser utilizadas pelos métodos ORM e EF5, as propriedades dessas classes devem ser decoradas com anotações.


Tarefa: com base no parágrafo 3.4 do [Création de la base à partir des entités] do [refEF5], adicione às entidades [Employe, Cotisations, Indemnites] as anotações necessárias para o EF5.


Dicas:

  • trata-se apenas de criar anotações. Não siga a parte [création de base] do parágrafo mencionado;
  • para a anotação [Table], siga o exemplo MySQL do parágrafo 4.2 de [refEF5];
  • para a anotação [ConcurrencyCheck] na propriedade [Versioning], siga o exemplo da Oracle do parágrafo 5.2 de [refEF5];
  • para a chave estrangeira que a tabela [employes] possui na tabela [indemnités], siga o exemplo 3.4.2 do documento [refEF5]. Dessa forma, você adicionará uma nova propriedade à entidade [Employe]:

    public int IndemniteId { get; set; }

cujo valor será o da coluna [INDEMNITES_ID] da tabela [employes]. Você deverá atribuir as anotações de chave estrangeira às propriedades [IndemniteId] e [Indemnites] da entidade [Employe]. Para isso, siga o exemplo 3.4.2 de [refEF5];

  • você não irá gerenciar as relações inversas das chaves estrangeiras;
  • esse trabalho requer uma breve leitura do [refEF5].

9.22.5. Configuração do ORM EF5

Vamos contextualizar o projeto como um todo:

A camada [EF5] acessará o banco de dados por meio do conector [ADO.NET] do SGBD MySQL. Ela precisa de algumas informações para acessar esse banco de dados. Essas informações estão localizadas em diversos pontos do projeto.

Primeiramente, precisamos criar o contexto do banco de dados. Esse contexto é uma classe derivada da classe de sistema [System.Data.Entity.DbContext]. Ela serve para definir as imagens-objeto das tabelas do banco de dados. Colocaremos essa classe na pasta [Models] do projeto, junto com as entidades EF5:

A classe [DbPamContext] será a seguinte:


using Pam.EF5.Entites;
using System.Data.Entity;

namespace Pam.Models
{
  public class DbPamContext : DbContext
  {
    public DbSet<Employe> Employes { get; set; }
    public DbSet<Cotisations> Cotisations { get; set; }
    public DbSet<Indemnites> Indemnites { get; set; }
  }
}
  • linha 6: a classe [DbPamContext] deriva da classe do sistema [DbContext];
  • linhas 8-10: as imagens-objeto das três tabelas do banco de dados. Seu tipo é [DbSet<Entity>], onde [Entity] é uma das entidades do Entity Framework que acabamos de definir. Pode-se considerar o tipo [DbSet] como uma coleção de entidades. Ela pode ser consultada com LINQ (Linguagem INtegrated Query). O leitor que não conheça LINQ deve consultar o parágrafo 3.5.4 [Apprentissage de LINQ avec LINQPad] de [refEF5].

A partir de agora, chamaremos a classe [DbPamContext] de contexto de persistência do banco de dados [dbpam_ef5]. Essa é uma terminologia comum nos ORM (Mapeadores Objeto-Relacional). Esse contexto de persistência é uma representação objetiva do banco de dados. Também se fala em sincronização do contexto de persistência com o banco de dados: as modificações, adições e exclusões feitas no contexto de persistência são refletidas no banco de dados. Essa sincronização ocorre em momentos específicos: ao fechar o contexto de persistência, ao final de uma transação ou antes de uma consulta SQL SELECT ao banco de dados.

As informações sobre o SGBD e o banco de dados são armazenadas no [App.config].

A configuração necessária no [app.config] é explicada nos parágrafos a seguir do [refEF5]:

  • 3.4 para o servidor SGBD SQL. É aqui que são estabelecidos os princípios gerais da configuração do EF5;
  • 4.2 para o SGBD MySQL.

Seguimos este último parágrafo e configuramos o arquivo [app.config] da seguinte maneira:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
    <!-- configuração EF5 -->
    <!-- cadeia de conexão com o banco de dados [dbam_ef5] -->
    <connectionStrings>
        <add name="DbPamContext"
         connectionString="Server=localhost;Database=dbpam_ef5;Uid=root;Pwd=;"
         providerName="MySql.Data.MySqlClient" />
    </connectionStrings>
    <!-- o provedor de fábrica de MySQL -->
    <system.data>
        <DbProviderFactories>
            <remove invariant="MySql.Data.MySqlClient"/>
            <add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL"
          type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=C5687FC88969C44D"
        />
        </DbProviderFactories>
    </system.data>
</configuration>
  • as linhas 6 a 21 foram adicionadas. Elas devem ser inseridas na tag <configuration> das linhas 2 e 22;
  • linhas 8 a 12: definem as cadeias de conexão com bancos de dados, um conceito do ADO.NET (consulte o parágrafo 7.3.5 no [refC#]);
  • linhas 9-11: definem a string de conexão com o banco de dados MySQL [dbpam_ef5];
  • linha 9: o nome da cadeia de conexão. Aqui, não é possível inserir qualquer valor. Por padrão, deve-se inserir o nome da classe que implementa o contexto do banco de dados:

  public class DbPamContext : DbContext
  {
    public DbSet<Employe> Employes { get; set; }
    public DbSet<Cotisations> Cotisations { get; set; }
    public DbSet<Indemnites> Indemnites { get; set; }
}

A classe se chama [DbPamContext]. Na linha 9 do [app.config], deve-se, portanto, inserir [name="DbPamContext"];

  • linha 10: uma string de conexão específica para SGBD MySQL:
    • [Server=localhost]: endereço IP da máquina que hospeda o SGBD. Neste caso, trata-se da máquina local [localhost];
    • [Database=dbpam_ef5;]: nome do banco de dados,
    • [Uid=root;]: nome de usuário com o qual vamos nos conectar ao banco de dados,
    • [Pwd=;]: senha desse login. Aqui, não há senha;
  • linha 10: [providerName="MySql.Data.MySqlClient"] é o nome do conector ADO.NET a ser utilizado. Esse nome corresponde ao atributo [invariant] da linha 17. É possível definir qualquer valor, desde que se respeite a regra anterior e que um provedor com o mesmo invariante ainda não tenha sido registrado;
  • linhas 15-20: definem uma fábrica (factory) de conectores (provider) ADO.NET. O [DbProviderFactory] é um conceito um pouco nebuloso para mim. A julgar pelo nome, seria uma classe capaz de gerar o conector ADO.NET, que dá acesso ao SGBD, neste caso, o MySQL5. Geralmente, faz-se copiar e colar essas linhas. Elas são necessárias. Preste atenção ao atributo [Version=6.5.4.0] da linha 16. Esse número de versão deve corresponder ao número de versão do DLL [MySql.Data] que você adicionou às referências do projeto:
  • A linha 16 é importante. Como não é possível instalar dois provedores com o mesmo nome, começamos removendo qualquer provedor já instalado que tenha o mesmo nome daquele que vamos instalar na linha 17;

É isso. É complicado e confuso quando se faz pela primeira vez, mas com o tempo fica simples, pois é sempre a mesma coisa que se repete.

9.22.6. Teste da camada [EF5]

Estamos prontos para testar nossa camada [EF5]. Fazemos isso usando o programa [Program.cs], que já está instalado:

Vamos exibir o conteúdo do banco de dados. Se conseguirmos, isso será um primeiro indício de que nossa configuração está correta. Um exemplo de código está disponível no parágrafo 3.5.3 do [refEF5]. O código do [Program.cs] será o seguinte:


using Pam.EF5.Entites;
using Pam.Models;
using System;

namespace Pam
{
  class Program
  {
    static void Main(string[] args)
    {
      try
      {
        using (var context = new DbPamContext())
        {
          // exibe o conteúdo das tabelas
          Console.WriteLine("Liste des employés ----------------------------------------");
          foreach (Employe employe in context.Employes)
          {
            Console.WriteLine(employe);
          }
          Console.WriteLine("Liste des indemnités --------------------------------------");
          foreach (Indemnites indemnite in context.Indemnites)
          {
            Console.WriteLine(indemnite);
          }
          Console.WriteLine("Liste des cotisations -------------------------------------");
          foreach (Cotisations cotisations in context.Cotisations)
          {
            Console.WriteLine(cotisations);
          }
        }
      }
      catch (Exception e)
      {
        Console.WriteLine(e);
        return;
      }
    }
  }
}
  • linha 13: toda operação no BD é realizada por meio do contexto desse banco de dados. Implementamos esse contexto com a classe [DbPamContext]. Também o chamamos de contexto de persistência do banco de dados;
  • linhas 13, 31: as operações no contexto de persistência são realizadas em uma cláusula [using]. O contexto de persistência é aberto no início da cláusula [using] e fechado automaticamente ao sair dessa cláusula. Isso significa que qualquer modificação feita no contexto de persistência na cláusula [using] será refletida no banco de dados ao sair da cláusula. Uma série de ordens SQL é então enviada para a BD dentro de uma transação. Isso significa que, se uma ordem SQL falhar, todas as ordens SQL emitidas anteriormente serão canceladas. Uma exceção é então lançada pela EF5;
  • linha 17: a expressão [context.Employes] designa a imagem-objeto da tabela [employes]. Vale lembrar que [Employes] é uma propriedade do contexto de persistência [DbPamContext]:

  public class DbPamContext : DbContext
  {
    public DbSet<Employe> Employes { get; set; }
    public DbSet<Cotisations> Cotisations { get; set; }
    public DbSet<Indemnites> Indemnites { get; set; }
}
  • linha 17: o fato de o [foreach] percorrer a coleção [context.Employes] trará todos os funcionários do banco de dados para o contexto de persistência. Portanto, uma ordem SQL SELECT será emitida por EF5;
  • linhas 17-20: percorre-se a coleção de funcionários e, na linha 19, utiliza-se o método [ToString] da classe [Employe] para exibir os funcionários no console;
  • linhas 21-25: o mesmo vale para a coleção de indenizações;
  • linhas 27-30: o mesmo vale para a coleção de contribuições.

Voltemos à definição da entidade [Employe]:


using System;

namespace Pam.EF5.Entites
{

  public class Employe
  {
    public int Id { get; set; }
    public string SS { get; set; }
    public string Nom { get; set; }
    public string Prenom { get; set; }
    public string Adresse { get; set; }
    public string Ville { get; set; }
    public string CodePostal { get; set; }
    public Indemnites Indemnites { get; set; }
    public int Versioning { get; set; }

    // assinatura
    public override string ToString()
    {
      return string.Format("Employé[{0},{1},{2},{3},{4},{5}, {6}, {7}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal);
    }
  }
}
  • linha 15: um funcionário possui uma referência a uma indenização.

Quando se traz um funcionário de volta ao contexto de persistência, sua indenização também é trazida? A resposta padrão é não. Esse é o conceito da entidade [Lazy Loading]. As entidades referenciadas dentro de outra entidade não são trazidas para o contexto de persistência junto com essa outra entidade. Elas só são trazidas quando solicitadas pelo código dentro de um contexto de persistência aberto. Se o contexto de persistência estiver fechado, uma exceção é lançada.

Assim, se o método [ToString] tivesse feito referência à propriedade [Indemnites] da seguinte forma:


    // assinatura
    public override string ToString()
    {
      return string.Format("Employé[{0},{1},{2},{3},{4},{5},{6},{7},{8}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
}

a operação a seguir em [Program.cs]:


          foreach (Employe employe in context.Employes)
          {
            Console.WriteLine(employe);
}

teria trazido de volta ao contexto de persistência não apenas os funcionários, mas também suas indenizações, pois, na linha 3, o método [Employe.ToString] é chamado e faz referência à entidade [Indemnites].

A execução de [Program.cs] produz os seguintes resultados:

1
2
3
4
5
6
7
8
Liste des employés -----------------------------------------
Employé[24,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203]
Employé[25,1,260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014]
Liste des indemnités -----------------------------------------
Indemnités[93,1,2,2,1,2,1,3,1,15]
Indemnités[94,1,1,1,93,2,3,12]
Liste des cotisations -----------------------------------------
Cotisations[11,1,3,49,6,15,9,39,7,88]

E se isso não funcionar? Você está com um problema... Existem várias possíveis fontes de erro:

  • verifique a configuração de EF5 (parágrafo 9.22.5);
  • verifique suas entidades do Entity Framework (parágrafo 9.22.4).

9.22.7. DLL da camada [EF5]

Transformamos nosso projeto em uma biblioteca de classes para que, na geração, seja gerado um assembly .dll em vez de um .exe. Isso é feito nas propriedades do projeto, conforme visto no parágrafo 9.7.6, para a camada de negócios simulada.


Tarefa: transforme o tipo do projeto [pam-ef5] em biblioteca de classes e, em seguida, gere novamente o projeto.


9.23. Etapa 16: implementação da camada [DAO]

9.23.1. A interface da camada [DAO]

Assim como fizemos para a camada [métier] simulada, a camada [DAO] estará acessível por meio de uma interface. Qual será ela?

Vejamos a interface [IPamMetier] da camada simulada [métier] que criamos:


    public interface IPamMetier {
        // lista de todas as identidades dos funcionários 
        Employe[] GetAllIdentitesEmployes();

        // ------- cálculo do salário 
        FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}

Na linha 3, o método [GetAllIdentitesEmployes] serve para alimentar a lista suspensa da página inicial:

Esses funcionários deverão ser pesquisados no banco de dados.

Na linha 6, o método [GetSalaire] permite calcular a folha de pagamento de um funcionário cujo número é SS. Vale lembrar a definição do tipo [FeuilleSalaire]:


  public class FeuilleSalaire
  {

    // propriedades automáticas 
    public Employe Employe { get; set; }
    public Cotisations Cotisations { get; set; }
    public ElementsSalaire ElementsSalaire { get; set; }
}

As informações das linhas 5 e 6 serão obtidas do banco de dados. Vale lembrar que um funcionário possui uma propriedade [Indemnites]. Essa informação também deverá ser recuperada.

Portanto, poderíamos partir da seguinte interface para a camada [DAO]:


    public interface IPamDao {
        // lista de todas as identidades dos funcionários 
        Employe[] GetAllIdentitesEmployes();
        // um funcionário específico com seus benefícios 
        Employe GetEmploye(string ss);
        // lista de todas as contribuições 
        Cotisations GetCotisations();
}

9.23.2. O projeto do Visual Studio


Tarefa: adicione à solução [pam-td] um novo projeto do tipo [console] chamado [pam-dao]. Defina-o como o projeto inicial da solução.


 

9.23.3. Adicionando as referências necessárias ao projeto

Vamos contextualizar o projeto como um todo:

O projeto [pam-dao] precisa de um determinado número de DLL:

  • todas as referenciadas pelo projeto [pam-ef5];
  • aquele do próprio projeto [pam-ef5].

Além disso, vamos utilizar o [Spring.net] para instanciar a camada [DAO]. Para isso, precisamos dos arquivos DLL, [Spring.core] e [Common.Logging]. Esses DLL estão na pasta [lib] do material de apoio do estudo de caso.


Tarefa: adicione essas diferentes referências ao projeto [pam-dao].


9.23.4. Implementação da camada [DAO]

Acima, a classe [PamException] é aquela definida no parágrafo 9.7.4. Basta alterar seu namespace (linha 1 abaixo):


namespace Pam.Dao.Entites
{
  // classe de exceção
  public class PamException : Exception
  {
....
  }
}

A interface [IPamDao] é aquela que acabamos de definir no parágrafo 9.23.1:


using Pam.EF5.Entites;

namespace Pam.Dao.Service
{
  public interface IPamDao
  {
    // lista de todas as identidades dos funcionários 
    Employe[] GetAllIdentitesEmployes();
    // um funcionário específico com suas indenizações 
    Employe GetEmploye(string ss);
    // lista de todas as contribuições 
    Cotisations GetCotisations();
  }
}

A classe [PamDaoEF5] implementa essa interface por meio de ORM e EF5. Seu código é o seguinte:


using Pam.Dao.Entites;
using Pam.EF5.Entites;
using Pam.Models;
using System;
using System.Linq;

namespace Pam.Dao.Service
{

  public class PamDaoEF5 : IPamDao
  {
    // campos privados 
    private Cotisations cotisations;
    private Employe[] employes;

    // Fabricante
    public PamDaoEF5()
    {
      // contribuição
      try
      {
....
      }
      catch (Exception e)
      {
        throw new PamException("Erreur système lors de la construction de la couche [DAO]", e, 1);
      }
    }

    // GetCotisations
    public Cotisations GetCotisations()
    {
      return cotisations;
    }

    // GetAllIdentitesEmploye
    public Employe[] GetAllIdentitesEmployes()
    {
      return employes;
    }

    // GetEmploye
    public Employe GetEmploye(string SS)
    {
      try
      {
....
      catch (Exception e)
      {
        throw new PamException(string.Format("Erreur système lors de la recherche de l'employé [{0}]", SS), e, 2);
      }
    }
  }
}

Observação:

  • linha 10: a classe [PamDaoEF5] implementa a interface [IPamDao];
  • as tabelas [cotisations] e [employes] são armazenadas em cache nas propriedades das linhas 13-14. Os funcionários não têm seus subsídios;
  • linhas 17-28: é o construtor que inicializa as linhas 13-14;
  • linhas 43-52: o método [GetEmploye] retorna um funcionário com seus benefícios. Ele recebe como parâmetro o número de previdência social desse funcionário. Se o funcionário não existir no banco de dados, o método retornará o ponteiro como nulo.

Tarefa: completar o código da classe [PamDaoEF5].


Para o construtor, inspirar-se-á no código de teste da camada [EF5] apresentado no parágrafo 9.22.6. Para o método [GetEmploye], inspire-se no exemplo do parágrafo 3.5.7 [Eager and Lazy loading] de [refEF5].

9.23.5. Configuração da camada [DAO]

Assim como foi feito no parágrafo 9.22.5, precisamos configurar EF5 no arquivo [App.config] do projeto:


Tarefa 1: configure EF5 no arquivo [App.config]. Basta repetir o que foi feito no arquivo [App.config] da camada [EF5].


Nosso programa de teste utilizará o [Spring.net] para obter uma referência na camada [DAO].


Tarefa 2: com base no que foi feito no parágrafo 9.20.2, modifique o arquivo de configuração [app.config] do projeto [pam-dao] para que ele defina um objeto Spring chamado [pamdao] associado à classe [PamDaoEF5] que acabamos de criar. Os arquivos [app.config] e [web.config] têm a mesma estrutura. É preciso ter cuidado para que a tag <configSections> seja a primeira tag encontrada após a tag raiz <configuration>.


9.23.6. Teste da camada [DAO]

Estamos prontos para testar nossa camada [DAO]. Fazemos isso com o programa [Program.cs], que já está instalado:

Vamos testar as diferentes funcionalidades da interface da camada [DAO]. O código de [Program.cs] será o seguinte:


using Pam.Dao.Service;
using Pam.EF5.Entites;
using Spring.Context.Support;
using System;

namespace Pam.Dao.Tests
{
  public class Program
  {
    public static void Main()
    {
      try
      {
        // instância da camada [dao]
        IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
        // lista de identidades dos funcionários 
        foreach (Employe Employe in pamDao.GetAllIdentitesEmployes())
        {
          Console.WriteLine(Employe.ToString());
        }
            // um funcionário com seus benefícios 
            Console.WriteLine("------------------------------------");
            Employe e = pamDao.GetEmploye("254104940426058");
            Console.WriteLine("employé= {0}, indemnités={1}", e, e.Indemnites);
            Console.WriteLine("------------------------------------");
        // um funcionário que não existe 
        Employe employe = pamDao.GetEmploye("xx");
        Console.WriteLine("Employé n° xx");
        Console.WriteLine((employe == null ? "null" : employe.ToString()));
        Console.WriteLine("------------------------------------");
        // lista de contribuições 
        Cotisations cotisations = pamDao.GetCotisations();
        Console.WriteLine(cotisations.ToString());
      }
      catch (Exception ex)
      {
        // exibição de exceção 
        Console.WriteLine(ex.ToString());
      }
      //pausa 
      Console.ReadLine();
    }
  }
}
  • linha 15: obtemos uma referência à camada [DAO] por meio de [Spring.net].

Os resultados da execução deste programa são os seguintes:

1
2
3
4
5
6
7
8
9
Employé[22,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203]
Employé[23,1,260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014]
------------------------------------
employé= Employé[22,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203], indemnités=Indemnités[91,1,2,2,1,2,1,3,1,15]
------------------------------------
Employé n° xx
null
------------------------------------
Cotisations[10,1,3,49,6,15,9,39,7,88]

9.23.7. DLL da camada [DAO]


Tarefa: altere o tipo do projeto [pam-dao] para biblioteca de classes e, em seguida, gere novamente o projeto (repita o que foi feito no parágrafo 9.22.7).


9.24. Etapa 17: implementação da camada [métier]

9.24.1. A interface da camada [métier]

A interface da camada [métier] será a interface [IPamMetier] da camada [métier] simulada que criamos no parágrafo 9.7.2.


    public interface IPamMetier {
        // lista de todas as identidades dos funcionários 
        Employe[] GetAllIdentitesEmployes();

        // ------- cálculo do salário 
        FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}

9.24.2. O projeto do Visual Studio


Tarefa: adicione à solução [pam-td] um novo projeto do tipo [console] chamado [pam-metier]. Defina-o como o projeto inicial da solução.


 

9.24.3. Adicionando as referências necessárias ao projeto

Vamos contextualizar o projeto como um todo:

O projeto [pam-metier] necessita de um determinado número de DLL:

  • todas as referenciadas pelos projetos [pam-dao] e [pam-ef5];
  • aquelas dos próprios projetos [pam-dao] e [pam-ef5].

Tarefa: adicione essas diferentes referências ao projeto [pam-metier].


 

9.24.4. Implementação da camada [métier]

Acima, encontramos quatro elementos já utilizados na camada [métier] simulada (consulte o parágrafo 9.7). Pode haver alterações nos espaços de nomes importados por essas diferentes classes. Gerencie-os. A classe [PamMetier] implementa a interface [IPamMetier] da seguinte maneira:


using Pam.Dao.Service;
using Pam.EF5.Entites;
using Pam.Metier.Entites;
using System;

namespace Pam.Metier.Service
{

  public class PamMetier : IPamMetier
  {

    // referência à camada [DAO] inicializada pelo Spring
    public IPamDao PamDao { get; set; }

    // lista de todas as identidades dos funcionários 
    public Employe[] GetAllIdentitesEmployes()
    {
      ...
    }

    // um funcionário específico com seus benefícios 
    public Employe GetEmploye(string ss)
    {
      ...
    }

    // as contribuições 
    public Cotisations GetCotisations()
    {
      ...
    }

    // cálculo do salário 
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
    {
      // SS: nº SS do funcionário 
      // HeuresTravaillées: número de horas trabalhadas 
      // Dias Trabalhados: número de dias trabalhados 
...
  }
}
  • linha 13: há uma referência à camada [DAO]. Ela será inicializada pelo Spring durante a instanciação da classe [PamMetier]. Portanto, quando os diferentes métodos forem executados, a linha 13 já terá sido inicializada.

Tarefa: completar o código da classe [PamMetier]. Se, em [GetSalaire], for constatado que o funcionário com o nº ss não existe, será executado um [PamException]. O modo de cálculo do salário é explicado no parágrafo 9.5. Deve-se ter o cuidado de arredondar todos os cálculos intermediários para duas casas decimais.


9.24.5. Configuração da camada [métier]

Assim como foi feito no parágrafo 9.22.5, precisamos configurar EF5 no arquivo [app.config] do projeto:


Tarefa 1: configure EF5 no arquivo [app.config]. Basta repetir o que foi feito no arquivo [app.config] da camada [EF5].


Nosso programa de teste utilizará o [Spring.net] para obter uma referência na camada [métier].


Tarefa 2: com base no que você fez anteriormente no parágrafo 9.23.5, modifique o arquivo de configuração [app.config] do projeto [pam-metier] para que ele defina um objeto Spring chamado [pammetier] associado à classe [PamMetier] que acabamos de criar. O mais simples é copiar o arquivo [app.config] do projeto [pam-dao] e adicionar o que estiver faltando.


Há uma dificuldade aqui. Não só é preciso instanciar a camada [métier] com a classe [PamMetier], como também é preciso inicializar sua propriedade [PamDao]:


    // referência à camada [DAO] inicializada pelo Spring
    public IPamDao PamDao { get; set; }

A configuração do Spring em [app.config] é, então, a seguinte:


  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object id="pamdao" type=" Pam.Dao.Service.PamDaoEF5, pam-dao"/>
      <object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier">
        <property name="PamDao" ref="pamdao" />
      </object>
    </objects>
</spring>
  • linha 6: define o objeto [pamdao] associado à classe [PamDaoEF5];
  • linha 7: define o objeto [pammetier] associado à classe [PamMetier];
  • linha 8: a tag [property] serve para inicializar uma propriedade pública da classe [PamMetier]. O atributo [name="PamDao"] corresponde ao nome da propriedade a ser inicializada na classe [PamMetier]. O atributo [ref="pamdao"] indica que a propriedade é inicializada com uma referência, a do objeto [pamdao] da linha 6, ou seja, com a referência da camada [DAO]. Era exatamente isso que queríamos.

9.24.6. Teste da camada [métier]

Estamos prontos para testar nossa camada [métier]. Fazemos isso usando o programa [Program.cs], que já está presente:

Vamos testar as diferentes funcionalidades da interface da camada [métier]. O código da [Program.cs] será o seguinte:


using System;
using Pam.Dao.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
using Pam.EF5.Entites;

namespace Pam.Metier.Tests
{
  public class Program
  {
    public static void Main()
    {
      try
      {
        // instanciação da camada [métier]
        IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        // lista de identidades dos funcionários
        Console.WriteLine("Employés -----------------------------");
        foreach (Employe Employe in pamMetier.GetAllIdentitesEmployes())
        {
          Console.WriteLine(Employe);
        }

        // cálculos das folhas de pagamento 
        Console.WriteLine("salaires -----------------------------");
        Console.WriteLine(pamMetier.GetSalaire("260124402111742", 30, 5));
        Console.WriteLine(pamMetier.GetSalaire("254104940426058", 150, 20));
        try
        {
          Console.WriteLine(pamMetier.GetSalaire("xx", 150, 20));
        }
        catch (PamException ex)
        {
          Console.WriteLine(string.Format("PamException : {0}", ex.Message));
        }
      }
      catch (Exception ex)
      {
        Console.WriteLine(string.Format("Exception : {0}, Exception interne : {1}", ex.Message, ex.InnerException == null ? "" : ex.InnerException.Message));
      }
      // pausa 
      Console.ReadLine();
    }
  }
}
  • linha 16: obtemos uma referência à camada [métier] por meio de [Spring.net].

Os resultados da execução deste programa são os seguintes:

1
2
3
4
5
6
7
Employés -----------------------------
Employé[24,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203]
Employé[25,1,260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014]
salaires -----------------------------
[Employé[25,1,260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014],Cotisations[11,1,3,49,6,15,9,39,7,88],[64,85 : 17,45 : 10 : 15 : 72,4]]
[Employé[24,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203],Cotisations[11,1,3,49,6,15,9,39,7,88],[362,25 : 97,48 : 42 : 62 : 368,77]]
PamException : L'employé de n° [xx] n'existe pas

9.24.7. DLL da camada [métier]


Tarefa: altere o tipo do projeto [pam-metier] para biblioteca de classes e, em seguida, gere novamente o projeto (repita o que foi feito no parágrafo 9.22.7).


9.25. Etapa 18: implementação da camada [web]

Chegamos à última camada da nossa arquitetura, a camada [web]:

Vamos reutilizar a camada [web] que desenvolvemos com a ajuda de uma camada [métier] simulada.

9.25.1. O projeto do Visual Studio

Voltamos ao Visual Studio Express 2012 para a Web a fim de conectar nossa camada da Web às camadas [métier, DAO, EF5] que acabamos de desenvolver. Trata-se principalmente de configurações a serem feitas e algumas alterações nos espaços de nomes.

No Visual Studio Express 2012 para a Web, carregue a solução [pam-td]:

  • no [1], a solução [pam-td] no VS Studio para a Web. O projeto web [pam-web-01] volta a ficar visível. Ele havia sido perdido no VS Studio para desktop.
  • A configuração do projeto web [pam-web-01] precisará ser alterada. Em vez de modificar um projeto que está funcionando, faremos as alterações em uma cópia desse projeto. Primeiro, no [2], removemos o projeto da solução (isso não exclui nada do sistema de arquivos).
  • no [3], usando o Explorador do Windows, duplicamos a pasta [pam-web-01] para [pam-web-02];
  • em [4], carrega-se o projeto [pam-web-02] na solução [pam-td]. Ele aparece com o nome [pam-web-01];
  • em [5], altere esse nome para [pam-web-02] e defina esse projeto como o projeto inicial;
  • em [6], carregue o projeto antigo [pam-web-01]. Agora você tem todos os seus projetos. Tome cuidado para trabalhar com o [pam-web-02].

9.25.2. Adicionando as referências necessárias ao projeto

Vamos contextualizar o projeto como um todo:

O projeto [pam-web-02] precisa de um certo número de DLL:

  • todos os referenciados pelos projetos [pam-metier], [pam-dao] e [pam-ef5];
  • aquelas dos próprios projetos [pam-metier], [pam-dao] e [pam-ef5].

Tarefa: adicione essas diferentes referências ao projeto [pam-web-02]. A referência ao projeto [pam-metier-simule] deve ser removida. Mudamos para a camada [métier]. Algumas referências DLL já estão presentes nas referências. Exclua-as e, em seguida, faça suas adições.


 

9.25.3. Implementação da camada [web]

Gere o projeto [pam-web-02]. Serão exibidos erros como o seguinte:

A classe [ApplicationModel] utiliza o tipo [Employe]. Com a camada [métier] simulada, esse tipo estava definido no espaço de nomes [Pam.Metier.Entites]. Agora, ele está no espaço de nomes [Pam.EF5.Entites]. Corrija esses erros conforme mostrado acima.

9.25.4. Configuração da camada [web]

Assim como foi feito no parágrafo 9.24.5, precisamos configurar EF5 no arquivo [web.config] do projeto:


Tarefa 1: substitua todo o conteúdo atual de [web.config] pelo conteúdo do arquivo [app.config] do projeto [pam-metier].


O arquivo [Global.asax] do nosso aplicativo web utiliza o [Spring.net] para recuperar uma referência na camada [métier]:


      try
      {
        // instanciação da camada [métier]
        application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
      }
      catch (Exception ex)
      {
        application.InitException = ex;
}

Na linha 4, solicita-se uma referência ao objeto Spring denominado [pammetier]. Esse é, de fato, o nome atribuído à camada [métier] (verifique isso no seu arquivo [web.config]).

9.25.5. Teste da camada [web]

Estamos prontos para testar nossa camada [web]. Primeiro, vamos alterar sua porta de trabalho. Por padrão, a [pam-web-02] tem a configuração da [pam-web-01] e, portanto, opera na mesma porta. A experiência mostra que isso causa problemas: o IIS continua, então, a utilizar os códigos do projeto [pam-web-01]. Proceda da seguinte maneira:

No [4], altere o número da porta, por exemplo, alterando o dígito das unidades.

Executa-se o projeto [pam-web-02] por meio do [Ctrl-F5]. Obtém-se, então, a seguinte página inicial:

No [1], obtêm-se os funcionários do banco de dados [dbpam_ef5]. Observe-se que não aparece mais o funcionário [X X] que tínhamos com a camada [métier] simulada. Vamos fazer uma simulação:

Em [2], obtemos de fato o salário real e não mais um salário fictício. Agora, vamos interromper o SGBD e o MySQL5 e fazer outra simulação:

No [3], obtivemos uma página de erros legível, mesmo que algumas mensagens estejam em inglês. Agora, vamos parar novamente o MySQL e reexecutar o aplicativo no VS, passando pelo [Ctrl-F5]:

 

Obtemos a visualização [initFailed.cshtml] criada no parágrafo 9.20.4. Ela exibe as mensagens de erro da pilha de exceções. O leitor é convidado a realizar outros testes.

9.26. Etapa 19: disponibilizar na Internet um aplicativo ASP.NET

Ao desenvolver um aplicativo ASP.NET com o Visual Studio, a configuração padrão faz com que o aplicativo desenvolvido só seja acessível no endereço [localhost]. Qualquer outro endereço é rejeitado pelo servidor integrado do Visual Studio, que, então, retorna o erro [400 Bad Request].

Isso pode ser observado da seguinte maneira:

  • em uma janela DOS, observe o endereço IP da sua máquina de desenvolvimento:

Microsoft Windows [version 6.3.9600]
(c) 2013 Microsoft Corporation. Tous droits réservés.

dos>ipconfig

Configuration IP de Windows



Carte Ethernet Connexion au réseau local :

   Suffixe DNS propre à la connexion. . . : ad.univ-angers.fr
   Adresse IPv6 de liaison locale. . . . .: fe80::698b:455a:925:6b13%4
   Adresse IPv4. . . . . . . . . . . . . .: 172.19.81.34
   Masque de sous-réseau. . . . . . . . . : 255.255.0.0
   Passerelle par défaut. . . . . . . . . : 172.19.0.254

Carte réseau sans fil Wi-Fi :

   Statut du média. . . . . . . . . . . . : Média déconnecté
   Suffixe DNS propre à la connexion. . . :

O endereço IP está indicado aqui na linha 14. Se você tiver uma conexão Wi-Fi, o endereço Wi-Fi do computador aparecerá nas linhas 20 e seguintes.

  • Verifique as propriedades do projeto [clic droit sur projet / propriétés / onglet web]:
 

O aplicativo será executado na porta [65010] da máquina [localhost].

  • Execute seu projeto por meio de [Ctrl-F5]

Image

  • Substitua [localhost] pelo endereço IP da estação:

Image

O servidor retornou uma resposta [400 Bad Request]. O servidor IIS Express utilizado pelo Visual Studio aceita apenas o nome [localhost].

Para tornar a aplicação desenvolvida acessível a um URL do tipo [http://adresseIP/contexte/...], é necessário utilizar um servidor diferente do IIS Express, por exemplo, um servidor IIS (não Express). Para verificar se ele está presente (normalmente nas versões Pro do Windows), é preciso acessar o Painel de Controle [Panneau de configuration\Système et sécurité\Outils d’administration]:

Image

Essa opção nem sempre está disponível. Nesse caso, é preciso acessar o [ Panneau de configuration \ Programmes] e instalar as Ferramentas de Administração da Web.

Assim que a opção [Gestionnaire des services internet (IIS)] estiver disponível, ative-a:

Inicializa-se o site padrão. Para isso, é necessário que o serviço [Service de publication World Wide Web] já esteja em execução:

Feito isso, acesse o URL [http://localhost] com um navegador. Verifique antes se outro servidor web já não está ocupando a porta 80. Se estiver, desligue-o.

O servidor IIS nos respondeu. Agora, substitua [localhost] pelo endereço IP do seu computador:

Funcionou. Voltemos agora ao Visual Studio:

  • primeiro, é preciso iniciar o Visual Studio no modo [administrateur]

Feito isso, é preciso alterar a configuração do projeto web que queremos implantar [clic droit sur projet / propriétés / onglet web]:

É preciso selecionar o servidor local IIS como servidor de implantação. O Visual Studio define o URL do aplicativo. É possível alterá-lo. Execute o projeto por meio de [Ctrl-F5]:

Agora, substitua [localhost] pelo endereço IP do seu computador:

Caso não haja disponibilidade do servidor IIS, é possível utilizar um servidor ASP.NET gratuito, como o [Ultidev Web Server Pro], disponível no URL [http://ultidev.com/Download/ ]. Após a instalação, há dois métodos para executar um aplicativo web com esse servidor:

A maneira rápida

Abra um explorador do Windows e selecione a pasta da aplicação ASP.NET a ser implantada:

O servidor web é então iniciado e a aplicação web exibida em um navegador:

  • Em [3], é possível parar ou iniciar o servidor web;
  • em [4], é possível alterar a porta de serviço do aplicativo web;

Antes de iniciar o servidor, é necessário que o serviço [UWS HiPriv Services] abaixo esteja em execução:

Depois que o servidor for iniciado, a interface será exibida da seguinte forma:

Ao clicar no link [6], é exibida a primeira página do aplicativo:

É possível, então, substituir [localhost] pelo endereço IP da máquina:

Portanto, também neste caso, apenas o nome [localhost] é aceito.

O método mais demorado

Inicie o aplicativo Ultidev Web Explorer

e siga as etapas a seguir:

  • em [8], especifique a pasta do aplicativo web a ser implantado;
  • devido a [10-11], a aplicação web deverá ser solicitada com o URL [http://localhost:81/];
  • inicie o servidor web com [14];
  • solicite o URL [19];
  • no [20], obtivemos a página desejada usando o endereço local IP da máquina, em vez do nome [localhost]. Era isso que buscávamos;

O servidor Ultidev foi instalado como um serviço do Windows que é iniciado automaticamente. Você pode desativar o início automático do servidor Ultidev da seguinte maneira:

  • selecione a opção [Panneau de configuration\Système et sécurité\Outils d’administration];
  • [1, 2]: selecione as propriedades do serviço [Ultidev Web Server Pro];
  • [3]: configure-o para inicialização manual.

Para iniciar o servidor manualmente, use, por exemplo, o aplicativo [Ultidev Web Explorer]:

9.27. Etapa 20: geração de um aplicativo nativo para Android

Quando se tem um aplicativo web do tipo APU (aplicativo de página única), é possível gerar um executável para dispositivos móveis (Android, IoS, Windows 8, ...) com a ferramenta [Phonegap] [http://phonegap.com/]. Existem outras maneiras de fazer isso, principalmente com o produto de código aberto Apache Cordova [https://cordova.apache.org/]. A ferramenta disponível online no site do Phonegap [http://build.phonegap.com/apps] “carrega” o arquivo zip do site a ser convertido. A página inicial deve se chamar [index.html] e deve ser uma página estática, ou seja, não deve ser gerada por um framework web (ASP.NET, JEE, PHP, ...). Vamos começar criando essa página.

9.27.1. A arquitetura do aplicativo

É importante lembrar aqui que queremos criar um aplicativo Android. Um aplicativo desse tipo geralmente possui a seguinte arquitetura:

  • em [1], o usuário utiliza um tablet Android que se comunica com um ou mais serviços web [2];

Voltemos ao modelo APU:

  • uma página inicial é carregada no navegador (o esquema acima não indica de onde ela vem);
  • as visualizações seguintes são obtidas por meio de chamadas Ajax [1-4]. Nenhuma nova página será carregada pelo navegador;

A visualização inicial pode ou não ser fornecida pelo mesmo servidor que as outras visualizações obtidas por meio de chamadas Ajax. Se não for fornecida pelo mesmo servidor, o JavaScript da página inicial deve conhecer o URL do servidor web que fornecerá as outras visualizações. Esse será o caso no aplicativo Android que vamos construir:

  • a página estática [index.html] será encapsulada em um aplicativo nativo para Android [1] que possui os recursos de um navegador, sendo, portanto, capaz de executar o JavaScript incorporado na página [index.html];
  • essa página obterá as demais visualizações por meio de chamadas Ajax ao servidor [2]. Para isso, ela precisa conhecer o URL do servidor web;

Vamos refatorar o aplicativo [pam-web-02] para que ele funcione nesse modo. Assim, a primeira página será a seguinte:

  • em [1], o URL da página inicial do aplicativo. Ele nos será fornecido pelo servidor Ultidev, abordado no parágrafo 9.26;
  • em [2], o usuário deverá inserir o URL do simulador de folha de pagamento. Seria possível inseri-lo diretamente no código JavaScript da página inicial, mas isso complicaria os testes: assim que alterássemos o endereço do simulador IP (ou a porta), seria necessário alterá-lo no código JavaScript;
  • para [3], o link [Connexion] que irá buscar a próxima visualização:
  • vale notar que, em [4], o URL do navegador não mudou. Continua sendo o da página inicial e permanecerá assim durante toda a vida útil do aplicativo.

Uma vez obtida essa visualização, tudo funciona como antes: as diferentes visualizações são obtidas por meio de chamadas Ajax. Veremos que muito pouco código precisa ser alterado.

9.27.2. Reestruturação do projeto [pam-web-02]

Dentro da pasta [Content] do projeto [pam-web-02], criamos a seguinte pasta [bootstrap] (o nome não importa):

Incluímos nela a página estática [index.html] e todos os recursos de que ela necessita (arquivos CSS e JS). A página [index.html] retoma o código da página mestre [_Layout.cshtml] do projeto do Visual Studio, removendo tudo o que não é estático. Isso resulta no seguinte código:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Simulateur de paie</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="Site.css" />
    <script type="text/javascript" src="jquery-1.8.2.min.js"></script>
    <script type="text/javascript" src="jquery.validate.min.js"></script>
    <script type="text/javascript" src="jquery.validate.unobtrusive.min.js"></script>
    <script type="text/javascript" src="globalize.js"></script>
    <script type="text/javascript" src="globalize.culture.fr-FR.js"></script>
    <script type="text/javascript" src="jquery.unobtrusive-ajax.min.js"></script>
    <script type="text/javascript" src="myScripts.js"></script>
</head>
<body>
    <table>
        <tbody>
            <tr>
                <td>
                    <h2>Simulateur de calcul de paie</h2>
                </td>
                <td style="width: 20px">
                    <img id="loading" style="display: none" src="indicator.gif" />
                </td>
                <td>
                    <a id="lnkConnexion" href="javascript:connexion()">
                        | Connexion<br />
                    </a>
                    <a id="lnkFaireSimulation" href="javascript:faireSimulation()">
                        | Faire la simulation<br />
                    </a>
                    <a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">
                        | Effacer la simulation<br />
                    </a>
                    <a id="lnkVoirSimulations" href="javascript:voirSimulations()">
                        | Voir les simulations<br />
                    </a>
                    <a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">
                        | Retour au formulaire de simulation<br />
                    </a>
                    <a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">
                        | Enregistrer la simulation<br />
                    </a>
                    <a id="lnkTerminerSession" href="javascript:terminerSession()">
                        | Terminer la session<br />
                    </a>
                </td>
        </tbody>
    </table>
    <hr />
    <div id="content">
        <table>
            <tr>
                <td>URL du simulateur</td>
                <td><input type="text" id="urlServiceWeb" name="urlServiceWeb" size="80"></td>
            </tr>
        </table>
        <div id="erreur">
            <h3>Réponse du serveur :</h3>
            <div id="erreur1"></div>
            <div id="erreur2"></div>
        </div>
    </div>
</body>
</html>

Adicionamos os seguintes itens:

  • linhas 27-29: adicionamos a opção de menu [Connexion] para permitir a conexão com o serviço de simulação;
  • linhas 55-56: a inserção do URL do simulador;
  • linhas 59-63: uma mensagem de erro caso a conexão falhe;

A refatoração do código ocorre exclusivamente no código [myScripts.js] da linha 14 acima. Nada mais muda. O código passa a ter a seguinte forma:


// ao carregar o documento
$(document).ready(function () {
    // recuperam-se as referências dos diferentes componentes da página
    loading = $("#loading");
    content = $("#content");
    erreur = $("#erreur");
    erreur1 = $("#erreur1");
    erreur2 = $("#erreur2");
    // os links do menu
    lnkConnexion = $("#lnkConnexion");
    lnkFaireSimulation = $("#lnkFaireSimulation");
    lnkEffacerSimulation = $("#lnkEffacerSimulation");
    lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
    lnkVoirSimulations = $("#lnkVoirSimulations");
    lnkTerminerSession = $("#lnkTerminerSession");
    lnkRetourFormulaire = $("#lnkRetourFormulaire");
    // eles são colocados em uma tabela
    options = [lnkConnexion, lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
    // ocultamos alguns elementos da página
    loading.hide();
    erreur.hide();
    // fixamos o menu
    setMenu([lnkConnexion]);
});
  • linhas 6-8: os identificadores da área que exibe os erros de conexão na página [index.html];
  • linha 10: o novo link para conexão com o simulador;
  • linha 21: a área de erros está inicialmente oculta;
  • linha 23: é exibido apenas o link de conexão;

Na página [index.html], o link de conexão é definido da seguinte forma:


<a id="lnkConnexion" href="javascript:connexion()">
| Connexion<br />
</a>

A função JS [connexion] (linha 1) é a seguinte:


var urlServiceWeb;
var erreur, erreur1, erreur2;


function connexion() {
    // recuperamos o urlServiceWeb do serviço web
    urlServiceWeb = $("#urlServiceWeb").val();
    // recupera-se o formulário de preenchimento
    $.ajax({
        url: urlServiceWeb + '/Pam/Formulaire',
        type: 'POST',
        dataType: 'html',
        beforeSend: function () {
            // luz de espera acesa
            loading.show();
        },
        success: function (data) {
            // exibição dos resultados
            content.html(data);
            // menu
            setMenu([lnkFaireSimulation]);
        },
        error: function (jqXHR) {
            erreur2.html(jqXHR.responseText);
            erreur1.html(jqXHR.getAllResponseHeaders().replace(/\r\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/\n/g, "<br/>"));
            erreur.show();
        },
        complete: function () {
            // luz de espera apagada
            loading.hide();
        }
    });
}
  • linha 7: recupera-se o valor URL inserido pelo usuário. Ele é armazenado na variável global da linha 1. Assim, estará disponível nas demais funções do arquivo;
  • linha 10: é feita uma chamada Ajax para o URL [/Pam/Formulaire] do simulador. Esse URL exibe uma visão parcial do preenchimento das informações da simulação (funcionários, horas trabalhadas, dias trabalhados). Na versão inicial do [pam-web-02], esse URL era suficiente. Ele era automaticamente precedido pelo URL, que havia carregado a página inicial. Agora, parte-se do pressuposto de que a página inicial pode ser fornecida por um servidor diferente daquele que hospeda o simulador. É necessário, então, prefixar o URL [/Pam/Formulaire] com a variável [urlServiceWeb] da linha 1, que é o URL do simulador (por exemplo, http://172.19.81.34/pam-web-02). Isso deverá ser feito para todas as chamadas Ajax do arquivo;
  • linhas 17-22: caso a conexão seja bem-sucedida, a visualização parcial [Formulaire.cshtml] é exibida e é exibido um menu com o único link [Faire la simulation] (linha 21);
  • linhas 23-27: caso a conexão falhe:
    • na linha 24, exibe-se a resposta HTML enviada pelo servidor web (se houver);
    • na linha 25, exibem-se os cabeçalhos HTTP enviados pelo servidor web (caso ele tenha respondido);

É isso. Em caso de sucesso, obtém-se a seguinte página:

Estamos, então, na situação anterior, em que agora as visualizações são obtidas por meio de chamadas Ajax. Assim, conforme mostrado acima, o clique no link [Faire la simulation] será executado pelo código a seguir, contido no arquivo [myScripts.js]:


function faireSimulation() {
    // recuperando referências
    var simulation = $("#simulation");
    var formulaire = $("#formulaire");
    // formulário válido?
    var formValid = formulaire.validate().form();
    if (!formValid) return;
    // fazendo uma chamada Ajax manualmente
    $.ajax({
        url: urlServiceWeb + '/Pam/FaireSimulation',
        type: 'POST',
        data: formulaire.serialize(),
        dataType: 'html',
        ...
    });
    // menu
    setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
  • foi feita uma única alteração, na linha 10, onde o código anterior URL agora é precedido pelo código do simulador;

9.27.3. Teste do projeto refatorado

No parágrafo 9.26, mostramos como instalar o aplicativo [pam-web-02] no servidor Ultidev. Partiremos daí:

  • em [6], solicitamos a exibição da página [bootstrap/index.html]. Obtemos a seguinte visualização:

Digitemos um URL incorreto:

  • em [10], os cabeçalhos HTTP da resposta do servidor;
  • em [11], o documento HTML da resposta do servidor;

Se digitarmos o código correto URL:

obtém-se a seguinte resposta:

9.27.4. Criação do binário para Android

Vamos criar o binário do Android a partir do site estático que acabamos de criar e testar [1]:

Adicionamos, em [2], um arquivo [config.xml] que servirá para configurar o plugin [Phonegap], responsável por gerar o binário do Android. Seu código é o seguinte:


<?xml version='1.0' encoding='utf-8'?>
<widget id="android.exemples.pam" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>Pam</name>
    <description>
        IstiA - Université d'Angers
    </description>
    <author email="serge.tahe@univ-angers.fr">
      Serge Tahé
    </author>
    <content src="index.html" />
    <access origin="*" />
    <allow-navigation href="*" />
    <allow-intent href="*" />
    <plugin name="cordova-plugin-whitelist" />
</widget>
  • linhas 7-9: insira aqui seus dados de contato;
  • linhas 11-13: essas linhas permitem que o JavaScript incorporado ao aplicativo web, que será executado no dispositivo Android, faça solicitações a arquivos URL externos a esse dispositivo;

Compactamos o conteúdo da pasta [Content/bootstrap]:

 

Em seguida, acessamos o site do PhoneGap [http://build.phonegap.com/apps]:

  • Antes de [1], talvez seja necessário criar uma conta;
  • em [1], começamos;
  • em [2], escolhe-se um plano gratuito que permite apenas um aplicativo PhoneGap;
  • em [3], baixe o aplicativo compactado em [4];
  • em [5], o nome do aplicativo;
  • clique no link [6] para compilar os binários de OS e IoS, para Android e Windows. Isso pode levar alguns segundos;
  • em [7-9], baixe o binário para Android;

Inicie um emulador [GenyMotion] para um tablet Android (consulte o parágrafo 11.1):

 

Acima, iniciamos um emulador de tablet com o Android API 21. Após iniciar o emulador,

  • desbloqueie-o deslizando a trava (se houver) para o lado e soltando-a;
  • com o mouse, arraste o arquivo [Pam-debug.apk] que você baixou e solte-o no emulador. Ele será então instalado e executado;

Configure o [1] e o URL do simulador conforme descrito no parágrafo 9.27.3. Feito isso, conecte-se ao simulador pelo link [2]:

Teste o aplicativo no emulador. Ele deve funcionar.