9. Estudo de caso
9.1. Introduction
Vamos apresentar um estudo de caso já publicado num artigo disponível no URL [http://tahe.developpez.com/dotnet/pam-aspnet/]. Neste artigo, o estudo de caso é realizado com o ASP.NET clássico e o ORM NHibernate. Aqui, vamos realizá-lo com o ASP.NET, o MVC e o ORM do Entity Framework. Tal como no artigo existente, o caso de estudo é apresentado como um projeto universitário. Destina-se, portanto, a estudantes. Para todas as questões, são feitas referências aos capítulos que acabámos de detalhar, a fim de indicar leituras úteis.
9.2. O problema a resolver
Pretendemos criar uma aplicação web que permita a um utilizador realizar simulações de cálculo dos salários das amas da associação «Maison de la petite enfance» de um município. Iremos centrar-nos tanto na organização do código DotNet da aplicação como no próprio código.
A aplicação será do tipo APU [Application à Page Unique] e utilizará exclusivamente chamadas Ajax para comunicar com o servidor. Apresentará ao utilizador as seguintes vistas:
- a vista [VueSaisies], que apresenta o formulário de simulação

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

- a vista [VueSimulations], que apresenta a lista das simulações realizadas pelo cliente

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

- a vista [VueErreurs], que indica um ou mais erros (neste caso, o SGBD e o MySQL foram interrompidos):

9.3. Arquitetura da aplicação
A arquitetura da aplicação será a seguinte:
![]() |
A camada [EF5] refere-se ao Entity Framework 5 ORM. O SGBD utilizado será o MySQL.
Vamos construir esta aplicação, em primeiro lugar, com uma camada [métier] simulada:
![]() |
Isto permitir-nos-á concentrar-nos exclusivamente na camada [web]. A camada [métier] simulada respeitará a interface da camada [métier] real. Quando a camada [web] estiver operacional, construir-se-ão então as camadas [métier], [DAO] e [EF5].
9.4. A base de dados
Os dados estáticos necessários para elaborar a folha de pagamento são armazenados numa base de dados MySQL denominada [dbpam_ef5] (pam=Paie Assistante Maternelle). Esta base de dados tem um administrador chamado «root» sem palavra-passe. Possui três tabelas:

Existe uma relação de chave estrangeira entre a coluna EMPLOYES (INDEMNITE_ID) e a coluna INDEMNITES (ID). A estrutura desta base de dados é determinada pela sua utilização com a tabela EF5. Voltaremos a este assunto quando construirmos as camadas inferiores da aplicação.
Estrutura:
![]() |
|
O seu conteúdo poderia ser o seguinte:
![]()
Estrutura:
![]() |
|
O seu conteúdo poderia ser o seguinte:
![]()
As taxas das contribuições sociais são independentes do trabalhador. A tabela anterior tem apenas uma linha.
![]() |
|
O seu conteúdo poderia ser o seguinte:
![]()
9.5. Modo de cálculo do salário de uma ama
Apresentamos agora o método de cálculo do salário mensal de uma ama. Tomamos como exemplo o salário da Sra. Marie Jouveinal, que trabalhou 150 horas ao longo de 20 dias durante o mês a pagar.
São tidos em conta os seguintes elementos: | [TOTALHEURES]: total de horas trabalhadas no mês [TOTALJOURS]: total de dias trabalhados no mês | [TOTALHEURES]=150 [TOTALJOURS]= 20 |
O salário base da ama é calculado através da seguinte fórmula: | [SALAIREBASE]=([TOTALHEURES]*[BASEHEURE])*(1+[INDEMNITESCP]/100) | [SALAIREBASE]=(150*[2.1])*(1+0,15)= 362,25 |
É necessário deduzir uma série de contribuições sociais deste salário base: | Contribuição social generalizada e contribuição para o reembolso da dívida social: [SALAIREBASE]*[CSGRDS/100] Contribuição social generalizada dedutível: [SALAIREBASE]*[CSGD/100] Segurança social, pensão de viuvez, velhice: [SALAIREBASE]*[SECU/100] Pensão complementar + AGPF + Seguro de desemprego: [SALAIREBASE]*[RETRAITE/100] | CSGRDS: 12,64 CSGD: 22,28 Segurança Social: 34,02 Pensão de reforma: 28,55 |
Total das contribuições sociais: | [COTISATIONSSOCIALES] = [SALAIREBASE] *(CSGRDS + CSGD + SECU + RETRAITE)/100 | [COTISATIONSSOCIALES]=97,48 |
Além disso, a ama tem direito, por cada dia trabalhado, a um subsídio de subsistência e a um subsídio de refeição. A este título, recebe os seguintes subsídios: | [Indemnités]=[TOTALJOURS]*(ENTRETIENJOUR+REPASJOUR) | [INDEMNITES]=104 |
No final, o salário líquido a pagar à ama é o seguinte: | [SALAIREBASE] - [COTISATIONSSOCIALES] + [INDEMNITÉS] | [salaire NET]=368,77 |
9.6. O projeto do Visual Studio da camada [web]
O projeto do Visual Web Developer da aplicação será o seguinte:
![]() |
- em [1], a estrutura geral do projeto [pam-web-01];
- em [2], a pasta [Content] é a pasta onde se colocam os recursos estáticos do projeto:
- [indicator.gif]: a imagem animada que indica a espera pelo fim de um pedido Ajax,
- [standard.jpg]: a imagem de fundo das diferentes vistas,
- [Site.css]: a folha de estilo da aplicação;
- em [3], o único controlador da aplicação [PamController];
- em [4], as classes necessárias à aplicação, mas que não podem ser classificadas como elementos do MVC:
- [ApplicationModelBinder]: a classe que permite incluir os dados do âmbito [Application] no modelo de ações,
- [SessionModelBinder]: a classe que permite incluir os dados do âmbito [Session] no modelo de ações,
- [Static]: uma classe auxiliar com métodos estáticos;
- em [5], os modelos da aplicação, quer sejam modelos de ações ou de vistas:
- [ApplicationModel]: modelo que contém os dados do escopo [Application],
- [SessionModel]: modelo que contém os dados do âmbito [Session],
- [Simulation]: 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, a validação do lado do cliente e a implementação de Ajax na 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,
- [Formulaire]: formulário de introdução dos dados do colaborador e das suas horas e dias trabalhados,
- [Simulation]: a vista que apresenta uma simulação,
- [Simulations]: a vista que apresenta a lista das simulações realizadas,
- [Erreurs]: a vista que apresenta a lista de eventuais erros,
- [InitFailed]: a vista que apresenta mensagens de erro caso a inicialização da aplicação falhe;
- em [10], a página principal da aplicação [_Layout];
- em [11], os ficheiros [Web.config] e [Global.asax] utilizados para configurar a aplicação.
9.7. Passo 1 – implementação da camada simulada [métier]
A partir de agora, descrevemos os passos a seguir para realizar o estudo de caso. Sempre que for útil, indicamos o número do capítulo que poderá ser consultado para realizar o trabalho solicitado. Alguns elementos do projeto são-lhe fornecidos numa pasta [aspnetmvc-support.zip], que se encontra no site deste documento. Nela encontrará a pasta [étudedecas-support] com o seguinte conteúdo:
![]() |
O projeto retoma, além disso, elementos apresentados nos capítulos anteriores. Basta, portanto, recuperar esses elementos através de copiar/colar entre este ficheiro PDF e o Visual Studio.
9.7.1. A solução do Visual Studio para a aplicação completa
Vamos, em primeiro lugar, criar uma solução do Visual Studio na qual iremos criar dois projetos:
- um projeto para a camada simulada [métier];
- um projeto para a camada web MVC.
![]() |
Iremos utilizar duas ferramentas:
- o Visual Studio Express 2012 para o ambiente de trabalho, que servirá para construir a camada [métier];
- o Visual Studio Express 2012 para a Web, que servirá para construir a camada [web].
Com o Visual Studio Express para o ambiente de trabalho, criamos uma solução [pam-td]:
![]() |
- em [1], selecione uma aplicação C#;
- em [2], selecione [Application console];
- em [3], atribua um nome à solução;
- em [4], crie uma pasta para esta solução;
- em [5], atribuir um nome à camada [métier];
- em [6], a solução gerada.
9.7.2. A interface da camada [métier]
Numa arquitetura em camadas, é boa prática que a comunicação entre camadas se realize através de interfaces:
![]() |
Que interface deve a camada [métier] apresentar à camada [web]? Quais são as interações possíveis entre estas duas camadas? Recordemos a interface web que será apresentada ao utilizador:
![]() |
- Na exibição inicial do formulário, deve constar na camada [1] a lista de funcionários. Basta uma lista simplificada (Apelido, Nome, SS). O n.º SS é necessário para aceder às informações adicionais sobre o funcionário selecionado (informações 6 a 11).
- As informações 12 a 15 correspondem às diferentes taxas de contribuição.
- As informações 16 a 19 correspondem aos subsídios do funcionário
- As informações 20 a 24 correspondem aos elementos do salário calculados com base nos dados introduzidos pelo utilizador nos campos 1 a 3.
A interface [IPamMetier] disponibilizada à camada [web] pela camada [métier] deve cumprir os requisitos acima referidos. Existem várias interfaces possíveis. Propomos a seguinte:
using Pam.Metier.Entites;
namespace Pam.Metier.Service
{
public interface IPamMetier
{
// lista de todas as identidades dos funcionários
Employe[] GetAllIdentitesEmployes();
// ------- cálculo do salário
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
}
- linha 7: o método que permitirá preencher o menu suspenso [1]
- linha 10: o método que permitirá obter as informações 6 a 24. Estas foram reunidas num objeto do tipo [FeuilleSalaire], que iremos descrever em breve.
Colocaremos esta interface numa pasta [metier/service]:
![]() |
9.7.3. As entidades da camada [métier]
A interface anterior utiliza duas classes, [Employe] e [FeuilleSalaire], que temos de definir:
- [Employe] é a imagem de uma linha da tabela [employes] da base de dados;
- [FeuilleSalaire] é a folha de vencimento de um funcionário.
As entidades serão colocadas numa pasta [metier / entites] do projeto:
![]() |
Na arquitetura final, a camada [métier] irá manipular entidades de imagens da base de dados:

Utilizaremos as seguintes classes para representar as linhas das três tabelas da base de dados. Consulte o parágrafo 9.4 para conhecer o significado dos diferentes campos.
Classe [Employe]
Representa uma linha da tabela [employes]. 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; }
// assinatura
public override string ToString()
{
return string.Format("Employé[{0},{1},{2},{3},{4},{5}]", SS, Nom, Prenom, Adresse, Ville, CodePostal);
}
}
}
Classe [Indemnites]
Representa uma linha da 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; }
// assinatura
public override string ToString()
{
return string.Format("Indemnités[{0},{1},{2},{3},{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
}
}
}
Classe [Cotisations]
Representa uma linha da tabela [cotisations]. 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; }
// assinatura
public override string ToString()
{
return string.Format("Cotisations[{0},{1},{2},{3}]", CsgRds, Csgd, Secu, Retraite);
}
}
}
Note-se que as classes não incluem as colunas [ID] e [VERSIONING] das tabelas. Estas colunas, úteis quando se utilizarem as tabelas ORM e EF5, não o são no contexto da camada simulada [métier].
A classe [FeuilleSalaire] encapsula as informações 6 a 24 do formulário já apresentado:
namespace Pam.Metier.Entites
{
public class FeuilleSalaire
{
// propriedades automáticas
public Employe Employe { get; set; }
public Cotisations Cotisations { get; set; }
public ElementsSalaire ElementsSalaire { get; set; }
// ToString
public override string ToString()
{
return string.Format("[{0},{1},{2}]", Employe, Cotisations, ElementsSalaire);
}
}
}
- linha 7: as informações 6 a 11 sobre o funcionário cujo salário está a ser calculado e as informações 16 a 19 sobre os seus subsídios. Não se deve esquecer aqui que um objeto [Employe] encapsula um objeto [Indemnites] que representa os seus subsídios;
- linha 8: as informações 12 a 15;
- linha 9: as informações 20 a 24;
- linhas 12-14: o método [ToString].
A classe [ElementsSalaire] encapsula as informações 20 a 24 do formulário:
namespace Pam.Metier.Entites
{
public class ElementsSalaire
{
// propriedades automáticas
public double SalaireBase { get; set; }
public double CotisationsSociales { get; set; }
public double IndemnitesEntretien { get; set; }
public double IndemnitesRepas { get; set; }
public double SalaireNet { get; set; }
// ToString
public override string ToString()
{
return string.Format("[{0} : {1} : {2} : {3} : {4} ]", SalaireBase, CotisationsSociales, IndemnitesEntretien, IndemnitesRepas, SalaireNet);
}
}
}
- linhas 6-10: os elementos do salário, tal como explicados nas regras de negócio descritas anteriormente;
- linha 6: o salário base do colaborador, em função do número de horas trabalhadas;
- linha 7: as contribuições deduzidas deste salário base;
- linhas 8 e 9: os subsídios a acrescentar ao salário base, em função do índice do empregado e do 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
{
// classe de exceção
public class PamException : Exception
{
// o código do erro
public int Code { get; set; }
// construtores
public PamException()
{
}
public PamException(int Code)
: base()
{
this.Code = Code;
}
public PamException(string message, int Code)
: base(message)
{
this.Code = Code;
}
public PamException(string message, Exception ex, int Code)
: base(message, ex)
{
this.Code = Code;
}
}
}
- linha 6: a classe deriva da classe [Exception];
- linha 10: 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:
- (continuação)
- ou o das linhas 29-33, destinado a reportar uma exceção ocorrida, encapsulando-a numa exceção do tipo [PamException]:
try{
....
}catch (IOException ex){
// encapsulamos a exceção ex
throw new PamException("Problème d'accès aux données",ex,10);
}
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 [métier]
A interface [IPamMetier] será implementada pela seguinte classe [PamMetier]:
using System;
using Pam.Metier.Entites;
using System.Collections.Generic;
namespace Pam.Metier.Service
{
public class PamMetier : IPamMetier
{
// lista de funcionários em cache
public Employe[] Employes { get; set; }
// funcionários indexados pelo seu n.º SS
private IDictionary<string, Employe> dicEmployes = new Dictionary<string, Employe>();
// lista de funcionários
public Employe[] GetAllIdentitesEmployes()
{
...
// apresenta a lista de funcionários
return Employes;
}
// cálculo do salário
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
{
...
}
}
- linha 7: a classe [PamMetier] implementa a interface [IPamMetier];
- linha 10: a classe [PamMetier] mantém a lista de funcionários em cache;
- linha 12: um dicionário que associa um funcionário ao seu número de 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:
// lista de funcionários
public Employe[] GetAllIdentitesEmployes()
{
if (Employes == null)
{
// cria-se uma tabela com três funcionários
Employes = new Employe[3];
Employes[0] = new Employe()
{
SS = "254104940426058",
Nom = "Jouveinal",
Prenom = "Marie",
Adresse = "5 rue des oiseaux",
Ville = "St Corentin",
CodePostal = "49203",
Indemnites = new Indemnites() { Indice = 2, BaseHeure = 2.1, EntretienJour = 2.1, RepasJour = 3.1, IndemnitesCp = 15 }
};
dicEmployes.Add(Employes[0].SS, Employes[0]);
Employes[1] = new Employe()
{
SS = "260124402111742",
Nom = "Laverti",
Prenom = "Justine",
Adresse = "La brûlerie",
Ville = "St Marcel",
CodePostal = "49014",
Indemnites = new Indemnites() { Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 }
};
dicEmployes.Add(Employes[1].SS, Employes[1]);
// um funcionário fictício que não será incluído no dicionário
// para simular um funcionário inexistente
Employes[2] = new Employe()
{
SS = "XX",
Nom = "X",
Prenom = "X",
Adresse = "X",
Ville = "X",
CodePostal = "X",
Indemnites = new Indemnites() { Indice = 0, BaseHeure = 0, EntretienJour = 0, RepasJour = 0, IndemnitesCp = 0 }
};
}
// apresenta-se a lista de funcionários
return Employes;
}
- linha 4: verifica-se se a lista de funcionários já não foi criada;
- linha 7: se não for esse o caso, cria-se uma tabela com três funcionários;
- linhas 8-17: o primeiro funcionário;
- linha 18: é inserido no dicionário;
- linhas 19-28: o segundo funcionário;
- linha 29: é inserido no dicionário;
- linhas 32-42: o terceiro funcionário. Este não é inserido no dicionário por uma razão que iremos explicar.
O método [GetSalaire] será o seguinte:
// cálculo do salário
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
{
// recuperamos o funcionário com o n.º SS
Employe e = dicEmployes.ContainsKey(ss) ? dicEmployes[ss] : null;
// existe?
if (e == null)
{
throw new PamException(string.Format("L'employé de n° SS [{0}] n'existe pas", ss), 10);
}
// gerar uma folha de salário fictícia
return new FeuilleSalaire()
{
Employe = e,
Cotisations = new Cotisations() { CsgRds = 3.49, Csgd = 6.15, Secu = 9.38, Retraite = 7.88 },
ElementsSalaire = new ElementsSalaire() { CotisationsSociales = 100, IndemnitesEntretien = 100, IndemnitesRepas = 100, SalaireBase = 100, SalaireNet = 100 }
};
}
- linha 2: o método recebe o n.º SS do funcionário cujo salário se pretende calcular, o número de horas trabalhadas e o número de dias trabalhados;
- linha 5: procura-se o funcionário no dicionário. Recorde-se que um deles não se encontra no dicionário;
- linhas 7-10: se o funcionário não for encontrado, é lançada uma exceção [PamException];
- linhas 12-17: é devolvida uma folha de salário fictícia.
9.7.6. O teste de consola da camada [métier]
O projeto da camada [métier] é atualmente o seguinte:
![]() |
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()
{
// instanciação da camada [métier]
IPamMetier pamMetier = new PamMetier();
// lista de funcionários
Employe[] employes = pamMetier.GetAllIdentitesEmployes();
Console.WriteLine("Liste des employés--------------------");
foreach (Employe e in employes)
{
Console.WriteLine(e);
}
// cálculos das folhas de pagamento
Console.WriteLine("Calculs de feuilles de salaire-----------------");
Console.WriteLine(pamMetier.GetSalaire(employes[0].SS, 30, 5));
Console.WriteLine(pamMetier.GetSalaire(employes[1].SS, 150, 20));
try
{
Console.WriteLine(pamMetier.GetSalaire(employes[2].SS, 150, 20));
}
catch (PamException ex)
{
Console.WriteLine(string.Format("PamException : {0}", ex.Message));
}
}
}
}
- linha 12: instanciação da camada [métier];
- linhas 14-19: teste do método [GetAllIdentitesEmploye] da interface [IPamMetier];
- linhas 21-31: teste do método [GetSalaire] da interface [IPamMetier].
A execução deste programa de consola produz os seguintes resultados:
Convida-se o leitor a estabelecer a ligação entre estes resultados e o código executado.
Para podermos utilizar este projeto no projeto web que vamos construir, transformamo-lo numa biblioteca de classes:
![]() |
- em [1], nas propriedades do ficheiro [Program.cs];
- em [2], indicamos que o ficheiro não fará parte do assembly gerado;
- no [3, 4], nas propriedades do projeto [pam-metier-simule], na opção [Application] [3], indica-se [4] que a geração deve fornecer uma biblioteca de classes (na forma de um DLL).
![]() |
- em [5], solicita-se um assembly do tipo [Release]. O outro tipo é [Debug]. O assembly contém, então, informações que facilitam a depuração;
- em [6], gera-se o projeto [pam-metier-simule];
![]() |
- no [7], exibem-se todos os ficheiros da solução;
- no [8], na pasta [bin / Release], encontra-se o DLL do nosso projeto.
9.8. Passo 2: implementação da aplicação web
Na solução anterior do Visual Studio, vamos criar o projeto para a camada web MVC.
![]() |
Com o Visual Studio Express para a Web, abrimos a solução [pam-td] criada anteriormente com o Visual Studio Express para o ambiente de trabalho.
![]() |
- em [1], a solução [pam-td] foi carregada no Visual Studio Express para a Web;
- em [2], a solução e o projeto para a camada [métier] simulada que acabámos de criar.
Nesta nova etapa, vamos criar a estrutura básica 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], seleciona-se o modelo base ASP.NET MVC;
- no [5], o projeto criado;
![]() |
- em [6], cria-se um novo projeto, o projeto inicial da solução, aquele que será executado quando se executar [Ctrl-F5];
- em [7], o nome do novo projeto aparece a negrito, indicando que é o projeto inicial da solução.
Agora, substituímos, através do Explorador do Windows, a pasta [Content] do projeto pela pasta [étudedecas-support / web / Content]. Feito isto, é necessário incluir os novos ficheiros no projeto [pam-web-01]. Procederemos da seguinte forma:
![]() |
- no [1], atualize a solução;
- em [2], exibam-se todos os ficheiros da solução;
- em [3], surge uma pasta [Images];
- que se inclui no projeto em [4].
Na pasta [Scripts], adicione os scripts JQuery Globalization e [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: linha 8, adapte a versão de jQuery à da 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 a introdução de números reais franceses com vírgula;
- linha 13: referência aos scripts necessários para o modo Ajax;
- linha 14: os scripts específicos da aplicação;
- linha 24: a imagem de espera até ao fim das chamadas Ajax;
- linhas 26-39: seis links JavaScript;
- linha 43: a secção onde serão apresentadas as diferentes vistas da aplicação;
- linha 44: o corpo das diferentes vistas da aplicação.
Em seguida, iremos alterar a rota predefinida 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 URL terão o formato [{controller}/{action}];
- linha 15: na ausência de ação, será utilizada a ação [Index]. Na ausência de controlador, será utilizado o controlador [Pam].
Desta configuração, conclui-se que o URL [/] é equivalente ao URL [/Pam/Index]. Como a nossa aplicação é do tipo APU, o URL [/] será o único URL desta aplicação.
Crie o controlador [Pam]:
Altere o controlador [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 espaço de nomes [Pam.Web.Controllers];
- linha 7: a ação [Index] processará apenas o comando HTTP GET;
- linha 8: devolvemos um tipo [ViewResult] em vez de um tipo [ActionResult].
Crie agora a vista [Index.cshtml] apresentada pela ação [Index] acima:
![]() |
Altere [Index.cshtml] da seguinte forma:
@{
ViewBag.Title = "Pam";
}
<h2>Formulaire</h2>
Execute a aplicação através de [Ctrl-F5]. Deverá obter a seguinte página:
![]() |
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: configuração do modelo APU
Pretendemos criar uma aplicação seguindo o modelo APU (Aplicação de Página Única) descrito no parágrafo 7.5, bem como no parágrafo 7.6. A página única é aquela carregada pelo navegador no arranque da aplicação:
![]() |
- a parte [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. Esta insere-se 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 diferentes 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, do qual vamos precisar:
![]() |
Seguimos agora o modelo APU descrito no parágrafo 7.6. Releia esse parágrafo se o tiver esquecido. Vamos agora implementar os diferentes fragmentos de página apresentados pela aplicação.
9.9.1. As ferramentas de desenvolvimento do JavaScript
Recordamos que, com o navegador Chrome, dispõe de um conjunto de ferramentas para depurar o JavaScript das suas páginas (HTML, CSS). Estas ferramentas foram apresentadas parcialmente no parágrafo 7.2. No modelo APU, os navegadores mantêm em cache os scripts JavaScript referenciados pela primeira página da aplicação. Por isso, é importante lembrar-se de esvaziar esse cache quando alterar os seus scripts; caso contrário, as alterações podem não ser tidas em conta. Eis como fazê-lo com o Chrome:
- execute o [Ctrl-Maj-I] para apresentar o ambiente de desenvolvimento
![]() |
- clique no ícone [1] no canto inferior direito da janela de desenvolvimento;
- depois marque a opção [2], que desativa a cache no modo de desenvolvimento.
9.9.2. Utilização de uma vista parcial para apresentar o formulário
O formulário de introdução de dados é um dos fragmentos apresentados pela aplicação. De momento, 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();
}
Na linha 4 acima, é efetivamente uma vista [View] e não uma vista parcial [PartialView] que está a ser apresentada. Precisamos de uma vista parcial para o formulário, que será um fragmento de página. Alteramos a vista [Index.cshtml] da seguinte forma:
@{
ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")
Na linha 4, o formulário já não faz parte da página [Index.cshtml]. Está agora alojado numa vista parcial [Formulaire.cshtml]:
![]() |
O código de [Formulaire.cshtml] é simplesmente o seguinte:
<h2>Formulaire</h2>
Efetue estas alterações e verifique se continua a obter a seguinte visualização ao iniciar a aplicação:
![]() |
9.9.3. A chamada Ajax [faireSimulation]
Estamos interessados no fragmento exibido quando o utilizador clica na ligação [Faire la simulation]:
![]() |
- em [1], o utilizador clica na ligação [Faire la simulation];
- em [2], a simulação aparece por baixo do formulário.
Alteramos da seguinte forma a vista parcial [Formulaire.cshtml] que apresenta o formulário:
<h2>Formulaire</h2>
<div id="simulation" />
Na linha 3, criamos uma região com o ID [simulation] para alojar o fragmento da simulação.
Criamos a seguinte vista parcial [Simulation.cshtml]:
![]() |
O conteúdo da vista [Simulation.cshtml] é o seguinte:
<hr />
<h2>Simulation</h2>
Temos agora de escrever o código JavaScript que gere o clique no link [Faire la simulation]. Vamos seguir o procedimento descrito no parágrafo 7.6.5. Em primeiro lugar, vejamos o código HTML do link em [_Layout.cshtml]:
<a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
</a>
Vemos que um clique no link [Faire la simulation] irá iniciar a execução da função JS [faireSimulation]. Esta função será gravada no ficheiro [myScripts.js], juntamente com as outras funções JS necessárias à aplicação:
// variáveis globais
var loading;
var content;
function faireSimulation() {
// efetua-se manualmente uma chamada Ajax
...
}
function effacerSimulation() {
// limpa-se o conteúdo do formulário
...
}
function enregistrerSimulation() {
// efetua-se uma chamada Ajax manualmente
...
}
function voirSimulations() {
// efetua-se uma chamada Ajax manualmente
...
}
function retourFormulaire() {
// é efetuada uma chamada Ajax manualmente
...
}
function terminerSession() {
...
}
// ao carregar o documento
$(document).ready(function () {
// recuperam-se as referências dos diferentes componentes da página
loading = $("#loading");
content = $("#content");
});
- linhas 35-39: a função JQuery executada no arranque da aplicação;
- linhas 37-38: inicializam-se as variáveis globais das linhas 2 e 3.
Recorde-se que os elementos com ID [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>
Trabalho: seguindo o procedimento descrito no parágrafo 7.6.5, escreva a função JS [faireSimulation]. Esta função emitirá uma chamada Ajax do tipo POST para a ação [/Pam/FaireSimulation]. Por enquanto, não serão enviados dados. Aação [/Pam/FaireSimulation] devolverá a vista parcial [Simulation.cshtml] à função JS [faireSimulation], que colocará então este fluxo HTML na região com o ID [simulation] do formulário.
Teste a ligação [Faire la simulation] da sua aplicação.
9.9.4. A chamada Ajax [enregistrerSimulation]
O link [Enregistrer la simulation] está definido da seguinte forma em [_Layout.cshtml]:
<a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la simulation<br />
</a>
Tarefa: seguindo o procedimento anterior, escreva a função JS [enregistrerSimulation]. Esta função irá emitir uma chamada Ajax do tipo POST para a ação [/Pam/EnregistrerSimulation]. Por enquanto, não serão enviados dados. Aação [/Pam/EnregistrerSimulation] devolverá a vista parcial [Simulations.cshtml] à função JS [enregistrerSimulation], que, por sua vez, colocará esse fluxo HTML na região com o ID [content] da página mestre.
A vista [Simulations.cshtml] é a seguinte:
![]() |
O seu conteúdo é o seguinte:
<h2>Simulations</h2>
Eis um exemplo de execução:
![]() | ![]() |
9.9.5. A chamada Ajax [voirSimulations]
O link [Voir les simulations] está definido da seguinte forma em [_Layout.cshtml]:
<a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
</a>
Tarefa: seguindo o procedimento anterior, escreva a função JS [voirSimulations]. Esta função enviará uma chamada Ajax do tipo POST para a ação [/Pam/VoirSimulations]. Por enquanto, não serão enviados dados. Aação [/Pam/VoirSimulations] devolverá a vista parcial [Simulations.cshtml] à função JS [voirSimulations], que, por sua vez, colocará esse fluxo HTML na região com o ID [content] da página mestre.
A vista [Simulations.cshtml] é a que já foi utilizada na pergunta anterior.
Eis um exemplo de execução:
![]() |
9.9.6. A chamada Ajax [retourFormulaire]
O link [Retour au formulaire de simulation] está definido da seguinte forma em [_Layout.cshtml]:
<a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de simulation<br />
</a>
Tarefa: seguindo o procedimento anterior, escreva a função JS [retourFormulaire]. Esta função enviará uma chamada Ajax do tipo POST para a ação [/Pam/Formulaire]. Por enquanto, não serão enviados dados. Aação [/Pam/Formulaire] devolverá a vista parcial [Formulaire.cshtml] à função JS [retourFormulaire], que, por sua vez, colocará esse fluxo HTML na região com o ID [content] da página mestre.
A vista [Formulaire .cshtml] já foi definida. Eis um exemplo de execução:
![]() |
9.9.7. A chamada Ajax [terminerSession]
O link [Terminer la session] está definido da seguinte forma em [_Layout.cshtml]:
<a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
</a>
Trabalho: seguindo o procedimento anterior, escreva a função JS [terminerSession]. Esta função irá emitir uma chamada Ajax do tipo POST para a ação [/Pam/TerminerSession]. Por enquanto, não serão enviados dados. Aação [/Pam/TerminerSession] devolverá a vista parcial [Formulaire.cshtml] à função JS [terminerSession], que, por sua vez, colocará esse fluxo HTML na região com o ID [content] da página mestre.
Eis um exemplo de execução:
![]() |
9.9.8. A função JS [effacerSimulation]
A ligação [Effacer la simulation] está definida da seguinte forma em [_Layout.cshtml]:
<a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
</a>
A função JS [effacerSimulation] tem como objetivo:
- ocultar o fragmento [Simulation], caso exista;
- de restabelecer os campos de preenchimento do formulário ao estado em que se encontravam no carregamento inicial da aplicação (quando houver campos de preenchimento — por enquanto, não há nenhum).
Tarefa: escreva a função JS [effacerSimulation]. Aqui não há nenhuma chamada Ajax. O que acontece é interno ao navegador e não envolve o servidor.
Eis um exemplo de execução:
![]() |
9.9.9. Gestão da navegação entre ecrãs
Por enquanto, os links continuam sempre visíveis. Vamos agora gerir a sua exibição com uma função JavaScript. Recorde-se, em primeiro lugar, 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á gerir os mesmos em JavaScript. Modificamos o método JS executado ao carregar a página da seguinte forma:
// variáveis globais
var loading;
var content;
var lnkFaireSimulation;
var lnkEffacerSimulation
var lnkEnregistrerSimulation;
var lnkTerminerSession;
var lnkVoirSimulations;
var lnkRetourFormulaire;
var options;
...
// ao carregar o documento
$(document).ready(function () {
// recuperam-se as referências dos diferentes componentes da página
loading = $("#loading");
content = $("#content");
// os links do menu
lnkFaireSimulation = $("#lnkFaireSimulation");
lnkEffacerSimulation = $("#lnkEffacerSimulation");
lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
lnkVoirSimulations = $("#lnkVoirSimulations");
lnkTerminerSession = $("#lnkTerminerSession");
lnkRetourFormulaire = $("#lnkRetourFormulaire");
// colocam-se numa tabela
options = [lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
// ocultam-se alguns elementos da página
loading.hide();
// fixa-se o menu
setMenu([lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]);
});
- linhas 19-24: recuperamos as referências dos seis links. Estas referências são definidas como variáveis globais nas linhas 4-9;
- linha 26: o array [options] é inicializado com as seis referências. Este array é definido como variável global na linha 10;
- linha 28: oculta-se a imagem animada que indica a espera pelo fim das chamadas Ajax;
- linha 30: exibem-se os links [lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]. Os restantes serão ocultados.
A função JS [setMenu] é a seguinte:
function setMenu(show) {
// exibimos os links da tabela [show]
...
}
Tarefa: escrever a função JS [setMenu].
Se T for uma matriz de ligações:
- T.length é o número de links;
- T[i] é o link n.º i;
- T[i].show() exibe o link n.º i;
- T[i].hide() oculta o link n.º i.
Com estas novas funções JS, a página apresentada no início é a seguinte:
![]() |
Adapte as funções JS e [faireSimulation, effacerSimulation, enregistrerSimulation, voirSimulations, retourFormulaire, terminerSession] para obter os seguintes ecrãs:
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Agora que o modelo APU e os links de navegação estão definidos, podemos passar à criação das ações e das vistas do lado do servidor. Ao longo das etapas, irá verificar que alguns dos links Ajax que funcionam atualmente deixarão de funcionar, uma vez que irá alterar as vistas parciais enviadas ao cliente. À medida que for construindo as diferentes ações e vistas do lado do servidor, os links Ajax do lado do cliente voltarão a funcionar como lhes tinha definido.
9.10. Etapa 4: criação da ação do servidor [Index]
Atualmente, ao iniciar a aplicação, temos o seguinte ecrã:
![]() |
Em vez deste ecrã, gostaríamos de ter o seguinte:
![]() |
É 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 preenchimento:
- 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 [Faire la simulation];
- a validade dos campos de introdução de dados [Heures travaillées] e [Jours travaillés] deve ser verificada;
- a lista de funcionários provém da camada [métier] que criámos anteriormente.
Recorde-se o código atual da ação [Index]:
[HttpGet]
public ViewResult Index()
{
return View();
}
e o da vista [Index.cshtml] que esta ação apresenta:
@{
ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")
e o da vista parcial [Formulaire.cshtml]:
<h2>Formulaire</h2>
Serão efetuadas alterações nestes três locais.
9.10.1. O modelo do formulário
Voltemos à cadeia de processamento do URL [/Pam/Index]:
![]() |
- o pedido HTTP do cliente chega como [1];
- em [2], as informações contidas na solicitação serão transformadas no modelo de ação [3], que servirá de entrada para a ação [4];
- em [4], a ação, a partir desse modelo, irá gerar uma resposta. Esta terá duas componentes: uma vista V [6] e o modelo M dessa vista [5];
- a vista V [6] utilizará o seu modelo M [5] para gerar a resposta HTTP destinada ao cliente.
A ação que nos interessa é a ação [Index], que, neste momento, é a seguinte:
[HttpGet]
public ViewResult Index()
{
return View();
}
A ação [Index] não passa nenhum modelo para a vista [Index.cshtml]. Por conseguinte, esta não poderá apresentar a lista de funcionários. Esta pode ser solicitada à camada [métier]. Para tal, é necessário que o projeto [pam-web-01] tenha uma referência ao projeto [pam-metier-simule]. Vamos criar essa referência agora:
![]() |
- em [1], clique com o botão direito do rato em [References] do projeto [pam-web-01] e, em seguida, em [Ajouter une référence];
- em [2], selecione a opção [Solution] e, em seguida, o projeto [pam-metier-simule] em [3];
- em [4], o projeto [pam-metier-simule] foi adicionado às referências do projeto [pam-web-01].
9.10.2. O modelo da aplicação
Introduzimos os conceitos importantes de modelo de aplicação e modelo de sessão no parágrafo 4.10, página 78. Vamos utilizá-los agora. Recorde-se que no modelo se colocam:
- no modelo de aplicação, dados de leitura apenas para todos os utilizadores. Este modelo constitui uma memória partilhada por todas as consultas de todos os utilizadores;
- no modelo de sessão, dados em modo de leitura e escrita para um determinado utilizador. Este modelo constitui uma memória partilhada por todas as solicitações desse utilizador.
O que vamos colocar no modelo de aplicação? Voltemos à arquitetura da mesma:
![]() |
A camada [web] possui uma referência à camada [métier]. Esta pode ser partilhada por todos os utilizadores. Podemos, portanto, incluí-la no modelo da aplicação. Além disso, vamos partir do princípio de que a lista de funcionários não se altera. Pode, portanto, ser lida uma única vez e depois partilhada entre todos os utilizadores. Propomos, assim, o seguinte modelo de aplicação:
![]() |
O código da classe [ApplicationModel] poderia ser o seguinte:
using Pam.Metier.Entites;
using Pam.Metier.Service;
namespace PamWeb.Models
{
public class ApplicationModel
{
// --- dados do âmbito da aplicação ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
}
}
Para apresentar uma lista suspensa numa vista, escreve-se algo como o seguinte:
<!-- a lista suspensa -->
<tr>
<td>Liste déroulante</td>
<td>@Html.DropDownListFor(m => m.DropDownListField,
new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
</td>
</tr>
O método [DropDownListFor] espera, como segundo parâmetro, um tipo SelectListItem[], que tinha sido fornecido acima por um tipo [SelectList]. Temos de construir essa matriz com a lista de funcionários. Uma vez que os funcionários não se alteram, esta matriz também pode ser colocada no modelo da aplicação. Atualizamos o modelo da seguinte forma:
using Pam.Metier.Entites;
using Pam.Metier.Service;
using System.Web.Mvc;
namespace Pam.Web.Models
{
public class ApplicationModel
{
// --- dados do âmbito da aplicação ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
}
}
Em que momento é que este modelo deve ser criado? Mostrámos isso no parágrafo 4.10. É durante a execução do método [Application_Start] do ficheiro [Global.asax]:
![]() |
O método [Application_Start] é, por enquanto, o seguinte:
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace pam_web_01
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
}
Vamos atualizá-lo da seguinte forma:
using Pam.Metier.Entites;
using Pam.Metier.Service;
using PamWeb.Infrastructure;
using PamWeb.Models;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace pam_web_01
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// ----------Gerado automaticamente
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// -------------------------------------------------------------------
// ---------- configuração específica
// -------------------------------------------------------------------
// dados do âmbito da aplicação
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
// instanciação da camada [métier]
application.PamMetier = ...
// tabela de funcionários
application.Employes = ...
// elementos da lista suspensa de funcionários
application.EmployesItems = ...
// model binder para [ApplicationModel]
...
}
}
}
Tarefa: completar o código do método [Application_Start]. Tudo o que precisa encontra-se no parágrafo 4.10. Reserve algum tempo para reler este parágrafo, que é longo, mas importante.
A linha 33 é, na verdade, composta por várias linhas. Para criar um objeto do tipo [SelectListItem], pode utilizar o seguinte método:
new SelectListItem() { Text = unTexte, Value = uneValeur };
Este [SelectListItem] servirá para gerar a seguinte baliza HTML <option>:
da lista suspensa. Asseguraremos que:
- unTexte seja o nome próprio seguido do apelido do funcionário;
- uneValeur seja o n.º SS do funcionário.
Na linha 35, acima, será necessária a classe [ApplicationModelBinder] descrita no parágrafo 4.10, página 82:
![]() |
9.10.3. O código da ação [Index]
Agora que definimos um modelo para a aplicação, podemos alterar o código da ação [Index] da seguinte forma:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View();
}
- linha 4: o modelo da aplicação é agora um parâmetro da ação [Index]. Explicámos no parágrafo 4.10 como este parâmetro era inicializado pelo framework.
9.10.4. O modelo da vista [Index.cshtml]
Agora, a ação [Index] tem acesso aos funcionários que estão registados no modelo da aplicação. Tem agora de os passar para a vista [Index.cshtml] que irá apresentar. Poderíamos passar um tipo [ApplicationModel] como modelo da vista [Index.cshtml], mas veremos rapidamente que esta vista necessita de outras informações que não se encontram em [ApplicationModel]. Vamos utilizar o seguinte modelo de vista [IndexModel]:
![]() |
namespace Pam.Web.Models
{
public class IndexModel
{
// dados de âmbito da aplicação
public ApplicationModel Application { get; set; }
}
}
- linha 6: [IndexModel] carrega 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 predefinida [Index.cshtml] é apresentada com um modelo do tipo [IndexModel], inicializado com os dados do modelo da aplicação.
Sabemos que a vista [Index.cshtml] deve apresentar um formulário:

Voltemos à cadeia de processamento de um pedido:
![]() |
Para a solicitação [GET /Pam/Index]:
- a ação é [Index];
- o modelo desta ação é [ApplicationModel];
- a vista é [Index.cshtml];
- o modelo desta vista é [IndexModel].
Quando o formulário for enviado, teremos uma cadeia de processamento semelhante:
- a ação é a que processa o POST;
- o seu modelo reúne os valores enviados, neste caso:
- o n.º SS do funcionário selecionado;
- o número de horas trabalhadas;
- o número de dias trabalhados;
Poderíamos criar um modelo de ação que reunisse estes três valores. É também frequente reutilizar o modelo que serviu para apresentar o formulário. É isso que vamos fazer 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
{
// dados do âmbito da aplicação
public ApplicationModel Application { get; set; }
// valores lançados
[Display(Name = "Employé")]
public string SS { get; set; }
[Display(Name = "Heures travaillées")]
[UIHint("Decimal")]
public double HeuresTravaillées { get; set; }
[Display(Name = "Jours travaillés")]
public double JoursTravaillés { get; set; }
}
}
- linhas 13, 16, 18: os três valores lançados. Note-se que [joursTravaillés] foi declarado como sendo do tipo [double], quando, na realidade, se espera um inteiro. O tipo [double] foi introduzido para facilitar a validação deste campo do lado do cliente, uma vez que a validação de um tipo [int] tinha causado problemas;
- linhas 12, 14, 17: descrições para os métodos [Html.LabelFor] da vista associada ao modelo;
- linha 15: uma anotação para que o campo [HeuresTravaillées] seja apresentado com duas casas decimais;
- linha 5: indica-se que a propriedade denominada [Application] não faz parte dos valores lançados.
9.10.5. As 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: integra a vista parcial [Formulaire.cshtml], mais uma vez sem passar para o modelo correspondente. Durante os testes, verificou-se que o modelo [IndexModel], passado para a vista [Index.cshtml], se propagava implicitamente para a vista parcial [Formulaire.cshtml]. Esta última vista poderia agora ter a seguinte forma:
@model Pam.Web.Models.IndexModel
@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
<table>
<thead>
<tr>
...
</tr>
</thead>
<tbody>
<tr>
...
</tr>
<tr>
...
</tr>
</tbody>
</table>
}
<div id="simulation" />
- linha 1: a vista recebe um modelo do tipo [IndexModel];
- linha 3: o formulário;
- linhas 6-10: os cabeçalhos da tabela de entradas;
- linhas 12-14: a linha de entradas;
- linhas 15-17: eventuais mensagens de erro.
Tarefa: completar o código da vista [Formulaire.cshtml]. Utilizar-se-ão os métodos [DropDownListFor, EditorFor, LabelFor, ValidationMessageFor] descritos no parágrafo 5.7.
9.10.6. Teste da ação [Index]
Escrevemos todos os elementos da cadeia de processamento da ação URL [/Pam/Index]:
![]() |
Estamos a testar a aplicação com o [Ctrl-F5]:
![]() | ![]() |
Deve verificar se a sua lista suspensa foi preenchida com a lista de funcionários que definimos na camada simulada [métier].
9.11. Passo 5: implementação da validação dos dados introduzidos
9.11.1. O problema
Embora não tenhamos feito nada para o efeito, já estão em funcionamento validações do lado do cliente:
![]() |
![]() |
A validação do lado do cliente está ativa 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 ficheiro [IndexModel] se declarou o campo [JoursTravaillés] como sendo do tipo [double]:
public double JoursTravaillés { get; set; }
é possível introduzir um número real neste campo:
![]() |
Além disso, é possível introduzir valores aleatórios nos dois campos:
![]() |
O modelo [IndexModel] do formulário é atualmente o seguinte:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Pam.Web.Models
{
[Bind(Exclude = "Application")]
public class IndexModel
{
// dados de âmbito da aplicação
public ApplicationModel Application { get; set; }
// valores lançados
[Display(Name = "Employé")]
public string SS { get; set; }
[Display(Name = "Heures travaillées")]
[UIHint("Decimal")]
public double HeuresTravaillées { get; set; }
[Display(Name = "Jours travaillés")]
public double JoursTravaillés { get; set; }
}
}
Tarefa: melhore este modelo para:
- ter mensagens de erro personalizadas;
- aceitar apenas valores reais no intervalo [0,400] para o campo [HeuresTravaillées];
- aceitar apenas valores inteiros no intervalo [0,31] para o campo [JoursTravaillées];
Pode-se recorrer ao exemplo do parágrafo 7.6.2. Para verificar se o número de dias trabalhados é um número inteiro, pode-se utilizar uma expressão regular (ver exemplos do parágrafo 5.9.1).
Eis alguns exemplos do que é esperado:
![]() |
![]() |
![]() |
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 o ponto decimal). O formato francês com a vírgula não é aceite:
![]() |
Este problema foi identificado e resolvido no parágrafo 6.1.
Tarefa: seguindo o procedimento descrito no parágrafo acima referido, efetue as alterações necessárias para que seja possível introduzir números reais no formato decimal francês. Teste a sua aplicação.
Agora, o ecrã anterior passa a ser:
![]() |
9.11.3. Validação do formulário através do link JavaScript [Faire la simulation]
Atualmente, é possível enviar valores inválidos, como mostra a sequência seguinte:
![]() |
![]() |
A presença da simulação em [1] e a alteração do menu em [2] demonstram que o clique no link [Faire la simulation] enviou o formulário, apesar de os valores introduzidos serem inválidos. Este problema foi identificado e resolvido no parágrafo 7.6.5.
Tarefa: seguindo o procedimento descrito no parágrafo acima referido, certifique-se de que o POST do link [Faire la simulation] não possa ser executado se os valores introduzidos forem inválidos. Lembre-se de esvaziar a cache do navegador antes de testar as suas alterações.
Recorde-se 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 exibindo o código-fonte do formulário no navegador:
<div id="content">
<form action="/Pam/FaireSimulation" id="formulaire" method="post">
...
</form>
<div id="simulation" />
</div>
9.12. Passo 6: realizar uma simulação
9.12.1. O problema
Quando fazemos uma simulação, pretendemos obter o seguinte resultado:
![]() |
A vista parcial [Simulation.cshtml] apresenta agora a folha de vencimento de um colaborador.
9.12.2. Criação da 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] tem como modelo o tipo [FeuilleSalaire] definido no parágrafo 9.7.3;
- a vista utiliza as classes [libellé, info, valeur] 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], define-se a altura das linhas das diferentes tabelas HTML da região com o ID [simulation], precisamente onde é apresentada a folha de salário:
#simulação da tabela tr {
height: 30px;
}
Tarefa: preencha a vista [Simulation.cshtml].
Para apresentar o valor em euros de uma quantia monetária, utilizar-se-á o método [string.Format]:
A instrução acima apresenta [somme] como valor monetário [C] (Moeda) com duas casas decimais [C2].
Para testar esta vista, é necessário fornecer-lhe uma folha de salário. Esta deve ser fornecida pela ação [/Pam/FaireSimulation], que é o destino da chamada Ajax do link [Faire la simulation]. Atualmente, esta ação é a seguinte:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
}
// efetuar uma simulação
[HttpPost]
public PartialViewResult FaireSimulation()
{
return PartialView("Simulation");
}
No exemplo acima, a ação [FaireSimulation] não passa nenhum modelo para a vista [Simulation.cshtml]. É necessário que ela lhe passe uma folha de salário. Sabemos que é a camada [métier] que efetua o cálculo das folhas de salário. Esta camada [métier] é acessível através do modelo da aplicação [ApplicationModel] que definimos no parágrafo 9.10.2:
public class ApplicationModel
{
// --- dados de âmbito da aplicação ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
}
A camada [métier] é acessível através da propriedade da linha 5 acima. Para que a ação [FaireSimulation] tenha acesso à camada [métier], vamos passar-lhe o modelo da aplicação, tal como fizemos para a ação [Index]. O código passa então a ter o seguinte aspeto:
// fazer uma simulação
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application)
{
return PartialView("Simulation");
}
Agora, dentro da ação, conseguimos calcular uma folha de vencimento fictícia. O código passa a ser o seguinte:
// fazer uma simulação
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application)
{
FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
return PartialView("Simulation", feuilleSalaire);
}
- Na linha 5, calcula-se um salário fictício. O primeiro parâmetro é um n.º SS existente. Foi definido na classe [métier] simulada no parágrafo 9.7.5. O segundo parâmetro é o número de horas trabalhadas e o terceiro, o número de dias trabalhados;
- linha 6: esta folha de salário é passada como modelo para a vista [Simulation.cshtml].
Estamos agora prontos para testar a vista [Simulation.cshtml]:
![]() |
Não se introduz qualquer dado e solicita-se a simulação. Obtém-se então o seguinte resultado:
![]() |
9.12.3. Cálculo do salário real
A nossa ação atual [FaireSimulation] calcula sempre a mesma folha de salário:
// fazer uma simulação
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application)
{
FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
return PartialView("Simulation", feuilleSalaire);
}
Não tem em conta as informações introduzidas:
- o colaborador cujo salário está a ser calculado;
- o número de horas trabalhadas;
- o número de dias trabalhados.
Os valores introduzidos chegam à ação [FaireSimulation] da seguinte forma:
- o utilizador clica na ligação [Faire la simulation]. Isto desencadeia a execução da função JS [faireSimulation] que já escrevemos;
- A função JS [faireSimulation] efetua, em seguida, uma chamada Ajax à ação do servidor [/Pam/FaireSimulation], na qual estamos a trabalhar atualmente. Por enquanto, a função JS [faireSimulation] não transmite qualquer informação à ação do servidor. Será necessário que ela transmita os valores introduzidos pelo utilizador;
- a ação do servidor [/Pam/FaireSimulation] irá recuperar os valores introduzidos a partir dos valores 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 recorrer-se ao exemplo do parágrafo 7.6.5, onde este problema foi abordado.
Vamos agora abordar o ponto 3 acima. A ação de servidor [/Pam/FaireSimulation] deve recuperar os valores enviados pela função JS [faireSimulation].
Tarefa: complete o método do servidor [FaireSimulation] para que calcule o salário com os valores lançados pelas funções JS e [faireSimulation]. Podemos recorrer novamente ao exemplo do parágrafo 7.6.5, onde este problema foi abordado. Por enquanto, vamos supor que o modelo resultante dos valores lançados continua a ser válido.
Dica: a ação do servidor [FaireSimulation] evolui da seguinte forma:
// fazer uma simulação
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
{
// criação do modelo da ação
...
// tenta-se recuperar os valores lançados neste modelo
...
// calcula-se o salário
FeuilleSalaire feuilleSalaire = ...
// exibe-se a folha de salário
return PartialView("Simulation", feuilleSalaire);
}
Eis um exemplo de execução:
![]() |
Escolhe-se [Justine Laverti]. Obtém-se então o seguinte resultado:
![]() |
Conseguimos, de facto, a folha de vencimento fictícia de [Justine Laverti]. Anteriormente, a única folha de vencimento que era calculada era a de [Marie Jouveinal]. Portanto, o valor lançado para a escolha do colaborador foi utilizado. Quanto ao número de horas e ao número de dias, não é possível fazer qualquer afirmação, uma vez que a nossa camada simulada [métier] não tem em conta esses dados.
9.12.4. Gestão de erros
Vejamos o exemplo seguinte:
![]() |
- em [1], escolhe-se um colaborador que não existe (ver a definição da camada [métier] simulada no parágrafo 9.7.5;
- em [2], realiza-se a simulação;
- em [3], abaixo, é apresentada uma página de erro.
![]() |
O que aconteceu?
A função JS [faireSimulation] foi executada. O seu código é semelhante ao seguinte:
function faireSimulation() {
...
// é efetuada uma chamada Ajax manualmente
$.ajax({
url: '/Pam/FaireSimulation',
...
beforeSend: function () {
// luz de espera acesa
loading.show();
},
success: function (data) {
...
},
error: function (jqXHR) {
// exibição de erro
simulation.html(jqXHR.responseText);
simulation.show();
},
complete: function () {
// luz de espera apagada
loading.hide();
}
});
// menu
setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
A chamada Ajax falhou e foi executada a função das linhas 14-18. A página de erro [jqXHR.responseText] devolvida pelo servidor foi apresentada. Esta é bastante precisa. A camada simulada [métier] lançou uma exceção porque o n.º SS que lhe foi fornecido não corresponde ao de um colaborador existente (ver código da camada simulada [métier] no parágrafo 9.7.5). Temos de tratar este caso de forma adequada.
Vamos criar uma vista parcial [Erreurs.chtml] que será devolvida ao cliente JS sempre que for detetado um erro no lado do servidor:
![]() |
O código da vista parcial [Erreurs.chtml] é o seguinte:
@model IEnumerable<string>
<hr />
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
@foreach (string msg in Model)
{
<li>@msg</li>
}
</ul>
- linha 1: a vista recebe como modelo uma lista de mensagens de erro;
- linhas 5-10: que são apresentadas numa lista HTML;
Agora, vamos alterar o código da ação do servidor [FaireSimulation] da seguinte forma:
// fazer uma simulação
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
{
...
// calcular o salário
FeuilleSalaire feuilleSalaire = null;
Exception exception=null;
try
{
// cálculo do salário
feuilleSalaire = ...
}
catch (Exception ex)
{
exception = ex;
}
// erro?
if (exception == null)
{
// exibir a folha de salário
return PartialView("Simulation", feuilleSalaire);
}
else
{
// exibir a página de erros
return PartialView("Erreurs", Static.GetErreursForException(exception));
}
}
- linhas 9-17: o cálculo do salário é agora efetuado num try/catch;
- linha 27: se tiver ocorrido um erro, é apresentada a vista parcial [Erreurs.cshtml], utilizando como modelo a lista de mensagens de erro fornecida pelo método estático [Static.GetErreursForException(exception)].
Agrupam-se na classe [Static] duas funções utilitárias estáticas [1]:
![]() |
using System;
using System.Collections.Generic;
using System.Web.Mvc;
namespace PamWeb.Infrastructure
{
public class Static
{
// lista de mensagens de erro de uma exceção
public static List<string> GetErreursForException(Exception ex)
{
List<string> erreurs = new List<string>();
while (ex != null)
{
erreurs.Add(ex.Message);
ex = ex.InnerException;
}
return erreurs;
}
// lista de mensagens de erro relacionadas com um modelo inválido
public static List<string> GetErreursForModel(ModelStateDictionary état)
{
List<string> erreurs = new List<string>();
if (!état.IsValid)
{
foreach (ModelState modelState in état.Values)
{
foreach (ModelError error in modelState.Errors)
{
erreurs.Add(getErrorMessageFor(error));
}
}
}
return erreurs;
}
// a mensagem de erro relacionada com um elemento do modelo da ação
static private string getErrorMessageFor(ModelError error)
{
if (error.ErrorMessage != null && error.ErrorMessage.Trim() != string.Empty)
{
return error.ErrorMessage;
}
if (error.Exception != null && error.Exception.InnerException == null && error.Exception.Message != string.Empty)
{
return error.Exception.Message;
}
if (error.Exception != null && error.Exception.InnerException != null && error.Exception.InnerException.Message != string.Empty)
{
return error.Exception.InnerException.Message;
}
return string.Empty;
}
}
}
- linhas 10-19: a função estática [GetErreursForException] devolve a lista de erros de uma pilha de exceções;
- linhas 22-36: a função estática [GetErreursForModel] devolve a lista de erros de um modelo de ação inválido. O código desta função, bem como o do método privado [getErrorMessageFor] (linhas 39-54), já foi abordado anteriormente.
Feito isto, podemos testar novamente o caso de erro:
![]() |
- em [1], selecionamos o funcionário que não existe;
- em [2], fazemos a simulação;
- em [3], recuperamos a nova página de erros.
Voltemos à ação do servidor [FaireSimulation]:
// fazer uma simulação
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
{
// criação do modelo da ação
IndexModel modèle = new IndexModel() { Application = application};
// tenta-se recuperar os valores lançados no modelo
TryUpdateModel(modèle, data);
// cálculo do salário
...
}
Na linha 8, atualizamos o modelo da linha 6 com os valores enviados pela chamada Ajax. Não verificamos a validade do modelo. É necessário fazê-lo, pois não podemos saber de onde provêm os valores enviados. Alguém pode ter manipulado um POST e enviado-nos dados inválidos.
Tarefa: seguindo o modelo que desenvolvemos para o caso da exceção, altere a ação do servidor [FaireSimulation] de forma a enviar uma página de erros quando os dados enviados forem inválidos. Para tal, utilizaremos o método estático [GetErreursForModel] da classe [Static].
Como testar esta alteração? No parágrafo 9.11.3, certificou-se de que a função JS [faireSimulation] não processasse os valores introduzidos através da função POST caso estes fossem inválidos. Coloque em comentário as linhas que realizam esta ação e, em seguida, faça o seguinte teste:
![]() |
- em [1], fazemos a simulação com valores inválidos;
- em [2], obtém-se corretamente a página de erros que acabámos de criar, o que prova que os validadores do lado do servidor funcionaram corretamente.
A seguir, não se esqueça de descomentar as linhas que acabou de colocar em comentário nas funções JS e [faireSimulation].
9.13. Passo 7: criação de uma sessão de utilizador
A aplicação [Simulateur de calcul de paie] permite ao utilizador realizar várias simulações de folha de pagamento através do link [Faire la simulation], guardá-las através do link [Enregistrer la simulation], visualizá-las através do link [Voir les simulations] e eliminá-las através do link [Retirer la simulation]. Sabemos que, entre duas solicitações sucessivas do utilizador, não existe memória, a menos que seja criada através do mecanismo de sessão (ver parágrafo 4.10). É bastante claro, neste contexto, que devemos conservar na sessão a lista das simulações registadas ao longo do tempo pelo utilizador. Há outros dados a memorizar: quando o utilizador realiza uma simulação, esta só é registada na lista de simulações se o utilizador o solicitar através do link [Enregistrer la simulation]. Quando o faz, temos de ser capazes de recuperar a simulação calculada no pedido anterior. Para tal, esta será também colocada na sessão. Por fim, vamos numerar as simulações a partir de 1. Para numerar corretamente uma nova simulação, é necessário ter guardado o número da simulação anterior, mais uma vez na sessão.
No parágrafo 4.10, introduzimos o conceito de modelo de sessão como parâmetro de entrada de uma ação, para que esta tenha acesso à sessão. Vamos retomar este conceito. Convidamo-lo a reler o parágrafo em questão se este conceito lhe parecer obscuro.
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
{
// a lista de simulações
public List<Simulation> Simulations { get; set; }
// n.º da próxima simulação
public int NumNextSimulation { get; set; }
// a última simulação
public Simulation Simulation { get; set; }
// fabricante
public SessionModel()
{
// lista de simulações vazia
Simulations = new List<Simulation>();
// n.º da próxima simulação
NumNextSimulation = 1;
}
}
}
A classe [Simulation], nas linhas 9 e 13, irá registar informações sobre uma simulação. O que precisamos de registar? A ligação [Faire la simulation] calcula uma folha de salário do tipo [FeuilleSalaire]. Parece natural incluir esta folha na simulação. Além disso, temos de memorizar as informações que conduziram a esta folha de salário:
- o colaborador selecionado. Este encontra-se no campo [FeuilleSalaire.Employe]. Por isso, não é necessário guardá-lo uma segunda vez;
- o número de horas e dias trabalhados. Estas informações não constam do tipo [FeuilleSalaire]. Por isso, temos de as memorizar.
Por fim, cada simulação é identificada por um número. Poderíamos, portanto, partir da seguinte classe [Simulation]:
using Pam.Metier.Entites;
namespace Pam.Web.Models
{
public class Simulation
{
// n.º da simulação
public int Num { get; set; }
// o número de horas trabalhadas
public double HeuresTravaillées { get; set; }
// o número de dias trabalhados
public int JoursTravaillés { get; set; }
// folha de vencimento
public FeuilleSalaire FeuilleSalaire { get; set; }
}
}
A ação do servidor [FaireSimulation] deve, além de calcular uma folha de salário, criar uma simulação e colocá-la na sessão. Para tal, receberá como parâmetro o modelo da sessão:
// fazer uma simulação
[HttpPost]
public PartialViewResult FaireSimulation(ApplicationModel application, SessionModel session, FormCollection data)
{
// criação do modelo da ação
IndexModel modèle = new IndexModel() { Application = application };
// tentamos recuperar os valores lançados no modelo
TryUpdateModel(modèle, data);
// modelo válido?
if (!ModelState.IsValid)
{
// exibe-se a página de erros
return PartialView("Erreurs", Static.GetErreursForModel(ModelState));
}
// cálculo do salário
FeuilleSalaire feuilleSalaire = null;
Exception exception = null;
try
{
// cálculo do salário
feuilleSalaire = application.PamMetier.GetSalaire(modèle.SS, modèle.HeuresTravaillées, (int)modèle.JoursTravaillés);
}
catch (Exception ex)
{
exception = ex;
}
// erro?
if (exception != null)
{
// é apresentada a página de erros
return PartialView("Erreurs", Static.GetErreursForException(exception));
}
// cria-se uma simulação e insere-se na sessão
session.Simulation = ...
// exibe-se a folha de salário
return PartialView("Simulation", feuilleSalaire);
}
- linha 3: a ação recebe como parâmetro o modelo da sessão;
Tarefa 1: completar o código da ação, linha 34
Tarefa 2: seguindo o procedimento descrito no parágrafo 4.10, faça o que for necessário para que o parâmetro [SessionModel session] da ação seja corretamente inicializado pelo framework. Se nada for feito, teremos um ponteiro null para este parâmetro.
9.14. Passo 8: gravar uma simulação
9.14.1. O problema
Quando realizámos uma simulação, podemos guardá-la:
![]() |

A vista parcial [Simulations.cshtml] apresenta agora a lista das simulações realizadas pelo utilizador. Recorde-se que a folha de salário calculada é fictícia.
9.14.2. Criação da ação do servidor [EnregistrerSimulation]
O link Ajax [Enregistrer la simulation] chama a ação do servidor [EnregistrerSimulation], cujo código era, até agora, o seguinte:
[HttpPost]
public PartialViewResult EnregistrerSimulation()
{
return PartialView("Simulations");
}
O código passa a ser o seguinte:
// guardar uma simulação
[HttpPost]
public PartialViewResult EnregistrerSimulation(SessionModel session)
{
// a última simulação realizada é registada na lista de simulações da sessão
...
// incrementa-se na sessão o número da próxima simulação
...
// exibe a lista de simulações
...
}
- linha 1: a ação [EnregistrerSimulation] necessita de ter acesso à sessão. É por isso que tem como parâmetro o modelo da sessão.
Tarefa: completar a ação de servidor [EnregistrerSimulation].
9.14.3. Gravação da vista parcial [Simulations.cshtml]
A ação anterior [EnregistrerSimulation] exibe a vista parcial [Simulations.cshtml], tendo como modelo a lista de simulações realizadas pelo utilizador. 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: completar o código da vista parcial [Simulations.cshtml]. Utilizar-se-á uma tabela HTML para a exibição das simulações. Poder-se-á recorrer aos exemplos do parágrafo 5.4.
Nota: o link [retirer] de cada simulação da tabela HTML será um link JavaScript com o seguinte formato:
onde N é o número da simulação.
Trabalho 2: teste a sua aplicação realizando simulações. Para o fazer, repita a seguinte sequência: 1) carregue a página da aplicação através de [F5], 2) realize uma simulação, 3) guarde-a. As simulações irão acumular-se na sessão, o que deverá ser refletido na vista [Simulations.cshtml].
Trabalho 3: melhore a vista parcial [Simulations.cshtml] de forma a que as cores das linhas da tabela HTML sejam alternadas.

Serão atribuídas, de forma alternada, às linhas <tr> da tabela HTML, as classes CSS, [pair] e [impair] definidas na folha de estilo [/Content/Site.css]:
.impair {
background-color: beige;
}
.pair {
background-color: lightsteelblue;
}
9.15. Passo 9: regressar ao formulário de introdução de dados
9.15.1. O problema
Depois de obtermos a lista de simulações, podemos regressar ao formulário de introdução de dados, algo que já não era possível há algum tempo:


9.15.2. Registo da ação do servidor [Formulaire]
O link Ajax [Retour au formulaire de simulation] chama a ação do servidor [Formulaire], cujo código era, até agora, o seguinte:
[HttpPost]
public PartialViewResult Formulaire()
{
return PartialView("Formulaire");
}
A vista parcial [Formulaire] que esta apresenta espera um modelo [IndexModel] (linha 1 abaixo):
@model Pam.Web.Models.IndexModel
@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
<div id="simulation" />
É por esta razão que o link [Retour au formulaire de simulation] já não funcionava.
Tarefa: escrever a nova versão da ação do servidor [Formulaire] (2 linhas a reescrever) e, em seguida, realizar os testes.
9.15.3. Alteração da função JavaScript [retourFormulaire]
Com a alteração efetuada anteriormente, já é possível regressar ao formulário, mas surge então uma anomalia:
![]() |
- em [1], regressa-se ao formulário de introdução de dados;
- em [2], faz-se uma simulação com entradas erradas. Descobre-se então que os validadores do lado do cliente já não funcionam. Aqui, o servidor foi chamado e devolveu uma página de erros graças ao trabalho realizado no parágrafo 9.12.4.
Esta anomalia foi identificada e tratada no parágrafo 7.6.7.
Tarefa: seguindo o procedimento descrito no parágrafo 7.6.7, corrija a função JavaScript [retourFormulaire] e, em seguida, realize testes para verificar se os validadores do lado do cliente voltaram a funcionar.
9.16. Etapa 10: consultar a lista de simulações
9.16.1. O problema
Ao trabalhar com o formulário de simulação, é possível visualizar a lista das simulações que foram realizadas:
![]() | ![]() |
9.16.2. Código da ação do servidor [VoirSimulations]
O link Ajax [Voir les simulations] chama a ação do servidor [VoirSimulations], cujo código era, até agora, o seguinte:
// ver as simulações
[HttpPost]
public PartialViewResult VoirSimulations()
{
return PartialView("Simulations");
}
A vista parcial [Simulations] que apresenta 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 esta razão que o link [Voir les simulations] já não funcionava.
Tarefa: escrever a nova versão da ação do servidor [VoirSimulations] (2 linhas a reescrever) e, em seguida, realizar os testes.
9.17. Passo 11: encerrar a sessão
9.17.1. O problema
É possível encerrar a sessão do utilizador a qualquer momento através do link [Ajax] [Terminer la session]. Isto tem como efeito encerrar a sessão atual para iniciar uma nova. Além disso, regressa-se à visualização do formulário:
![]() |
![]() |
- em [1], realizámos duas simulações e, em seguida, encerrámos a sessão;
- em [2], regressamos ao formulário de introdução de dados. Queremos ver as simulações;
- em [3], devido à mudança de sessão, a lista de simulações está agora vazia.
9.17.2. Registo da ação do servidor [TerminerSession]
O link Ajax [Terminer la session] chama a ação do servidor [TerminerSession], cujo código era, até agora, o seguinte:
// encerrar a sessão
[HttpPost]
public PartialViewResult TerminerSession()
{
return PartialView("Formulaire");
}
A vista parcial [Formulaire] que apresenta espera um modelo [IndexModel] (linha 1 abaixo):
@model Pam.Web.Models.IndexModel
@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
<div id="simulation" />
É por esta razão que o link [Terminer la session] já não funcionava.
Tarefa: escrever a nova versão da ação do servidor [TerminerSession] (2 linhas a reescrever) e, em seguida, realizar os testes.
Nota: para encerrar a sessão na ação, escreve-se:
9.17.3. Alteração da função JavaScript [terminerSession]
Com a alteração efetuada anteriormente, já é possível regressar ao formulário, mas surge então uma anomalia, a que foi descrita anteriormente no parágrafo 9.15.3.
Tarefa: seguindo o procedimento que seguiu no parágrafo 9.15.3, corrija a função JavaScript [terminerSession] e, em seguida, realize testes para verificar se os validadores do lado do cliente voltaram a funcionar.
9.18. Passo 12: apagar a simulação
9.18.1. O problema
Depois de realizar uma simulação, é possível apagá-la através do link JavaScript [Effacer la simulation]:
![]() | ![]() |
9.18.2. Registo da ação do cliente [effacerSimulation]
A função JavaScript [effacerSimulation] tem, por enquanto, o seguinte código:
function effacerSimulation() {
// apagam-se os dados introduzidos no formulário
// ...
// oculta a simulação, caso exista
$("#simulation").hide();
// menu
setMenu([lnkFaireSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
Tarefa: complete este código. Pode inspirar-se no exemplo do parágrafo 7.6.6
9.19. Passo 13: remover uma simulação
9.19.1. O problema
Quando se está na página das simulações, é possível eliminar algumas delas através do link JavaScript [retirer]:


9.19.2. Registo da ação do cliente [retirerSimulation]
Os links [retirer] têm o seguinte formato: HTML
onde N é o número da simulação.
Tarefa: seguindo o procedimento descrito nos parágrafos 9.9.3, escreva a função JS [retirerSimulation]. Esta função enviará uma chamada Ajax do tipo POST para a ação [/Pam/RetirerSimulation]. Enviará o dado N na forma num=N.
Nota: a função JS [retirerSimulation] é análoga às outras funções JS que escreveu e que efetuam uma chamada Ajax ao servidor. A única novidade aqui é o envio de um valor que não se encontra num formulário. Sabe-se que os valores enviados são reunidos numa cadeia de caracteres na forma:
a função JS [retirerSimulation] terá, portanto, a seguinte forma:
function retirerSimulation(N) {
// efetua-se manualmente uma chamada Ajax
$.ajax({
url: '/Pam/RetirerSimulation',
...
data:"num="+N,
...
});
// menu
setMenu([lnkRetourFormulaire, lnkTerminerSession]);
}
- linha 6: a propriedade [data] de uma chamada Ajax JQuery representa a cadeia enviada para o servidor.
9.19.3. Codificação da ação do servidor [RetirerSimulation]
A ação do servidor [RetirerSimulation]:
- recebe um parâmetro enviado chamado [num], que corresponde ao número de uma simulação;
- deve retirar da lista de simulações registadas na sessão a simulação com esse número;
- deve, em seguida, apresentar a nova lista de simulações.
Tarefa: escrever a ação do servidor [RetirerSimulation]. Consulte o parágrafo 4.1 para saber como recuperar o parâmetro enviado via POST denominado [num].
9.20. Passo 14: melhoria do método de inicialização da aplicação
A nossa aplicação web está concluída. Está funcional com uma classe [métier] simulada. Recorde-se a arquitetura que desenvolvemos:
![]() |
Restam alguns detalhes a resolver antes de passarmos à implementação real da camada [métier], 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 uma única vez no arranque da aplicação. É aqui que o ficheiro de configuração [Web.config] pode ser utilizado. Por enquanto, o nosso método [Application_Start] tem o seguinte aspeto:
// aplicação
protected void Application_Start()
{
// ----------Gerado automaticamente
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// -------------------------------------------------------------------
// ---------- configuração específica
// -------------------------------------------------------------------
// dados do âmbito da aplicação
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
// instanciação da camada [métier]
application.PamMetier = new PamMetier();
...
// ligadores de modelos
...
}
Na linha 17, a camada de negócio é instanciada através do operador «new». Além disso, o modelo da aplicação é definido da seguinte forma:
public class ApplicationModel
{
// --- dados do âmbito da aplicação ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
}
Na linha 5 acima, vemos que o tipo da propriedade [PamMetier] é o da interface [IPamMetier]. Isto significa que esta propriedade pode ser inicializada por qualquer objeto que implemente esta interface. No entanto, na linha 17 de [Application_Start], definimos explicitamente o nome de uma classe de implementação de [IPamMetier]. Assim, se a camada [métier] viesse a ser implementada com uma nova classe que implementasse [IPamMetier], seria necessário alterar esta linha. Não é muito importante, mas pode ser evitado. A definição da classe de implementação da interface [IPamMetier] pode ser transferida para um ficheiro de configuração. Para alterar a implementação, basta alterar o conteúdo desse ficheiro de configuração. O código .NET não precisa de ser alterado.
Iremos utilizar aqui o contentor de injeção de dependências [Spring.net]. Existem outros frameworks .NET para 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] irá solicitar ao [Spring.net] uma referência à camada [métier] simulada;
- em [B], [Spring.net] irá criar a camada simulada [métier], utilizando o seu ficheiro de configuração para determinar qual a classe que deve instanciar;
- em [C], [Spring.net] irá atribuir a referência da camada simulada [métier] à camada [ASP.NET MVC].
Note-se que, por predefinição, os objetos geridos pelo [Spring.net] são singletons: existem apenas numa única instância. Assim, se mais tarde, no nosso exemplo, o código voltar a solicitar ao [Spring.net] uma referência à camada [métier] simulada, o [Spring.net] limita-se a devolver a referência ao objeto inicialmente criado.
9.20.1. Adicionar as referências [Spring] ao projeto web
Vamos utilizar o [Spring.net]. Este framework vem na forma de DLL, que deve ser adicionado às referências do projeto. Podemos proceder da seguinte forma:
![]() |
No [1], clicar com o botão direito do rato no ramo [References] do projeto e, em seguida, selecionar a opção [Gérer les packages NuGet]. É necessária uma ligação à Internet. Em seguida, proceder-se-á tal como foi feito anteriormente para a biblioteca JQuery [Globalize]. Procurar-se-á a palavra-chave [Spring.core] e instalar-se-á este pacote. A instalação inclui dois pacotes DLL: [Spring.core] e [2], e [Common.Logging] e [3]. Nos exemplos que se seguem, foi utilizada a versão 1.3.2 do Spring.
Nota: se não tiver ligação à Internet, encontrará estes ficheiros DLL numa pasta [lib] do material de apoio deste estudo de caso.
9.20.2. Configuração do [web.config]
A definição da classe de implementação da interface [IPamMetier] é feita no 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>
<!-- configuração do Spring -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier-simule"/>
</objects>
</spring>
...
- linhas 2-8: localize a baliza <configSections> no ficheiro e insira nela as linhas 4-7;
- linha 4: o atributo [name="spring"] indica informações relativas à secção [spring] das linhas 10 a 17;
- linha 5: define a classe [Spring.Context.Support.DefaultSectionHandler], situada na DLL [Spring.Core], como a capaz de processar a secção [objects] das linhas 14 a 16;
- linha 6: define a classe [Spring.Context.Support.ContextHandler], situada na DLL [Spring.Core], como a capaz de processar a secção [context] das linhas 11-13;
- linhas 11-13: esta secção fornece a informação [<resource uri="config://spring/objects" />], que indica que os objetos Spring se encontram no ficheiro de configuração na secção [/spring/objects], ou seja, nas linhas 14-16;
- linhas 14-16: a baliza [objects] introduz os objetos Spring;
- linha 15: define um objeto identificado por [id="pammetier"], que é uma instância da classe [Pam.Metier.Service.PamMetier] localizada na DLL [pam-metier-simule]. Aqui, é importante não se enganar. Quanto ao atributo [id], pode introduzir o que quiser. Irá utilizar este identificador em [Global.asax]. A classe [Pam.Metier.Service.PamMetier] é a da nossa camada simulada [métier]. É necessário voltar à sua definição para saber o seu nome completo:
namespace Pam.Metier.Service
{
public class PamMetier : IPamMetier
{
...
Para o DLL e o [pam-metier-simule], é necessário consultar as propriedades do projeto C# [pam-metier-simule]:
![]() |
É necessário utilizar o nome indicado em [1].
9.20.3. Alteração de [Application_Start]
O método [Application_Start] passa a ter a seguinte forma:
using Spring.Context.Support;
// aplicação
protected void Application_Start()
{
// ----------Gerado automaticamente
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// -------------------------------------------------------------------
// ---------- configuração específica
// -------------------------------------------------------------------
// dados do âmbito da aplicação
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
// instanciação da camada [métier]
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
...
// ligadores de modelos
...
}
- linha 19: utiliza-se a classe Spring [ContextRegistry], que é uma classe capaz de processar o ficheiro [web.config]. Para tal, é necessário importar o espaço de nomes da linha 1. O método estático [GetContext] permite obter o conteúdo das balizas [context], que indicam onde se encontram os objetos Spring. O método estático [GetObject] permite, em seguida, obter um objeto específico identificado pelo seu atributo id. Note-se que, agora, o nome da classe de implementação da interface [IPamMetier] já não está hardcoded no código. Encontra-se agora no ficheiro [web.config].
Depois de efetuar todas estas alterações, teste a sua aplicação. Deve funcionar.
9.20.4. Gerir 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 nos enganámos no nome do objeto a instanciar;
- outra é que a instanciação da camada [métier] falha. Este não pode ser o caso da nossa camada simulada [métier], mas poderá ser o caso da nossa camada real [métier], que estará ligada a uma base de dados. A camada SGBD pode não ser iniciada, as informações sobre a base de dados a gerir podem estar incorretas, etc...
Vamos tratar uma eventual exceção num try/catch. O código passa a ter a seguinte forma:
// aplicação
protected void Application_Start()
{
// ----------Gerado automaticamente
...
// -------------------------------------------------------------------
// ---------- configuração específica
// -------------------------------------------------------------------
// dados de âmbito da aplicação
ApplicationModel application = new ApplicationModel();
Application["data"] = application;
application.InitException = null;
try
{
// instanciação da camada [métier]
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
catch (Exception ex)
{
application.InitException = ex;
}
//se não houver erros
if (application.InitException == null)
{
....
}
// ligadores de modelos
...
}
- na linha 12, introduzimos uma nova propriedade denominada [InitException] no modelo da aplicação:
public class ApplicationModel
{
// --- dados de âmbito da aplicação ---
public Employe[] Employes { get; set; }
public IPamMetier PamMetier { get; set; }
public SelectListItem[] EmployesItems { get; set; }
public Exception InitException { get; set; }
}
- na 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 [métier] é agora feita num try/catch;
- linha 20: a exceção é registada;
- linhas 23-26: se não tiver ocorrido nenhum erro, executa-se o código que tínhamos anteriormente;
- linha 28: os [ModelBinders] são criados, quer tenha ocorrido um erro ou não. Isto é importante. Queremos garantir que o modelo da aplicação [ApplicationModel] será efetivamente associado pelo framework.
Sabemos que, ao iniciar a aplicação, a ação do servidor [Index] é executada. Por enquanto, é a seguinte:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
return View(new IndexModel() { Application = application });
}
Na linha 2, a ação [Index] recebe o modelo da aplicação. Assim, pode verificar se a inicialização decorreu corretamente ou não e apresentar uma página de erros caso a inicialização tenha falhado de alguma forma. Alteramos o código da seguinte forma:
[HttpGet]
public ViewResult Index(ApplicationModel application)
{
// erro de inicialização?
if (application.InitException != null)
{
// página de erros sem menu
return View("InitFailed",Static.GetErreursForException(application.InitException));
}
// sem erros
return View(new IndexModel() { Application = application });
}
Na linha 8, em caso de erro de inicialização, apresentamos a vista [InitFailed.cshtml] com, como modelo, a lista de mensagens de erro da exceção que ocorreu durante a inicialização. O método [Static.GetErreursForException] foi apresentado e explicado no parágrafo 9.12.4. A vista [InitFailed.cshtml] terá o seguinte aspeto:
![]() |
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 da vista é 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]. Com efeito, não se pretende o menu fornecido por esse documento. Por isso, constrói-se uma página HTML completa (linhas 5-23).
Para testar, basta alterar, em [Application_Start], a instância da camada [métier] da seguinte forma:
try
{
// instanciação da camada [métier]
application.PamMetier = ContextRegistry.GetContext().GetObject("xx") as IPamMetier;
}
catch (Exception ex)
{
application.InitException = ex;
}
Na linha 4, procura-se um objeto que não existe nos objetos Spring.
Quando se validam estas alterações e se inicia a aplicação, obtém-se a seguinte página:
![]() |
É apresentada uma página de erros sem menu. O utilizador não pode fazer nada além de constatar o erro. Era isso que se pretendia.
9.21. Em que ponto estamos?
Temos agora uma aplicação web operacional que funciona com uma camada de negócio simulada. A sua arquitetura é a seguinte:
![]() |
A camada [ASP.NET MVC] interage com a camada de negócio simulada através da interface [IPamMetier]. Se substituirmos essa camada de negócio simulada por uma camada de negócio real que cumpra essa interface, não teremos de alterar o código da camada web. Graças à [Spring.net], bastará alterar, na [web.config], a classe de implementação da interface [IPamMetier]. Vamos seguir por este caminho.
A nova arquitetura será a seguinte:
![]() |
Vamos descrever sucessivamente:
- a camada [EF5] ligada à SGBD. Será implementada com o Entity Framework 5 (EF5);
- a camada [DAO], que gere o acesso aos dados através da camada [EF5]. Isto permite-lhe ignorar a existência da camada SGBD. Esta camada limita-se a manipular as entidades da aplicação [Employe, Cotisations, Indemnites];
- a camada [métier], que implementa o cálculo do salário.
A nova arquitetura é a apresentada no início deste documento, no parágrafo 1.1, e que agora recordamos:
![]() |
- a camada [Web] é a camada em contacto com o utilizador da aplicação Web. Este interage com a aplicação Web através de páginas Web visualizadas por um navegador. É nesta camada que se situam ASP.NET e MVC, e apenas nesta camada;
- a camada [métier] implementa as regras de gestão da aplicação, tais como o cálculo de um salário ou de uma fatura. Esta camada utiliza dados provenientes do utilizador através da camada [Web] e da camada 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 da camada SGBD. A camada [ORM] faz a ponte entre os objetos manipulados pela camada [DAO] e as linhas e colunas dos dados de uma base de dados relacional. Dois ORM são amplamente utilizados no mundo: o NET, NHibernate (http://sourceforge.net/projects/nhibernate/) e o Entity Framework (http://msdn.microsoft.com/en-us/data/ef.aspx);
- a integração das camadas pode ser realizada por um contentor de injeção de dependências (Dependency Injection Container), como o Spring (http://www.springframework.net/);
As camadas [métier], [DAO] e [EF5] serão implementadas utilizando projetos em C#. A partir de agora, vamos trabalhar com o Visual Studio Express 2012 para o ambiente de trabalho.
9.22. Passo 15: implementação da camada Entity Framework 5
![]() |
A criação da camada [EF5] tem menos a ver com programação do que com configuração. Para compreender a criação desta camada, consulte o documento [Introduction à Entity Framework 5 Code First] disponível no URL [http://tahe.developpez.com/dotnet/ef5cf-02/]. Trata-se de um documento bastante extenso. Os conceitos fundamentais encontram-se nos quatro primeiros capítulos. Os parágrafos a ler com especial atenção serão indicados. Quando fizermos referência a este documento, utilizaremos a notação [refEF5].
Além disso, por vezes, precisaremos de conceitos de C#. Nesse caso, faremos referência ao curso [Introduction au langage C#], disponível em URL [http://tahe.developpez.com/dotnet/csharp/], utilizando a notação [refC#].
9.22.1. A base de dados
A base de dados da aplicação foi apresentada no parágrafo 9.4. Trata-se de uma base de dados MySQL denominada [dbpam_ef5] (pam=Paie Assistante Maternelle). Esta base de dados tem um administrador chamado root, sem palavra-passe.
Recorde-se o esquema da base de dados. Esta possui três tabelas:

Existe uma relação de chave estrangeira entre a coluna EMPLOYES (INDEMNITE_ID) e a coluna INDEMNITES (ID). Parte da estrutura desta base de dados é determinada pela sua utilização com a tabela EF5.
O script SQL para a criação da base de dados é o seguinte:
-- phpMyAdmin SQL Dump
-- versão 3.5.1
-- http://www.phpmyadmin.net
--
-- Cliente: localhost
-- Gerado em: Seg, 4 de novembro de 2013, às 09:34
-- Versão do servidor: 5.5.24-log
-- Versão do PHP: 5.4.3
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
--
-- Base de dados: `dbpam_ef5`
--
-- --------------------------------------------------------
--
-- Estrutura da tabela `cotizações`
--
CREATE TABLE IF NOT EXISTS `cotisations` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`SECU` double NOT NULL,
`RETRAITE` double NOT NULL,
`CSGD` double NOT NULL,
`CSGRDS` double NOT NULL,
`VERSIONING` int(11) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=12 ;
--
-- Conteúdo da tabela `cotisations`
--
INSERT INTO `cotisations` (`ID`, `SECU`, `RETRAITE`, `CSGD`, `CSGRDS`, `VERSIONING`) VALUES
(11, 9.39, 7.88, 6.15, 3.49, 1);
--
-- Disparadores `cotisations`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_COTISATIONS`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_COTISATIONS` BEFORE UPDATE ON `cotisations`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_COTISATIONS`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_COTISATIONS` BEFORE INSERT ON `cotisations`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=1;
END
//
DELIMITER ;
-- --------------------------------------------------------
--
-- Estrutura da tabela `employes`
--
CREATE TABLE IF NOT EXISTS `employes` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`PRENOM` varchar(20) CHARACTER SET latin1 NOT NULL,
`SS` varchar(15) CHARACTER SET latin1 NOT NULL,
`ADRESSE` varchar(50) CHARACTER SET latin1 NOT NULL,
`CP` varchar(5) CHARACTER SET latin1 NOT NULL,
`VILLE` varchar(30) CHARACTER SET latin1 NOT NULL,
`NOM` varchar(30) CHARACTER SET latin1 NOT NULL,
`VERSIONING` int(11) NOT NULL,
`INDEMNITE_ID` bigint(20) NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `SS` (`SS`),
KEY `FK_EMPLOYES_INDEMNITE_ID` (`INDEMNITE_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=26 ;
--
-- Conteúdo da tabela `employes`
--
INSERT INTO `employes` (`ID`, `PRENOM`, `SS`, `ADRESSE`, `CP`, `VILLE`, `NOM`, `VERSIONING`, `INDEMNITE_ID`) VALUES
(24, 'Marie', '254104940426058', '5 rue des oiseaux', '49203', 'St Corentin', 'Jouveinal', 1, 93),
(25, 'Justine', '260124402111742', 'La Brûlerie', '49014', 'St Marcel', 'Laverti', 1, 94);
--
-- Triggers `employes`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_EMPLOYES`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_EMPLOYES` BEFORE UPDATE ON `employes`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_EMPLOYES`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_EMPLOYES` BEFORE INSERT ON `employes`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=1;
END
//
DELIMITER ;
-- --------------------------------------------------------
--
-- Estrutura da tabela `indemnizações`
--
CREATE TABLE IF NOT EXISTS `indemnites` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
`ENTRETIEN_JOUR` double NOT NULL,
`REPAS_JOUR` double NOT NULL,
`INDICE` int(11) NOT NULL,
`INDEMNITES_CP` double NOT NULL,
`BASE_HEURE` double NOT NULL,
`VERSIONING` int(11) NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `INDICE` (`INDICE`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=95 ;
--
-- Conteúdo da tabela `indemnites`
--
INSERT INTO `indemnites` (`ID`, `ENTRETIEN_JOUR`, `REPAS_JOUR`, `INDICE`, `INDEMNITES_CP`, `BASE_HEURE`, `VERSIONING`) VALUES
(93, 2.1, 3.1, 2, 15, 2.1, 1),
(94, 2, 3, 1, 12, 1.93, 1);
--
-- Triggers `indemnites`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_INDEMNITES`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_INDEMNITES` BEFORE UPDATE ON `indemnites`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_INDEMNITES`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_INDEMNITES` BEFORE INSERT ON `indemnites`
FOR EACH ROW BEGIN
SET NEW.VERSIONING:=1;
END
//
DELIMITER ;
--
-- Restrições para as tabelas exportadas
--
--
-- Restrições para a tabela `employes`
--
ALTER TABLE `employes`
ADD CONSTRAINT `FK_EMPLOYES_INDEMNITE_ID` FOREIGN KEY (`INDEMNITE_ID`) REFERENCES `indemnites` (`ID`);
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
Deve-se ter em conta os seguintes pontos:
- linhas 30, 73, 122: as chaves primárias das tabelas estão no modo [AUTO_INCREMENT]. É o MySQL que as gere e não o EF5;
- linha 83: o n.º SS tem uma restrição de unicidade;
- linha 130: o índice do funcionário tem uma restrição de unicidade;
- linhas 168-169: a chave estrangeira da tabela [employes] para a tabela [indemnites];
- linha 49: um disparador [Trigger] é um script SQL incorporado pelo SGBD e que é executado em determinados momentos;
- linhas 51-54: o disparador [INCR_VERSIONING_COTISATIONS] é acionado antes de qualquer alteração numa linha da tabela [cotisations]. Em seguida, incrementa em uma unidade a coluna [VERSIONING];
- linhas 59-62: o disparador [START_VERSIONING_COTISATIONS] é acionado antes de qualquer inserção de uma nova linha na tabela [cotisations]. Inicializa então a coluna [VERSIONING] para 1;
- por fim, a coluna [VERSIONING] assume o valor 1 quando é criada uma linha na tabela [cotisations] e é incrementada em 1 a cada alteração efetuada nessa linha. Este mecanismo permite que o EF5 gere a concorrência de acesso a uma linha da tabela [cotisations] da seguinte forma:
- um processo P1 lê uma linha L da tabela [cotisations] no momento T1. A linha tem na coluna [VERSIONING] o valor V1;
- um processo P2 lê a mesma linha L da tabela [cotisations] no momento T2. A linha tem as colunas [VERSIONING] e V1 porque o processo P1 ainda não validou a sua alteração;
- O processo P1 altera a linha L e valida essa alteração. A coluna [VERSIONING] da linha L passa então para V1+1 devido ao gatilho [INCR_VERSIONING_COTISATIONS];
- o processo P2 faz, em seguida, o mesmo. O EF5 lança então uma exceção, pois o processo P2 tem uma linha com uma coluna [VERSIONING] cujo valor V1 é diferente do encontrado na base de dados, que é V1+1. Só é possível alterar uma linha se o valor de [VERSIONING] for idêntico ao da base de dados.
A isto chama-se gestão otimista de acessos concorrentes. Com EF5, um campo que desempenhe esta função deve ter a anotação [ConcurrencyCheck].
- É criado um mecanismo análogo 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, uma vez que o script não a cria. Em seguida, executaremos o script SQL nessa base de dados.
9.22.2. O projeto do Visual Studio
Com o Visual Studio Express 2012 para o ambiente de trabalho, carregamos a solução [pam-td] utilizada durante a construção da camada [web]:
![]() |
- no [1], o VS 2012 Express para o ambiente de trabalho não consegue carregar o projeto web [pam-web-01]. Isto é normal e não constitui um problema;
- em [2], adiciona-se um novo projeto à solução [pam-td];
![]() |
- no [3], o projeto é do tipo [console] e chama-se [4] [pam-ef5];
- em [5], o projeto criado. O seu nome não está a negrito, pelo que não se trata do projeto inicial da solução;
![]() |
- em [6] e [7], define-se o novo projeto como projeto inicial.
9.22.3. Adicionar as referências necessárias ao projeto
Vejamos o projeto no seu conjunto:
![]() |
O nosso projeto necessita de um determinado número de DLL:
- o DLL do Entity Framework 5;
- o DLL do conector ADO.NET do SGBD MySQL.
O parágrafo 4.2 do [refEF5] explica como instalar estes DLL 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 do SGBD MySQL disponível (novembro de 2013) através do [NuGet] não seja compatível com o EF6. Por isso, colocámos numa pasta o [lib], o [1] e o DLL deEF5, bem como os outros DLL necessários para o projeto [pam-ef5]
![]() |
Colocámos outros DLL na pasta [lib]. Iremos utilizá-los posteriormente. No [2], adicionamos estes novos DLL ao projeto.
![]() |
- no [3], percorremos o sistema de ficheiros até à pasta [lib];
- em [4], selecionamos os três DLL e, em seguida, confirmamos duas vezes;
- em [5], os três DLL foram adicionados às referências do projeto.
Precisamos de outro DLL. Este será encontrado entre os do framework .NET da máquina.
![]() |
- em [1], adicione uma nova referência ao projeto;
![]() |
- em [2], selecione [Assemblys];
- em [3], introduza [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. As entidades do Entity Framework
As entidades do Entity Framework são classes nas quais se encapsulam as linhas das diferentes tabelas da base de dados. Recorde-se que estas são:

Na camada [web], utilizámos as entidades [Employe, Cotisations, Indemnités] (ver parágrafo 9.7.3, página 219). Estas não eram representações fiéis das tabelas. Assim, as colunas [ID, VERSIONING] tinham sido ignoradas. Neste caso, isso não vai acontecer, pois são utilizadas pelas entidades ORM e EF5. Por isso, vamos adicionar-lhes as propriedades em falta. Criamos estas entidades numa pasta [Models] 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; }
// assinatura
public override string ToString()
{
return string.Format("Cotisations[{0},{1},{2},{3}, {4}, {5}]", Id, Versioning, CsgRds, Csgd, Secu, Retraite);
}
}
}
- linha 3: o espaço de nomes foi adaptado ao novo projeto;
- as propriedades das linhas 7 e 12 foram adicionadas para refletir a estrutura da tabela [cotisations];
- linha 17: o método [ToString] apresenta agora os dois novos campos.
Classe [Indemnites]
using System;
namespace Pam.EF5.Entites
{
public class Indemnites
{
public int Id { get; set; }
public int Indice { get; set; }
public double BaseHeure { get; set; }
public double EntretienJour { get; set; }
public double RepasJour { get; set; }
public double IndemnitesCp { get; set; }
public int Versioning { get; set; }
// assinatura
public override string ToString()
{
return string.Format("Indemnités[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Versioning, Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
}
}
}
- linha 3: o espaço de nomes foi adaptado ao novo projeto;
- as propriedades das linhas 7 e 13 foram adicionadas para refletir a estrutura da tabela [indemnites];
- linha 18: o método [ToString] apresenta agora os dois novos campos.
Classe [Employe]
using System;
namespace Pam.EF5.Entites
{
public class Employe
{
public int Id { get; set; }
public string SS { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
public string Adresse { get; set; }
public string Ville { get; set; }
public string CodePostal { get; set; }
public Indemnites Indemnites { get; set; }
public int Versioning { get; set; }
// assinatura
public override string ToString()
{
return string.Format("Employé[{0},{1},{2},{3},{4},{5}, {6}, {7}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal);
}
}
}
- linha 3: o espaço de nomes foi adaptado ao novo projeto;
- as propriedades das linhas 8 e 16 foram adicionadas para refletir a estrutura da tabela [employes];
- linha 21: o método [ToString] apresenta agora os dois novos campos.
Para que possam ser utilizadas pelos métodos ORM e EF5, as propriedades destas classes devem ser decoradas com anotações.
Tarefa: com base no parágrafo 3.4 do [Création de la base à partir des entités] do [refEF5], adicione às entidades [Employe, Cotisations, Indemnites] as anotações necessárias para o EF5.
Dicas:
- trata-se apenas de criar anotações. Não siga a parte [création de base] do parágrafo referido;
- para a anotação [Table], siga o exemplo MySQL do parágrafo 4.2 de [refEF5];
- para a anotação [ConcurrencyCheck] na propriedade [Versioning], siga o exemplo Oracle do parágrafo 5.2 de [refEF5];
- para a chave estrangeira que a tabela [employes] possui na tabela [indemnités], siga o exemplo 3.4.2 do documento [refEF5]. Desta forma, irá adicionar uma nova propriedade à entidade [Employe]:
public int IndemniteId { get; set; }
cujo valor será o da coluna [INDEMNITES_ID] da tabela [employes]. Deve atribuir as anotações de chave estrangeira às propriedades [IndemniteId] e [Indemnites] da entidade [Employe]. Para tal, siga o exemplo 3.4.2 de [refEF5];
- não irá gerir as relações inversas das chaves estrangeiras;
- este trabalho requer alguma leitura do [refEF5].
9.22.5. Configuração do ORM EF5
Vamos contextualizar o projeto no seu conjunto:
![]() |
A camada [EF5] irá aceder à base de dados através do conector [ADO.NET] do SGBD MySQL. Esta camada necessita de uma série de informações para aceder a essa base de dados. Estas informações estão localizadas em vários pontos do projeto.
Em primeiro lugar, temos de criar o contexto da base de dados. Este contexto é uma classe derivada da classe de sistema [System.Data.Entity.DbContext]. Serve para definir as imagens-objeto 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 imagens-objeto das três tabelas da base de dados. O seu tipo é [DbSet<Entity>], sendo que [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 com LINQ (Language INtegrated Query). O leitor que não conheça LINQ é convidado a ler o parágrafo 3.5.4 [Apprentissage de LINQ avec LINQPad] de [refEF5].
Doravante, designaremos a classe [DbPamContext] como contexto de persistência da base de dados [dbpam_ef5]. Trata-se de uma terminologia habitual nos ORM (Mapeadores Objeto-Relacionais). Este contexto de persistência é uma representação objeto da base de dados. Fala-se também de sincronização do contexto de persistência com a base de dados: as alterações, adições e eliminações efetuadas no contexto de persistência são refletidas na base de dados. Esta sincronização ocorre em momentos específicos: ao fechar o contexto de persistência, no final de uma transação ou antes de uma consulta SQL SELECT à base de dados.
As informações sobre o SGBD e a base de dados são colocadas no [App.config].
![]() |
A configuração necessária no [app.config] é explicada nos parágrafos seguintes do [refEF5]:
- 3.4 para o servidor SGBD SQL. É aqui que são estabelecidos os princípios gerais da configuração do EF5;
- 4.2 para o SGBD MySQL.
Seguimos este último parágrafo e configuramos o 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>
<!-- configuração EF5 -->
<!-- cadeia de ligação à base de dados [dbam_ef5] -->
<connectionStrings>
<add name="DbPamContext"
connectionString="Server=localhost;Database=dbpam_ef5;Uid=root;Pwd=;"
providerName="MySql.Data.MySqlClient" />
</connectionStrings>
<!-- o fornecedor padrão de MySQL -->
<system.data>
<DbProviderFactories>
<remove invariant="MySql.Data.MySqlClient"/>
<add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL"
type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=C5687FC88969C44D"
/>
</DbProviderFactories>
</system.data>
</configuration>
- as linhas 6-21 foram adicionadas. Devem ser inseridas na baliza <configuration> das linhas 2 e 22;
- linhas 8-12: definem cadeias de ligação a bases de dados, um conceito do ADO.NET (ver parágrafo 7.3.5 no [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 se pode introduzir qualquer valor. Por predefinição, deve-se 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 de [app.config], deve-se, portanto, inserir [name="DbPamContext"];
- linha 10: uma cadeia de ligação específica para a SGBD MySQL:
- [Server=localhost]: endereço IP do computador que aloja o SGBD. Neste caso, trata-se do computador local [localhost];
- [Database=dbpam_ef5;]: nome da base de dados,
- [Uid=root;]: nome de utilizador com o qual nos vamos ligar à base de dados,
- [Pwd=;]: palavra-passe desse nome de utilizador. Neste caso, não há palavra-passe;
- linha 10: [providerName="MySql.Data.MySqlClient"] é o nome do conector ADO.NET a utilizar. Este nome corresponde ao atributo [invariant] da linha 17. Pode-se introduzir qualquer valor, desde que se respeite a regra anterior e que um provedor com o mesmo invariante ainda não tenha sido registado;
- linhas 15-20: definem uma fábrica (factory) de conectores (provider) ADO.NET. O [DbProviderFactory] é um conceito um pouco nebuloso para mim. A julgar pelo nome, seria uma classe capaz de gerar o conector ADO.NET que dá acesso ao SGBD, neste caso o MySQL5. Normalmente, estas linhas são copiadas e coladas. São necessárias. Preste atenção ao atributo [Version=6.5.4.0] da linha 16. Este número de versão deve corresponder ao número de versão do DLL [MySql.Data] que adicionou às referências do projeto:
![]() |
- A linha 16 é importante. Como não é possível instalar dois provedores com o mesmo nome, começamos por eliminar qualquer provedor já instalado que tenha o mesmo nome daquele que vamos instalar na linha 17;
É tudo. É complicado e confuso quando se faz pela primeira vez, mas com o tempo torna-se simples, porque é sempre a mesma coisa que se repete.
9.22.6. Teste da camada [EF5]
Estamos prontos para testar a nossa camada [EF5]. Fazemo-lo com a ajuda do programa [Program.cs] já existente:
![]() |
Vamos apresentar o conteúdo da base de dados. Se conseguirmos, será um primeiro indício de que a nossa configuração está correta. Está disponível um exemplo de código no parágrafo 3.5.3 do [refEF5]. O código do [Program.cs] será o seguinte:
using Pam.EF5.Entites;
using Pam.Models;
using System;
namespace Pam
{
class Program
{
static void Main(string[] args)
{
try
{
using (var context = new DbPamContext())
{
// é apresentado o conteúdo das tabelas
Console.WriteLine("Liste des employés ----------------------------------------");
foreach (Employe employe in context.Employes)
{
Console.WriteLine(employe);
}
Console.WriteLine("Liste des indemnités --------------------------------------");
foreach (Indemnites indemnite in context.Indemnites)
{
Console.WriteLine(indemnite);
}
Console.WriteLine("Liste des cotisations -------------------------------------");
foreach (Cotisations cotisations in context.Cotisations)
{
Console.WriteLine(cotisations);
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
return;
}
}
}
}
- linha 13: qualquer operação no BD é realizada através do contexto desta base de dados. Implementámos este contexto com a classe [DbPamContext]. Também lhe chamámos contexto de persistência da base de dados;
- linhas 13, 31: as operações no contexto de persistência são realizadas numa cláusula [using]. O contexto de persistência é aberto no início da cláusula [using] e fechado automaticamente ao sair dessa cláusula. Isto implica que qualquer alteração efetuada no contexto de persistência na cláusula [using] será refletida na base de dados ao sair da cláusula. É então enviada uma série de ordens SQL para a BD no âmbito de uma transação. O que significa que, se uma ordem SQL falhar, todas as ordens SQL emitidas anteriormente são anuladas. É então lançada uma exceção pela EF5;
- linha 17: a expressão [context.Employes] designa a imagem objeto da tabela [employes]. Recorde-se que [Employes] é uma propriedade do contexto de persistência [DbPamContext]:
public class DbPamContext : DbContext
{
public DbSet<Employe> Employes { get; set; }
public DbSet<Cotisations> Cotisations { get; set; }
public DbSet<Indemnites> Indemnites { get; set; }
}
- linha 17: o facto de o [foreach] percorrer a coleção [context.Employes] irá trazer todos os funcionários da base de dados para o contexto de persistência. Será, portanto, emitida uma ordem SQL SELECT pelo EF5;
- linhas 17-20: percorre-se a coleção de funcionários e, na linha 19, utiliza-se o método [ToString] da classe [Employe] para apresentar os funcionários na consola;
- linhas 21-25: o mesmo se aplica à coleção de indemnizações;
- linhas 27-30: o mesmo se aplica à coleção de contribuições.
Voltemos à definição da entidade [Employe]:
using System;
namespace Pam.EF5.Entites
{
public class Employe
{
public int Id { get; set; }
public string SS { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
public string Adresse { get; set; }
public string Ville { get; set; }
public string CodePostal { get; set; }
public Indemnites Indemnites { get; set; }
public int Versioning { get; set; }
// assinatura
public override string ToString()
{
return string.Format("Employé[{0},{1},{2},{3},{4},{5}, {6}, {7}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal);
}
}
}
- linha 15: um colaborador tem uma referência a uma indemnização.
Quando se traz um funcionário para o contexto de persistência, traz-se também a sua indemnização? A resposta é «não» por predefinição. É este o conceito da entidade [Lazy Loading]. As entidades referenciadas no interior de outra entidade não são transportadas para o contexto de persistência juntamente com essa outra entidade. Só o são quando solicitadas pelo código no âmbito de um contexto de persistência aberto. Se o contexto de persistência estiver fechado, é lançada uma exceção.
Assim, se o método [ToString] tivesse referenciado a propriedade [Indemnites] da seguinte forma:
// assinatura
public override string ToString()
{
return string.Format("Employé[{0},{1},{2},{3},{4},{5},{6},{7},{8}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
}
a operação seguinte em [Program.cs]:
foreach (Employe employe in context.Employes)
{
Console.WriteLine(employe);
}
teria trazido para o contexto de persistência não só os funcionários, mas também as suas indemnizações, porque na linha 3 é chamado o método [Employe.ToString], que faz referência à entidade [Indemnites].
A execução de [Program.cs] produz os seguintes resultados:
O que fazer se não funcionar? Está a fazer algo errado... Existem várias fontes de erro possíveis:
- verifique a configuração de EF5 (parágrafo 9.22.5);
- verifique as suas entidades do Entity Framework (parágrafo 9.22.4).
9.22.7. DLL da camada [EF5]
Transformamos o nosso projeto numa biblioteca de classes para que, durante a geração, seja gerado um assembly .dll em vez de um .exe. Isto é feito nas propriedades do projeto, tal como foi visto no parágrafo 9.7.6, para a camada de negócio simulada.
Tarefa: altere o tipo do projeto [pam-ef5] para biblioteca de classes e, em seguida, volte a gerar o projeto.
9.23. Etapa 16: implementação da camada [DAO]
9.23.1. A interface da camada [DAO]
![]() |
Tal como fizemos para a camada [métier] simulada, a camada [DAO] estará acessível através de uma interface. Qual será essa interface?
Vejamos a interface [IPamMetier] da camada simulada [métier] que construímos:
public interface IPamMetier {
// lista de todas as identidades dos funcionários
Employe[] GetAllIdentitesEmployes();
// ------- cálculo do salário
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
Na linha 3, o método [GetAllIdentitesEmployes] serve para preencher a lista suspensa da página inicial:
![]() |
Estes funcionários terão de ser pesquisados na base de dados.
Na linha 6, o método [GetSalaire] permite calcular a folha de vencimento de um funcionário cujo n.º é SS. Recorde-se a definição do tipo [FeuilleSalaire]:
public class FeuilleSalaire
{
// propriedades automáticas
public Employe Employe { get; set; }
public Cotisations Cotisations { get; set; }
public ElementsSalaire ElementsSalaire { get; set; }
}
As informações das linhas 5 e 6 provêm da base de dados. Recorde-se que um funcionário possui uma propriedade [Indemnites]. Esta informação também deverá ser recuperada.
Poderíamos, portanto, partir da seguinte interface para a camada [DAO]:
public interface IPamDao {
// lista de todas as identidades dos funcionários
Employe[] GetAllIdentitesEmployes();
// um funcionário específico com os seus subsídios
Employe GetEmploye(string ss);
// lista de todas as contribuições
Cotisations GetCotisations();
}
9.23.2. O projeto do Visual Studio
Tarefa: adicione à solução [pam-td] um novo projeto do tipo [console] denominado [pam-dao]. Defina-o como o projeto inicial da solução.
![]() |
9.23.3. Adicionar as referências necessárias ao projeto
Vejamos o projeto no seu conjunto:
![]() |
O projeto [pam-dao] necessita de um determinado número de DLL:
- todas as referenciadas pelo projeto [pam-ef5];
- aquele do próprio projeto [pam-ef5].
Além disso, vamos utilizar o [Spring.net] para instanciar a camada [DAO]. Para tal, precisamos dos DLL, [Spring.core] e [Common.Logging]. Estes DLL encontram-se na pasta [lib] do material de apoio do estudo de caso.
Tarefa: adicione estas referências ao projeto [pam-dao].
![]() |
9.23.4. Implementação da camada [DAO]
![]() |
Acima, a classe [PamException] é a que foi definida no parágrafo 9.7.4. Basta alterar o seu espaço de nomes (linha 1 abaixo):
namespace Pam.Dao.Entites
{
// classe de exceção
public class PamException : Exception
{
....
}
}
A interface [IPamDao] é a que acabámos de definir no parágrafo 9.23.1:
using Pam.EF5.Entites;
namespace Pam.Dao.Service
{
public interface IPamDao
{
// lista de todas as identidades dos funcionários
Employe[] GetAllIdentitesEmployes();
// um funcionário específico com as suas indemnizações
Employe GetEmploye(string ss);
// lista de todas as contribuições
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
{
// campos privados
private Cotisations cotisations;
private Employe[] employes;
// Construtor
public PamDaoEF5()
{
// contribuição
try
{
....
}
catch (Exception e)
{
throw new PamException("Erreur système lors de la construction de la couche [DAO]", e, 1);
}
}
// GetCotisations
public Cotisations GetCotisations()
{
return cotisations;
}
// GetAllIdentitesEmploye
public Employe[] GetAllIdentitesEmployes()
{
return employes;
}
// GetEmploye
public Employe GetEmploye(string SS)
{
try
{
....
catch (Exception e)
{
throw new PamException(string.Format("Erreur système lors de la recherche de l'employé [{0}]", SS), e, 2);
}
}
}
}
Nota:
- linha 10: a classe [PamDaoEF5] implementa a interface [IPamDao];
- as tabelas [cotisations] e [employes] são armazenadas em cache nas propriedades das linhas 13-14. Os funcionários não têm as suas indemnizações;
- linhas 17-28: é o construtor que inicializa as linhas 13-14;
- linhas 43-52: o método [GetEmploye] devolve um funcionário com os seus subsídios. Recebe como parâmetro o número de segurança social desse funcionário. Se o funcionário não existir na base de dados, o método devolverá o ponteiro como nulo.
Tarefa: completar o código da classe [PamDaoEF5].
Para o construtor, inspirar-se-á no código de teste da camada [EF5] apresentado no parágrafo 9.22.6. Para o método [GetEmploye], inspire-se no exemplo do parágrafo 3.5.7 [Eager and Lazy loading] de [refEF5].
9.23.5. Configuração da camada [DAO]
Tal como foi feito no parágrafo 9.22.5, é necessário configurar o EF5 no ficheiro [App.config] do projeto:
![]() |
Tarefa 1: configure EF5 no ficheiro [App.config]. Basta repetir o que foi feito no ficheiro [App.config] da camada [EF5].
O nosso programa de teste irá utilizar o [Spring.net] para obter uma referência na camada [DAO].
Trabalho 2: com base no que foi feito no parágrafo 9.20.2, altere o ficheiro de configuração [app.config] do 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. É necessário ter cuidado para que a baliza <configSections> seja a primeira baliza encontrada a seguir à baliza raiz <configuration>.
9.23.6. Teste da camada [DAO]
Estamos prontos para testar a nossa camada [DAO]. Fazemo-lo com a ajuda do programa [Program.cs] já existente:
![]() |
Vamos testar as diferentes funcionalidades da interface da camada [DAO]. O código de [Program.cs] será o seguinte:
using Pam.Dao.Service;
using Pam.EF5.Entites;
using Spring.Context.Support;
using System;
namespace Pam.Dao.Tests
{
public class Program
{
public static void Main()
{
try
{
// instância da camada [dao]
IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
// lista de identidades dos funcionários
foreach (Employe Employe in pamDao.GetAllIdentitesEmployes())
{
Console.WriteLine(Employe.ToString());
}
// um colaborador com os seus subsídios
Console.WriteLine("------------------------------------");
Employe e = pamDao.GetEmploye("254104940426058");
Console.WriteLine("employé= {0}, indemnités={1}", e, e.Indemnites);
Console.WriteLine("------------------------------------");
// um colaborador que não existe
Employe employe = pamDao.GetEmploye("xx");
Console.WriteLine("Employé n° xx");
Console.WriteLine((employe == null ? "null" : employe.ToString()));
Console.WriteLine("------------------------------------");
// lista de contribuições
Cotisations cotisations = pamDao.GetCotisations();
Console.WriteLine(cotisations.ToString());
}
catch (Exception ex)
{
// exibição de exceção
Console.WriteLine(ex.ToString());
}
//pausa
Console.ReadLine();
}
}
}
- linha 15: obtém-se uma referência à camada [DAO] através de [Spring.net].
Os resultados da execução deste programa são os seguintes:
9.23.7. DLL da camada [DAO]
Tarefa: altere o tipo do projeto [pam-dao] para biblioteca de classes e, em seguida, regenere o projeto (repita o que foi feito no parágrafo 9.22.7).
9.24. Passo 17: implementação da camada [métier]
9.24.1. A interface da camada [métier]
![]() |
A interface da camada [métier] será a interface [IPamMetier] da camada [métier] simulada que construímos no parágrafo 9.7.2.
public interface IPamMetier {
// lista de todas as identidades dos funcionários
Employe[] GetAllIdentitesEmployes();
// ------- cálculo do salário
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
9.24.2. O projeto do Visual Studio
Tarefa: adicione à solução [pam-td] um novo projeto do tipo [console] denominado [pam-metier]. Defina-o como o projeto inicial da solução.
![]() |
9.24.3. Adicionar as referências necessárias ao projeto
Vamos contextualizar o projeto no seu conjunto:
![]() |
O projeto [pam-metier] necessita de um determinado número de DLL:
- todas as referenciadas pelos projetos [pam-dao] e [pam-ef5];
- as dos próprios projetos [pam-dao] e [pam-ef5].
Tarefa: adicione estas diferentes referências ao projeto [pam-metier].
![]() |
9.24.4. Implementação da camada [métier]
![]() |
Acima, encontram-se quatro elementos já utilizados na camada [métier] simulada (ver parágrafo 9.7). Podem ocorrer alterações nos espaços de nomes importados por estas diferentes classes. Faça a sua gestão. 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
{
// referência à camada [DAO] inicializada pelo Spring
public IPamDao PamDao { get; set; }
// lista de todas as identidades dos funcionários
public Employe[] GetAllIdentitesEmployes()
{
...
}
// um funcionário específico com os seus subsídios
public Employe GetEmploye(string ss)
{
...
}
// as contribuições
public Cotisations GetCotisations()
{
...
}
// cálculo do salário
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
{
// SS: n.º SS do colaborador
// HeuresTravaillées: o número de horas trabalhadas
// Dias Trabalhados: número de dias trabalhados
...
}
}
- linha 13: existe uma referência à camada [DAO]. Esta será inicializada pelo Spring durante a instanciação da classe [PamMetier]. Assim, quando os diferentes métodos forem executados, a linha 13 já terá sido inicializada.
Tarefa: completar o código da classe [PamMetier]. Se, em [GetSalaire], se verificar que o funcionário com o n.º de segurança social não existe, será lançado um [PamException]. O método de cálculo do salário é explicado no parágrafo 9.5. Deve-se ter o cuidado de arredondar todos os cálculos intermédios para duas casas decimais.
9.24.5. Configuração da camada [métier]
Tal como foi feito no parágrafo 9.22.5, é necessário configurar a EF5 no ficheiro [app.config] do projeto:
![]() |
Tarefa 1: configure EF5 no ficheiro [app.config]. Basta repetir o que foi feito no ficheiro [app.config] da camada [EF5].
O nosso programa de teste irá utilizar o [Spring.net] para obter uma referência na camada [métier].
Trabalho 2: com base no que fez anteriormente no parágrafo 9.23.5, altere o ficheiro de configuração [app.config] do projeto [pam-metier] para que defina um objeto Spring denominado [pammetier] associado à classe [PamMetier] que acabámos de criar. A forma mais simples é copiar o ficheiro [app.config] do projeto [pam-dao] e adicionar o que falta.
Há aqui uma dificuldade. Não só é necessário instanciar a camada [métier] com a classe [PamMetier], como também é necessário inicializar a sua propriedade [PamDao]:
// referência à camada [DAO] inicializada pelo Spring
public IPamDao PamDao { get; set; }
A configuração do Spring em [app.config] é, então, a seguinte:
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="pamdao" type=" Pam.Dao.Service.PamDaoEF5, pam-dao"/>
<object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier">
<property name="PamDao" ref="pamdao" />
</object>
</objects>
</spring>
- linha 6: define o objeto [pamdao] associado à classe [PamDaoEF5];
- linha 7: define o objeto [pammetier] associado à classe [PamMetier];
- linha 8: a baliza [property] serve para inicializar uma propriedade pública da classe [PamMetier]. O atributo [name="PamDao"] corresponde ao nome da propriedade a inicializar na classe [PamMetier]. O atributo [ref="pamdao"] indica que a propriedade é inicializada com uma referência, a do objeto [pamdao] da linha 6, ou seja, com a referência da camada [DAO]. Era isso que pretendíamos.
9.24.6. Teste da camada [métier]
Estamos prontos para testar a nossa camada [métier]. Fazemo-lo com a ajuda do programa [Program.cs] já existente:
![]() |
Vamos testar as diferentes funcionalidades da interface da camada [métier]. O código da [Program.cs] será o seguinte:
using System;
using Pam.Dao.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
using Pam.EF5.Entites;
namespace Pam.Metier.Tests
{
public class Program
{
public static void Main()
{
try
{
// instanciação da camada [métier]
IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// lista das identidades dos funcionários
Console.WriteLine("Employés -----------------------------");
foreach (Employe Employe in pamMetier.GetAllIdentitesEmployes())
{
Console.WriteLine(Employe);
}
// cálculos das folhas de pagamento
Console.WriteLine("salaires -----------------------------");
Console.WriteLine(pamMetier.GetSalaire("260124402111742", 30, 5));
Console.WriteLine(pamMetier.GetSalaire("254104940426058", 150, 20));
try
{
Console.WriteLine(pamMetier.GetSalaire("xx", 150, 20));
}
catch (PamException ex)
{
Console.WriteLine(string.Format("PamException : {0}", ex.Message));
}
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Exception : {0}, Exception interne : {1}", ex.Message, ex.InnerException == null ? "" : ex.InnerException.Message));
}
// pausa
Console.ReadLine();
}
}
}
- linha 16: obtém-se uma referência à camada [métier] através de [Spring.net].
Os resultados da execução deste programa são os seguintes:
9.24.7. DLL da camada [métier]
Tarefa: altere o tipo do projeto [pam-metier] para biblioteca de classes e, em seguida, regenere o projeto (repita o que foi feito no parágrafo 9.22.7).
9.25. Etapa 18: implementação da camada [web]
Chegamos à última camada da nossa arquitetura, a camada [web]:
![]() |
Vamos reutilizar a camada [web] que desenvolvemos com a ajuda de uma camada [métier] simulada.
9.25.1. O projeto do Visual Studio
Voltamos ao Visual Studio Express 2012 para a Web para ligar a nossa camada Web às camadas [métier, DAO, EF5] que acabámos de desenvolver. Trata-se principalmente de configuração e de algumas alterações nos espaços de nomes.
No Visual Studio Express 2012 para a Web, carregue a solução [pam-td]:
![]() |
- no [1], a solução [pam-td] no VS Studio para a Web. O projeto web [pam-web-01] volta a ficar visível. Tínhamos-o perdido no VS Studio para o ambiente de trabalho.
- A configuração do projeto web [pam-web-01] terá de ser alterada. Em vez de alterar um projeto que funciona, vamos efetuar as alterações numa cópia desse projeto. Em primeiro lugar, no [2], removemos o projeto da solução (isto não elimina nada do sistema de ficheiros).
![]() |
- no [3], utilizando o Explorador do Windows, duplicamos a pasta [pam-web-01] para [pam-web-02];
- em [4], carrega-se o projeto [pam-web-02] na solução [pam-td]. Este surge com o nome [pam-web-01];
- em [5], altere este nome para [pam-web-02] e defina este projeto como projeto inicial;
![]() |
- em [6], carregue o projeto antigo [pam-web-01]. Agora já tem todos os seus projetos. Tenha o cuidado de trabalhar com o [pam-web-02].
9.25.2. Adicionar as referências necessárias ao projeto
Vamos contextualizar o projeto na sua totalidade:
![]() |
O projeto [pam-web-02] necessita de um determinado número de DLL:
- todos os referenciados pelos projetos [pam-metier], [pam-dao] e [pam-ef5];
- as dos próprios projetos [pam-metier], [pam-dao] e [pam-ef5].
Tarefa: adicione estas diferentes referências ao projeto [pam-web-02]. A referência ao projeto [pam-metier-simule] deve ser removida. Mudamos para a camada [métier]. Algumas referências DLL já se encontram nas referências. Elimine-as e, em seguida, efetue as suas adições.
![]() |
9.25.3. Implementação da camada [web]
Gere o projeto [pam-web-02]. Serão apresentados erros como o seguinte:
![]() |
A classe [ApplicationModel] utiliza o tipo [Employe]. Com a camada [métier] simulada, este tipo estava definido no espaço de nomes [Pam.Metier.Entites]. Agora encontra-se no espaço de nomes [Pam.EF5.Entites]. Corrija estes erros conforme indicado acima.
9.25.4. Configuração da camada [web]
Tal como foi feito no parágrafo 9.24.5, temos de configurar EF5 no ficheiro [web.config] do projeto:
![]() |
Tarefa 1: substitua todo o conteúdo atual de [web.config] pelo conteúdo do ficheiro [app.config] do projeto [pam-metier].
O ficheiro [Global.asax] da nossa aplicação web utiliza o [Spring.net] para recuperar uma referência na camada [métier]:
try
{
// instanciação da camada [métier]
application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
catch (Exception ex)
{
application.InitException = ex;
}
Na linha 4, solicita-se uma referência ao objeto Spring denominado [pammetier]. Este é, de facto, o nome que foi atribuído à camada [métier] (verifique-o no seu ficheiro [web.config]).
9.25.5. Teste da camada [web]
Estamos prontos para testar a nossa camada [web]. Vamos, em primeiro lugar, alterar a sua porta de trabalho. Por predefinição, a [pam-web-02] tem a configuração da [pam-web-01] e, por isso, funciona na mesma porta. A experiência mostra que isto causa problemas: o IIS continua então a utilizar os códigos do projeto [pam-web-01]. Proceda da seguinte forma:
![]() |
![]() |
No [4], altere o número da porta, por exemplo, alterando o dígito das unidades.
Executa-se o projeto [pam-web-02] através do [Ctrl-F5]. Obtém-se então a seguinte página inicial:
![]() |
No [1], obtêm-se os funcionários da base de dados [dbpam_ef5]. Note-se que já não existe o funcionário [X X] que tínhamos com a camada [métier] simulada. Façamos uma simulação:
![]() |
Em [2], obtém-se efetivamente o salário real e já não um salário fictício. Agora, vamos parar o SGBD e o MySQL5 e fazer outra simulação:
![]() |
No [3], obtivemos uma página de erros legível, apesar de algumas mensagens estarem em inglês. Agora, vamos parar novamente o MySQL e voltar a executar a aplicação no VS, passando pelo [Ctrl-F5]:
![]() |
Obtenemos a vista [initFailed.cshtml] criada no parágrafo 9.20.4. Esta apresenta as mensagens de erro da pilha de exceções. O leitor é convidado a realizar outros testes.
9.26. Passo 19: tornar acessível na Internet uma aplicação ASP.NET
Quando se desenvolve uma aplicação ASP.NET com o Visual Studio, a configuração utilizada por predefinição faz com que a aplicação desenvolvida só seja acessível na morada [localhost]. Qualquer outro endereço é rejeitado pelo servidor incorporado do Visual Studio, que devolve então o erro [400 Bad Request].
Isto pode ser verificado da seguinte forma:
- numa janela DOS, repare no endereço IP da sua máquina de desenvolvimento:
Microsoft Windows [version 6.3.9600]
(c) 2013 Microsoft Corporation. Tous droits réservés.
dos>ipconfig
Configuration IP de Windows
Carte Ethernet Connexion au réseau local :
Suffixe DNS propre à la connexion. . . : ad.univ-angers.fr
Adresse IPv6 de liaison locale. . . . .: fe80::698b:455a:925:6b13%4
Adresse IPv4. . . . . . . . . . . . . .: 172.19.81.34
Masque de sous-réseau. . . . . . . . . : 255.255.0.0
Passerelle par défaut. . . . . . . . . : 172.19.0.254
Carte réseau sans fil Wi-Fi :
Statut du média. . . . . . . . . . . . : Média déconnecté
Suffixe DNS propre à la connexion. . . :
O endereço IP está aqui indicado na linha 14. Se tiver uma ligação Wi-Fi, o endereço Wi-Fi do computador aparecerá nas linhas 20 e seguintes.
- Verifique as propriedades do projeto [clic droit sur projet / propriétés / onglet web]:
![]() |
A aplicação será executada na porta [65010] da máquina [localhost].
- Execute o seu projeto através de [Ctrl-F5]

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

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 a um URL do tipo [http://adresseIP/contexte/...], é necessário utilizar um servidor diferente do IIS Express, por exemplo, um servidor IIS (não Express). Para verificar a existência deste (normalmente nas versões Pro do Windows), é necessário aceder ao Painel de Configuração [Panneau de configuration\Système et sécurité\Outils d’administration]:

Esta opção nem sempre está disponível. Nesse caso, é necessário aceder a [ Panneau de configuration \ Programmes] e instalar as Ferramentas de Administração Web.
![]() |
Assim que a opção [Gestionnaire des services internet (IIS)] estiver disponível, ative-a:
![]() |
Inicia-se o site web por predefinição. Para tal, é necessário que o serviço [Service de publication World Wide Web] já tenha sido iniciado:
![]() |
Feito isto, aceda ao URL [http://localhost] através de um navegador. Verifique previamente se outro servidor web já não está a ocupar a porta 80. Se estiver, encerre-o.
![]() |
O servidor IIS respondeu-nos. Agora, substitua [localhost] pelo endereço IP do seu computador:
![]() |
Funciona. Voltemos agora ao Visual Studio:
- em primeiro lugar, é necessário iniciar o Visual Studio no modo [administrateur]
![]() |
Feito isto, é necessário alterar a configuração do projeto web que se pretende implementar [clic droit sur projet / propriétés / onglet web]:
![]() |
É necessário selecionar o servidor local IIS como servidor de implementação. O Visual Studio define o URL da aplicação. É possível alterá-lo. Execute o projeto através de [Ctrl-F5]:
![]() |
Substitua agora [localhost] pelo endereço IP do seu computador:
![]() |
Se não dispuser do servidor IIS, poderá 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 um explorador do Windows e selecione a pasta da aplicação ASP.NET a ser implementada:
![]() |
O servidor web é então iniciado e a aplicação web é apresentada num navegador:
![]() |
- Em [3], é possível parar/iniciar o servidor web;
- em [4], é possível alterar a porta de serviço da aplicação web;
Antes de iniciar o servidor, é necessário que o serviço [UWS HiPriv Services] abaixo esteja em execução:
![]() |
Assim que o servidor for iniciado, a interface apresenta-se da seguinte forma:
![]() |
Ao clicar na ligação [6], é apresentada a primeira página da aplicação:
![]() |
Pode-se então substituir [localhost] pelo endereço IP da máquina:
![]() |
Portanto, também neste caso, apenas o nome [localhost] é aceite.
O método mais demorado
Inicie a aplicação Ultidev Web Explorer
![]() |
e, em seguida, siga os seguintes passos:
![]() |
![]() |
![]() |
- em [8], indique a pasta da aplicação web a implementar;
![]() |
- devido a [10-11], a aplicação web deverá ser solicitada com o URL [http://localhost:81/];
![]() |
![]() |
- inicie o servidor web com o [14];
![]() |
- solicite o URL [19] ;
![]() |
- no [20], obtivemos a página pretendida utilizando o endereço local IP da máquina, em vez do nome [localhost]. Era isso que procurávamos;
O servidor Ultidev foi instalado como um serviço do Windows que é iniciado automaticamente. Pode desativar o arranque automático do servidor Ultidev da seguinte forma:
- selecione a opção [Panneau de configuration\Système et sécurité\Outils d’administration];
![]() |
- [1, 2]: selecione as propriedades do serviço [Ultidev Web Server Pro];
- [3]: defina-o para arranque manual.
Para iniciar manualmente o servidor, utilize, por exemplo, a aplicação [Ultidev Web Explorer]:
![]() |
9.27. Passo 20: geração de uma aplicação nativa para Android
Quando se tem uma aplicação web do tipo APU (Aplicação de Página Única), é possível gerar um executável para dispositivos móveis (Android, IoS, Windows 8, ...) com a ferramenta [Phonegap] [http://phonegap.com/]. Existem outras formas de o fazer, nomeadamente com o produto de código aberto Apache Cordova [https://cordova.apache.org/]. A ferramenta disponível online no site do Phonegap [http://build.phonegap.com/apps] «carrega» o 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 deve ser gerada por um framework web (ASP.NET, JEE, PHP, ...). Vamos começar por criar essa página.
9.27.1. A arquitetura da aplicação
É importante lembrar que o objetivo é 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 esquema acima não indica de onde ela provém);
- as vistas seguintes são obtidas através de chamadas Ajax [1-4]. O navegador não carregará nenhuma página nova;
A vista inicial pode ou não ser fornecida pelo mesmo servidor que as outras vistas obtidas através de chamadas Ajax. Se não for fornecida pelo mesmo servidor, o JavaScript da página inicial deve conhecer o URL do servidor web que irá fornecer as outras vistas. Será este 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 as capacidades de um navegador, sendo, portanto, capaz de executar o JavaScript incorporado na página [index.html];
- esta página irá obter as outras vistas através de chamadas Ajax ao servidor [2]. Para tal, precisa de conhecer o URL do servidor web;
Vamos refatorar a aplicação [pam-web-02] para que funcione neste modo. Assim, a primeira página será a seguinte:
![]() |
- em [1], o URL da página inicial da aplicação. Este será-nos fornecido pelo servidor Ultidev, analisado no parágrafo 9.26;
- em [2], o utilizador deverá introduzir o URL do simulador de salários. Poderíamos inseri-lo diretamente no código JavaScript da página inicial, mas isso complicaria os testes: assim que alterássemos o endereço do simulador IP (ou a porta), teríamos de o alterar no código JavaScript;
- para [3], o link [Connexion] que irá buscar a vista seguinte:
![]() |
- note-se que, em [4], o URL do navegador não se alterou. Continua a ser o da página inicial e permanecerá assim durante todo o ciclo de vida da aplicação.
Assim que esta vista for obtida, tudo funciona como anteriormente: as diferentes vistas são obtidas através de chamadas Ajax. Veremos que é necessário alterar 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 nela a página estática [index.html] e todos os recursos de que necessita (ficheiros CSS e JS). A página [index.html] retoma o código da página-mestre [_Layout.cshtml] do projeto do Visual Studio, eliminando tudo o que não é estático. O resultado é o 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 os seguintes elementos:
- linhas 27-29: adicionámos a opção de menu [Connexion] para permitir a ligação ao serviço de simulação;
- linhas 55-56: a 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] da linha 14 acima. Nada mais muda. O código evolui da seguinte forma:
// ao carregar o documento
$(document).ready(function () {
// recuperam-se as referências dos diferentes componentes da página
loading = $("#loading");
content = $("#content");
erreur = $("#erreur");
erreur1 = $("#erreur1");
erreur2 = $("#erreur2");
// os links do menu
lnkConnexion = $("#lnkConnexion");
lnkFaireSimulation = $("#lnkFaireSimulation");
lnkEffacerSimulation = $("#lnkEffacerSimulation");
lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
lnkVoirSimulations = $("#lnkVoirSimulations");
lnkTerminerSession = $("#lnkTerminerSession");
lnkRetourFormulaire = $("#lnkRetourFormulaire");
// colocam-se numa tabela
options = [lnkConnexion, lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
// ocultam-se alguns elementos da página
loading.hide();
erreur.hide();
// fixa-se o menu
setMenu([lnkConnexion]);
});
- linhas 6-8: os identificadores da área que apresenta os erros de ligação na página [index.html];
- linha 10: o novo link para a ligação ao simulador;
- linha 21: a área de erros está inicialmente oculta;
- linha 23: apenas é apresentado o link de ligação;
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() {
// recuperamos o urlServiceWeb do serviço web
urlServiceWeb = $("#urlServiceWeb").val();
// recupera-se o formulário de introdução de dados
$.ajax({
url: urlServiceWeb + '/Pam/Formulaire',
type: 'POST',
dataType: 'html',
beforeSend: function () {
// luz de espera acesa
loading.show();
},
success: function (data) {
// exibição dos resultados
content.html(data);
// menu
setMenu([lnkFaireSimulation]);
},
error: function (jqXHR) {
erreur2.html(jqXHR.responseText);
erreur1.html(jqXHR.getAllResponseHeaders().replace(/\r\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/\n/g, "<br/>"));
erreur.show();
},
complete: function () {
// luz de espera apagada
loading.hide();
}
});
}
- linha 7: recupera-se o valor URL introduzido pelo utilizador. Este valor é armazenado na variável global da linha 1. Desta forma, estará disponível nas outras funções do ficheiro;
- linha 10: é efetuada uma chamada Ajax ao URL [/Pam/Formulaire] do simulador. Este URL apresenta a vista parcial da introdução das informações da simulação (colaboradores, horas trabalhadas, dias trabalhados). Na versão inicial do [pam-web-02], este URL era suficiente. Era automaticamente precedido pelo URL, que tinha carregado a página inicial. Agora, parte-se do princípio de que a página inicial pode ser fornecida por um servidor diferente daquele que aloja o simulador. É então necessário prefixar o URL [/Pam/Formulaire] com a variável [urlServiceWeb] da linha 1, que corresponde ao URL do simulador (por exemplo, http://172.19.81.34/pam-web-02). Isto deverá ser feito para todas as chamadas Ajax do ficheiro;
- linhas 17-22: caso a ligação seja bem-sucedida, é apresentada a vista parcial [Formulaire.cshtml] e é exibido um menu com o único link [Faire la simulation] (linha 21);
- linhas 23-27: em caso de falha na ligação:
- na linha 24, é apresentada a resposta HTML enviada pelo servidor web (caso exista);
- na linha 25, são exibidos os cabeçalhos HTTP enviados pelo servidor web (caso tenha respondido);
É tudo. Em caso de sucesso, obtém-se a seguinte página:
![]() |
Estamos então na situação anterior, em que agora as visualizações são obtidas através de chamadas Ajax. Assim, como acima, o clique no link [Faire la simulation] será executado pelo seguinte código do ficheiro [myScripts.js]:
function faireSimulation() {
// recuperam-se as referências
var simulation = $("#simulation");
var formulaire = $("#formulaire");
// formulário válido?
var formValid = formulaire.validate().form();
if (!formValid) return;
// está a ser efetuada uma chamada Ajax manualmente
$.ajax({
url: urlServiceWeb + '/Pam/FaireSimulation',
type: 'POST',
data: formulaire.serialize(),
dataType: 'html',
...
});
// menu
setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
- Foi feita uma única alteração, na linha 10, onde o código anterior URL passa a ser precedido pelo prefixo do simulador;
9.27.3. Teste do projeto refatorado
No parágrafo 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:
![]() |
Digitemos um URL incorreto:
![]() |
- em [10], os cabeçalhos HTTP da resposta do servidor;
- em [11], o documento HTML da resposta do servidor;
Se introduzirmos o código correto URL:
![]() |
obtém-se 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]:
![]() | ![]() |
Adicionamos, em [2], um ficheiro [config.xml] que servirá para configurar o plugin [Phonegap], responsável por 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: insira aqui os seus dados de contacto;
- linhas 11-13: estas linhas permitem que o JavaScript incorporado na aplicação web, que será executado no dispositivo Android, solicite ficheiros URL externos a esse dispositivo;
Comprimimos o conteúdo da pasta [Content/bootstrap]:
![]() |
Em seguida, acedemos 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], escolhe-se um plano gratuito que permite apenas uma aplicação Phonegap;
- em [3], descarrega-se a aplicação compactada [4];
![]() |
![]() |
- em [5], o nome da aplicação;
- clique na ligação [6] para compilar os binários de OS e IoS, para Android e Windows. Este processo pode demorar alguns segundos;
![]() |
- em [7-9], descarregue o ficheiro binário para Android;
![]() |
Inicie um emulador [GenyMotion] para um tablet Android (ver parágrafo 11.1):
![]() |
Acima, iniciamos um emulador de tablet com o Android API 21. Assim que o emulador estiver a funcionar,
- desbloqueie-o arrastando o fecho (se houver) para o lado e, em seguida, soltando-o;
- com o rato, arraste o ficheiro [Pam-debug.apk] que descarregou e solte-o no emulador. Este será então instalado e executado;
![]() |
Configure o [1], o URL do simulador, tal como descrito no parágrafo 9.27.3. Feito isto, ligue-se ao simulador através do link [2]:
![]() |
Teste a aplicação no emulador. Deve funcionar.








































































































































































































