Skip to content

9. Estudo de caso

9.1. Introdução

Apresentaremos um estudo de caso publicado anteriormente num artigo disponível no URL [http://tahe.developpez.com/dotnet/pam-aspnet/]. Nesse artigo, o estudo de caso é implementado utilizando o ASP.NET clássico e o ORM NHibernate. Aqui, iremos implementá-lo utilizando o ASP.NET MVC e o ORM Entity Framework. Tal como no artigo existente, o estudo de caso é apresentado como um trabalho de laboratório universitário. Destina-se, portanto, a estudantes. Para todas as questões, são fornecidas referências aos capítulos que acabámos de detalhar, a fim de indicar material de leitura útil.

9.2. : O Problema a Resolver

Queremos escrever uma aplicação web que permita a um utilizador simular cálculos de folha de pagamento para prestadores de cuidados infantis na associação «Maison de la petite enfance» num município local. Iremos concentrar-nos tanto na organização do código .NET da aplicação como no próprio código.

A aplicação será uma aplicação de página única (SPA) e utilizará apenas chamadas Ajax para comunicar com o servidor. Apresentará ao utilizador as seguintes vistas:

  • a vista [VueSaisies], que exibe o formulário de simulação

Image

  • a vista [VueSimulation], utilizada para apresentar os resultados detalhados da simulação:

Image

  • a vista [SimulationsView], que lista as simulações realizadas pelo cliente

Image

  • a vista [VueSimulationsVides], que indica que o cliente não tem simulações ou já não tem mais simulações:

Image

  • a vista [VueErreurs], que indica um ou mais erros (neste caso, o SGBD MySQL foi encerrado):

Image

9.3. Arquitetura da aplicação

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

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

Começaremos por construir esta aplicação com uma camada [de negócio] simulada:

Isto permitir-nos-á concentrar-nos exclusivamente na camada [web]. A camada [business] simulada seguirá a interface da camada [business] real. Assim que a camada [web] estiver operacional, construiremos então as camadas [business], [DAO] e [EF5].

9.4. A base de dados

Os dados estáticos necessários para criar o recibo de vencimento estão armazenados numa base de dados MySQL denominada [dbpam_ef5] (pam = Paie Assistante Maternelle). Esta base de dados tem um administrador denominado root, sem palavra-passe. Possui três tabelas:

Image

Existe uma relação de chave estrangeira entre a coluna EMPLOYEES(INDEMNITE_ID) e a coluna INDEMNITIES(ID). A estrutura desta base de dados é ditada pela sua utilização com o EF5. Voltaremos a este assunto quando construirmos as camadas inferiores da aplicação.

Tabela EMPLOYEES: contém informações sobre os vários prestadores de cuidados infantis

Estrutura:

ID: chave primária incrementada automaticamente pelo SGBDSS: número de segurança social do funcionário - único NOME Apelido do funcionário NOME PRÓPRIO Nome próprio ENDEREÇO Endereço dele/dela CIDADE Cidade dele/dela CÓDIGO POSTAL Código postal dele/dela VERSIONAMENTO Um número inteiro que é incrementado automaticamente cada vez que o registo é modificado ID_INDEMNIZAÇÃO Chave estrangeira no campo [ID] da tabela [INDEMNIZAÇÕES]

Image

O seu conteúdo poderia ser o seguinte:

Image

Tabela COTISATIONS: contém as taxas das contribuições para a segurança social deduzidas do salário

Estrutura:

IDChave primária incrementada automaticamente pelo SGBDCsGRDSpercentagem: contribuição social geral + contribuição para o pagamento da dívida socialCSGDpercentagem: contribuição social geral dedutívelSECUpercentagem: segurança socialPENSIONpercentagem: pensão complementar + seguro de desempregoVERSIONINGum número inteiro que é incrementado automaticamente cada vez que o registo é modificado

Image

O seu conteúdo pode ser o seguinte:

Image

As taxas de contribuição para a segurança social são independentes do trabalhador. A tabela anterior tem apenas uma linha.

Tabela INDEMNITIES: lista os vários subsídios com base no índice do funcionário

ID: chave primária incrementada automaticamente pelo SGBDI; INDEX: índice salarial - único; BASE_HOUR: valor líquido em euros por uma hora de serviço de plantão; DAILY_MAINTENANCE: subsídio diário em euros por dia de assistência; MEAL_DAY: subsídio de refeição em euros por dia de assistência; PAID_LEAVE_ALLOWANCE: subsídio de férias pagas. Trata-se de uma percentagem aplicada ao salário base. VERSIONING: um número inteiro que é autoincrementado cada vez que o registo é modificado

Image

O seu conteúdo pode ser o seguinte:

Image

9.5. Método de cálculo do salário de uma prestadora de cuidados infantis

Vamos agora explicar como é calculado o salário mensal de uma prestadora de cuidados infantis. Como exemplo, utilizaremos o salário da Sra. Marie Jouveinal, que trabalhou 150 horas ao longo de 20 dias durante o período de pagamento.

, são tidos em conta os seguintes fatores:
[TOTALHOURS]: total de horas trabalhadas no mês
[TOTALDAYS]: total de dias trabalhados no mês
[TOTALHOURS]=150
[TOTALDAYS] = 20
O salário base do prestador de cuidados infantis é calculado utilizando a seguinte fórmula:
[SALÁRIO BASE] = ([TOTALHOURS] * [TARIFA HORÁRIA]) * (1 + [CPALLOWANCE] / 100)
[SALÁRIO BASE] = (150 * [2,1]) * (1 + 0,15) = 362,25
É necessário deduzir várias contribuições para a segurança social deste salário base:
Contribuição social geral e contribuição para o reembolso da dívida social: [SALÁRIO BASE]*[CSGRDS/100]
Contribuição social geral dedutível: [SALÁRIO BASE]*[CSGD/100]
Segurança Social, Pensões de Viúva e de Velhice: [SALÁRIO BASE]*[SECU/100]
Pensão complementar + AGPF + Seguro de desemprego: [SALÁRIO BASE]*[PENSÃO/100]
CSGRDS: 12,64
CSGD: 22,28
Segurança Social: 34,02
Pensão: 28,55
Total das contribuições para a Segurança Social:
[CONTRIBUIÇÕES SOCIAIS] = [SALÁRIO BASE] * (CSGRDS + CSGD + SECU + REFORMA) / 100
[CONTRIBUIÇÕES-SOCIAIS] = 97,48
Além disso, a prestadora de cuidados infantis tem direito a um subsídio de subsistência diária e a um subsídio de refeição por cada dia trabalhado. Como tal, recebe os seguintes subsídios:
[Subsídios]=[TOTALDIAS]*(SUBSÍDIO DE SUBSISTÊNCIA DIÁRIO+SUBSÍDIO DE REFEIÇÃO DIÁRIO)
[SUBSÍDIOS]=104
No final, o salário líquido a ser pago à prestadora de cuidados infantis é o seguinte:
[SALÁRIO BASE] - [CONTRIBUIÇÕES PARA A SEGURANÇA SOCIAL] + [SUBSÍDIOS]
[SALÁRIO LÍQUIDO]=368,77

9.6. O projeto do Visual Studio para a camada [web]

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

  • em [1], a estrutura geral do projeto [pam-web-01];
  • em [2], a pasta [Content] é onde os recursos estáticos do projeto são armazenados:
    • [indicator.gif]: a imagem animada que mostra a espera pela conclusão de um pedido Ajax,
    • [standard.jpg]: a imagem de fundo para as várias vistas,
    • [Site.css]: a folha de estilo da aplicação;
  • em [3], o único controlador da aplicação [PamController];
  • em [4], classes necessárias à aplicação, mas que não podem ser classificadas como componentes MVC:
    • [ApplicationModelBinder]: a classe que permite que os dados do âmbito [Application] sejam incluídos no modelo de ação,
    • [SessionModelBinder]: a classe que permite que os dados do âmbito [Session] sejam incluídos no modelo de ação,
    • [Static]: uma classe auxiliar com métodos estáticos;
  • em [5], os modelos da aplicação, sejam eles modelos de ação ou de visualização:
    • [ApplicationModel]: um modelo que contém dados do âmbito [Application],
    • [SessionModel]: um modelo que contém dados do âmbito [Session],
    • [Simulation]: uma classe que encapsula os elementos de uma simulação de cálculo salarial,
    • [IndexModel]: modelo da primeira vista [Index] apresentada pela aplicação;
  • em [6], os scripts JS necessários para a globalização da aplicação;
  • em [7], os scripts JS da família JQuery necessários para a internacionalização, validação do lado do cliente e funcionalidade AJAX da aplicação;
  • em [8], [myScripts.js] é o ficheiro que contém os nossos próprios scripts JS;
  • em [9], as vistas da aplicação:
    • [Index]: a página inicial,
    • [Form]: formulário para introduzir informações dos funcionários e as suas horas e dias trabalhados,
    • [Simulation]: a vista que exibe uma simulação,
    • [Simulações]: a vista que exibe a lista de simulações realizadas,
    • [Erros]: a vista que exibe uma lista de eventuais erros,
    • [InitFailed]: a vista que exibe mensagens de erro caso a inicialização da aplicação falhe;
  • em [10], a página mestre da aplicação [_Layout];
  • em [11], os ficheiros [Web.config] e [Global.asax] utilizados para configurar a aplicação.

9.7. Passo 1 – Configurar a camada [de negócios] simulada

A partir deste ponto, descrevemos os passos a seguir para concluir o estudo de caso. Quando relevante, indicamos o número do capítulo para que possa consultá-lo, se necessário, para concluir a tarefa. Alguns elementos do projeto estão disponíveis numa pasta [aspnetmvc-support.zip] no site deste documento. Nela, encontrará a pasta [case-study-support] com o seguinte conteúdo:

Image

O projeto também inclui elementos apresentados em capítulos anteriores. Pode simplesmente recuperá-los copiando e colando entre este PDF e o Visual Studio.

9.7.1. A solução do Visual Studio para a aplicação completa

Primeiro, iremos criar uma solução do Visual Studio na qual iremos criar dois projetos:

  • um projeto para a camada [business] simulada;
  • um projeto para a camada web MVC.

Iremos utilizar duas ferramentas:

  • Visual Studio Express 2012 for Desktop, que será utilizado para construir a camada [de negócios];
  • Visual Studio Express 2012 for the Web, que será utilizado para construir a camada [web].

Utilizando o Visual Studio Express for Desktop, criamos uma solução [pam-td]:

  • Em [1], selecione uma aplicação C#;
  • Em [2], selecione [Aplicação de Consola];
  • Em [3], atribua um nome à solução;
  • Em [4], crie um diretório para esta solução;
  • em [5], nomeie a camada [business];
  • em [6], a solução gerada.

9.7.2. A interface da camada [business]

Numa arquitetura em camadas, é boa prática que a comunicação entre camadas ocorra através de interfaces:

Que interface deve a camada [de negócios] apresentar à camada [web]? Que interações são possíveis entre estas duas camadas? Recordemos a interface web que será apresentada ao utilizador:

  1. Ao abrir o formulário, a lista de funcionários deve aparecer em [1]. Basta uma lista simplificada (Apelido, Nome, Número de Segurança Social). O Número de Segurança Social é necessário para aceder a informações adicionais sobre o funcionário selecionado (campos 6 a 11).
  2. As informações 12 a 15 correspondem às várias taxas de contribuição.
  3. As informações 16 a 19 são os subsídios do funcionário
  4. As informações 20 a 24 são os componentes salariais calculados com base nas entradas do utilizador 1 a 3.

A interface [IPamMetier] fornecida à camada [web] pela camada [business] deve cumprir os requisitos acima. Existem muitas interfaces possíveis. Propomos o seguinte:


using Pam.Metier.Entites;
namespace Pam.Metier.Service
{
  public interface IPamMetier
  {
    // list of all employee identities 
    Employe[] GetAllIdentitesEmployes();
 
    // ------- salary calculation 
    FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
  }
}
  • linha 7: o método que irá preencher a caixa de combinação [1]
  • linha 10: o método que irá recuperar as informações 6 a 24. Estas foram recolhidas num objeto do tipo [PayrollSheet], que descreveremos em breve.

Colocaremos esta interface numa pasta [business/department]:

9.7.3. Entidades na camada [business]

A interface anterior utiliza duas classes, [Employee] e [PayStub], que precisamos de definir:

  • [Employee] representa uma linha na tabela [employees] da base de dados;
  • [PayStub] representa o recibo de vencimento de um funcionário.

As entidades serão colocadas numa pasta [business/entities] dentro do projeto:

Na arquitetura final, a camada [business] irá manipular as representações das entidades da base de dados:

Image

Utilizaremos as seguintes classes para representar as linhas das três tabelas da base de dados. Consulte a Secção 9.4 para conhecer o significado dos vários campos.

Classe [Employee]

Representa uma linha na tabela [employees]. O 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; }
 
    // signature
    public override string ToString()
    {
      return string.Format("Employé[{0},{1},{2},{3},{4},{5}]", SS, Nom, Prenom, Adresse, Ville, CodePostal);
    }
  }
}

Classe [Subsídios]

Representa uma linha na tabela [indemnites]. O 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; }
    // signature
    public override string ToString()
    {
      return string.Format("Indemnités[{0},{1},{2},{3},{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
    }
  }
}

Classe [Contribuições]

Representa uma linha na tabela [contributions]. O 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; }
    // signature
    public override string ToString()
    {
      return string.Format("Cotisations[{0},{1},{2},{3}]", CsgRds, Csgd, Secu, Retraite);
    }
  }
}

Note que as classes não incluem as colunas [ID] e [VERSIONING] das tabelas. Estas colunas, que são úteis ao utilizar o ORM EF5, não são necessárias no contexto da camada [de negócio] simulada.

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


namespace Pam.Metier.Entites
{
  public class FeuilleSalaire
  {
 
    // automatic properties 
    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: campos 6 a 11 para o funcionário cujo salário está a ser calculado e campos 16 a 19 para os seus subsídios. Note-se que um objeto [Employee] encapsula um objeto [Allowances] que representa os seus subsídios;
  • linha 8: informações 12 a 15;
  • linha 9: informações 20 a 24;
  • linhas 12–14: o método [ToString].

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


namespace Pam.Metier.Entites
{
  public class ElementsSalaire
  {
    // automatic properties 
    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: componentes salariais, conforme explicado nas regras de negócio descritas acima;
  • linha 6: o salário base do funcionário, com base no número de horas trabalhadas;
  • linha 7: contribuições deduzidas deste salário base;
  • linhas 8 e 9: subsídios a adicionar ao salário base, com base no grau do funcionário e no número de dias trabalhados;
  • linha 10: o salário líquido a pagar;
  • linhas 14–17: o método [ToString] da classe.

9.7.4. A classe [PamException]

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


using System;
 
namespace Pam.Metier.Entites
{
  // exceptional class
  public class PamException : Exception
  {
 
    // the error code 
    public int Code { get; set; }
 
    // manufacturers 
    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: possui uma propriedade pública [Code] que é um código de erro;
  • Na nossa aplicação, utilizaremos dois tipos de construtores:
  • o das linhas 23–27, que pode ser utilizado como mostrado abaixo:
throw new PamException("Problème d'accès aux données",5);
  • (continuação)
    • ou o das linhas 29–33, concebido para propagar uma exceção que tenha ocorrido, envolvendo-a numa exceção [PamException]:
try{
....
}catch (IOException ex){
    // on encapsule l'exception ex    
    throw new PamException("Problème d'accès aux données",ex,10);
}

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

9.7.5. Implementação da camada [business]

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
  {
    // list of cached employees
    public Employe[] Employes { get; set; }
    // employees indexed by their SS number
    private IDictionary<string, Employe> dicEmployes = new Dictionary<string, Employe>();
 
    // list of employees
    public Employe[] GetAllIdentitesEmployes()
    {
...
      // we return the list of employees
      return Employes;
    }
 
    // salary calculation
    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 segurança social;
  • linhas 15–20: o método que devolve 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:


// list of employees
    public Employe[] GetAllIdentitesEmployes()
    {
      if (Employes == null)
      {
        // we create a table of three employees
        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]);
        // a fictitious employee who will not be included in the dictionary
        // to simulate a non-existent employee
        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 }
        };
      }
      // we return the list of employees
      return Employes;
    }
  • linha 4: verificamos se a lista de funcionários ainda não foi criada;
  • linha 7: se não, crie uma matriz com três funcionários;
  • linhas 8–17: o primeiro funcionário;
  • linha 18: é adicionado ao dicionário;
  • linhas 19–28: o segundo funcionário;
  • linha 29: ele é adicionado ao dicionário;
  • linhas 32–42: o terceiro funcionário. Este não é adicionado ao dicionário por uma razão que explicaremos.

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


    // salary calculation
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
    {
      // we retrieve employee n° SS
      Employe e = dicEmployes.ContainsKey(ss) ? dicEmployes[ss] : null;
      // exists?
      if (e == null)
      {
        throw new PamException(string.Format("L'employé de n° SS [{0}] n'existe pas", ss), 10);
      }
      // a fictitious payslip is returned
      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 de segurança social do funcionário para quem queremos calcular o salário, o número de horas trabalhadas e o número de dias trabalhados;
  • linha 5: procuramos o funcionário no dicionário. Lembre-se de que um deles não está lá;
  • linhas 7–10: se o funcionário não for encontrado, é lançada uma [PamException];
  • linhas 12–17: é devolvido um recibo de vencimento fictício.

9.7.6. O teste de consola para a camada [business]

O projeto da camada [business] tem atualmente o seguinte aspeto:

A classe [Program] acima irá 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()
    {
      // instantiation layer [business]
      IPamMetier pamMetier = new PamMetier();
      // list of employees
      Employe[] employes = pamMetier.GetAllIdentitesEmployes();
      Console.WriteLine("Liste des employés--------------------");
      foreach (Employe e in employes)
      {
        Console.WriteLine(e);
      }
      // payslip calculations 
      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 [business];
  • linhas 14–19: teste do método [GetAllEmployeeIDs] da interface [IPamMetier];
  • linhas 21–31: teste do método [GetSalaire] da interface [IPamMetier].

A execução deste programa de consola 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

Convidamos o leitor a estabelecer a ligação entre estes resultados e o código executado.

Para utilizar este projeto no projeto web que vamos construir, vamos transformá-lo numa biblioteca de classes:

  • em [1], nas propriedades do ficheiro [Program.cs];
  • em [2], especificamos que o ficheiro não fará parte do assembly gerado;
  • em [3, 4], nas propriedades do projeto [pam-metier-simule], na opção [Application] [3], especificamos [4] que a compilação deve produzir uma biblioteca de classes (na forma de uma DLL).
  • em [5], especificamos um assembly [Release]. O outro tipo é [Debug]. O assembly contém, então, informações para facilitar a depuração;
  • Em [6], gere o projeto [pam-metier-simule];
  • Em [7], exiba todos os ficheiros da solução;
  • Em [8], na pasta [bin/Release], encontra-se a DLL do nosso projeto.

9.8. Passo 2: Configurar a aplicação web

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

Utilizando o Visual Studio Express for the Web, abrimos a solução [pam-td] criada anteriormente com o Visual Studio Express for the Desktop.

  • Em [1], a solução [pam-td] foi carregada no Visual Studio Express for the Web;
  • Em [2], a solução e o projeto para a camada [business] simulada que acabámos de criar.

Nesta nova etapa, iremos criar o esqueleto da aplicação 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], selecione o modelo ASP.NET MVC Básico;
  • em [5], o projeto é criado;
  • em [6], definimos o novo projeto como o projeto de inicialização da solução, aquele que será executado quando premirmos [Ctrl-F5];
  • Em [7], o nome do novo projeto aparece em negrito, indicando que é o projeto de inicialização da solução.

Agora, utilizando o Explorador do Windows, substitua a pasta [Content] do projeto pela pasta [étudedecas-support / web / Content]. Depois de fazer isso, deve incluir os novos ficheiros no projeto [pam-web-01]. Proceda da seguinte forma:

  • Em [1], atualize a solução;
  • em [2], exiba todos os ficheiros da solução;
  • em [3], surge uma pasta [Images];
  • que deve incluir no projeto em [4].

Na pasta [Scripts], adicione os scripts JQuery Globalization [1] necessários para a validação do 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>

Nota: Na linha 8, ajuste a versão do jQuery para corresponder à sua versão do Visual Studio.

  • Linha 7: referência à folha de estilo da aplicação;
  • 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 introduzir números decimais franceses com vírgula;
  • linha 13: referência aos scripts necessários para o modo Ajax;
  • Linha 14: scripts específicos da aplicação;
  • linha 24: a imagem de carregamento para chamadas Ajax;
  • linhas 26–39: seis ligações JavaScript;
  • Linha 43: a secção onde serão exibidas as várias vistas da aplicação;
  • Linha 44: o corpo das várias vistas da aplicação.

A seguir, vamos modificar a rota padrão da aplicação:

O ficheiro [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 URLs terão o formato [{controlador}/{ação}];
  • linha 15: se nenhuma ação for especificada, será utilizada a ação [Index]. Se nenhum controlador for especificado, será utilizado o controlador [Pam].

Como resultado desta configuração, a URL [/] é equivalente à URL [/Pam/Index]. Uma vez que a nossa aplicação é uma API, a URL [/] será a sua única URL.

Crie o controlador [Pam]:

Image

Modifique o [PamController] da seguinte forma:


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

  • linha 3: colocamos o controlador no namespace [Pam.Web.Controllers];
  • linha 7: a ação [Index] irá apenas tratar a solicitação HTTP GET;
  • linha 8: devolvemos um tipo [ViewResult] em vez de um tipo [ActionResult].

Agora crie a vista [Index.cshtml] exibida pela ação [Index] acima:

Modifique [Index.cshtml] da seguinte forma:


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

Execute a aplicação premindo [Ctrl-F5]. Deverá ver a seguinte página:

Image


Tarefa: Explique o que aconteceu.


A aplicação 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 da aplicação:


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

9.9. Passo 3: Implementação do modelo SPU

Queremos escrever uma aplicação seguindo o modelo SPU (Single-Page Application) descrito nas Secções 7.5 e 7.6. A página única é aquela carregada pelo navegador quando a aplicação é iniciada:

  • a secção [1] acima é a parte fixa da página única. Vimos que esta é fornecida pela página mestre [_Layout.cshtml];
  • A parte [2] é a parte variável da página única. Está localizada na região com o 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 vários fragmentos de página da aplicação serão apresentados na região com o ID [content] na linha 13. Serão apresentados através de chamadas Ajax. Os scripts JavaScript que executam estas chamadas encontram-se no ficheiro [myScripts.js] referenciado na linha 6. Crie este ficheiro, de que iremos precisar:

Estamos agora a seguir o modelo APU descrito na Secção 7.6. Releia essa secção se a tiver esquecido. Vamos agora configurar os vários fragmentos de página exibidos pela aplicação.

9.9.1. Ferramentas de desenvolvimento JavaScript

Lembre-se de que, com o navegador Chrome, dispõe de um conjunto de ferramentas para depurar o HTML, CSS e JavaScript das suas páginas. Estas ferramentas foram parcialmente apresentadas na Secção 7.2. No modelo APU, os navegadores armazenam em cache os scripts JavaScript referenciados pela primeira página da aplicação. Por isso, deve lembrar-se de limpar este cache quando modificar os seus scripts; caso contrário, as alterações podem não ser aplicadas. Veja como fazê-lo no Chrome:

- Prima [Ctrl-Shift-I] para abrir as ferramentas de desenvolvimento

  • Clique no ícone [1] no canto inferior direito da consola do programador;
  • depois marque a opção [2] que desativa a cache no modo de programador.

9.9.2. Utilizar uma vista parcial para apresentar o formulário

O formulário de entrada é um dos fragmentos apresentados pela aplicação. Atualmente, este formulário é apresentado pela vista [Index.cshtml], que é uma vista completa:


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

Esta vista é apresentada pela ação [Index]:


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

A linha 4 acima mostra que está a ser apresentada uma [View], e não uma [PartialView]. Precisamos de uma vista parcial para o formulário, que será um fragmento de página. Modificamos a vista [Index.cshtml] da seguinte forma:


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

Linha 4: O formulário já não faz parte da página [Index.cshtml]. Agora está alojado numa vista parcial [Form.cshtml]:

O código para [Form.cshtml] é simplesmente o seguinte:


<h2>Formulaire</h2>

Efetue estas alterações e verifique se continua a obter a seguinte visualização quando a aplicação é iniciada:

Image

9.9.3. A chamada Ajax [runSimulation]

Estamos interessados no fragmento exibido quando o utilizador clica na ligação [Executar Simulação]:

  • Em [1], o utilizador clica na ligação [Executar simulação];
  • em [2], a simulação aparece abaixo do formulário.

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


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

Linha 3: Criamos uma região com o ID [simulation] para conter o fragmento de simulação.

Criamos a seguinte vista parcial [Simulation.cshtml]:

O conteúdo da vista [Simulation.cshtml] é o seguinte:


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

Precisamos agora de escrever o código JavaScript que trata do clique no link [Run Simulation]. Seguiremos o procedimento descrito na Secção 7.6.5. Primeiro, vamos ver o código HTML do link em [_Layout.cshtml]:


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

Podemos ver que clicar no link [Executar Simulação] irá acionar a execução da função JS [runSimulation]. Esta função será escrita no ficheiro [myScripts.js], juntamente com as outras funções JS necessárias à aplicação:


// global variables
var loading;
var content;
 
function faireSimulation() {
  // make a manual Ajax call
...
}
 
function effacerSimulation() {
  // delete form entries
...
}
 
function enregistrerSimulation() {
  // make a manual Ajax call
  ...
}
 
function voirSimulations() {
  // make a manual Ajax call
  ...
}
 
function retourFormulaire() {
  // make a manual Ajax call
...
}
 
function terminerSession() {
...
}
 
// document loading
$(document).ready(function () {
  // retrieve the references of the page's various components
  loading = $("#loading");
  content = $("#content");
});

  • linhas 35–39: a função jQuery executada quando a aplicação é iniciada;
  • linhas 37-38: inicializam as variáveis globais das linhas 2 e 3.

Note que os elementos com os IDs [loading] e [content] estã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 na Secção 7.6.5, escreva a função JS [faireSimulation]. Esta função enviará um pedido Ajax do tipo POST para a ação [/Pam/FaireSimulation]. Nenhum dado será enviado neste momento. A ação [/Pam/FaireSimulation] devolverá a vista parcial [Simulation.cshtml] à função JS [faireSimulation], que, por sua vez, colocará esta saída HTML na região com o id [simulation] do formulário.


Teste o link [Run Simulation] na sua aplicação.

9.9.4. A chamada Ajax [enregistrerSimulation]

O link [Guardar Simulação] está definido da seguinte forma em [_Layout.cshtml]:


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


Tarefa: Seguindo os passos anteriores, escreva a função JS [enregistrerSimulation]. Esta função enviará uma chamada Ajax do tipo POST para a ação [/Pam/EnregistrerSimulation]. Nenhum dado será enviado neste momento. A ação [/Pam/EnregistrerSimulation] devolverá a vista parcial [Simulations.cshtml] à função JS [enregistrerSimulation], que, por sua vez, colocará este fluxo HTML na região com o id [content] na página mestre.


A vista [Simulations.cshtml] é a seguinte:

O seu conteúdo é o seguinte:


<h2>Simulations</h2>

Aqui está um exemplo de execução:

Image

Image

9.9.5. A chamada Ajax [viewSimulations]

O link [Ver simulações] é 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 [viewSimulations]. Esta função enviará uma chamada Ajax do tipo POST para a ação [/Pam/ViewSimulations]. Por enquanto, não serão enviados dados. A ação [/Pam/VoirSimulations] irá devolver a vista parcial [Simulations.cshtml] à função JS [voirSimulations], que irá então colocar esta saída HTML na região com o id [content] na página mestre.


A vista [Simulations.cshtml] é a mesma já utilizada na questão anterior.

Eis um exemplo de execução:

9.9.6. A chamada Ajax [returnToForm]

O link [Voltar ao formulário de simulação] é definido da seguinte forma em [_Layout.cshtml]:


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


Tarefa: Seguindo os passos anteriores, escreva a função JS [returnForm]. Esta função enviará um pedido Ajax do tipo POST para a ação [/Pam/Form]. Não serão enviados dados neste momento. A ação [/Pam/Formulaire] irá devolver a vista parcial [Formulaire.cshtml] à função JS [retourFormulaire], que irá então colocar este fluxo HTML na região com o id [content] na página mestre.


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

9.9.7. A chamada Ajax [endSession]

O link [End Session] está definido da seguinte forma em [_Layout.cshtml]:


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


Tarefa: Seguindo o procedimento anterior, escreva a função JS [terminerSession]. Esta função enviará uma chamada Ajax do tipo POST para a ação [/Pam/TerminerSession]. Por enquanto, não serão enviados dados. A ação [/Pam/TerminerSession] irá devolver a vista parcial [Formulaire.cshtml] à função JS [terminerSession], que irá então colocar este fluxo HTML na região com o id [content] na página mestre.


Eis um exemplo de execução:

9.9.8. A função JS [clearSimulation]

O link [Clear Simulation] é definido da seguinte forma em [_Layout.cshtml]:


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

O objetivo da função JS [clearSimulation] é:

  • ocultar o fragmento [Simulation], caso exista;
  • repor os campos de entrada do formulário ao seu estado no carregamento inicial da aplicação (quando existem campos de entrada — por enquanto, não há nenhum).


Tarefa: Escreva a função JS [clearSimulation]. Não há nenhuma chamada Ajax aqui. Este processo ocorre dentro do navegador e não envolve o servidor.


Aqui está um exemplo de execução:

9.9.9. Gerir a navegação entre ecrãs

Por enquanto, os links estão sempre visíveis. Vamos agora gerir a sua exibição utilizando uma função JavaScript. Primeiro, vamos rever 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 têm um atributo [id] que nos permitirá gerenciá-los em JavaScript. Modificamos o método JavaScript executado quando a página é carregada da seguinte forma:


// variables globales
var loading;
var content;
var lnkFaireSimulation;
var lnkEffacerSimulation
var lnkEnregistrerSimulation;
var lnkTerminerSession;
var lnkVoirSimulations;
var lnkRetourFormulaire;
var options;
 
...
// au chargement du document
$(document).ready(function () {
  // on récupère les références des différents composants de la page
  loading = $("#loading");
  content = $("#content");
  // les liens du menu
  lnkFaireSimulation = $("#lnkFaireSimulation");
  lnkEffacerSimulation = $("#lnkEffacerSimulation");
  lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
  lnkVoirSimulations = $("#lnkVoirSimulations");
  lnkTerminerSession = $("#lnkTerminerSession");
  lnkRetourFormulaire = $("#lnkRetourFormulaire");
  // on les met dans un tableau
  options = [lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
  // on cache certains éléments de la page
  loading.hide();
  // on fixe le menu
  setMenu([lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]);
});
 

  • linhas 19–24: recuperam as referências para os seis links. Estas referências são definidas como variáveis globais nas linhas 4–9;
  • linha 26: a matriz [options] é inicializada com as seis referências. Esta matriz é definida como uma variável global na linha 10;
  • linha 28: ocultar a imagem animada que indica que há chamadas Ajax pendentes;
  • linha 30: os links [lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession] são exibidos. Os restantes serão ocultados.

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


function setMenu(show) {
  // display table links [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úmero i;
  • T[i].show() exibe o link número i;
  • T[i].hide() oculta o link número i.

Com estas novas funções JS, a página apresentada no arranque é a seguinte:

Image

Adapte as funções JS [runSimulation, clearSimulation, saveSimulation, viewSimulations, returnToForm, endSession] para exibir os seguintes ecrãs:

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Agora que o modelo APU e os links de navegação estão definidos, podemos passar à criação das ações e vistas do lado do servidor. À medida que avança pelas etapas, irá verificar que alguns dos links Ajax que funcionam atualmente deixarão de funcionar, porque irá modificar as vistas parciais enviadas ao cliente. À medida que cria as várias ações e vistas do lado do servidor, os links Ajax do lado do cliente voltarão a funcionar como pretendido.

9.10. Passo 4: Escrever a ação do servidor [Index]

Atualmente, quando a aplicação é iniciada, vemos o seguinte ecrã:

Image

Em vez desta tela, gostaríamos de ver o seguinte:

Image

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

  • A página apresenta um formulário com três campos de entrada:
    • o funcionário cujo salário está a ser calculado,
    • o número de horas trabalhadas,
    • o número de dias trabalhados;
  • o formulário é enviado através do link [Executar Simulação];
  • a validade dos campos de entrada [Horas trabalhadas] e [Dias trabalhados] deve ser verificada;
  • a lista de funcionários provém da camada [negócio] que criámos anteriormente.

Vamos rever o código atual para a ação [Índice]:


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

aquele da vista [Index.cshtml] que esta ação exibe:


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

e a vista parcial [Formulaire.cshtml]:


<h2>Formulaire</h2>

As alterações serão feitas nestes três locais.

9.10.1. O modelo do formulário

Voltemos ao fluxo de processamento da URL [/Pam/Index]:

  • a solicitação HTTP do cliente chega em [1];
  • em [2], a informação contida na solicitação é convertida num modelo de ação [3] que serve como entrada para a ação [4];
  • em [4], a ação, com base neste modelo, irá gerar uma resposta. Esta resposta terá dois componentes: uma vista V [6] e o modelo M desta vista [5];
  • a vista V [6] utilizará o seu modelo M [5] para gerar a resposta HTTP para o cliente.

A ação que nos interessa é a ação [Index], que atualmente tem o seguinte aspeto:


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

A ação [Index] não passa nenhum modelo para a vista [Index.cshtml]. Por isso, não será possível exibir a lista de funcionários. Esta lista pode ser solicitada a partir da camada [business]. Para tal, o projeto [pam-web-01] deve ter uma referência ao projeto [pam-metier-simule]. Vamos criar essa referência agora:

  • em [1], clique com o botão direito do rato em [Referências] no projeto [pam-web-01] e, em seguida, selecione [Adicionar Referência];
  • em [2], selecione a opção [Solução] 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 de Aplicação

Apresentámos os conceitos importantes do modelo de aplicação e do modelo de sessão na Secção 4.10, na página 70. Vamos agora utilizá-los. Recorde-se que colocamos no modelo:

  • um modelo de aplicação contendo dados de leitura apenas para todos os utilizadores. Este modelo constitui uma memória partilhada para todos os pedidos de todos os utilizadores;
  • dados de sessão que são de leitura e escrita para um determinado utilizador. Este modelo constitui uma memória partilhada para todos os pedidos desse utilizador.

O que iremos incluir no modelo de aplicação? Vamos rever a sua arquitetura:

A camada [web] mantém uma referência à camada [negócio]. Esta pode ser partilhada por todos os utilizadores. Podemos, portanto, colocá-la no modelo de aplicação. Além disso, vamos assumir que a lista de funcionários não se altera. Pode, portanto, ser lida uma vez e depois partilhada entre todos os utilizadores. Propomos, assim, o seguinte modelo de aplicação:

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


using Pam.Metier.Entites;
using Pam.Metier.Service;
namespace PamWeb.Models
{
  public class ApplicationModel
  {
    // --- application scope data ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
  }
}

Para apresentar uma lista suspensa numa vista, escreve-se algo semelhante ao seguinte:


        <!-- the drop-down list -->
        <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 um tipo SelectListItem[] como segundo parâmetro, que foi fornecido acima por um tipo [SelectList]. Precisamos de construir essa matriz com a lista de funcionários. Uma vez que os funcionários não mudam, esta matriz também pode ser colocada no modelo da aplicação. Atualizamo-la da seguinte forma:


using Pam.Metier.Entites;
using Pam.Metier.Service;
using System.Web.Mvc;
 
namespace Pam.Web.Models
{
  public class ApplicationModel
  {
    // --- application scope data ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
  }
}

Quando é que este modelo deve ser construído? Demonstrámos isto na Secção 4.10. Ocorre quando o método [Application_Start] no ficheiro [Global.asax] é executado:

O método [Application_Start] está atualmente definido da seguinte forma:


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);
    }
  }
}

Estendemos-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()
    {
      // ----------Auto-generated
      AreaRegistration.RegisterAllAreas();
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // -------------------------------------------------------------------
      // ---------- specific configuration
      // -------------------------------------------------------------------
      // application scope data
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
        // instantiation layer [business]
        application.PamMetier = ...
        // employee roster 
        application.Employes = ...
        // employee combo items
        application.EmployesItems = ...
      // model binder for [ApplicationModel]
      ...
    }
  }
}


Tarefa: Complete o código para o método [Application_Start]. Tudo o que precisa está na secção 4.10. Reserve algum tempo para reler esta secção, que é longa, mas importante.


A linha 33 consiste, na verdade, em várias linhas. Para criar um objeto do tipo [SelectListItem], pode utilizar o seguinte método:


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

Este [SelectListItem] será utilizado para gerar a seguinte tag HTML: <option>

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

na lista suspensa. Iremos garantir que:

  • o texto seja o primeiro nome do funcionário seguido do seu apelido;
  • aValue seja o número de segurança social do funcionário.

Na linha 35, acima, irá precisar da classe [ApplicationModelBinder] descrita na secção 4.10, página 74:

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

Agora que definimos um modelo para a aplicação, podemos atualizar o código para a ação [Index] da seguinte forma:


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

  • Linha 4: O modelo de aplicação é agora um parâmetro da ação [Index]. Explicámos na Secção 4.10 como este parâmetro é inicializado pelo framework.

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

Agora, a ação [Index] tem acesso aos funcionários armazenados no modelo de aplicação. Deve agora passá-los para a vista [Index.cshtml], que irá exibi-los. Poderíamos passar um tipo [ApplicationModel] como modelo de vista para a vista [Index.cshtml], mas veremos rapidamente que esta vista necessita de informações adicionais que não se encontram no [ApplicationModel]. Iremos utilizar o seguinte modelo de visualização [IndexModel]:


namespace Pam.Web.Models
{
  public class IndexModel
  {
    // application scope data
    public ApplicationModel Application { get; set; }
  }
}

  • Linha 6: [IndexModel] contém o modelo da aplicação.

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 vista padrão [Index.cshtml] é apresentada utilizando um tipo [IndexModel] como modelo, inicializado com dados do modelo da aplicação.

Sabemos que a vista [Index.cshtml] deve apresentar um formulário:

Image

Voltemos ao fluxo de tratamento de pedidos:

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

  • a ação é [Index];
  • o modelo para esta ação é [ApplicationModel];
  • a vista é [Index.cshtml];
  • o modelo para esta vista é [IndexModel].

Quando o formulário for enviado, o fluxo de processamento será semelhante:

  • a ação é a que trata do POST;
  • o seu modelo recolhe os valores introduzidos, neste caso:
    • o número de segurança social do funcionário selecionado;
    • o número de horas trabalhadas;
    • o número de dias trabalhados;

Poderíamos criar um modelo de ação que combinasse estes três valores. Também é comum reutilizar o modelo utilizado para apresentar o formulário. É isso que faremos aqui. A classe [IndexModel] evolui da seguinte forma:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Pam.Web.Models
{
  [Bind(Exclude = "Application")]
  public class IndexModel
  {
    // application scope data
    public ApplicationModel Application { get; set; }
 
    // posted values
    [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 inseridos. Note-se que [daysWorked] foi declarado como tipo [double], embora na realidade se espere um inteiro. O tipo [double] foi introduzido para facilitar a validação deste campo no lado do cliente, uma vez que a validação de um tipo [int] tinha causado problemas;
  • linhas 12, 14, 17: rótulos para os métodos [Html.LabelFor] da vista associada ao modelo;
  • linha 15: uma anotação para apresentar o campo [HoursWorked] com duas casas decimais;
  • linha 5: especifica que a propriedade denominada [Application] não está incluída nos valores enviados.

9.10.5. As vistas [Index.cshtml] e [Formulaire.cshtml]

A vista [Index.cshtml] é apresentada pela seguinte ação [Index]:


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

Curiosamente, a vista [Index.cshtml] permanece inalterada:


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

  • a vista não declara nenhum modelo;
  • linha 4: inclui a vista parcial [Form.cshtml], mais uma vez sem lhe passar um modelo. Durante os testes, observou-se que o modelo [IndexModel] passado para a vista [Index.cshtml] foi implicitamente propagado para a vista parcial [Form.cshtml]. Esta última vista poderia agora assumir 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 vista recebe um modelo do tipo [IndexModel];
  • linha 3: o formulário;
  • linhas 6–10: os cabeçalhos da tabela de entrada;
  • linhas 12–14: a linha de entrada;
  • linhas 15–17: quaisquer mensagens de erro.


Tarefa: Complete o código para a vista [Formulaire.cshtml]. Utilize os métodos [DropDownListFor, EditorFor, LabelFor, ValidationMessageFor] descritos na Secção 5.7.


9.10.6. Testar a ação [Index]

Escrevemos todos os elementos da cadeia de processamento da URL [/Pam/Index]:

Testamos a aplicação com [Ctrl-F5]:

Image

Image

Deve verificar se a sua lista suspensa foi preenchida com a lista de funcionários que definimos na camada [de negócios] simulada.

9.11. Passo 5: Implementação da validação de entrada

9.11.1. O problema

Embora não tenhamos feito nada para a ativar, a validação do lado do cliente já está em vigor:

Image

Image

A validação do lado do cliente está ativada por predefinição devido à linha 3 abaixo no ficheiro [Web.config] da aplicação.


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

No entanto, como no [IndexModel] declarámos o campo [JoursTravaillés] como sendo do tipo [double]:


    public double JoursTravaillés { get; set; }

podemos inserir um número real neste campo:

Image

Além disso, podemos inserir valores arbitrários em ambos os campos:

Image

O [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
  {
    // application scope data
    public ApplicationModel Application { get; set; }
 
    // posted values
    [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: Melhore este modelo para:


  • exibir mensagens de erro personalizadas;
  • aceitar apenas valores reais no intervalo [0,400] para o campo [HoursWorked];
  • aceitar apenas valores inteiros no intervalo [0,31] para o campo [DaysWorked];



Pode consultar o exemplo na Secção 7.6.2. Para verificar se o número de dias trabalhados é um número inteiro, pode utilizar uma expressão regular (ver exemplos na Secção 5.9.1).

Aqui estão alguns exemplos do que é esperado:

Image

Image

Image

9.11.2. Introdução de números reais no formato francês

Na versão atual da aplicação, o número de horas trabalhadas deve ser um número decimal no formato anglo-saxónico (com ponto decimal). O formato francês com vírgula não é aceite:

Image

Esta questão foi identificada e abordada na Secção 6.1.


Tarefa: Seguindo o procedimento descrito na secção acima mencionada, efetue as alterações necessárias para que os números reais possam ser introduzidos utilizando o formato decimal francês. Teste a sua aplicação.


Agora, o ecrã anterior fica assim:

Image

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

Image

A presença da simulação em [1] e a alteração do menu em [2] mostram que clicar no link [Executar simulação] enviou o formulário, apesar de os valores introduzidos serem inválidos. Esta questão foi identificada e resolvida na secção 7.6.5.


Tarefa: Seguindo o procedimento descrito na secção acima mencionada, certifique-se de que a ação POST para o link [Executar simulação] não pode ser realizada se os valores introduzidos forem inválidos. Lembre-se de limpar a cache do seu navegador antes de testar as suas alterações.


Note que a vista 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" }))
{
...
}

Isto pode ser verificado visualizando 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. Passo 6: Executar uma simulação

9.12.1. O problema

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

A vista parcial [Simulation.cshtml] apresenta agora o recibo de vencimento de um funcionário.

9.12.2. Escrever a vista [Simulation.cshtml]

A vista [Simulation.cshtml] é alterada da 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 vista [Simulation.cshtml] baseia-se no tipo [PayrollSheet] definido na secção 9.7.3;
  • a vista utiliza as classes [label, info, value] definidas na folha de estilo da aplicação [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], definimos a altura das linhas das várias tabelas HTML na região com o ID [simulation], especificamente onde o recibo de vencimento é apresentado:


#simulation table tr {
  height: 30px;
}


Tarefa: Conclua a vista [Simulation.cshtml].


Para apresentar o valor em euros de uma quantia, utilizaremos o método [string.Format]:

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

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

Para testar esta visualização, deve fornecer-lhe um recibo de vencimento. Este deve ser fornecido pela ação [/Pam/FaireSimulation], que é o destino da chamada Ajax a partir da ligação [Executar Simulação]. Atualmente, esta ação é a seguinte:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View(new IndexModel() { Application = application });
    }
 
    // make a simulation
    [HttpPost]
    public PartialViewResult FaireSimulation()
    {
      return PartialView("Simulation");
}

No código acima, a ação [FaireSimulation] não passa nenhum modelo para a vista [Simulation.cshtml]. É necessário passar-lhe um recibo de vencimento. Sabemos que a camada [business] calcula os recibos de vencimento. Esta camada [business] é acessível através do modelo de aplicação [ApplicationModel] que definimos na secção 9.10.2:


  public class ApplicationModel
  {
     // --- application scope data ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
}

A camada [business] é acessível através da propriedade na linha 5 acima. Para dar à ação [RunSimulation] acesso à camada [business], vamos passar-lhe o modelo da aplicação, tal como fizemos para a ação [Index]. O código evolui então da seguinte forma:


    // make a simulation
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application)
    {
      return PartialView("Simulation");
}

Agora, dentro da ação, podemos calcular um recibo de vencimento fictício. O código evolui da seguinte forma:


// make a simulation
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application)
    {
      FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
      return PartialView("Simulation", feuilleSalaire);
    }

  • Na linha 5, é calculado um salário fictício. O primeiro parâmetro é um número de segurança social existente. Foi definido na classe [business] simulada na secção 9.7.5. O segundo parâmetro é o número de horas trabalhadas e o terceiro é o número de dias trabalhados;
  • Linha 6: Este recibo de vencimento é passado como um modelo para a vista [Simulation.cshtml].

Estamos agora prontos para testar a vista [Simulation.cshtml]:

Image

Não introduzimos quaisquer dados e solicitamos a simulação. Obtemos então o seguinte resultado:

Image

9.12.3. Cálculo do salário efetivo

A nossa ação [RunSimulation] atual calcula sempre o mesmo recibo de vencimento:


    // make a simulation
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application)
    {
      FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
      return PartialView("Simulation", feuilleSalaire);
}

Não tem em conta a informação introduzida:

  • o funcionário cujo salário está a ser calculado;
  • o número de horas trabalhadas;
  • o número de dias trabalhados.

Os valores introduzidos são passados para a ação [RunSimulation] da seguinte forma:

  1. o utilizador clica na ligação [Run Simulation]. Isto desencadeia a execução da função JS [runSimulation] que já escrevemos;
  2. a função JS [faireSimulation] faz então uma chamada Ajax para a ação do servidor [/Pam/FaireSimulation] na qual estamos a trabalhar atualmente. Por enquanto, a função JS [faireSimulation] não envia nenhuma informação para a ação do servidor. Ela precisará enviar os valores introduzidos pelo utilizador;
  3. a ação do servidor [/Pam/FaireSimulation] irá recuperar os valores introduzidos a partir dos dados enviados pela função JS [faireSimulation].

Comecemos pelo ponto 2: a função JS [faireSimulation] deve enviar os valores introduzidos pelo utilizador para a ação do servidor [/Pam/FaireSimulation].


Tarefa: Complete a função JS [faireSimulation] para que envie os valores introduzidos pelo utilizador. Pode consultar o exemplo na secção 7.6.5, onde esta questão foi abordada.


Agora, abordemos o ponto 3 acima. A ação do 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 utilizando os valores enviados pela função JS [faireSimulation]. Pode consultar novamente o exemplo na secção 7.6.5, onde este problema foi abordado. Por enquanto, vamos assumir que o modelo derivado dos valores enviados é sempre válido.


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


// make a simulation
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
    {
      // action model creation
      ...
      // we try to retrieve the values posted in this model
      ...
      // salary calculation
      FeuilleSalaire feuilleSalaire = ...
      // the salary sheet is displayed
      return PartialView("Simulation", feuilleSalaire);
    }

Aqui está um exemplo de execução:

Selecionamos [Justine Laverti]. Obtemos então o seguinte resultado:

De facto, obtivemos o recibo de vencimento fictício de [Justine Laverti]. Anteriormente, o único recibo de vencimento que tinha sido calculado era o de [Marie Jouveinal]. Por isso, foi utilizado o valor registado para a seleção da funcionária. Quanto ao número de horas e ao número de dias, não podemos dizer nada, uma vez que a nossa camada [de negócios] simulada não os tem em conta.

9.12.4. Tratamento de erros

Vejamos o seguinte exemplo:

  • em [1], selecionamos um funcionário que não existe (ver a definição da camada [de negócios] simulada na secção 9.7.5;
  • em [2], executamos a simulação;
  • em [3] abaixo, é apresentada uma página de erro.

O que aconteceu?

A função JS [doSimulation] foi executada. O seu código tem o seguinte aspeto:


function faireSimulation() {
...
  // make a manual Ajax call
  $.ajax({
    url: '/Pam/FaireSimulation',
...
    beforeSend: function () {
      // wait signal on
      loading.show();
    },
    success: function (data) {
...
    },
    error: function (jqXHR) {
      // error display
      simulation.html(jqXHR.responseText);
      simulation.show();
    },
    complete: function () {
      // wait signal off
      loading.hide();
    }
  });
  // menu
  setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}

A chamada Ajax falhou e a função nas linhas 14–18 foi executada. A página de erro [jqXHR.responseText] devolvida pelo servidor foi apresentada. Isto é bastante específico. A camada [business] simulada lançou uma exceção porque o SSN que lhe foi fornecido não pertence a um funcionário existente (ver o código da camada [business] simulada na secção 9.7.5). Temos de tratar este caso de forma adequada.

Iremos criar uma vista parcial [Errors.chtml] que será devolvida ao cliente JavaScript sempre que for detetado um erro no lado do servidor:

O código para a vista parcial [Errors.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 vista recebe uma lista de mensagens de erro como modelo;
  • linhas 5–10: que são apresentadas numa lista HTML;

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


    // make a simulation
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
    {
    ...
      // salary calculation
      FeuilleSalaire feuilleSalaire = null;
      Exception exception=null;
      try
      {
        // salary calculation
        feuilleSalaire = ...
      }
      catch (Exception ex)
      {
        exception = ex;
      }
      // mistake?
      if (exception == null)
      {
        // the salary sheet is displayed
        return PartialView("Simulation", feuilleSalaire);
      }
      else
      {
        // the error page is displayed
        return PartialView("Erreurs", Static.GetErreursForException(exception));
      }
}

  • linhas 9–17: o cálculo do salário é agora realizado dentro de um bloco try/catch;
  • linha 27: se ocorrer um erro, a vista parcial [Errors.cshtml] é apresentada, utilizando a lista de mensagens de erro fornecida pelo método estático [Static.GetErrorsForException(exception)] como modelo.

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


using System;
using System.Collections.Generic;
using System.Web.Mvc;
 
namespace PamWeb.Infrastructure
{
  public class Static
  {
    // list of exception error messages
    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;
    }
 
    // list of error messages linked to an invalid model
    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;
    }
 
    // the error message linked to an element of the action model
    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 [GetErrorsForException] devolve a lista de erros numa pilha de exceções;
  • linhas 22–36: a função estática [GetErrorsForModel] devolve a lista de erros para um modelo de ação inválido. O código para esta função, bem como o do método privado [getErrorMessageFor] (linhas 39–54), já foi abordado anteriormente.

Feito isso, podemos testar o caso de erro novamente:

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

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


    // make a simulation
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
    {
      // action model creation
      IndexModel modèle = new IndexModel() { Application = application};
      // we try to retrieve the values posted in the model
      TryUpdateModel(modèle, data);
      // salary calculation
...
}

Na linha 8, atualizamos o modelo da linha 6 com os valores enviados pela chamada Ajax. Não validamos o modelo. Temos de fazer isto porque não podemos saber de onde vêm os valores enviados. Alguém poderia ter adulterado um pedido POST e enviado-nos dados inválidos.


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


Como testar esta alteração? Na Secção 9.11.3, assegurou que a função JS [faireSimulation] não enviaria os valores introduzidos via POST se estes fossem inválidos. Coloque em comentário as linhas que fazem isso e, em seguida, execute o seguinte teste:

  • em [1], execute a simulação com valores inválidos;
  • em [2], recuperamos com sucesso a página de erro que acabámos de criar, comprovando que os validadores do lado do servidor funcionaram corretamente.

Em seguida, lembre-se de descomentar as linhas que acabou de comentar na função JS [faireSimulation].

9.13. Passo 7: Configurar uma sessão de utilizador

A aplicação [Calculadora de Folha de Pagamento] permite ao utilizador realizar várias simulações de folha de pagamento utilizando o link [Executar Simulação], guardá-las utilizando o link [Guardar Simulação], visualizá-las utilizando o link [Ver Simulações] e eliminá-las utilizando o link [Eliminar Simulação]. Sabemos que, entre dois pedidos sucessivos do utilizador, não existe estado, a menos que criemos um através do mecanismo de sessão (ver Secção 4.10). É bastante claro aqui que devemos armazenar na sessão a lista de simulações guardadas pelo utilizador ao longo do tempo. Há outros dados a armazenar: quando o utilizador realiza uma simulação, esta só é guardada na lista de simulações se o utilizador o solicitar através do link [Guardar Simulação]. Quando o fizer, temos de ser capazes de recuperar a simulação calculada no pedido anterior. Para tal, esta também será armazenada na sessão. Por fim, iremos numerar as simulações a partir de 1. Para numerar corretamente uma nova simulação, temos de ter retido o número da simulação anterior, novamente na sessão.

Na Secção 4.10, introduzimos o conceito de um modelo de sessão como parâmetro de entrada para uma ação, para que a ação possa aceder à sessão. Iremos revisitar este conceito. Recomendamos que releia a secção relevante se este conceito não lhe for claro.

Criamos a seguinte classe [SessionModel]:

O seu código é o seguinte:


using Pam.Web.Models;
using System.Collections.Generic;
 
namespace Pam.Web.Models
{
  public class SessionModel
  {
    // list of simulations
    public List<Simulation> Simulations { get; set; }
    // n° of next simulation
    public int NumNextSimulation { get; set; }
    // the last simulation
    public Simulation Simulation { get; set; }
 
    // manufacturer
    public SessionModel()
    {
      // empty simulation list
      Simulations = new List<Simulation>();
      // next simulation no
      NumNextSimulation = 1;
    }
  }
}

A classe [Simulation] nas linhas 9 e 13 irá armazenar informações sobre uma simulação. O que precisamos de armazenar? A ligação [Run Simulation] calcula uma folha de pagamentos do tipo [Payroll]. Parece natural incluir isto na simulação. Além disso, precisamos de armazenar a informação que conduziu a esta folha de pagamentos:

  • o funcionário selecionado. Esta informação pode ser encontrada no campo [PayrollSheet.Employee]. Portanto, não há necessidade de a armazenar uma segunda vez;
  • o número de horas e dias trabalhados. Esta informação não está incluída na classe [Payroll]. Por isso, precisamos de a armazenar.

Por fim, cada simulação é identificada por um número. Poderíamos, assim, começar com a seguinte classe [Simulação]:


using Pam.Metier.Entites;
 
namespace Pam.Web.Models
{
  public class Simulation
  {
    // simulation no
    public int Num { get; set; }
    // number of hours worked
    public double HeuresTravaillées { get; set; }
    // number of days worked
    public int JoursTravaillés { get; set; }
    // payslip
    public FeuilleSalaire FeuilleSalaire { get; set; }
  }
}

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


// make a simulation
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, SessionModel session, FormCollection data)
    {
      // action model creation
      IndexModel modèle = new IndexModel() { Application = application };
      // we try to retrieve the values posted in the model
      TryUpdateModel(modèle, data);
      // valid model?
      if (!ModelState.IsValid)
      {
        // the error page is displayed
        return PartialView("Erreurs", Static.GetErreursForModel(ModelState));
      }
      // salary calculation
      FeuilleSalaire feuilleSalaire = null;
      Exception exception = null;
      try
      {
        // salary calculation
        feuilleSalaire = application.PamMetier.GetSalaire(modèle.SS, modèle.HeuresTravaillées, (int)modèle.JoursTravaillés);
      }
      catch (Exception ex)
      {
        exception = ex;
      }
      // mistake?
      if (exception != null)
      {
        // the error page is displayed
        return PartialView("Erreurs", Static.GetErreursForException(exception));
      }
      // create a simulation and place it in the session
      session.Simulation = ...
      // the salary sheet is displayed
      return PartialView("Simulation", feuilleSalaire);
    }

  • linha 3: a ação recebe o modelo da sessão como parâmetro;


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



Tarefa 2: Seguindo o procedimento da Secção 4.10, faça o que for necessário para garantir que o parâmetro [SessionModel session] da ação seja devidamente inicializado pela estrutura. Se nada for feito, este parâmetro terá um ponteiro nulo.


9.14. Passo 8: Guardar uma simulação

9.14.1. O Problema

Depois de realizarmos uma simulação, podemos guardá-la:

Image

A vista parcial [Simulations.cshtml] apresenta agora a lista de simulações realizadas pelo utilizador. Note-se que o recibo de vencimento calculado é fictício.

9.14.2. Escrever a ação do servidor [SaveSimulation]

O link Ajax [Guardar Simulação] chama a ação do servidor [SaveSimulation], cujo código era anteriormente o seguinte:


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

Evolui da seguinte forma:


    // save a simulation
    [HttpPost]
    public PartialViewResult EnregistrerSimulation(SessionModel session)
    {
      // save the last simulation run in the session's simulation list
      ...
      // increment the number of the next simulation in the session
      ...
      // the list of simulations is displayed
      ...
}

  • Linha 1: A ação [SaveSimulation] necessita de acesso à sessão. É por isso que recebe o modelo da sessão como parâmetro.


Tarefa: Conclua a ação do servidor [SaveSimulation].


9.14.3. Escrever a vista parcial [Simulations.cshtml]

A ação anterior [SaveSimulation] apresenta a vista parcial [Simulations.cshtml] com a lista de simulações realizadas pelo utilizador como modelo. O 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: Complete o código para a vista parcial [Simulations.cshtml]. Utilize uma tabela HTML para apresentar as simulações. Pode consultar os exemplos na Secção 5.4.


Nota: O link [remove] para cada simulação na tabela HTML será um link JavaScript no seguinte formato:

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

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


Tarefa 2: Teste a sua aplicação executando simulações. Para tal, repita a seguinte sequência: 1) atualize a página da aplicação premindo [F5], 2) execute uma simulação, 3) guarde-a. As simulações irão acumular-se na sessão, o que deverá refletir-se na vista [Simulations.cshtml].



Tarefa 3: Melhore a vista parcial [Simulations.cshtml] para que as cores das linhas na tabela HTML sejam alternadas.


Image

Atribua alternadamente as classes CSS [even] e [odd], definidas na folha de estilos [/Content/Site.css], às linhas <tr> da tabela HTML:


.impair {
  background-color: beige;
}
 
.pair {
  background-color: lightsteelblue;
}

9.15. Passo 9: Voltar ao formulário de entrada

9.15.1. O problema

Depois de obtermos a lista de simulações, podemos voltar ao formulário de entrada, o que não temos conseguido fazer há algum tempo:

Image

Image

9.15.2. Escrever a ação do servidor [Form]

O link Ajax [Voltar ao formulário de simulação] chama a ação do servidor [Form], cujo código era anteriormente o seguinte:


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

A vista parcial [Form] que é apresentada espera um [IndexModel] (linha 1 abaixo):


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

É por isso que o link [Voltar ao formulário de simulação] já não estava a funcionar.


Tarefa: Escreva a nova versão da ação de servidor [Formulário] (2 linhas para reescrever) e, em seguida, execute os testes.


9.15.3. Modificar a função JavaScript [returnToForm]

Com a alteração feita anteriormente, agora podemos voltar ao formulário, mas surge então um problema:

  • Em [1], voltamos ao formulário de entrada;
  • em [2], realizamos uma simulação com entradas incorretas. Descobrimos então que os validadores do lado do cliente já não funcionam. Aqui, o servidor foi chamado e devolveu uma página de erro graças ao trabalho realizado na secção 9.12.4.

Este problema foi identificado e resolvido na Secção 7.6.7.


Tarefa: Seguindo o procedimento da secção 7.6.7, corrija a função JavaScript [returnToForm] e, em seguida, execute testes para verificar se os validadores do lado do cliente estão a funcionar novamente.


9.16. Passo 10: Consulte a lista de simulações

9.16.1. O problema

Ao trabalhar com o formulário de simulação, pode visualizar a lista de simulações que realizou:

Image

Image

9.16.2. Escrever a ação do servidor [ViewSimulations]

O link Ajax [View Simulations] chama a ação do servidor [ViewSimulations], cujo código era anteriormente o seguinte:


    // see simulations
    [HttpPost]
    public PartialViewResult VoirSimulations()
    {
      return PartialView("Simulations");
}

A vista parcial [Simulations] que é apresentada 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 isso que o link [Ver simulações] já não estava a funcionar.


Tarefa: Escreva a nova versão da ação do servidor [ViewSimulations] (2 linhas para reescrever) e, em seguida, execute os testes.


9.17. Passo 11: Terminar a sessão

9.17.1. O problema

Pode encerrar a sessão do utilizador a qualquer momento utilizando o link [Ajax] [Encerrar sessão]. Isto encerra a sessão atual e inicia uma nova. Além disso, regressa à visualização do formulário:

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

9.17.2. Escrever a ação do servidor [EndSession]

O link Ajax [End Session] chama a ação do servidor [EndSession], cujo código era anteriormente o seguinte:


    // end session
    [HttpPost]
    public PartialViewResult TerminerSession()
    {
      return PartialView("Formulaire");
}

A vista parcial [Form] que apresenta espera um [IndexModel] (linha 1 abaixo):


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

É por isso que o link [Terminar sessão] já não estava a funcionar.


Tarefa: Escreva a nova versão da ação do servidor [EndSession] (2 linhas para reescrever) e, em seguida, execute os testes.


Nota: Para encerrar a sessão na ação, escreva:

Session.Abandon() ;

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

Com a modificação feita anteriormente, podemos agora regressar ao formulário, mas surge então uma anomalia — a descrita anteriormente na secção 9.15.3.


Tarefa: Seguindo o procedimento utilizado na secção 9.15.3, corrija a função JavaScript [terminerSession] e, em seguida, execute testes para verificar se os validadores do lado do cliente estão novamente a funcionar.


9.18. Passo 12: Limpar a simulação

9.18.1. O problema

Quando uma simulação é criada, pode ser apagada utilizando o link JavaScript [Apagar Simulação]:

Image

Image

9.18.2. Escrever a ação do lado do cliente [clearSimulation]

A função JavaScript [clearSimulation] tem atualmente o seguinte código:


function effacerSimulation() {
  // delete form entries
  // ...
  // hide the simulation if it exists
  $("#simulation").hide();
  // menu
  setMenu([lnkFaireSimulation, lnkTerminerSession, lnkVoirSimulations]);
}


Tarefa: Complete este código. Pode usar o exemplo da secção 7.6.6 como guia


9.19. Passo 13: Remover uma simulação

9.19.1. O problema

Na página de simulações, pode eliminar determinadas simulações utilizando o link JavaScript [remover]:

Image

Image

9.19.2. Escrever a ação do cliente [removeSimulation]

Os links [remove] 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 nas secções 9.9.3, escreva a função JS [removeSimulation]. Esta função enviará um pedido Ajax do tipo POST para a ação [/Pam/RemoveSimulation]. Enviará os dados N no formato num=N.


Nota: A função JS [retirerSimulation] é semelhante às outras funções JS que escreveu e que fazem uma chamada Ajax para o servidor. A única diferença aqui é o envio de um valor que não está num formulário. Sabemos que os valores enviados são combinados numa cadeia de caracteres no seguinte formato:

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

A função JS [removeSimulation] terá, portanto, o seguinte formato:


function retirerSimulation(N) {
  // make a manual Ajax call
  $.ajax({
    url: '/Pam/RetirerSimulation',
...
    data:"num="+N,
...
  });
  // menu
  setMenu([lnkRetourFormulaire, lnkTerminerSession]);
}

  • Linha 6: A propriedade [data] de uma chamada jQuery Ajax representa a string enviada para o servidor.

9.19.3. Escrever a ação do servidor [RemoveSimulation]

A ação do servidor [RemoveSimulation]:

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


Tarefa: Escreva a ação do servidor [RemoveSimulation]. Consulte a Secção 4.1 para saber como recuperar o parâmetro enviado chamado [num].


9.20. Passo 14: Melhorar o método de inicialização da aplicação

A nossa aplicação web está concluída. Está funcional com uma classe [business] simulada. Vamos rever a arquitetura que desenvolvemos:

Ainda há alguns detalhes a resolver antes de avançarmos para a implementação efetiva da camada [business], e isso ocorre no método de inicialização da aplicação: o método [Application_Start] em [Global.asax]:

O método [Application_Start] em [Global.asax] é executado apenas uma vez, quando a aplicação é iniciada. É aqui que o ficheiro de configuração [Web.config] pode ser utilizado. Por enquanto, o nosso método [Application_Start] tem o seguinte aspeto:


// application
    protected void Application_Start()
    {
      // ----------Auto-generated
      AreaRegistration.RegisterAllAreas();
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // -------------------------------------------------------------------
      // ---------- specific configuration
      // -------------------------------------------------------------------
      // application scope data
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
      // instantiation layer [business]
      application.PamMetier = new PamMetier();
...
      // model binders
...
}

Na linha 17, a camada de negócios é instanciada utilizando o operador new. Além disso, o modelo da aplicação é definido da seguinte forma:


  public class ApplicationModel
  {
     // --- application scope data ---
    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]. Isto significa que esta propriedade pode ser inicializada por qualquer objeto que implemente esta interface. No entanto, na linha 17 de [Application_Start], codificámos de forma rígida o nome de uma classe que implementa [IPamMetier]. Portanto, se a camada [business] fosse implementada com uma nova classe que implementasse [IPamMetier], esta linha teria de ser alterada. Isto não é um problema grave, mas pode ser evitado. A definição da classe que implementa a interface [IPamMetier] pode ser movida para um ficheiro de configuração. Para alterar a implementação, modificamos então o conteúdo deste ficheiro de configuração. O código .NET não precisa de ser alterado.

Aqui, utilizaremos o contentor de injeção de dependências [Spring.net]. Existem outras estruturas .NET que podem fazer o mesmo, talvez de forma 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á uma referência à camada [business] simulada ao [Spring.net];
  • em [B], o [Spring.net] criará a camada [business] simulada utilizando o seu ficheiro de configuração para determinar qual a classe a instanciar;
  • em [C], o [Spring.net] devolverá a referência à camada [business] simulada à camada [ASP.NET MVC].

Note que, por predefinição, os objetos geridos pelo [Spring.net] são singletons: existe apenas uma instância de cada. Assim, se mais tarde no nosso exemplo, o código solicitar novamente uma referência à camada [business] simulada ao [Spring.net], o [Spring.net] simplesmente devolve a referência ao objeto inicialmente criado.

9.20.1. Adicionar referências [Spring] ao projeto web

Iremos utilizar o [Spring.net]. Esta estrutura vem na forma de uma DLL que deve ser adicionada às referências do projeto. Eis como fazê-lo:

Em [1], clique com o botão direito do rato no ramo [Referências] do projeto e selecione a opção [Gerir Pacotes NuGet]. É necessária uma ligação à Internet. Em seguida, proceda como feito anteriormente para a biblioteca JQuery [Globalize]. Procure a palavra-chave [Spring.core] e instale este pacote. A instalação inclui duas DLLs: [Spring.core] [2] e [Common.Logging] [3]. Nos exemplos seguintes, foi utilizada a versão 1.3.2 do Spring.

Nota: Se não tiver ligação à Internet, encontrará estas DLLs na pasta [lib] dos materiais deste estudo de caso.

9.20.2. Configurar o [web.config]

A definição da classe de implementação para a interface [IPamMetier] está definida no ficheiro [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>
  <!-- spring configuration -->
  <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 ficheiro e insira as linhas 4–7 dentro dela;
  • linha 4: o atributo [name="spring"] fornece informações sobre a secção [spring] nas linhas 10-17;
  • linha 5: define a classe [Spring.Context.Support.DefaultSectionHandler] localizada na DLL [Spring.Core] como a capaz de lidar com a secção [objects] nas linhas 14–16;
  • linha 6: define a classe [Spring.Context.Support.ContextHandler], localizada na DLL [Spring.Core], como a responsável pelo processamento da secção [context] nas linhas 11–13;
  • linhas 11–13: esta secção fornece a informação [<resource uri="config://spring/objects" />], que indica que os objetos Spring estão localizados no ficheiro de configuração dentro da secção [/spring/objects], ou seja, nas linhas 14–16;
  • linhas 14–16: a tag [objects] apresenta 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]. Tenha cuidado para não cometer um erro aqui. Para o atributo [id], pode usar o que quiser. Irá utilizar este identificador em [Global.asax]. A classe [Pam.Metier.Service.PamMetier] é a da nossa camada [business] simulada. Precisa de voltar à sua definição para encontrar o seu nome completo:

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

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

Deve utilizar o nome indicado em [1].

9.20.3. Modificação de [Application_Start]

O método [Application_Start] é alterado da seguinte forma:


using Spring.Context.Support;
 
// application
    protected void Application_Start()
    {
      // ----------Auto-generated
      AreaRegistration.RegisterAllAreas();
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // -------------------------------------------------------------------
      // ---------- specific configuration
      // -------------------------------------------------------------------
      // application scope data
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
      // instantiation layer [business]
      application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
...
      // model binders
...
}
  • Linha 19: Utilizamos a classe Spring [ContextRegistry], que é capaz de processar o ficheiro [web.config]. Para tal, precisamos de importar o namespace da linha 1. O método estático [GetContext] recupera o conteúdo das tags [context], que indicam onde se encontram os objetos Spring. O método estático [GetObject] permite-nos, então, recuperar um objeto específico identificado pelo seu atributo id. Note que o nome da classe que implementa a interface [IPamMetier] já não está codificado no código. Encontra-se agora no ficheiro [web.config].

Depois de efetuar todas estas alterações, teste a sua aplicação. Deverá funcionar.

9.20.4. Tratamento de um erro de inicialização da aplicação

No método [Application_Start], escrevemos:


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

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

  • a mais óbvia é que cometemos um erro no nome do objeto a ser instanciado;
  • outra é que a instanciação da camada [de negócios] falha. Este não pode ser o caso da nossa camada [de negócios] simulada, mas poderia ser da nossa camada [de negócios] real, que estará ligada a uma base de dados. O SGBD pode não estar a funcionar, as informações sobre a base de dados a gerir podem estar incorretas, etc...

Vamos tratar quaisquer exceções num bloco try/catch. O código evolui da seguinte forma:


// application
    protected void Application_Start()
    {
      // ----------Auto-generated
...
      // -------------------------------------------------------------------
      // ---------- specific configuration
      // -------------------------------------------------------------------
      // application scope data
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
      application.InitException = null;
      try
      {
        // instantiation layer [business]
        application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
      }
      catch (Exception ex)
      {
        application.InitException = ex;
      }
      //if no error
      if (application.InitException == null)
      {
....
      }
      // model binders
...
    }

  • Na linha 12, introduzimos uma nova propriedade chamada [InitException] no modelo da aplicação:

  public class ApplicationModel
  {
     // --- application scope data ---
    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 da aplicação;
  • linhas 13–21 de [Application_Start]: a instanciação da camada [business] ocorre agora dentro de um bloco try/catch;
  • linha 20: a exceção é capturada;
  • linhas 23–26: se não ocorreu nenhum erro, o código anterior é executado;
  • linha 28: os [ModelBinders] são criados independentemente de ter ocorrido um erro ou não. Isto é importante. Queremos garantir que o modelo da aplicação [ApplicationModel] é devidamente vinculado pela estrutura.

Sabemos que, quando a aplicação é iniciada, a ação do servidor [Index] é executada. Por enquanto, é a seguinte:


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

Linha 2: A ação [Index] recebe o modelo da aplicação. Pode, portanto, determinar se a inicialização foi bem-sucedida ou não e apresentar uma página de erro caso a inicialização tenha falhado de alguma forma. Modificamos o código da seguinte forma:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      // initialization error?
      if (application.InitException != null)
      {
        // error page without menu
        return View("InitFailed",Static.GetErreursForException(application.InitException));
      }
      // no error
      return View(new IndexModel() { Application = application });
}

Linha 8: No caso de um erro de inicialização, exibimos a vista [InitFailed.cshtml], utilizando a lista de mensagens de erro da exceção que ocorreu durante a inicialização como modelo. O método [Static.GetErrorsForException] foi apresentado e explicado na Secção 9.12.4. A vista [InitFailed.cshtml] será a seguinte:

O 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 de visualização é uma lista de mensagens de erro. Estas são apresentadas numa lista HTML nas linhas 24–29;
  • linha 3: esta vista não utiliza a página mestre [_Layout.cshtml]. Isto porque não queremos o menu fornecido por esse documento. Por isso, criamos uma página HTML completa (linhas 5–23).

Para testar isto, basta modificar a instanciação da camada [business] em [Application_Start] da seguinte forma:


      try
      {
        // instantiation layer [business]
        application.PamMetier = ContextRegistry.GetContext().GetObject("xx") as IPamMetier;
      }
      catch (Exception ex)
      {
        application.InitException = ex;
}

Linha 4: Estamos à procura de um objeto que não existe nos objetos Spring.

Quando guardamos estas alterações e executamos a aplicação, obtemos a seguinte página:

Image

Recebemos uma página de erro sem menu. O utilizador não pode fazer nada além de confirmar o erro. Era isto que queríamos.

9.21. Em que ponto estamos agora?

Temos agora uma aplicação web funcional que opera com uma camada de negócios simulada. A sua arquitetura é a seguinte:

A camada [ASP.NET MVC] funciona com a camada de negócios simulada através da interface [IPamMetier]. Se substituirmos esta camada de negócios simulada por uma camada de negócios real que implemente esta interface, não teremos de modificar o código da camada web. Graças ao [Spring.net], bastará alterar a classe de implementação da interface [IPamMetier] no ficheiro [web.config]. Vamos avançar com esta abordagem.

A nova arquitetura será a seguinte:

Descreveremos o seguinte, por ordem:

  • a camada [EF5] ligada ao SGBD. Será implementada utilizando o Entity Framework 5 (EF5);
  • a camada [DAO], que gere o acesso aos dados através da camada [EF5]. Isto permite que ela não tenha conhecimento do SGBD. Esta camada limita-se a manipular as entidades da aplicação [Empregado, Contribuições, Subsídios];
  • a camada [business] que implementa o cálculo do salário.

A nova arquitetura é a apresentada no início deste documento, na secção 1.1, que resumimos agora:

  • a camada [Web] é a camada em contacto com o utilizador da aplicação web. O utilizador interage com a aplicação web através de páginas web visualizadas num navegador. O ASP.NET MVC reside nesta camada e apenas nesta camada;
  • a camada [de negócios] implementa as regras de negócio da aplicação, tais como o cálculo de um salário ou de uma fatura. Esta camada utiliza dados do utilizador através da camada [Web] e do SGBD através da camada [DAO];
  • A camada [DAO] (Data Access Objects), a camada [ORM] (Object Relational Mapper) e o conector ADO.NET gerem o acesso aos dados do SGBD. A camada [ORM] atua como uma ponte entre os objetos tratados pela camada [DAO] e as linhas e colunas de dados numa base de dados relacional. Dois ORMs são comumente utilizados no mundo .NET: NHibernate (http://sourceforge.net/projects/nhibernate/) e Entity Framework (http://msdn.microsoft.com/en-us/data/ef.aspx);
  • A integração das camadas pode ser alcançada utilizando um contentor de injeção de dependências, como o Spring (http://www.springframework.net/);

As camadas [business], [DAO] e [EF5] serão implementadas utilizando projetos C#. A partir de agora, iremos trabalhar com o Visual Studio Express 2012 para Desktop.

9.22. Passo 15: Configurar a camada Entity Framework 5

A criação da camada [EF5] envolve menos programação e mais configuração. Para compreender como escrever esta camada, leia o documento [Introdução ao 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 fundamentais são abordados nos primeiros quatro capítulos. As secções específicas a ler serão indicadas. Quando nos referirmos a este documento, utilizaremos a notação [refEF5].

Além disso, por vezes precisaremos de recorrer a conceitos de C#. Nesses casos, faremos referência ao curso [Introdução à Linguagem C#], disponível no URL [http://tahe.developpez.com/dotnet/csharp/], utilizando a notação [refC#].

9.22.1. A Base de Dados

A base de dados da aplicação foi apresentada na Secção 9.4. Trata-se de uma base de dados MySQL denominada [dbpam_ef5] (pam = Paie Assistante Maternelle). Esta base de dados tem um administrador denominado root, sem palavra-passe.

Vamos rever o esquema da base de dados. Tem três tabelas:

Image

Existe uma relação de chave estrangeira entre a coluna EMPLOYEES(INDEMNITY_ID) e a coluna INDEMNITIES(ID). Parte da estrutura desta base de dados é ditada pela sua utilização com o EF5.

O script SQL para criar a base de dados é o seguinte:


-- phpMyAdmin SQL Dump
-- version 3.5.1
-- http://www.phpmyadmin.net
--
-- Customer: localhost
-- Generated on: Mon November 04, 2013 at 09:34 am
-- Server version: 5.5.24-log
-- Version of 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 */;
 
--
-- Database: `dbpam_ef5`
--
 
-- --------------------------------------------------------
 
--
-- Structure of the `contributions` table
--
 
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 ;
 
--
-- Contents of the `contributions` table
--
 
INSERT INTO `cotisations` (`ID`, `SECU`, `RETRAITE`, `CSGD`, `CSGRDS`, `VERSIONING`) VALUES
(11, 9.39, 7.88, 6.15, 3.49, 1);
 
--
-- Contribution triggers
--
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 ;
 
-- --------------------------------------------------------
 
--
-- Structure of the `employees` table
--
 
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 ;
 
--
-- Contents of the `employees` table
--
 
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);
 
--
-- Used' triggers
--
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 ;
 
-- --------------------------------------------------------
 
--
-- Structure of the `indemnities` table
--
 
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 ;
 
--
-- Contents of the `indemnities` table
--
 
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);
 
--
-- Compensation triggers
--
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 ;
 
--
-- Constraints for exported tables
--
 
--
-- Constraints for the `employees` table
--
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 */;

Tenha em atenção os seguintes pontos:

  • linhas 30, 73, 122: as chaves primárias das tabelas estão no modo [AUTO_INCREMENT]. Estas são geridas pelo MySQL, não pelo EF5;
  • linha 83: o número SS tem uma restrição de exclusividade;
  • linha 130: o ID do funcionário tem uma restrição de exclusividade;
  • linhas 168–169: a chave estrangeira da tabela [employees] para a tabela [benefits];
  • linha 49: um trigger é um script SQL incorporado pelo SGBD que é executado em momentos específicos;
  • linhas 51–54: o gatilho [INCR_VERSIONING_COTISATIONS] é acionado antes de qualquer modificação de uma linha na tabela [cotisations]. Em seguida, ele incrementa a coluna [VERSIONING] em um;
  • Linhas 59–62: O gatilho [START_VERSIONING_COTISATIONS] é acionado antes de qualquer nova linha ser inserida na tabela [cotisations]. Em seguida, inicializa a coluna [VERSIONING] para 1;
  • Em última análise, a coluna [VERSIONING] é definida como 1 quando uma linha é criada na tabela [contributions] e é então incrementada em 1 cada vez que é feita uma modificação nessa linha. Este mecanismo permite ao EF5 gerir o acesso simultâneo a uma linha na tabela [contributions] da seguinte forma:

    • Um processo P1 lê uma linha L da tabela [contributions] no momento T1. A linha tem um valor na coluna [VERSIONING] igual a V1;
    • um processo P2 lê a mesma linha L da tabela [contributions] no momento T2. A linha tem um valor na coluna [VERSIONING] de V1, porque o processo P1 ainda não confirmou a sua modificação;
    • O processo P1 modifica a linha L e confirma a sua alteração. A coluna [VERSIONING] da linha L muda então para V1+1 devido ao gatilho [INCR_VERSIONING_COTISATIONS];
    • O processo P2 faz então o mesmo. O EF5 lança então uma exceção porque o processo P2 tem uma linha com uma coluna [VERSIONING] com o valor V1, o que difere do valor encontrado na base de dados, que é V1+1. Uma linha só pode ser modificada se tiver o mesmo valor [VERSIONING] que o da base de dados.

Isto é chamado de controlo de concorrência otimista. Com o EF5, um campo que desempenhe esta função deve ter a anotação [ConcurrencyCheck].

  • É criado um mecanismo semelhante para a tabela [employes] (linhas 98–113) e para a tabela [indemnites] (linhas 144–159).


Tarefa: Crie a base de dados MySQL [dbpam_ef5] utilizando o script SQL anterior. A base de dados [dbpam_ef5] deve ser criada previamente, pois o script não a cria. Em seguida, executaremos o script SQL nesta base de dados.


9.22.2. O projeto do Visual Studio

Usando o Visual Studio Express 2012 for Desktop, carregamos a solução [pam-td] usada na construção da camada [web]:

  • em [1], o VS 2012 Express for Desktop não consegue carregar o projeto web [pam-web-01]. Isto é normal e não constitui um problema;
  • em [2], adicionamos um novo projeto à solução [pam-td];
  • em [3], o projeto é do tipo [console] e tem o nome [4] [pam-ef5];
  • em [5], o projeto é criado. O seu nome não está em negrito, pelo que não é o projeto de arranque da solução;
  • Em [6] e [7], definimos o novo projeto como projeto de inicialização.

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

Vamos analisar o projeto na sua totalidade:

O nosso projeto requer várias DLLs:

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

A secção 4.2 de [refEF5] explica como instalar estas DLLs utilizando 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 para MySQL disponível (novembro de 2013) através do [NuGet] não é compatível com o EF6. Por isso, colocámos a DLL do EF5 e as outras DLLs necessárias para o projeto [pam-ef5] numa pasta [lib] [1]

Colocámos outras DLLs na pasta [lib]. Iremos utilizá-las mais tarde. Em [2], adicionamos estas novas DLLs ao projeto.

  • Em [3], navegue pelo sistema de ficheiros até à pasta [lib];
  • Em [4], selecione as três DLLs e clique em OK duas vezes;
  • Em [5], as três DLLs foram adicionadas às referências do projeto.

Precisamos de outra DLL. Esta encontra-se entre as que estão no .NET Framework da máquina.

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

Estamos agora prontos para programar e configurar.

9.22.4. Entidades do Entity Framework

As entidades do Entity Framework são classes que encapsulam as linhas das várias tabelas da base de dados. Vamos revê-las:

Image

Na camada [web], utilizámos as entidades [Employee, Contributions, Benefits] (ver secção 9.7.3, página 211). Estas não eram representações exatas das tabelas. Assim, as colunas [ID, VERSIONING] foram ignoradas. Aqui, isso não acontecerá, porque são utilizadas pelo ORM do EF5. Por isso, iremos adicionar-lhes as propriedades em falta. Criamos estas entidades numa pasta [Models] dentro do projeto:

O 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; }
 
    // signature
    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 nas linhas 7 e 12 foram adicionadas para refletir a estrutura da tabela [contributions];
  • 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; }
 
    // signature
    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 nas linhas 7 e 13 foram adicionadas para refletir a estrutura da tabela [indemnites];
  • linha 18: o método [ToString] apresenta agora os dois novos campos.

Classe [Employee]


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; }
 
    // signature
    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 nas linhas 8 e 16 foram adicionadas para refletir a estrutura da tabela [employees];
  • linha 21: o método [ToString] agora exibe os dois novos campos.

Para poderem ser utilizadas pelo ORM do EF5, as propriedades destas classes devem ser anotadas.


Tarefa: Utilizando a secção 3.4 [Criação da base de dados a partir de entidades] de [refEF5], adicione as anotações exigidas pelo EF5 às entidades [Employee, Contributions, Benefits].


Dicas:

  • só precisa de criar anotações. Não siga a secção [criação da base de dados] do parágrafo referido;
  • para a anotação [Table], siga o exemplo do MySQL na secção 4.2 de [refEF5];
  • Para a anotação [ConcurrencyCheck] na propriedade [Versioning], siga o exemplo do Oracle na secção 5.2 de [refEF5];
  • para a chave estrangeira que a tabela [employes] possui na tabela [indemnités], siga o exemplo 3.4.2 de [refEF5]. Assim, irá adicionar uma nova propriedade à entidade [Employe]:

    public int IndemniteId { get; set; }

cujo valor será o da coluna [INDEMNITES_ID] na tabela [employes]. Aplicará anotações de chave estrangeira às propriedades [IndemniteId] e [Indemnites] da entidade [Employe]. Para tal, siga o Exemplo 3.4.2 em [refEF5];

  • não irá gerir as relações inversas das chaves estrangeiras;
  • esta tarefa requer alguma leitura de [refEF5].

9.22.5. Configurar o ORM do EF5

Vamos contextualizar o projeto:

A camada [EF5] irá aceder à base de dados através do conector [ADO.NET] para o SGBD MySQL. São necessárias determinadas informações para aceder a esta base de dados. Estas informações encontram-se em várias partes do projeto.

Primeiro, temos de criar o contexto da base de dados. Este contexto é uma classe derivada da classe de sistema [System.Data.Entity.DbContext]. É utilizado para definir as representações objetivas das tabelas da base de dados. Colocaremos esta classe na pasta [Models] do projeto, juntamente 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 de sistema [DbContext];
  • linhas 8–10: as representações de objeto das três tabelas da base de dados. O seu tipo é [DbSet<Entity>], onde [Entity] é uma das entidades do Entity Framework que acabámos de definir. O tipo [DbSet] pode ser visto como uma coleção de entidades. Pode ser consultado utilizando LINQ (Language-Integrated Query). Os leitores que não estejam familiarizados com o LINQ são encorajados a ler a secção 3.5.4 [Aprender LINQ com o LINQPad] em [refEF5].

Doravante, referir-nos-emos à classe [DbPamContext] como o contexto de persistência da base de dados [dbpam_ef5]. Esta é a terminologia padrão em ORMs (Mapeadores Objeto-Relacionais). Este contexto de persistência é uma representação orientada para objetos da base de dados. Referimo-nos também à sincronização do contexto de persistência com a base de dados: as modificações, adições e eliminações feitas no contexto de persistência são refletidas na base de dados. Esta sincronização ocorre em momentos específicos: quando o contexto de persistência é fechado, no final de uma transação ou antes de uma consulta SQL SELECT na base de dados.

As informações sobre o SGBD e a base de dados são armazenadas em [App.config].

A configuração necessária no ficheiro [app.config] é explicada nas seguintes secções do [refEF5]:

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

Seguimos esta última secção e configuramos o ficheiro [app.config] da seguinte forma:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
    <!-- configuration EF5 -->
    <!-- database connection string [dbam_ef5] -->
    <connectionStrings>
        <add name="DbPamContext"
         connectionString="Server=localhost;Database=dbpam_ef5;Uid=root;Pwd=;"
         providerName="MySql.Data.MySqlClient" />
    </connectionStrings>
    <!-- the MySQL factory provider -->
    <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>
  • Foram adicionadas as linhas 6–21. Estas devem ser inseridas dentro da tag <configuration> nas linhas 2 e 22;
  • Linhas 8–12: definem cadeias de ligação à base de dados, um conceito do ADO.NET (ver secção 7.3.5 em [refC#]);
  • Linhas 9–11: definem a cadeia de ligação à base de dados MySQL [dbpam_ef5];
  • linha 9: o nome da cadeia de ligação. Aqui, não pode introduzir qualquer coisa. Por predefinição, deve introduzir o nome da classe que implementa o contexto da base 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 chama-se [DbPamContext]. Na linha 9 do [app.config], deve definir [name="DbPamContext"];

  • Linha 10: uma cadeia de ligação específica para o SGBD MySQL:
    • [Server=localhost]: endereço IP da máquina que hospeda o SGBD. Aqui, trata-se da máquina local [localhost];
    • [Database=dbpam_ef5;]: nome da base de dados,
    • [Uid=root;]: o nome de utilizador utilizado para se ligar à base de dados,
    • [Pwd=;]: palavra-passe para este login. Aqui, sem palavra-passe;
  • linha 10: [providerName="MySql.Data.MySqlClient"] é o nome do fornecedor ADO.NET a utilizar. Este nome corresponde ao atributo [invariant] na linha 17. Pode utilizar qualquer nome, desde que siga a regra anterior e que um fornecedor com o mesmo invariant ainda não tenha sido registado;
  • Linhas 15–20: definem uma fábrica de fornecedores ADO.NET. O [DbProviderFactory] é um conceito um pouco vago para mim. A julgar pelo nome, parece ser uma classe capaz de gerar o fornecedor ADO.NET que permite o acesso ao SGBD, neste caso o MySQL 5. Estas linhas são normalmente copiadas e coladas. São necessárias. Preste atenção ao atributo [Version=6.5.4.0] na linha 16. Este número de versão deve corresponder ao número de versão da DLL [MySql.Data] que adicionou às referências do projeto:
  • A linha 16 é importante. Como não é possível instalar dois fornecedores com o mesmo nome, comece por remover qualquer fornecedor existente que possa ter o mesmo nome que aquele que está a instalar na linha 17;

É isso. É complicado e confuso na primeira vez que o faz, mas com o tempo torna-se simples, porque é sempre o mesmo processo que repete.

9.22.6. Testar a camada [EF5]

Estamos prontos para testar a nossa camada [EF5]. Fazemos isso utilizando o programa [Program.cs] existente:

Vamos exibir o conteúdo da base de dados. Se formos bem-sucedidos, será uma indicação inicial de que a nossa configuração está correta. Está disponível um exemplo de código na secção 3.5.3 de [refEF5]. O código para [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())
        {
          // display table contents
          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: todas as operações de base de dados são realizadas através deste contexto de base de dados. Implementámos este contexto utilizando a classe [DbPamContext]. Também nos referimos a ele como o contexto de persistência da base de dados;
  • linhas 13, 31: as operações no contexto de persistência são realizadas dentro de um bloco [using]. O contexto de persistência é aberto no início do bloco [using] e fechado automaticamente quando o bloco termina. Isto significa que quaisquer alterações feitas no contexto de persistência dentro do bloco [using] serão refletidas na base de dados quando o bloco terminar. Uma série de instruções SQL é então enviada para a base de dados dentro de uma transação. Isto significa que, se uma instrução SQL falhar, todas as instruções SQL emitidas anteriormente são revertidas. Uma exceção é então lançada pelo EF5;
  • linha 17: a expressão [context.Employees] refere-se ao modelo de objeto da tabela [employees]. Recorde-se que [Employees] é 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 facto de o ciclo [foreach] iterar sobre a coleção [context.Employees] irá buscar todos os funcionários da base de dados para o contexto de persistência. Uma instrução SQL SELECT será, portanto, emitida pelo EF5;
  • linhas 17–20: iteramos pela coleção de funcionários e, na linha 19, usamos o método [ToString] da classe [Employee] para exibir os funcionários na consola;
  • linhas 21–25: o mesmo para a coleção de benefícios;
  • linhas 27–30: o mesmo para a coleção de contribuições.

Vamos rever a definição da entidade [Employee]:


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; }
 
    // signature
    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 tem uma referência a um benefício.

Quando um funcionário é inserido no contexto de persistência, o seu subsídio é também inserido? A resposta padrão é não. Este é o conceito de [Carregamento Preguiçoso]. As entidades referenciadas dentro de outra entidade não são inseridas no contexto de persistência juntamente com essa outra entidade. Só são inseridas quando solicitadas pelo código dentro de um contexto de persistência aberto. Se o contexto de persistência for fechado, é lançada uma exceção.

Assim, se o método [ToString] tivesse referenciado a propriedade [Indemnites] da seguinte forma:


    // signature
    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 seguinte operação em [Program.cs]:


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

teria devolvido não só os funcionários, mas também os seus benefícios ao contexto de persistência, porque na linha 3, o método [Employee.ToString] é chamado e faz referência à entidade [Benefits].

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]

O que fazer se não funcionar? Está com problemas... Existem muitas fontes possíveis de erro:

  • verifique a configuração do EF5 (secção 9.22.5);
  • verifique as suas entidades do Entity Framework (secção 9.22.4).

9.22.7. [EF5] DLL da camada

Estamos a transformar o nosso projeto numa biblioteca de classes para que, após a geração, seja criado um assembly .dll em vez de um .exe. Isto é feito nas propriedades do projeto, conforme visto na secção 9.7.6, para a camada de negócios simulada.


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


9.23. Passo 16: Implementação da camada [DAO]

9.23.1. A interface da camada [DAO]

Tal como fizemos para a camada [business] simulada, a camada [DAO] será acessível através de uma interface. Qual será?

Vejamos a interface [IPamMetier] da camada [business] simulada que criámos:


    public interface IPamMetier {
        // list of all employee identities 
        Employe[] GetAllIdentitesEmployes();
 
        // ------- salary calculation 
        FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}

Linha 3: O método [GetAllEmployeeIDs] é utilizado para preencher a lista suspensa na página inicial:

Estes funcionários devem ser recuperados da base de dados.

Na linha 6, o método [GetSalary] calcula o recibo de vencimento de um funcionário cujo SSN é conhecido. Recorde a definição do tipo [PayStub]:


  public class FeuilleSalaire
  {
 
    // automatic properties 
    public Employe Employe { get; set; }
    public Cotisations Cotisations { get; set; }
    public ElementsSalaire ElementsSalaire { get; set; }
}

As informações nas linhas 5 e 6 provêm da base de dados. Lembre-se de que um funcionário tem uma propriedade [Allowances]. Esta informação também deve ser recuperada.

Podemos, portanto, começar com a seguinte interface para a camada [DAO]:


    public interface IPamDao {
        // list of all employee identities 
        Employe[] GetAllIdentitesEmployes();
        // an individual employee with benefits 
        Employe GetEmploye(string ss);
        // list of all contributions 
        Cotisations GetCotisations();
}

9.23.2. O projeto do Visual Studio


Tarefa: Adicione um novo projeto [console] chamado [pam-dao] à solução [pam-td]. Defina-o como o projeto de inicialização da solução.


Image

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

Vamos analisar o projeto como um todo:

O projeto [pam-dao] requer várias DLLs:

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

Além disso, utilizaremos o [Spring.net] para instanciar a camada [DAO]. Para tal, necessitamos das DLLs [Spring.core] e [Common.Logging]. Estas DLLs encontram-se na pasta [lib] dos materiais do estudo de caso.


Tarefa: Adicione estas várias referências ao projeto [pam-dao].


9.23.4. Implementação da camada [DAO]

Acima, a classe [PamException] é a definida na secção 9.7.4. Basta alterar o seu namespace (linha 1 abaixo):


namespace Pam.Dao.Entites
{
  // exceptional class
  public class PamException : Exception
  {
....
  }
}

A interface [IPamDao] é aquela que acabámos de definir na Secção 9.23.1:


using Pam.EF5.Entites;
 
namespace Pam.Dao.Service
{
  public interface IPamDao
  {
    // list of all employee identities 
    Employe[] GetAllIdentitesEmployes();
    // an individual employee with benefits 
    Employe GetEmploye(string ss);
    // list of all contributions 
    Cotisations GetCotisations();
  }
}

A classe [PamDaoEF5] implementa esta interface utilizando o ORM EF5. O 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
  {
    // private fields 
    private Cotisations cotisations;
    private Employe[] employes;
 
    // Manufacturer
    public PamDaoEF5()
    {
      // contribution
      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);
      }
    }
  }
}

Nota:

  • linha 10: a classe [PamDaoEF5] implementa a interface [IPamDao];
  • as tabelas [contributions] e [employees] são armazenadas em cache nas propriedades das linhas 13–14. Os funcionários não incluem os seus subsídios;
  • linhas 17–28: o construtor inicializa as linhas 13–14;
  • Linhas 43–52: O método [GetEmploye] devolve um funcionário juntamente com os seus subsídios. Aceita o número de segurança social do funcionário como parâmetro. Se o funcionário não existir na base de dados, o método devolverá um ponteiro nulo.


Tarefa: Complete o código para a classe [PamDaoEF5].


Para o construtor, utilize o código de teste da camada [EF5] apresentado na secção 9.22.6 como orientação. Para o método [GetEmploye], utilize o exemplo da secção 3.5.7 [Carregamento antecipado e diferido] de [refEF5] como orientação.

9.23.5. Configurar a camada [DAO]

Tal como feito na secção 9.22.5, precisamos de configurar o EF5 no ficheiro [App.config] do projeto:


Tarefa 1: Configure o EF5 no [App.config]. Basta replicar o que foi feito no ficheiro [App.config] da camada [EF5].


O nosso programa de teste utilizará o [Spring.net] para obter uma referência à camada [DAO].


Tarefa 2: Utilizando as informações da Secção 9.20.2, modifique o ficheiro de configuração [app.config] no projeto [pam-dao] para que defina um objeto Spring denominado [pamdao] associado à classe [PamDaoEF5] que acabámos de criar. Os ficheiros [app.config] e [web.config] têm a mesma estrutura. Certifique-se de que a tag <configSections> é a primeira tag encontrada após a tag raiz <configuration>.


9.23.6. Testar a camada [DAO]

Estamos prontos para testar a nossa camada [DAO]. Fazemos isso utilizando o programa [Program.cs] existente:

Iremos testar as várias funcionalidades da interface da camada [DAO]. O código para [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
      {
        // layer instantiation [dao]
        IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
        // list of employee identities 
        foreach (Employe Employe in pamDao.GetAllIdentitesEmployes())
        {
          Console.WriteLine(Employe.ToString());
        }
            // an employee with benefits 
            Console.WriteLine("------------------------------------");
            Employe e = pamDao.GetEmploye("254104940426058");
            Console.WriteLine("employé= {0}, indemnités={1}", e, e.Indemnites);
            Console.WriteLine("------------------------------------");
        // an employee who doesn't exist 
        Employe employe = pamDao.GetEmploye("xx");
        Console.WriteLine("Employé n° xx");
        Console.WriteLine((employe == null ? "null" : employe.ToString()));
        Console.WriteLine("------------------------------------");
        // list of contributions 
        Cotisations cotisations = pamDao.GetCotisations();
        Console.WriteLine(cotisations.ToString());
      }
      catch (Exception ex)
      {
        // exception display 
        Console.WriteLine(ex.ToString());
      }
      //break 
      Console.ReadLine();
    }
  }
}

  • Linha 15: Obtemos uma referência à camada [DAO] utilizando [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 de camada [DAO]


Tarefa: Converta o tipo de projeto [pam-dao] numa biblioteca de classes e, em seguida, gere novamente o projeto (repita os passos da secção 9.22.7).


9.24. Passo 17: Configurar a camada [business]

9.24.1. A interface da camada [business]

A interface da camada [business] será a interface [IPamMetier] da camada [business] simulada que construímos na secção 9.7.2.


    public interface IPamMetier {
        // list of all employee identities 
        Employe[] GetAllIdentitesEmployes();
 
        // ------- salary calculation 
        FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}

9.24.2. O projeto do Visual Studio


Tarefa: Adicione um novo projeto [console] chamado [pam-metier] à solução [pam-td]. Defina-o como o projeto de inicialização da solução.


Image

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

Vamos analisar o projeto como um todo:

O projeto [pam-metier] requer várias DLLs:

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

Tarefa: Adicionar estas várias referências ao projeto [pam-metier].


Image

9.24.4. Implementação da camada [business]

Acima, encontramos quatro elementos já utilizados na camada [business] simulada (ver Secção 9.7). Poderá haver alterações nos namespaces importados por estas diferentes classes. Trate-as. A classe [PamMetier] implementa a interface [IPamMetier] da seguinte forma:


using Pam.Dao.Service;
using Pam.EF5.Entites;
using Pam.Metier.Entites;
using System;
 
namespace Pam.Metier.Service
{
 
  public class PamMetier : IPamMetier
  {
 
    // reference to layer [DAO] initialized by Spring
    public IPamDao PamDao { get; set; }
 
    // list of all employee identities 
    public Employe[] GetAllIdentitesEmployes()
    {
      ...
    }
 
    // an individual employee with benefits 
    public Employe GetEmploye(string ss)
    {
      ...
    }
 
    // contributions 
    public Cotisations GetCotisations()
    {
      ...
    }
 
    // wage calculation 
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
    {
      // SS : employee's SS number 
      // HeuresTravaillées: number of hours worked 
      // Days worked: number of days worked 
...
  }
}

  • Linha 13: Temos uma referência à camada [DAO]. Esta será inicializada pelo Spring quando a classe [PamMetier] for instanciada. Portanto, quando os vários métodos forem executados, a linha 13 já terá sido inicializada.


Tarefa: Complete o código da classe [PamMetier]. Se em [GetSalaire] verificarmos que o funcionário com o número de segurança social não existe, lançaremos uma [PamException]. O método para calcular o salário é explicado na secção 9.5. Certifique-se de arredondar todos os cálculos intermédios para duas casas decimais.


9.24.5. Configurar a camada [business]

Tal como feito na secção 9.22.5, precisamos de configurar o EF5 no ficheiro [app.config] do projeto:


Tarefa 1: Configure o EF5 no [app.config]. Basta replicar o que foi feito no ficheiro [app.config] da camada [EF5].


O nosso programa de testes utilizará o [Spring.net] para obter uma referência à camada [de negócios].


Tarefa 2: Utilizando o que fez anteriormente na secção 9.23.5, modifique o ficheiro de configuração [app.config] do projeto [pam-metier] de modo a que defina um objeto Spring denominado [pammetier] associado à classe [PamMetier] que acabámos de criar. A forma mais fácil é copiar o ficheiro [app.config] do projeto [pam-dao] e adicionar o que falta.


Há aqui um desafio. Não só tem de instanciar a camada [business] com a classe [PamMetier], como também tem de inicializar a sua propriedade [PamDao]:


    // référence sur la couche [DAO] initialisée par 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] é utilizada 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, e, portanto, com a referência da camada [DAO]. Era isso que pretendíamos.

9.24.6. Testar a camada [business]

Estamos prontos para testar a nossa camada [business]. Fazemos isto utilizando o programa [Program.cs] existente:

Vamos testar as várias funcionalidades da interface da camada [business]. O código do ficheiro [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
      {
        // instantiation layer [business]
        IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        // list of employee identities
        Console.WriteLine("Employés -----------------------------");
        foreach (Employe Employe in pamMetier.GetAllIdentitesEmployes())
        {
          Console.WriteLine(Employe);
        }
 
        // payslip calculations 
        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));
      }
      // break 
      Console.ReadLine();
    }
  }
}

  • Linha 16: Obtemos uma referência à camada [business] utilizando [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 [business]


Tarefa: Converta o tipo de projeto [pam-business] numa biblioteca de classes e, em seguida, regenere o projeto (repita os passos da secção 9.22.7).


9.25. Passo 18: Implementação da camada [web]

Chegámos à camada final da nossa arquitetura, a camada [web]:

Iremos reutilizar a camada [web] que desenvolvemos utilizando uma camada [business] simulada.

9.25.1. O projeto do Visual Studio

Voltamos ao Visual Studio Express 2012 for the Web para ligar a nossa camada web às camadas [lógica de negócio, DAO, EF5] que acabámos de desenvolver. Isto envolve principalmente alguma configuração e algumas alterações no namespace.

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

  • em [1], a solução [pam-td] no Visual Studio Express for Web. O projeto web [pam-web-01] volta a ficar visível. Tínhamos-o perdido no Visual Studio Express for Desktop.
  • A configuração do projeto web [pam-web-01] terá de ser modificada. Em vez de modificar um projeto em funcionamento, faremos as alterações numa cópia deste projeto. Primeiro, em [2], removemos o projeto da solução (isto não elimina nada do sistema de ficheiros).
  • Em [3], utilizando o Explorador do Windows, duplicamos a pasta [pam-web-01] para [pam-web-02];
  • Em [4], adicione o projeto [pam-web-02] à solução [pam-td]. Ele aparece com o nome [pam-web-01];
  • Em [5], altere este nome para [pam-web-02] e defina este projeto como o projeto de inicialização;
  • Em [6], carregue o projeto antigo [pam-web-01]. Agora tem todos os seus projetos. Certifique-se de que trabalha com [pam-web-02].

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

Vamos analisar o projeto como um todo:

O projeto [pam-web-02] requer várias DLLs:

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

Tarefa: Adicione estas várias referências ao projeto [pam-web-02]. A referência ao projeto [pam-metier-simule] deve ser removida. Estamos a mudar para a camada [business]. Algumas DLLs já estão presentes nas referências. Remova-as e, em seguida, faça as suas adições.


Image

9.25.3. Implementação da camada [web]

Compile o projeto [pam-web-02]. Surgirão erros, tais como os seguintes:

Image

A classe [ApplicationModel] utiliza o tipo [Employee]. Com a camada [business] simulada, este tipo foi definido no namespace [Pam.Business.Entities]. Agora encontra-se no namespace [Pam.EF5.Entities]. Corrija estes erros conforme indicado acima.

9.25.4. Configurar a camada [web]

Tal como feito na Secção 9.24.5, precisamos de configurar o EF5 no ficheiro [web.config] do projeto:


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


O ficheiro [Global.asax] da nossa aplicação web utiliza o [Spring.net] para obter uma referência à camada [business]:


      try
      {
        // instantiation layer [business]
        application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
      }
      catch (Exception ex)
      {
        application.InitException = ex;
}

Linha 4: Solicitamos uma referência ao objeto Spring denominado [pammetier]. Este é, de facto, o nome atribuído à camada [business] (verifique no seu ficheiro [web.config]).

9.25.5. Testar a camada [web]

Estamos prontos para testar a nossa camada [web]. Primeiro, vamos alterar a sua porta de funcionamento. Por predefinição, [pam-web-02] tem a mesma configuração que [pam-web-01] e, por isso, funciona na mesma porta. A experiência mostra que isto causa problemas: o IIS continua a utilizar o código do projeto [pam-web-01]. Proceda da seguinte forma:

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

Execute o projeto [pam-web-02] premindo [Ctrl-F5]. Verá então a seguinte página inicial:

Em [1], recuperamos os funcionários da base de dados [dbpam_ef5]. Note que o funcionário [X X], que estava presente na camada [business] simulada, já não se encontra lá. Vamos executar uma simulação:

Em [2], vemos o salário real em vez de um salário fictício. Agora, vamos parar o SGBD MySQL5 e executar outra simulação:

Em [3], obtivemos uma página de erro legível, embora algumas mensagens estejam em inglês. Agora, vamos parar o MySQL novamente e reexecutar a aplicação no VS usando [Ctrl-F5]:

Image

Obtemos a vista [initFailed.cshtml] criada na Secção 9.20.4. Esta apresenta as mensagens de erro da pilha de exceções. O leitor é convidado a realizar mais testes.

9.26. Passo 19: Tornar uma aplicação ASP.NET acessível na Internet

Ao desenvolver uma aplicação ASP.NET com o Visual Studio, a configuração predefinida garante que a aplicação só é acessível no endereço [localhost]. Qualquer outro endereço é rejeitado pelo servidor incorporado do Visual Studio, que, em seguida, devolve o erro [400 Bad Request].

Isto pode ser observado da seguinte forma:

  • Numa janela do DOS, anote 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 é apresentado aqui na linha 14. Se tiver uma ligação Wi-Fi, o endereço Wi-Fi do dispositivo aparecerá nas linhas 20 e seguintes.

  • Verifique as propriedades do projeto [clique com o botão direito do rato no projeto / propriedades / separador web]:

Image

A aplicação será executada na porta [65010] da máquina [localhost].

  • Execute o seu projeto premindo [Ctrl-F5]

Image

  • Substitua [localhost] pelo endereço IP do computador:

Image

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

Para tornar a aplicação desenvolvida acessível através de um URL como [http://adresseIP/contexte/...], deve utilizar um servidor diferente do IIS Express, como um servidor IIS (não Express). Para verificar se está disponível (normalmente nas versões Pro do Windows), aceda ao Painel de Controlo [Painel de Controlo\Sistema e Segurança\Ferramentas Administrativas]:

Image

Esta opção nem sempre está presente. Nesse caso, aceda a [Painel de Controlo \ Programas] e instale as Ferramentas de Administração Web.

Assim que a opção [Gestor dos Serviços de Informação da Internet (IIS)] estiver disponível, ative-a:

Inicie o site predefinido. Para tal, o [Serviço de Publicação na World Wide Web] deve estar em execução:

Depois de fazer isso, introduza o URL [http://localhost] num navegador. Primeiro, verifique se outro servidor Web já não está a utilizar a porta 80. Se estiver, pare-o.

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

Funciona. Agora, voltemos ao Visual Studio:

  • Primeiro, tem de iniciar o Visual Studio no modo [Administrador]

Depois de fazer isso, tem de alterar a configuração do projeto web que pretende implementar [clique com o botão direito do rato no projeto / Propriedades / separador Web]:

Deve selecionar o servidor IIS local como servidor de implementação. O Visual Studio define o URL da aplicação. Pode alterá-lo. Execute o projeto premindo [Ctrl-F5]:

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

Se não tiver um servidor IIS, pode utilizar um servidor ASP.NET gratuito, como o [Ultidev Web Server Pro], disponível no URL [http://ultidev.com/Download/]. Depois de instalado, existem duas formas de iniciar uma aplicação web com este servidor:

A forma rápida

Abra o Explorador do Windows e selecione a pasta que contém a aplicação ASP.NET que pretende implementar:

O servidor web será então iniciado e a aplicação web será apresentada num navegador:

  • Em [3], pode parar ou iniciar o servidor web;
  • Em [4], pode alterar a porta de serviço da aplicação web;

Antes de iniciar o servidor, o serviço [UWS HiPriv Services] abaixo deve estar em execução:

Assim que o servidor estiver a funcionar, a interface terá o seguinte aspeto:

Ao clicar no link [6], é apresentada a primeira página da aplicação:

Pode então introduzir o endereço IP da máquina no lugar de [localhost]:

Portanto, também aqui, apenas o nome [localhost] é aceite.

O caminho mais longo

Inicie a aplicação Ultidev Web Explorer

e siga estes passos:

  • Em [8], especifique a pasta da aplicação web a ser implementada;
  • em [10-11], a aplicação web deve ser acedida através do URL [http://localhost:81/];
  • Inicie o servidor web utilizando [14];
  • aceda ao URL [19];
  • Em [20], conseguimos aceder à página pretendida utilizando o endereço IP local da máquina em vez do nome [localhost]. Era exatamente isso que procurávamos;

O servidor Ultidev está instalado como um serviço do Windows que inicia automaticamente. Pode desativar o arranque automático do servidor Ultidev da seguinte forma:

  • Vá para [Painel de Controlo\Sistema e Segurança\Ferramentas Administrativas];
  • [1, 2]: Selecione as propriedades do serviço [Ultidev Web Server Pro];
  • [3]: Defina-o para início manual.

Para iniciar o servidor manualmente, utilize a aplicação [Ultidev Web Explorer], por exemplo:

9.27. Passo 20: Gerar uma aplicação nativa para Android

Quando se tem uma Aplicação de Página Única (SPA), é possível gerar um executável móvel (Android, iOS, Windows 8, etc.) utilizando a ferramenta [PhoneGap] [http://phonegap.com/]. Existem outras formas de o fazer, nomeadamente com o produto de código aberto Apache Cordova [https://cordova.apache.org/]. A ferramenta online disponível no site do PhoneGap [http://build.phonegap.com/apps] «carrega» o ficheiro ZIP do site a converter. A página inicial deve chamar-se [index.html] e deve ser uma página estática, ou seja, não gerada por um framework web (ASP.NET, JEE, PHP, etc.). Começaremos por construir esta página.

9.27.1. A arquitetura da aplicação

É importante lembrar aqui que queremos criar uma aplicação Android. Uma aplicação deste tipo tem frequentemente a seguinte arquitetura:

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

Voltemos ao modelo APU:

  • uma página inicial é carregada no navegador (o diagrama acima não especifica de onde ela vem);
  • as visualizações subsequentes são recuperadas através de chamadas Ajax [1-4]. O navegador não carregará novas páginas;

A visualização inicial pode ou não ser servida pelo mesmo servidor que as outras visualizações recuperadas através de chamadas Ajax. Se não for servida pelo mesmo servidor, o JavaScript na página inicial deve conhecer o URL do servidor web que irá fornecer as outras visualizações. Este será o caso na aplicação Android que vamos construir:

  • a página estática [index.html] será encapsulada numa aplicação nativa para Android [1] que possui capacidades de navegador e, portanto, é capaz de executar o JavaScript incorporado na página [index.html];
  • esta página irá recuperar as outras vistas através de chamadas Ajax ao servidor [2]. Para tal, precisa de conhecer o URL do servidor web;

Iremos refatorar a aplicação [pam-web-02] para que funcione neste modo. Assim, a primeira página será a seguinte:

  • em [1], a URL da página inicial da aplicação. Esta será fornecida pelo servidor Ultidev discutido na Secção 9.26;
  • em [2], o utilizador deve introduzir a URL do simulador de folhas de pagamento. Poderíamos codificá-la diretamente no JavaScript da página inicial, mas isso complicaria os testes: assim que alterássemos o endereço IP (ou porta) do simulador, teríamos de a alterar no código JavaScript;
  • em [3], o link [Login] que irá carregar a seguinte vista:
  • Note-se que em [4], o URL do navegador não mudou. Continua a ser o da página inicial e assim permanecerá durante todo o tempo de vida da aplicação.

Assim que esta vista for carregada, tudo funciona como antes: as diferentes vistas são carregadas através de chamadas Ajax. Veremos que é necessário modificar muito pouco código.

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 a página estática [index.html] e todos os recursos de que necessita (ficheiros CSS e JS). A página [index.html] utiliza o código da página mestre [_Layout.cshtml] do projeto do Visual Studio, removendo tudo o que não é estático. Isto 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>

Adicionámos o seguinte:

  • linhas 27-29: adicionámos a opção de menu [Login] para permitir a ligação ao serviço de simulação;
  • linhas 55-56: o campo de introdução do URL do simulador;
  • linhas 59-63: uma mensagem de erro caso a ligação falhe;

A refatoração do código é feita apenas no código [myScripts.js] na linha 14 acima. Nada mais muda. O código evolui da seguinte forma:


// au chargement du document
$(document).ready(function () {
    // on récupère les références des différents composants de la page
    loading = $("#loading");
    content = $("#content");
    erreur = $("#erreur");
    erreur1 = $("#erreur1");
    erreur2 = $("#erreur2");
    // les liens du menu
    lnkConnexion = $("#lnkConnexion");
    lnkFaireSimulation = $("#lnkFaireSimulation");
    lnkEffacerSimulation = $("#lnkEffacerSimulation");
    lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
    lnkVoirSimulations = $("#lnkVoirSimulations");
    lnkTerminerSession = $("#lnkTerminerSession");
    lnkRetourFormulaire = $("#lnkRetourFormulaire");
    // on les met dans un tableau
    options = [lnkConnexion, lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
    // on cache certains éléments de la page
    loading.hide();
    erreur.hide();
    // on fixe le menu
    setMenu([lnkConnexion]);
});
  • linhas 6-8: os IDs da área que exibe erros de ligação na página [index.html];
  • linha 10: o novo link para ligar ao simulador;
  • linha 21: a área de erros está inicialmente oculta;
  • linha 23: apenas o link de ligação é apresentado;

Na página [index.html], o link de ligaçã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() {
    // retrieve the urlServiceWeb from the web service
    urlServiceWeb = $("#urlServiceWeb").val();
    // retrieve the input form
    $.ajax({
        url: urlServiceWeb + '/Pam/Formulaire',
        type: 'POST',
        dataType: 'html',
        beforeSend: function () {
            // wait signal on
            loading.show();
        },
        success: function (data) {
            // displaying results
            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 () {
            // wait signal off
            loading.hide();
        }
    });
}
  • linha 7: recuperamos a URL introduzida pelo utilizador. Esta está armazenada na variável global da linha 1. Desta forma, estará disponível nas outras funções do ficheiro;
  • linha 10: fazemos uma chamada Ajax para a URL do simulador [/Pam/Form]. Esta URL apresenta a vista parcial para a introdução de dados de simulação (funcionários, horas trabalhadas, dias trabalhados). Na versão inicial do [pam-web-02], esta URL era suficiente. Era automaticamente prefixada com a URL que tinha aberto a página inicial. Agora, partimos do princípio de que a página inicial pode ser servida por um servidor diferente daquele que aloja o simulador. A URL [/Pam/Formulaire] deve, portanto, ser prefixada com a variável [urlServiceWeb] da linha 1, que é a URL do simulador (por exemplo, http://172.19.81.34/pam-web-02). Isto deve ser feito para todas as chamadas Ajax no ficheiro;
  • linhas 17–22: se a ligação for bem-sucedida, a vista parcial [Formulaire.cshtml] é apresentada e é exibido um menu contendo apenas o link [Run Simulation] (linha 21);
  • linhas 23–27: se a ligação falhar:
    • na linha 24, é exibida a resposta HTML enviada pelo servidor web (se houver);
    • na linha 25, são exibidos os cabeçalhos HTTP enviados pelo servidor web (se este tiver respondido);

É isso. Se for bem-sucedido, a seguinte página é exibida:

Estamos agora na situação anterior, em que as visualizações são agora obtidas através de chamadas Ajax. Assim, como mostrado acima, clicar no link [Run Simulation] será executado pelo seguinte código no ficheiro [myScripts.js]:


function faireSimulation() {
    // on récupère des références
    var simulation = $("#simulation");
    var formulaire = $("#formulaire");
    // formulaire valide ?
    var formValid = formulaire.validate().form();
    if (!formValid) return;
    // on fait un appel Ajax à la main
    $.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 URL anterior agora é precedido pelo URL do simulador;

9.27.3. Testar o projeto refatorado

Na Secção 9.26, mostrámos como instalar a aplicação [pam-web-02] no servidor Ultidev. Vamos partir daí:

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

Vamos introduzir um URL incorreto:

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

Se introduzir o URL correto:

obtemos a seguinte resposta:

9.27.4. Criação do binário Android

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

Image

Image

Adicionamos em [2] um ficheiro [config.xml] que será utilizado para configurar o plugin [Phonegap], o qual irá gerar o binário Android. O 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: introduza aqui as suas informações de contacto;
  • linhas 11-13: estas linhas permitem que o JavaScript incorporado na aplicação web, que será executado no dispositivo Android, solicite URLs fora do dispositivo;

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

Image

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

  • Antes de [1], poderá ser necessário criar uma conta;
  • em [1], começamos;
  • em [2], escolha um plano gratuito que permita apenas uma aplicação PhoneGap;
  • em [3], descarregue a aplicação compactada [4];
  • em [5], introduza o nome da aplicação;
  • clique no link [6] para compilar os binários para iOS, Android e Windows. Isto pode demorar alguns segundos;
  • Em [7-9], descarregue o binário para Android;

Inicie um emulador [GenyMotion] para um tablet Android (consulte a secção 11.1):

Image

Acima, iniciamos um emulador de tablet com a API 21 do Android. Assim que o emulador estiver a funcionar,

  • desbloqueie-o arrastando o cadeado (se houver) para o lado e, em seguida, soltando-o;
  • Usando o rato, arraste o ficheiro [Pam-debug.apk] que descarregou e solte-o no emulador. Este será então instalado e executado;

Introduza [1] o URL do simulador, conforme descrito na secção 9.27.3. Quando terminar, ligue-se ao simulador utilizando o link [2]:

Image

Teste a aplicação no emulador. Deverá funcionar.