7. Implementação de Ajax numa aplicação ASP.NET MVC
7.1. O papel do AJAX numa aplicação web
Por enquanto, os exemplos de aprendizagem analisados têm a seguinte arquitetura:
![]() |
Para passar de uma vista [Vue1] para uma vista [Vue2], o navegador:
- envia um pedido à aplicação web;
- recebe a vista [Vue2] e apresenta-a no lugar da vista [Vue1].
Este é o esquema clássico:
- pedido do navegador;
- elaboração de uma vista pelo servidor web em resposta ao cliente;
- exibição dessa nova vista pelo navegador.
Existe outro modo de interação entre o navegador e o servidor web: AJAX (Asynchronous JavaScript and XML). Trata-se, na verdade, de interações entre a vista apresentada pelo navegador e o servidor web. O navegador continua a fazer o que sabe fazer, ou seja, apresentar uma vista HTML, mas agora é controlado por JavaScript incorporado na vista HTML apresentada. O esquema é o seguinte:
![]() |
- em [1], ocorre um evento na página apresentada no navegador (clique num botão, alteração de um texto, etc.). Este evento é interceptado pelo JavaScript (JS) incorporado na página;
- em [2], o código JavaScript efetua uma solicitação HTTP, tal como o navegador teria feito. A solicitação é assíncrona: o utilizador pode continuar a interagir com a página sem ficar bloqueado pela espera pela resposta à solicitação HTTP. A solicitação segue o processo clássico de processamento. Nada (ou quase nada) a distingue de uma solicitação clássica;
- em [3], é enviada uma resposta ao cliente JS. Em vez de uma vista HTML completa, é enviada uma vista HTML parcial, um fluxo XML ou JSON (Notação de Objeto JavaScript) que é enviado;
- em [4], o JavaScript recupera essa resposta e utiliza-a para atualizar uma região da página HTML apresentada.
Para o utilizador, verifica-se uma alteração na visualização, pois o que vê mudou. No entanto, não há um recarregamento total da página, mas sim uma simples modificação parcial da página apresentada. Isto contribui para conferir fluidez e interatividade à página: como não há uma recarga total da página, é possível gerir eventos que anteriormente não eram geridos. Por exemplo, apresentar ao utilizador uma lista de opções à medida que este introduz caracteres numa caixa de entrada. A cada novo caractere digitado, é enviada uma solicitação AJAX ao servidor, que, por sua vez, devolve outras sugestões. Sem o Ajax, este tipo de ajuda à digitação era anteriormente impossível. Não era possível recarregar uma nova página a cada caractere digitado.
7.2. Noções básicas de JQuery e de JavaScript
Frequentemente, incluímos a biblioteca JavaScript JQuery nas nossas páginas. Nela, encontramos a seguinte linha:
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
Nota: adapte a versão do jQuery à da sua versão do Visual Studio.
A tecnologia Ajax da ASP.NET MVC utiliza a JQuery. Vamos escrever nós próprios alguns scripts JQuery. Por isso, apresentamos agora os conceitos básicos de JQuery que é necessário conhecer para compreender os scripts deste capítulo.
Criamos um novo projeto [Exemple-04] dentro da nossa solução [Exemples]:
![]() |
Para utilizar o Ajax com o ASP.NET e o MVC, deve existir uma linha no ficheiro de configuração [Web.config] e [1]:
<appSettings>
...
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
A linha 3 permite a utilização do Ajax nas vistas ASP.NET. Está presente por predefinição.
Criamos um ficheiro HTML [JQuery-01.html] na pasta [Content] do novo projeto [2]:
![]() |
Este ficheiro terá o seguinte conteúdo:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>JQuery-01</title>
<script type="text/javascript" src="/Scripts/jquery-1.8.2.min.js"></script>
</head>
<body>
<h3>Rudiments de JQuery</h3>
<div id="element1">
Elément 1
</div>
</body>
</html>
- linha 6: importação de JQuery (adapte a versão à da sua versão do Visual Studio);
- linhas 10-12: um elemento da página com o ID [element1]. Vamos experimentar com este elemento.
Visualizamos este ficheiro no navegador Google Chrome [4] e [5]:
![]() |
No Google Chrome, digite [Ctrl-Maj-I] para aceder às ferramentas de desenvolvimento [6]. O separador [Console] [7] permite executar código JavaScript. Apresentamos a seguir alguns comandos JavaScript a introduzir, acompanhados de uma explicação.
JS | resultado |
|
: devolve a coleção de todos os elementos com o id [element1]; por isso, normalmente, uma coleção de 0 ou 1 elemento, uma vez que não é possível ter dois ids idênticos numa página HTML. | ![]() |
|
: aplica o texto [blabla] a todos os elementos da coleção. Isto tem como efeito alterar o conteúdo apresentado pela página | ![]() |
|
oculta os elementos da coleção. O texto [blabla] já não é apresentado. | ![]() |
|
: volta a exibir a coleção. Isto permite-nos ver que o elemento com o ID [element1] tem o atributo CSS style='display: none;', o que faz com que o elemento fique oculto. | |
|
: apresenta os elementos da coleção. O texto [blabla] volta a aparecer. É o atributo CSS style='display : block;' que garante esta visualização. | ![]() |
|
: define um atributo para todos os elementos da coleção. O atributo é, neste caso, [style] e o seu valor é [color: red]. O texto [blabla] fica a vermelho. | ![]() |
![]() | |
![]() |
Note-se que o URL do navegador não se alterou durante todas estas operações. Não houve qualquer comunicação com o servidor web. Tudo decorre no interior do navegador. Agora, vamos visualizar o código-fonte da página:
![]() |
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>JQuery-01</title>
<script type="text/javascript" src="/Scripts/jquery-1.8.2.min.js"></script>
</head>
<body>
<h3>Rudiments de JQuery</h3>
<div id="element1">
Elément 1
</div>
</body>
</html>
Este é o texto inicial. Não reflete de forma alguma as alterações que fizemos no elemento nas linhas 10 a 12. É importante ter isto em conta ao depurar JavaScript. Por isso, muitas vezes é inútil visualizar o código-fonte da página apresentada. Para conhecer o código-fonte da página atualmente apresentada, devemos proceder da seguinte forma:
![]() |
Já sabemos o suficiente para compreender os scripts JS que se seguem.
7.3. Atualização de uma página com um fluxo HTML
7.3.1. As vistas
Propomos estudar a seguinte aplicação:
![]() |
- em [1], a hora de carregamento da página;
- em [2], realizam-se as quatro operações aritméticas sobre dois números reais A e B;
- em [3], a resposta do servidor é inserida numa área da página;
- em [4], a hora do cálculo. Esta é diferente da hora de carregamento da página [5]. Esta última é igual a [1], o que demonstra que a região [6] não foi recarregada. Além disso, o URL e o [7] da página não se alteraram.
7.3.2. O controlador, as ações, o modelo, a vista
Criamos um controlador denominado [Premier]:
![]() |
Para apresentar a vista inicial, criamos a seguinte ação [Action01Get]:
[HttpGet]
public ViewResult Action01Get()
{
ViewModel01 modèle = new ViewModel01();
modèle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
return View(modèle);
}
- linha 4: instanciação do modelo da vista;
- linha 5: inicialização da hora de carregamento da vista;
- linha 6: exibição da vista [Action10Get.cshtml] e do seu modelo.
O modelo [ViewModel01] é o seguinte:
![]() |
using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_04.Models
{
[Bind(Exclude = "AplusB, AmoinsB, AmultipliéparB, AdiviséparB, Erreur, HeureChargement, HeureCalcul")]
public class ViewModel01
{
// formulário
[Required(ErrorMessage="Donnée requise")]
[Display(Name="Valeur de A")]
[Range(0, Double.MaxValue, ErrorMessage = "Tapez un nombre positif ou nul")]
public double A { get; set; }
[Required(ErrorMessage = "Donnée requise")]
[Display(Name = "Valeur de B")]
[Range(0, Double.MaxValue, ErrorMessage="Tapez un nombre positif ou nul")]
public double B { get; set; }
// resultados
public string AplusB { get; set; }
public string AmoinsB { get; set; }
public string AmultipliéparB { get; set; }
public string AdiviséparB { get; set; }
public string Erreur { get; set; }
public string HeureChargement { get; set; }
public string HeureCalcul { get; set; }
}
}
- linhas 11-14: o valor A do formulário;
- linhas 15-18: o valor B do formulário;
- linhas 21-24: os resultados das quatro operações aritméticas sobre A e B;
- linha 25: o texto de um eventual erro;
- linha 26: a hora em que a vista foi carregada no navegador;
- linha 27: a hora do cálculo dos campos das linhas 21-24;
- linha 7: este modelo de vista é também um modelo de ação. Excluem-se deste último os campos que não são enviados pelo navegador.
A vista [Action01Get.cshtml] é a seguinte:
![]() |
@model Exemple_04.Models.ViewModel01
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
UpdateTargetId = "résultats",
HttpMethod = "post",
Url = Url.Action("Action01Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000
};
}
<!DOCTYPE html>
<html lang="fr-FR">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-01</title>
<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/globalize/cultures/globalize.culture.en-US.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<script type="text/javascript" src="~/Scripts/myScripts-01.js"></script>
</head>
<body>
<h2>Ajax - 01</h2>
<p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
<h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
@using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
{
<table>
<thead>
<tr>
<th>@Html.LabelFor(m => m.A)</th>
<th>@Html.LabelFor(m => m.B)</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Html.TextBoxFor(m => m.A)</td>
<td>@Html.TextBoxFor(m => m.B)</td>
</tr>
<tr>
<td>@Html.ValidationMessageFor(m => m.A)</td>
<td>@Html.ValidationMessageFor(m => m.B)</td>
</tr>
</tbody>
</table>
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
<hr />
<div id="résultats" />
</body>
</html>
- linha 1: a vista tem como modelo um tipo [ViewModel01];
- linha 21: é necessário o JQuery tanto para as validações como para o Ajax;
- linhas 22-23: as bibliotecas de validação;
- linhas 24-26: as bibliotecas de internacionalização;
- linha 27: a biblioteca Ajax;
- linha 28: uma biblioteca JavaScript local;
- linha 33: exibição da hora de carregamento da vista;
- linha 35: um formulário Ajax — voltaremos a este assunto;
- linhas 40-41: rótulos para a introdução dos números A e B;
- linhas 46-47: campos de introdução dos números A e B;
- linhas 50-51: mensagens de erro para os campos de introdução dos números A e B;
- linha 56: o botão que envia o formulário. Este será enviado através de uma solicitação Ajax;
- linha 57: uma imagem de espera exibida durante a solicitação Ajax;
- linha 58: um link para enviar o formulário através de uma solicitação Ajax;
- linha 62: uma baliza <div> com o ID [résultats]. É aqui que iremos inserir o fluxo HTML devolvido pelo servidor web.
Esta vista apresenta a seguinte página:
![]() |
Vamos agora analisar o código que implementa o Ajax no formulário:
...
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
UpdateTargetId = "résultats",
HttpMethod = "post",
Url = Url.Action("Action01Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000
};
}
...
@using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
{
....
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
...
<div id="résultats" />
- linha 15: em vez de utilizar [@Html.BeginForm], utiliza-se [@Ajax.BeginForm]. Este método admite várias sobrecargas. A utilizada tem a seguinte assinatura:
Ajax.BeginForm(string ActionName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string,object> htmlAttributes)
Utilizamos aqui os seguintes parâmetros efetivos:
Action01Post: o nome da ação que irá processar o POST do formulário,
null: não há informações de rota a fornecer,
ajaxOpts: as opções da chamada Ajax. Estas foram definidas nas linhas 6 a 10,
new { id = "formulário" }: para atribuir o atributo [id='formulaire'] à tag <form> gerada;
As opções Ajax utilizadas são as seguintes:
- linha 8: o URL destino da solicitação HTTP Ajax;
- linha 7: método da solicitação Ajax HTTP;
- linha 6: ID da área da página que será atualizada pela resposta à solicitação Ajax;
- linha 9: id da área da página que será exibida durante a solicitação Ajax – geralmente uma imagem de espera. Neste caso, será exibida a linha 20. Esta contém uma imagem animada que simboliza a espera. Inicialmente, esta imagem está oculta pelo estilo [display : none];
- linha 10: tempo de espera, em milissegundos, antes de a imagem animada ser exibida; neste caso, 1 segundo.
O código HTML gerado pelo formulário Ajax é o seguinte:
<form action="/Premier/Action01Post" data-ajax="true" data-ajax-loading="#loading" data-ajax-loading-duration="1000" data-ajax-method="post" data-ajax-mode="replace" data-ajax-update="#résultats" data-ajax-url="/Premier/Action01Post" id="formulaire" method="post"> <table>
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
</form>
<hr />
<div id="résultats" />
- linha 1: a baliza <form> gerada. Note-se os atributos [data-ajax-attr], que refletem os valores dos campos do objeto do tipo [AjaxOptions] que foi associado à solicitação Ajax. Estes atributos são geridos pela biblioteca Ajax. Sem eles, a baliza <form> passa a ser:
<form action="/Premier/Action01Post" id="formulaire" method="post">
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
</form>
Estamos então perante um formulário HTML clássico. É este código que será executado se o utilizador desativar o JavaScript no seu navegador. As linhas 5 e 6 ficam, então, sem utilização.
7.3.3. A ação [Action01Post]
A ação [Action01Post], que processa a solicitação Ajax HTTP, é a seguinte:
[HttpPost]
public PartialViewResult Action01Post(FormCollection postedData, SessionModel session)
{
// simulação de espera
Thread.Sleep(2000);
// instanciação do modelo da ação
ViewModel01 modèle = new ViewModel01();
// hora do cálculo
modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
// atualização do modelo
TryUpdateModel(modèle, postedData);
if (!ModelState.IsValid)
{
// é devolvido um erro
modèle.Erreur = getErrorMessagesFor(ModelState);
return PartialView("Action01Error", modèle);
}
// de cada duas vezes, simula-se um erro
int val = session.Randomizer.Next(2);
if (val == 0)
{
modèle.Erreur = "[erreur aléatoire]";
return PartialView("Action01Error", modèle);
}
// cálculos
modèle.AplusB = string.Format("{0}", modèle.A + modèle.B);
modèle.AmoinsB = string.Format("{0}", modèle.A - modèle.B);
modèle.AmultipliéparB = string.Format("{0}", modèle.A * modèle.B);
modèle.AdiviséparB = string.Format("{0}", modèle.A / modèle.B);
// visualização
return PartialView("Action01Success", modèle);
}
- linha 1: a ação processa apenas um [POST];
- linha 2: aceita como modelo de ação:
- [FormCollection postedData]: o conjunto de valores enviados pela solicitação Ajax POST,
- [SessionModel session]: os elementos da sessão. Aqui, utiliza-se uma técnica descrita no parágrafo 4.10;
- linha 2: a ação irá devolver um fragmento HTML e não uma página HTML completa;
- linha 5: artificialmente, faz-se uma pausa de dois segundos para simular uma ação Ajax demorada;
- linha 7: é instanciado um modelo do tipo [ViewModel01];
- linha 9: o tempo de cálculo é inicializado;
- linha 11: tenta-se atualizar o modelo do tipo [ViewModel01] com os valores enviados. Recorde-se que existem dois: os valores dos números A e B;
- linha 12: verifica-se se esta atualização foi bem-sucedida ou não;
- linha 15: em caso de erro, preenche-se o campo [Erreur] do modelo;
- linha 16: devolve-se uma vista parcial [Action01Error.cshtml] que utiliza o modelo [ViewModel01];
- linhas 19-24: uma em cada duas vezes, simula-se um erro;
- linha 19: gera-se um número inteiro aleatório no intervalo [0,1]. O gerador de números é obtido na sessão;
- linha 20: se o valor gerado for 0, simula-se um erro;
- linha 22: a mensagem de erro é inserida no modelo;
- linha 23: é devolvida uma vista parcial [Action01Error.cshtml] que utiliza o modelo [ViewModel01];
- linhas 26-29: são efetuados os cálculos aritméticos sobre os números A e B e os resultados são inseridos no modelo sob a forma de cadeias de caracteres;
- linha 31: é devolvida uma vista parcial [Action01Success.cshtml] que utiliza o modelo [ViewModel01];
7.3.4. A vista [Action01Error]
A vista [Action01Error.cshtml] é a seguinte:
@model Exemple_04.Models.ViewModel01
<h4>Résultats</h4>
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p style="color: red;">Une erreur s'est produite : @Model.Erreur</p>
Recorde-se que este fluxo parcial HTML será enviado em resposta à solicitação Ajax HTTP do tipo POST e colocado na página na região com o ID [résultats]. Todas estas informações provêm da configuração Ajax utilizada na página principal [Action01Get.cshtml]:
@model Exemple_04.Models.ViewModel01
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
UpdateTargetId = "résultats",
HttpMethod = "post",
Url = Url.Action("Action01Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000
};
}
Eis um exemplo de resposta com erro:
![]() |
7.3.5. A vista [Action01Success]
A vista [Action01Success.cshtml] é a seguinte:
@model Exemple_04.Models.ViewModel01
<h4>Résultats</h4>
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>
Mais uma vez, este fluxo parcial HTML será enviado em resposta à solicitação Ajax HTTP do tipo POST e inserido na página na região com o ID [résultats]:
![]() |
7.3.6. G estão da sessão
Vimos que o [Action01Post] utilizava a sessão. O modelo da sessão é do tipo [SessionModel], conforme se segue:
using System;
namespace Exemple_03.Models
{
public class SessionModel
{
public Random Randomizer { get; set; }
}
}
A sessão é inicializada em [Global.asax]:
// Sessão
protected void Session_Start()
{
SessionModel sessionModel=new SessionModel();
sessionModel.Randomizer=new Random(DateTime.Now.Millisecond);
Session["data"] = sessionModel;
}
A associação da sessão a um modelo é efetuada no [Application_Start]:
protected void Application_Start()
{
...
// ligadores de modelos
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}
A classe [SessionModelBinder] foi descrita.
7.3.7. Gestão da imagem de espera
@model Exemple_04.Models.ViewModel01
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
...
LoadingElementId = "loading",
LoadingElementDuration = 1000
};
}
...
<body>
...
@using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
{
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
...
Quando a solicitação Ajax é iniciada, a região com o ID [loading], linha 7, é exibida após um segundo [ligne 8]. Esta região corresponde à imagem da linha 21, inicialmente oculta. Isto resulta na seguinte interface:
![]() |
7.3.8. Gestão do link [Calculer]
Analisemos o link [Calculer] da página principal [Action01Get.cshtml]:
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-01</title>
...
<script type="text/javascript" src="~/Scripts/myScripts-01.js"></script>
</head>
<body>
<h2>Ajax - 01</h2>
<p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
<h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
@using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
{
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
<hr />
<div id="résultats" />
- linha 18: um clique no link [Calculer] provoca a execução da função JS [postForm]. Esta função está definida no ficheiro [myScripts-01.js], na linha 5. O script é o seguinte:
![]() |
function postForm() {
// faz-se uma chamada Ajax manualmente com JQuery
var loading = $("#loading");
var formulaire = $("#formulaire");
var résultats = $('#resultados');
$.ajax({
url: '/Premier/Action01Post',
type: 'POST',
data: formulaire.serialize(),
dataType: 'html',
begin: loading.show(),
success: function (data) {
loading.hide()
résultats.html(data);
}
})
}
// http://blog.instance-factory.com/?p=268
$.validator.methods.number = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseFloat(value));
}
$.validator.methods.date = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseDate(value));
}
jQuery.extend(jQuery.validator.methods, {
range: function (value, element, param) {
//Utilize o plugin de globalização para analisar o valor
var val = Globalize.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
As funções das linhas 19-37 já foram abordadas no parágrafo 6.1. Estas funções gerem a internacionalização das páginas. Não voltaremos a abordá-las. Nas linhas 1 a 17, efetuamos manualmente a chamada Ajax que, no caso do botão [Calculer], era feita pela biblioteca Ajax associada ao projeto. Para tal, utilizamos a biblioteca JQuery associada ao projeto.
- linha 3: uma referência ao componente com o ID [loading]. [$("#loading")] devolve a coleção de elementos com o ID [loading]. Existe apenas um;
- linha 4: uma referência ao componente com o ID [formulaire];
- linha 5: uma referência ao componente com o ID [résultats];
- linha 6: a chamada Ajax com as respetivas opções;
- linha 7: o URL, destino da chamada Ajax;
- linha 8: o método HTTP utilizado;
- linha 9: os dados enviados. Cria a cadeia de caracteres do formulário com o ID [formulaire];
- linha 10: o tipo de dados esperado em resposta. Sabe-se que o servidor irá devolver um fluxo HTML;
- linha 11: o método a executar quando a solicitação for iniciada. Aqui, indica-se que é necessário apresentar o componente com o ID [loading]. Trata-se da imagem animada de espera;
- linha 12: o método a executar caso a solicitação Ajax seja bem-sucedida. O parâmetro [data] é a resposta completa do servidor. Sabemos que se trata de um fluxo HTML;
- linha 13: ocultamos o sinal de espera;
- linha 14: atualiza-se o componente com o ID [résultats] com o valor HTML do parâmetro [data].
Sugere-se ao leitor que teste o link [Calculer]. Funciona como o botão [Calculer], com uma única an . Depois de utilizar este link, é possível enviar valores inválidos para A e B:
![]() |
- nos links [1] e [2], introduzimos valores inválidos. Estes são sinalizados pelos validadores do lado do cliente;
- em [3], clicou-se no link [Calculer];
- no [4], ocorreu um [POST], uma vez que se obtém a resposta [4].
Quando os valores são inválidos e se clica no botão [Calculer], o [POST] para o servidor não ocorre. No mesmo caso, com o link [Calculer], o [POST] para o servidor é enviado. Existe, portanto, um comportamento do botão [Calculer] que não conseguimos reproduzir com o link [Calculer]. Em vez de tentarmos resolver este problema agora, deixamo-lo para um exemplo posterior que também ilustrará outro problema de validação do lado do cliente.
7.4. Atualização de uma página HTML com um fluxo JSON
No exemplo anterior, o servidor web respondia ao pedido Ajax HTTP com um fluxo HTML. Neste fluxo, havia dados acompanhados de formatação HTML. Propomos retomar o exemplo anterior, mas desta vez com respostas JSON (JavaScript Object Notation) que contêm apenas os dados. A vantagem é que, desta forma, são transmitidos menos bytes.
7.4.1. A ação [Action02Get]
A ação [Action02Get] será o ponto de entrada da nova aplicação. O seu código é o seguinte:
@model Exemple_04.Models.ViewModel02
@{
Layout = null;
AjaxOptions ajaxOpts = new AjaxOptions
{
HttpMethod = "post",
Url = Url.Action("Action02Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000,
OnBegin = "OnBegin",
OnFailure = "OnFailure",
OnSuccess = "OnSuccess",
OnComplete = "OnComplete"
};
}
<!DOCTYPE html>
<html lang="fr-FR">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-02</title>
....
<script type="text/javascript" src="~/Scripts/myScripts-02.js"></script>
</head>
<body>
<h2>Ajax - 02</h2>
<p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
<h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
@using (Ajax.BeginForm("Action02Post", null, ajaxOpts, new { id = "formulaire" }))
{
...
<p>
<input type="submit" value="Calculer" />
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<a href="javascript:postForm()">Calculer</a>
</p>
}
<hr />
<div id="entete">
<h4>Résultats</h4>
<p><strong>Heure de calcul : <span id="heureCalcul"/></strong></p>
</div>
<div id="résultats">
<p>A+B=<span id="AplusB"/></p>
<p>A-B=<span id="AmoinsB"/></p>
<p>A*B=<span id="AmultipliéparB"/></p>
<p>A/B=<span id="AdiviséparB"/></p>
</div>
<div id="erreur">
<p style="color: red;">Une erreur s'est produite : <span id="msg"/></p>
</div>
</body>
</html>
- linhas 4-14: as opções da chamada Ajax;
- linha 10: a função JS a executar no início da solicitação. Esta função está definida no ficheiro JS referenciado na linha 24;
- linha 11: a função JS a executar em caso de falha da solicitação;
- linha 12: a função JS a ser executada em caso de sucesso da solicitação;
- linha 13: a função JS a executar após a requisição Ajax ter obtido o seu resultado (falha ou sucesso);
- linhas 40-43: uma região com o ID [entete];
- linhas 44-49: uma região com o ID [résultats]. Esta irá apresentar os resultados das quatro operações aritméticas;
- linhas 50-52: uma região com o ID [erreur]. Esta irá apresentar uma eventual mensagem de erro.
7.4.2. A ação [Action02Post]
A solicitação Ajax é processada pela seguinte ação [Action02Post]:
[HttpPost]
public JsonResult Action02Post(FormCollection postedData, SessionModel session)
{
// simulação em espera
Thread.Sleep(2000);
// validação do modelo
ViewModel02 modèle = new ViewModel02();
// tempo de carregamento e cálculo
string HeureChargement = DateTime.Now.ToString("hh:mm:ss");
string HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
// atualização do modelo
TryUpdateModel(modèle, postedData);
if (!ModelState.IsValid)
{
// é devolvido um erro
return Json(new { Erreur = getErrorMessagesFor(ModelState), HeureCalcul = HeureCalcul });
}
// de cada duas vezes, simula-se um erro
int val = session.Randomizer.Next(2);
if (val == 0)
{
// é devolvido um erro
return Json(new { Erreur = "[erreur aléatoire]", HeureCalcul = HeureCalcul });
}
// cálculos
string AplusB = string.Format("{0}", modèle.A + modèle.B);
string AmoinsB = string.Format("{0}", modèle.A - modèle.B);
string AmultipliéparB = string.Format("{0}", modèle.A * modèle.B);
string AdiviséparB = string.Format("{0}", modèle.A / modèle.B);
// são devolvidos os resultados
return Json(new { Erreur = "", AplusB = AplusB, AmoinsB = AmoinsB, AmultipliéparB = AmultipliéparB, AdiviséparB = AdiviséparB, HeureCalcul = HeureCalcul });
}
- linha 2: o método devolve um tipo [JsonResult], ou seja, um texto no formato JSON;
- linha 16: as informações são apresentadas sob a forma de uma instância de classe anónima serializada em JSON. O método [getErrorMessagesFor] já foi apresentado. A cadeia JSON enviada ao navegador terá o seguinte formato:
- linha 31: o mesmo procedimento aplica-se aos resultados aritméticos. Desta vez, a cadeia JSON enviada ao navegador terá o seguinte formato:
{"Erreur":"","AplusB":"4","AmoinsB":"-2","AmultipliéparB":"3","AdiviséparB":"0,333333333333333","HeureCalcul":"05:52:17"}
7.4.3. O código JavaScript do lado do cliente
Recorde-se a configuração da chamada Ajax na página HTML enviada ao navegador do cliente:
AjaxOptions ajaxOpts = new AjaxOptions
{
HttpMethod = "post",
Url = Url.Action("Action02Post"),
LoadingElementId = "loading",
LoadingElementDuration = 1000,
OnBegin = "OnBegin",
OnFailure = "OnFailure",
OnSuccess = "OnSuccess",
OnComplete = "OnComplete"
};
As funções JS referenciadas nas linhas 7-10 (à direita do sinal =) estão definidas no seguinte ficheiro [myScripts-02.js]:
// dados globais
var entete;
var loading;
var résultats;
var erreur;
var heureCalcul;
var msg;
var AplusB;
var AmoinsB;
var AmultipliéparB;
var AdiviséparB;
var formulaire;
...
function postForm() {
...
}
// ao carregar o documento
$(document).ready(function () {
formulaire = $("#formulaire");
entete = $("#entete");
loading = $("#loading");
erreur = $("#erreur");
résultats = $('#resultados');
heureCalcul = $("#heureCalcul");
msg = $("#msg");
AplusB = $("#AplusB");
AmoinsB = $("#AmoinsB");
AmultipliéparB = $("#AmultipliéparB");
AdiviséparB = $("#AdiviséparB");
// ocultam-se alguns elementos da página
entete.hide();
résultats.hide();
erreur.hide();
});
// início
function OnBegin() {
....
}
// fim da consulta
function OnComplete() {
...
}
// sucesso
function OnSuccess(data) {
....
}
// erro
function OnFailure(request, error) {
...
}
- linha 19: a função JS é executada no final do carregamento da página no navegador;
- linhas 20-30: recuperam-se as referências de todos os componentes da página que nos interessam. A pesquisa de um componente numa página tem um custo e é preferível fazê-la apenas uma vez;
- linhas 33-35: os componentes [entete], [résultats] e [loading] são ocultados;
Ao iniciar a solicitação Ajax, é executada a seguinte função:
// início
function OnBegin() {
// luz de espera acesa
loading.show();
// ocultar determinados elementos da página
entete.hide();
résultats.hide();
erreur.hide();
}
- linha 4: o componente [loading] é apresentado. Trata-se da imagem animada;
- linhas 6-8: os componentes [entete], [résultats] e [erreur] ficam ocultos;
Se a solicitação Ajax for bem-sucedida, executa-se o seguinte código JS:
// sucesso
function OnSuccess(data) {
// exibição dos resultados
heureCalcul.text(data.HeureCalcul);
entete.show();
if (data.Erreur != '') {
msg.text(data.Erreur);
erreur.show();
return;
}
// sem erros
AplusB.text(data.AplusB);
AmoinsB.text(data.AmoinsB);
AmultipliéparB.text(data.AmultipliéparB);
AdiviséparB.text(data.AdiviséparB);
résultats.show();
}
Para compreender este código, é necessário ter em conta os dois textos JSON que podem ser enviados como resposta ao navegador:
em caso de erro; caso contrário, a cadeia:
{"Erreur":"","AplusB":"4","AmoinsB":"-2","AmultipliéparB":"3","AdiviséparB":"0,333333333333333","HeureCalcul":"05:52:17"}
Se designarmos esta cadeia por [data], o valor do campo [Erreur] é obtido através da notação [data.Erreur] ou [data["Erreur"]], à escolha. O mesmo se aplica aos outros campos da cadeia JSON. Além disso, para atribuir um texto não formatado a um componente com o ID X, escreve-se [X.text(chaine)]. Voltemos ao código da função [OnSuccess]:
- linha 2: [data] é a cadeia JSON recebida;
- linha 4: o componente [heureCalcul] recebe o seu valor;
- linha 5: o componente [entete] é apresentado;
- linha 6: teste do campo [Erreur] da cadeia JSON;
- linha 7: o componente [msg] recebe o seu valor;
- linha 8: o componente [erreur] é apresentado;
- linha 9: o processo de erro está concluído;
- linha 12: o componente [AplusB] recebe o seu valor;
- linha 13: o componente [AmoinsB] recebe o seu valor;
- linha 14: o componente [AmultipliéparB] recebe o seu valor;
- linha 15: o componente [AdiviséparB] recebe o seu valor;
- linha 16: o componente [résultats] é apresentado.
A função [OnFailure] será executada caso a solicitação Ajax HTTP falhe. Esta falha é identificada pelo código HTTP devolvido pelo servidor. O código 500 [Internal Server Error], por exemplo, indica que o servidor não conseguiu executar a solicitação. A função [OnFailure] é a seguinte:
// erro
function OnFailure(request, error) {
alert("L'erreur suivante s'est produite :" + error);
}
Limita-se a apresentar uma caixa de diálogo com o erro que ocorreu. Na prática, seria necessário ser mais preciso. Em breve iremos propor outra solução.
Por fim, a função [OnComplete] é executada quando a consulta termina, quer tenha sido bem-sucedida quer tenha falhado.
// fim da consulta
function OnComplete() {
// sinal de espera desativado
loading.hide();
}
Recorde-se aqui que é a configuração da chamada Ajax na vista [Action02Get.cshtml] que faz com que estas diferentes funções sejam chamadas:
AjaxOptions ajaxOpts = new AjaxOptions
{
...
OnBegin = "OnBegin",
OnFailure = "OnFailure",
OnSuccess = "OnSuccess",
OnComplete = "OnComplete"
};
7.4.4. O link [Calculer]
O código HTML do link [Calculer] na vista [Action02Get.cshtml] é o seguinte:
<a href="javascript:postForm()">Calculer</a>
A função JS [postForm] encontra-se no ficheiro importado [myScripts-02.js]:
<script type="text/javascript" src="~/Scripts/myScripts-02.js"></script>
O seu código é o seguinte:
function postForm() {
// é efetuada uma chamada Ajax manualmente com JQuery
$.ajax({
url: '/Premier/Action02Post',
type: 'POST',
data: formulaire.serialize(),
dataType: 'json',
beforeSend: OnBegin,
success: OnSuccess,
error: OnFailure,
complete: OnComplete
})
}
Já nos deparámos com um código semelhante.
- linha 4: URL, destino da chamada Ajax;
- linha 5: comando HTTP utilizado pela chamada Ajax;
- linha 6: valores enviados. São o resultado da serialização dos valores do formulário. Este, identificado pelo id [formulaire], é referenciado pela variável [formulaire]. [data] será uma cadeia de caracteres com o formato [A=val1&B=val2];
- linha 7: tipo de formatação da resposta esperada. Trata-se de uma cadeia de caracteres JSON;
- linha 8: função JS a executar no início da chamada Ajax;
- linha 9: função JS a executar se a chamada Ajax for bem-sucedida;
- linha 10: função JS a executar se a chamada Ajax falhar;
- linha 11: função JS a ser executada assim que for recebida a resposta do servidor, independentemente de ser um sucesso ou um erro.
Voltemos à função JavaScript que gere o caso em que a chamada Ajax falha (linha 10). A chamada Ajax falha em várias situações, por exemplo, quando o servidor devolve um código de erro como [403 Forbidden], [404 Not Found], [500 Internal Server Error], [301 Moved Permanently], ...
No exemplo anterior, a função [OnFailure] é a seguinte:
// erro
function OnFailure(request, error) {
alert("L'erreur suivante s'est produite :" + error);
}
Normalmente, a visualização do objeto [error] não fornece nenhuma informação relevante. Se for utilizada uma chamada Ajax efetuada com JQuery, pode-se utilizar o seguinte método [OnFailure];
// erro
function OnFailure(jqXHR) {
alert("Erreur : " + jqXHR.status + " " + jqXHR.statusText);
msg.html(jqXHR.responseText);
erreur.show();
}
O objeto JQuery [jqXHR] possui, entre as suas propriedades, as seguintes:
- responseText: o texto da resposta do servidor;
- status: o código de erro devolvido pelo servidor;
- statusText: o texto associado a este código de erro.
- linha 3: exibe-se o código de erro e a descrição correspondente;
- linha 4: insere-se a resposta HTML do servidor no componente com o ID [msg];
- linha 5: exibe-se a região com o ID [erreur].
Para testar esta função de erro, vamos criar artificialmente uma exceção na ação [Action02Post]:
[HttpPost]
public JsonResult Action02Post(FormCollection postedData, SessionModel session)
{
// uma exceção artificial para testar a função de erro da chamada Ajax
throw new Exception();
// simulação de espera
Thread.Sleep(2000);
// validação do modelo
...
A linha 5 lança uma exceção. Agora, vamos testar a aplicação:
![]() |
Obtemos a seguinte resposta: [1] e [2]:
![]() |
A resposta do servidor permite-nos ver em que linha do código do servidor ocorreu o erro. Esta é frequentemente uma informação útil de se saber. A partir de agora, utilizaremos esta técnica para gerir os erros das chamadas Ajax.
7.5. Aplicação web de página única
A tecnologia Ajax permite criar aplicações de página única:
- a primeira página resulta de um pedido clássico de um navegador;
- as páginas seguintes são obtidas através de chamadas Ajax. Assim, no final, o navegador nunca muda de URL e nunca carrega uma nova página. A este tipo de aplicação chama-se «Aplicação de Página Única» (APU) ou, em inglês, «Single Page Application» (SPA).
Eis um exemplo básico de uma aplicação deste tipo. A nova aplicação terá duas vistas:
![]() |
![]() |
- em [1], a ação [Action03Get] permite-nos aceder à primeira página, a página 1;
- em [2], um link permite-nos passar para a página 2 através de uma chamada Ajax;
- em [3], o URL não sofreu alterações. A página apresentada é a página 2;
- em [4], um link permite-nos regressar à página 1 através de uma chamada Ajax;
- em [5], o URL não sofreu alterações. A página apresentada é a página 1.
O código da ação [Action03Get] é o seguinte:
[HttpGet]
public ViewResult Action03Get()
{
return View();
}
- linha 4: a vista [Action03Get.cshtml] é apresentada.
A vista [Action03Get.cshtml] é a seguinte:
![]() |
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action03Get</title>
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
</head>
<body>
<h3>Ajax - 03 - Single Page Application</h3>
<div id="content">
@Html.Partial("Page1")
</div>
</body>
</html>
- linhas 16-18: um elemento com o ID [content]. É neste elemento que as diferentes páginas serão apresentadas;
- linha 17: por predefinição, é a página [Page1.cshtml] que será exibida em primeiro lugar.
A página [Page1.cshtml] é a seguinte:
<h4>Page 1</h4>
<p>
@Ajax.ActionLink("Page 2", "Action04", new { Page = 2 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>
- linha 1: o título da página para a diferenciar da página 2;
- linha 3: um link Ajax com os seguintes parâmetros:
- o texto do link [Page 2];
- a ação de destino do link [Action04];
- os parâmetros da solicitação URL. Este será [/Premier/Action04?Page=2];
- as opções da chamada Ajax. Neste caso, apenas o ID da região a atualizar com a resposta do servidor. Para as restantes opções, são utilizados valores por predefinição, sempre que existam. O método HTTP por predefinição é GET.
Vamos ver o que acontece quando se clica no link. O URL [/Premier/Action04?Page=2] é solicitado com um GET. A ação [Action04] é então executada:
[HttpGet]
public PartialViewResult Action04(string page = "1")
{
string vue = "Page1";
if (page == "2")
{
vue = "Page2";
}
return PartialView(vue);
}
- linha 2: a ação devolve um fluxo HTML parcial;
- linha 2: a ação tem como modelo a cadeia [page]. Ora, sabe-se que o URL contém esta informação: [/Premier/Action04?Page=2]. Recorde-se que o modelo não distingue maiúsculas de minúsculas;
- linhas 4-8: [vue] irá receber o valor [Page2];
- linha 9: a vista parcial [Page2.cshtml] é apresentada.
A vista parcial [Page2.cshtml] é a seguinte:
<h4>Page 2</h4>
<p>
@Ajax.ActionLink("Page 1", "Action04", new { Page = 1 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>
O servidor devolve, portanto, o fluxo HTML acima como resposta à chamada Ajax GET [/Premier/Action04?Page=2]. Recorde-se que esta chamada Ajax utiliza esta resposta para atualizar a região com o ID [content] (linha 3 abaixo):
<h4>Page 1</h4>
<p>
@Ajax.ActionLink("Page 2", "Action04", new { Page = 2 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>
Isto resulta na seguinte nova exibição: [1]:
![]() |
Seguindo o mesmo raciocínio, verifica-se que clicar na ligação [Page 1] a partir de [1] irá provocar a exibição de [2].
Voltemos ao esquema geral de uma aplicação ASP.NET MVC:
![]() |
Graças ao JavaScript incorporado nas páginas HTML e executado no navegador, é possível transferir código para o navegador e obter a seguinte arquitetura:
![]() |
- em [1], a camada Web ASP.NET MVC tornou-se uma interface Web de acesso aos dados, geralmente alojados numa base de dados. As vistas apresentadas contêm apenas dados e nenhum elemento de apresentação HTML, por exemplo, fluxos XML ou JSON;
- em [2]: o navegador apresenta vistas estáticas (ou seja, não geradas dinamicamente) fornecidas por um servidor web que pode ou não estar na mesma máquina que o servidor [1]. Estas visualizações estáticas são posteriormente enriquecidas com os dados obtidos pelo JavaScript a partir da interface web [1];
- o código JavaScript incorporado nas páginas HTML pode ser estruturado em camadas:
- a camada [présentation] trata das interações com o utilizador,
- a camada [DAO] trata do acesso aos dados através do servidor web [1],
- a camada [métier] corresponde à camada [métier], que anteriormente se encontrava no servidor [1] e que foi transferida para o navegador [2];
A vantagem desta arquitetura é que envolve competências diferentes:
- o código do servidor web [1] requer competências em .NET, mas não em JavaScript, HTML, CSS;
- o código incorporado no navegador [2] requer competências em JavaScript, HTML, CSS, mas é independente da tecnologia do servidor web [1].
Assim, esta arquitetura facilita o trabalho em paralelo de equipas com competências diferentes. Aplica-se particularmente bem a aplicações de página única.
7.6. Aplicação web de página única e validação do lado do cliente
Já mencionámos anteriormente uma anomalia no exemplo Ajax-01. Recordamos o contexto:
![]() |
- em [1] e [2], foram introduzidos valores inválidos. Estes são sinalizados pelos validadores do lado do cliente;
- no [3], clicou-se na ligação [Calculer];
- em [4], ocorreu um [POST], uma vez que se obtém a resposta [4].
Quando os valores são inválidos e se clica no botão [Calculer], o [POST] para o servidor não ocorre. No mesmo caso, com o link [Calculer], o [POST] para o servidor ocorre. Existe, portanto, um comportamento do botão [Calculer] que não conseguimos reproduzir com o link [Calculer].
Vamos retomar este exemplo num novo contexto: a aplicação terá várias vistas e será do tipo [Application à Page Unique] que acabámos de descrever.
7.6.1. As vistas do exemplo
O exemplo tem várias vistas:
![]() |
- em [1], a vista [Action05Get];
- em [2], a vista parcial [Formulaire05];
- em [3], a vista parcial [Failure05];
![]() |
- em [4], a vista parcial [Success05].
A aplicação é de página única: esta é carregada pelo navegador aquando do primeiro pedido. É posteriormente atualizada através de chamadas Ajax.
As páginas anteriores são geradas pelas seguintes vistas [cshtml]:
![]() |
A vista carregada inicialmente é a seguinte vista [Action05Get.cshtml]:
@model Exemple_04.Models.ViewModel05
@{
Layout = null;
}
<!DOCTYPE html>
<html lang="fr-FR">
<head>
<meta name="viewport" content="width=device-width" />
<title>Ajax-05</title>
<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/globalize/cultures/globalize.culture.en-US.js"></script>
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
<script type="text/javascript" src="~/Scripts/myScripts-05.js"></script>
</head>
<body>
<h2>Ajax - 05, Page unique - Validation formulaire côté client</h2>
<p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
<h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
<img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
<div id="content">
@Html.Partial("Formulaire05", Model)
</div>
</body>
</html>
É importante destacar os seguintes pontos:
- linha 1: o modelo da vista é do tipo [ViewModel05], que iremos apresentar em breve;
- linhas 13-19: encontram-se aqui os scripts JavaScript necessários para implementar Ajax e validação do lado do cliente;
- linha 20: vamos adicionar as nossas próprias funções JavaScript no [myScripts-05.js];
- linha 27: a imagem animada de espera;
- linhas 28-30: uma baliza com o ID [content]. É nesta baliza que as vistas parciais [Formulaire05, Success05, Failure05] serão inseridas;
- linha 29: inserção da vista parcial [Formulaire05].
A vista [Action05Get] é responsável pela exibição da parte [1] da página inicial:
![]() |
A vista parcial [Formulaire05] irá gerar a parte [2] acima referida. O seu código é o seguinte:
@model Exemple_04.Models.ViewModel05
@using (Html.BeginForm("Action05Post", "Premier", FormMethod.Post, new { id = "formulaire" }))
{
<table>
<thead>
<tr>
<th>@Html.LabelFor(m => m.A)</th>
<th>@Html.LabelFor(m => m.B)</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Html.TextBoxFor(m => m.A)</td>
<td>@Html.TextBoxFor(m => m.B)</td>
</tr>
<tr>
<td>@Html.ValidationMessageFor(m => m.A)</td>
<td>@Html.ValidationMessageFor(m => m.B)</td>
</tr>
</tbody>
</table>
<p>
<table>
<tbody>
<tr>
<td><a href="javascript:calculer()">Calculer</a>
</td>
<td style="width: 20px" />
<td><a href="javascript:effacer()">Effacer</a>
</td>
</tr>
</tbody>
</table>
</p>
}
- linha 1: a vista parcial utiliza como modelo um tipo [ViewModel05];
- linha 3: o formulário gerado pelo método [Html.BeginForm]. Como este formulário será enviado através de uma chamada Ajax, os três primeiros parâmetros do método serão ignorados. A menos que o utilizador tenha desativado o JavaScript no seu navegador. Ignoramos aqui essa possibilidade. O quarto parâmetro é importante. O formulário terá o ID [formulaire];
- linhas 5-22: o formulário para introduzir os números A e B;
- linha 27: um link JavaScript que inicia a execução das quatro operações aritméticas em A e B;
- linha 30: um link JavaScript que apaga os valores introduzidos e as eventuais mensagens de erro a eles associadas.
Note-se que o formulário não possui um botão do tipo [submit]. Teremos de calcular manualmente o [Post] a partir dos valores A e B introduzidos.
Se não houver erros, os resultados são apresentados:
![]() |
A parte [4] acima é gerada pela seguinte vista parcial [Success05.cshtml]:
@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h4>Résultats</h4>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>
<p>
<a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
- linha 1: a vista parcial [Success05.cshtml] recebe um modelo do tipo [ViewModel05];
- linha 12: um link JavaScript para regressar aos dados introduzidos.
Em caso de erro, é apresentada outra vista parcial [3]:
![]() |
Esta vista é gerada pelo seguinte código [Failure05.cshtml]:
@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
@foreach (string msg in Model.Erreurs)
{
<li>@msg</li>
}
</ul>
<p>
<a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
- linha 1: a vista parcial [Failure05.cshtml] recebe um modelo do tipo [ViewModel05];
- linha 14: um link JavaScript para regressar aos dados introduzidos.
7.6.2. O modelo das vistas
Todas as vistas anteriores partilham o mesmo modelo [ViewModel05]:
![]() |
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_04.Models
{
[Bind(Exclude = "AplusB, AmoinsB, AmultipliéparB, AdiviséparB, Erreurs, HeureChargement, HeureCalcul")]
public class ViewModel05
{
// formulário
[Required(ErrorMessage="Donnée A requise")]
[Display(Name="Valeur de A")]
[Range(0, Double.MaxValue, ErrorMessage = "Tapez un nombre A positif ou nul")]
public string A { get; set; }
[Required(ErrorMessage = "Donnée B requise")]
[Display(Name = "Valeur de B")]
[Range(0, Double.MaxValue, ErrorMessage="Tapez un nombre B positif ou nul")]
public string B { get; set; }
// resultados
public string AplusB { get; set; }
public string AmoinsB { get; set; }
public string AmultipliéparB { get; set; }
public string AdiviséparB { get; set; }
public List<string> Erreurs { get; set; }
public string HeureChargement { get; set; }
public string HeureCalcul { get; set; }
}
}
Trata-se do modelo [ViewModel01] já apresentado, com algumas pequenas diferenças:
- linhas 15 e 19: os campos A e B são agora do tipo [string], de modo a apresentar campos de introdução de dados vazios, em vez de campos com o valor 0, aquando da exibição inicial do formulário de introdução de dados;
- linhas 14 e 18: isto não impede a verificação do valor introduzido com um validador [Range];
- linha 26: uma lista de mensagens de erro apresentada pela vista [Failure05].
7.6.3. Os dados do escopo [Session]
No parágrafo 7.3.6, vimos que os dados da sessão estavam encapsulados no seguinte modelo [SessionModel]:
![]() |
using System;
namespace Exemple_03.Models
{
public class SessionModel
{
// o gerador de números aleatórios
public Random Randomizer { get; set; }
}
}
Este modelo de sessão é ampliado para integrar os valores de A e B:
using System;
namespace Exemple_03.Models
{
public class SessionModel
{
// o gerador de números aleatórios
public Random Randomizer { get; set; }
// os valores de A e B
public string A { get; set; }
public string B { get; set; }
}
}
É, de facto, necessário memorizar os valores de A e B na sessão, tal como mostra a sequência seguinte:
Consulta 1
![]() |
Solicitação 2
![]() |
Em [4], encontram-se os dados introduzidos em [1]. No entanto, existem duas consultas distintas: HTTP. Sabe-se que a memória entre duas solicitações HTTP é a sessão. Para que a segunda solicitação possa recuperar os valores enviados pela primeira, estes têm de ser guardados na sessão.
7.6.4. A ação do servidor [Action05Get]
A ação [Action05Get] é a ação que faz com que a página inicial única seja apresentada. O seu código é o seguinte:
[HttpGet]
public ViewResult Action05Get()
{
ViewModel05 modèle = new ViewModel05();
modèle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
return View(modèle);
}
- linha 6: a vista [Action05Get.cshtml], já analisada, é apresentada com um modelo do tipo [ViewModel05];
7.6.5. A ação do cliente [Calculer]
Analisemos as interações do utilizador com as vistas:
![]() |
O link [1] é um link JavaScript:
<a href="javascript:calculer()">Calculer</a>
A função JavaScript [calculer] encontra-se no ficheiro [myScripts-05.js]:
<script type="text/javascript" src="~/Scripts/myScripts-05.js"></script>
O código da função JavaScript [calculer] é o seguinte:
// dados globais
var content;
var loading;
function calculer() {
// primeiro, as referências no DOM
var formulaire = $("#formulaire");
// depois, validação do formulário
if (!formulaire.validate().form()) {
// formulário inválido - concluído
return;
}
// faz-se uma chamada Ajax manualmente
$.ajax({
url: '/Premier/Action05FaireCalcul',
type: 'POST',
data: formulaire.serialize(),
dataType: 'html',
beforeSend: function () {
loading.show();
},
success: function (data) {
content.html(data);
},
complete: function () {
loading.hide();
},
error: function (jqXHR) {
// exibição da resposta do servidor
content.html(jqXHR.responseText);
}
})
}
function retourSaisies() {
...
}
function effacer() {
...
}
// ao carregar o documento
$(document).ready(function () {
// recuperam-se as referências dos diferentes componentes da página
loading = $("#loading");
content = $("#content");
// armazenamos a imagem animada na cache
loading.hide();
});
- Recorde-se que o código JavaScript é sempre executado do lado do cliente, no navegador;
- linha 44: a função JS é executada quando o carregamento inicial da página única estiver concluído;
- linha 46: referência à imagem animada com o id [loading];
- linha 47: referência à região com o id [content]. É esta região que recebe as vistas parciais [Formulaire05, Success05, Failure05];
- linhas 2-3: as variáveis das linhas 46-47 são declaradas globais para que as outras funções tenham acesso às mesmas. A pesquisa de elementos numa página (linhas 46-47) tem um custo. Não há motivo para repetir essa pesquisa se for possível evitá-la;
- linha 5: a função [calculer];
- linha 7: obtém-se uma referência ao formulário. A vista parcial [Formulaire05] atribuiu-lhe o ID [formulaire];
- linha 9: esta instrução executa os validadores do formulário do lado do cliente. Era isto que faltava na anomalia constatada na página 183. Este método é fornecido pela biblioteca [jquery.unobstrusive-ajax] utilizada pela página única:
<script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
A instrução devolve [false] se o formulário for considerado inválido;
- linha 11: a chamada Ajax ao servidor não é efetuada se o formulário for inválido;
- linhas 14-32: é efetuada a chamada Ajax ao servidor;
- linha 15: o URL tem como destino a ação do servidor [Action05FaireCalcul];
- linha 16: é solicitada através de um [POST];
- linha 17: os valores enviados. Trata-se dos dados introduzidos no formulário, neste caso os valores de A e B;
- linhas 22-24: caso a chamada Ajax seja bem-sucedida, a função [calculer] atualiza a região com o ID [content] com o fluxo HTML enviado pelo servidor.
Este fluxo HTML é o enviado pela ação [Action05FaireCalcul] a que a chamada Ajax se destina. O código desta ação do lado do servidor é o seguinte:
[HttpPost]
public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
{
// modelo
ViewModel05 modèle = new ViewModel05();
// hora do cálculo
modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
// atualização do modelo
TryUpdateModel(modèle, postedData);
if (!ModelState.IsValid)
{
// retorna um erro
modèle.Erreurs = getListOfMessagesFor(ModelState);
return PartialView("Failure05", modèle);
}
...
}
- linha 1: a ação aceita apenas um [post];
- linha 2: devolve uma vista parcial;
- linha 2: recebe como parâmetros os valores enviados (postedData) e o modelo da sessão (session);
- linha 5: o modelo da vista parcial é criado;
- linha 7: é atualizado com a hora de cálculo;
- linha 9: tenta-se aplicar os valores enviados ao modelo. Os validadores deste serão então executados. Podemos questionar-nos por que razão nos damos a este trabalho, uma vez que os validadores do lado do cliente impedem o POST se os dados introduzidos forem inválidos. Na verdade, não temos a certeza da origem do POST. Pode ter sido gerado por um código que não é o nosso. Por isso, devemos sempre efetuar as verificações do lado do servidor;
- linha 10: verificamos se os validadores foram bem-sucedidos;
- linha 13: se o modelo for inválido, este é atualizado com uma lista de erros. Não iremos detalhar o método interno [getListOfMessagesFor], semelhante ao método [GetErrorMessagesFor] descrito na página 65;
- linha 14: a vista parcial [Failure05] é apresentada com o seu modelo. Recordamos o código desta vista;
@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
@foreach (string msg in Model.Erreurs)
{
<li>@msg</li>
}
</ul>
<p>
<a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
- linhas 7-12: a lista de erros do modelo é apresentada através de uma baliza <ul>.
Recorde-se que a função JS [calculer], naorigem do [Post] na ação do servidor [Action05FaireCalcul] irá inserir este fluxo HTML na região com o ID [content]. O resultado é algo semelhante a isto:
![]() |
Vamos continuar a analisar o código da ação [Action05FaireCalcul]:
[HttpPost]
public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
{
// modelo
ViewModel05 modèle = new ViewModel05();
...
// os valores de A e B são colocados na sessão
session.A = modèle.A;
session.B = modèle.B;
// sem erros por enquanto
List<string> erreurs = new List<string>();
// de cada duas vezes, simula-se um erro
int val = session.Randomizer.Next(2);
if (val == 0)
{
erreurs.Add("[erreur aléatoire]");
}
if (erreurs.Count != 0)
{
modèle.Erreurs = erreurs;
return PartialView("Failure05", modèle);
}
// cálculos
double A = double.Parse(modèle.A);
double B = double.Parse(modèle.B);
modèle.AplusB = string.Format("{0}", A + B);
modèle.AmoinsB = string.Format("{0}", A - B);
modèle.AmultipliéparB = string.Format("{0}", A * B);
modèle.AdiviséparB = string.Format("{0}", A / B);
// visualização
return PartialView("Success05", modèle);
}
- linha 7: o modelo foi declarado válido;
- linhas 8-9: os valores introduzidos A e B são guardados na sessão. Pretende-se poder recuperá-los na consulta que se seguirá;
- linhas 11-22: gera-se aleatoriamente um erro uma vez em cada duas;
- linhas 24-29: realizam-se as quatro operações aritméticas sobre os números reais introduzidos;
- linha 31: devolve-se a vista parcial [Success05] com o seu modelo. Esta vista parcial é a seguinte:
@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h4>Résultats</h4>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>
<p>
<a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
Recorde-se que a função JS [calculer], naorigem do [Post] na ação do servidor [Action05FaireCalcul] irá colocar este fluxo HTML na região de ID [content]. O resultado é algo semelhante a isto:
![]() |
7.6.6. A ação do cliente [Effacer]
O link JavaScript [Effacer] permite repor o formulário ao seu estado inicial:
![]() | ![]() |
No formulário, o link JS [Effacer] está definido da seguinte forma:
<a href="javascript:effacer()">Effacer</a>
A função JS [effacer] está definida no ficheiro [myScripts-05.js] da seguinte forma:
// dados globais
var content;
var loading;
function calculer() {
...
}
function retourSaisies() {
...
}
function effacer() {
// primeiro as referências no DOM
var formulaire = $("#formulaire");
var A = $("#A");
var B = $("#B");
// atribuem-se valores válidos aos dados introduzidos
A.val("0");
B.val("0");
// depois valida-se o formulário para que desapareçam
// as eventuais mensagens de erro
formulaire.validate().form();
// e, em seguida, atribuem-se cadeias de caracteres vazias aos campos de introdução de dados
A.val("");
B.val("");
}
// ao carregar o documento
$(document).ready(function () {
// recuperam-se as referências dos diferentes componentes da página
loading = $("#loading");
content = $("#content");
// ocultamos a imagem animada
loading.hide();
});
- linhas 15-17: recuperam-se referências a vários elementos do DOM (Document Object Model);
- linhas 19-20: inserem-se valores válidos nos campos de introdução dos números A e B;
- linha 23: executam-se os validadores do lado do cliente. Como os valores de A e B são válidos, isto fará com que desapareçam eventuais mensagens de erro que pudessem ser apresentadas;
- linhas 25-26: inserem-se cadeias vazias nos campos de introdução dos números A e B;
7.6.7. A ação do cliente [Retour aux Saisies]
O link JavaScript [Retour aux Saisies] permite regressar ao formulário após obter os resultados:
![]() | ![]() |
No formulário, o link JS [Retour aux Saisies] está definido da seguinte forma:
<a href="javascript:retourSaisies()">Retour aux saisies</a>
A função JS [retourSaisies] está definida no ficheiro [myScripts-05.js] da seguinte forma:
// dados globais
var content;
var loading;
function calculer() {
...
}
function retourSaisies() {
// efetua-se manualmente uma chamada Ajax
$.ajax({
url: '/Premier/Action05RetourSaisies',
type: 'POST',
dataType: 'html',
beforeSend: function () {
loading.show();
},
success: function (data) {
content.html(data);
},
complete: function () {
loading.hide();
// IMPORTANT !! validação
$.validator.unobtrusive.parse($("#formulaire"));
},
error: function (jqXHR) {
content.html(jqXHR.responseText);
}
})
}
function effacer() {
...
}
// ao carregar o documento
$(document).ready(function () {
// recuperam-se as referências dos diferentes componentes da página
loading = $("#loading");
content = $("#content");
// ocultamos a imagem animada
loading.hide();
});
- linhas 11-29: uma chamada Ajax;
- linha 12: o URL de destino;
- linha 13: será solicitada por um comando HTTP POST. Trata-se de um POST sem parâmetros enviados. É por isso que não se encontra uma linha do tipo:
na chamada Ajax;
- linha 14: o fluxo esperado do servidor é um fluxo HTML;
- linhas 18-20: este fluxo HTML servirá para atualizar a região com o ID [content];
A ação do servidor [Action05RetourSaisies] é a seguinte:
[HttpPost]
public PartialViewResult Action05RetourSaisies(SessionModel session)
{
// visualização
return PartialView("Formulaire05", new ViewModel05() { A = session.A, B = session.B });
}
- linha 2: a ação recebe como parâmetro o modelo da sessão no qual armazenámos anteriormente os valores de A e B introduzidos;
- linha 5: devolve-se a vista parcial [Formulaire05] com um modelo do tipo [ViewModel05], no qual se tem o cuidado de inicializar os campos A e B com os valores de A e B retirados da sessão;
Agora, voltemos ao código da função JavaScript [retourSaisies]:
function retourSaisies() {
// efetua-se manualmente uma chamada Ajax
$.ajax({
url: '/Premier/Action05RetourSaisies',
type: 'POST',
dataType: 'html',
beforeSend: function () {
loading.show();
},
success: function (data) {
content.html(data);
},
complete: function () {
loading.hide();
// IMPORTANT !! validação
$.validator.unobtrusive.parse($("#formulaire"));
},
error: function (jqXHR) {
content.html(jqXHR.responseText);
}
})
}
- linha 13: o método executado quando a chamada Ajax estiver concluída;
- linha 14: a imagem animada de espera é ocultada;
- linha 16: uma instrução um pouco obscura para mim, encontrada na Internet, para resolver o seguinte problema: no formulário apresentado pelo link [Retour aux saisies], os validadores do lado do cliente já não funcionavam. Ao procurar informações sobre a biblioteca JS [jquery.unobtrusive-ajax], encontrei a solução da linha 16. Esta analisa o formulário, talvez para ativar os validadores do lado do cliente.
7.7. Tornar acessível na Internet uma aplicação ASP.NET
Consulte o parágrafo 9.26.
7.8. Geração de uma aplicação nativa para Android a partir de uma aplicação de página única APU
Consulte o parágrafo 9.27.

















































