Skip to content

7. Ajaxificar uma aplicação ASP.NET MVC

7.1. O papel do AJAX numa aplicação Web

Por enquanto, os exemplos de aprendizagem que estudámos têm a seguinte arquitetura:

Para mudar de uma vista [View1] para uma vista [View2], o navegador:

  • envia um pedido à aplicação web;
  • recebe a vista [View2] e exibe-a no lugar da vista [View1].

Este é o padrão clássico:

  • pedido do navegador;
  • o servidor web gera uma vista em resposta ao cliente;
  • exibição desta nova vista pelo navegador.

Existe outro modo de interação entre o navegador e o servidor web: AJAX (Asynchronous JavaScript and XML). Isto envolve interações entre a visualização apresentada pelo navegador e o servidor web. O navegador continua a fazer o que faz de melhor — apresentar uma visualização HTML — mas agora é controlado por JavaScript incorporado na visualização HTML apresentada. O processo é o seguinte:

  • em [1], ocorre um evento na página exibida no navegador (clique num botão, alteração no texto, etc.). Este evento é interceptado pelo JavaScript (JS) incorporado na página;
  • em [2], o código JavaScript faz um pedido HTTP tal como o navegador teria feito. O pedido é assíncrono: o utilizador pode continuar a interagir com a página sem ficar bloqueado enquanto aguarda a resposta HTTP. O pedido segue o fluxo de processamento padrão. Nada (ou muito pouco) o distingue de um pedido padrão;
  • Em [3], é enviada uma resposta ao cliente JavaScript. Em vez de uma visualização HTML completa, é normalmente enviada uma visualização HTML parcial, um feed XML ou JSON (JavaScript Object Notation);
  • Em [4], o JavaScript recupera esta resposta e utiliza-a para atualizar uma região da página HTML apresentada.

Para o utilizador, há uma alteração na visualização porque o que vê mudou. No entanto, não ocorre uma atualização completa da página; em vez disso, ocorre apenas uma modificação parcial da página apresentada. Isto ajuda a tornar a página mais fluida e interativa: como não há uma atualização completa da página, podemos gerir eventos que anteriormente não podiam ser tratados. Por exemplo, oferecer ao utilizador uma lista de opções à medida que este digita caracteres num campo de entrada. Com cada novo caractere digitado, é enviada uma solicitação AJAX ao servidor, que, por sua vez, devolve sugestões adicionais. Sem o AJAX, este tipo de assistência à entrada era anteriormente impossível. Não podíamos recarregar uma nova página com cada caractere digitado.

7.2. Noções básicas de jQuery e JavaScript

Incluímos frequentemente a biblioteca JavaScript jQuery nas nossas páginas. Por exemplo, encontrará a seguinte linha:


  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>

Nota: Certifique-se de que a versão do jQuery corresponde à sua versão do Visual Studio.

A tecnologia Ajax do ASP.NET MVC utiliza o jQuery. Iremos escrever alguns scripts jQuery nós próprios. Por isso, vamos agora abordar os conceitos básicos do jQuery que precisa de saber para compreender os scripts deste capítulo.

Criamos um novo projeto [Example-04] dentro da nossa solução [Examples]:

Para utilizar o Ajax com o ASP.NET MVC, a seguinte linha deve estar presente no ficheiro de configuração [Web.config] [1]:


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

A linha 3 permite a utilização de 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 do jQuery (ajuste a versão para corresponder à sua versão do Visual Studio);
  • Linhas 10–12: um elemento de página com o ID [element1]. Iremos trabalhar com este elemento.

Visualize este ficheiro no navegador Google Chrome [4] e [5]:

No Google Chrome, prima [Ctrl-Shift-I] para abrir as ferramentas de programador [6]. O separador [Console] [7] permite-lhe executar código JavaScript. Abaixo, fornecemos comandos JavaScript para digitar e explicamos o que fazem.

JS
result
$("#element1")
: devolve a coleção de todos os elementos com o ID [element1], pelo que normalmente será uma coleção de 0 ou 1 elemento, uma vez que não é possível ter dois IDs idênticos numa página HTML.
$("#element1").text("blabla")
: define o texto [blabla] para todos os elementos da coleção. Isto altera o conteúdo exibido na página
$("#element1").hide()
oculta os elementos da coleção. O texto [blabla] deixa de ser exibido.
$("#element1")
: exibe a coleção novamente. 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.
$("#element1").show()
: exibe os elementos da coleção. O texto [blabla] aparece novamente. O atributo CSS style='display: block;' é responsável por esta exibição.
$("#element1").attr('style','color: red')
: define um atributo em todos os elementos da coleção. O atributo aqui é [style] e o seu valor é [color: red]. O texto [blabla] fica vermelho.
Tabela
Dicionário

Note que o URL do navegador não mudou durante todas estas operações. Não houve comunicação com o servidor web. Tudo acontece dentro do navegador. Agora, vamos ver o código-fonte da página:

Image


<!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 as alterações feitas ao elemento nas linhas 10–12. É importante ter isto em conta ao depurar JavaScript. Nesses casos, muitas vezes não é necessário visualizar o código-fonte da página apresentada. Para visualizar o código-fonte da página atualmente apresentada, proceda da seguinte forma:

Image

Agora sabemos o suficiente para compreender os scripts JS que se seguem.

7.3. Atualizar uma página com um feed HTML

7.3.1. As Visualizações

Vamos examinar a seguinte aplicação:

  • em [1], o tempo de carregamento da página;
  • em [2], realizamos as quatro operações aritméticas em dois números reais A e B;
  • em [3], a resposta do servidor é apresentada numa região da página;
  • em [4], o tempo do cálculo. Este é diferente do tempo de carregamento da página [5]. Este último é igual a [1], o que mostra que a região [6] não foi recarregada. Além disso, o URL da página [7] não se alterou.

7.3.2. O controlador, as ações, o modelo, a vista

Criamos um controlador chamado [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 de visualização;
  • linha 5: inicialização do tempo 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
  {
    // form
    [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; }
 
    // results
    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 em A e B;
  • linha 25: o texto de qualquer erro;
  • linha 26: a hora em que a vista foi carregada no navegador;
  • linha 27: a hora em que os campos nas linhas 21–24 foram calculados;
  • linha 7: este modelo de vista é também um modelo de ação. Os campos que não são enviados pelo navegador são excluídos deste último.

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 baseia-se num tipo [ViewModel01];
  • linha 21: o jQuery é necessário tanto para a validação 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: exibe o tempo de carregamento da vista;
  • linha 35: um formulário Ajax — voltaremos a este ponto;
  • linhas 40–41: rótulos para os campos de entrada dos números A e B;
  • linhas 46–47: campos de entrada para os números A e B;
  • linhas 50-51: mensagens de erro para as entradas numéricas A e B;
  • linha 56: o botão que envia o formulário. Este será enviado através de um pedido Ajax;
  • linha 57: uma imagem de carregamento exibida durante a solicitação Ajax;
  • linha 58: um link para enviar o formulário com uma solicitação Ajax;
  • linha 62: uma tag div com o id [results]. É aqui que colocaremos a saída HTML devolvida pelo servidor web.

Esta vista apresenta a seguinte página:

Image

Agora vamos examinar o código que lida com a funcionalidade Ajax do 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 usar [@Html.BeginForm], usamos [@Ajax.BeginForm]. Este método suporta várias sobrecargas. A utilizada tem a seguinte assinatura:

Ajax.BeginForm(string ActionName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string,object> htmlAttributes)

Aqui, utilizamos os seguintes parâmetros reais:

Action01Post: o nome da ação que irá processar o pedido POST do formulário,

null: não há informações de rota a fornecer,

ajaxOpts: as opções para a chamada Ajax. Foram definidas nas linhas 6–10,

new { id = "form" }: para atribuir o atributo [id='form'] à tag <form> gerada;

As opções Ajax utilizadas são as seguintes:

  • linha 8: o URL de destino da solicitação HTTP Ajax;
  • linha 7: método da solicitação HTTP Ajax;
  • linha 6: ID da região da página que será atualizada pela resposta à solicitação Ajax;
  • linha 9: o ID da região da página que será exibida durante a solicitação Ajax — geralmente uma imagem de carregamento. Aqui, a linha 20 será exibida. Ela contém uma imagem animada simbolizando um estado de carregamento. Inicialmente, esta imagem está oculta pelo estilo [display: none];
  • linha 10: tempo de espera em milissegundos antes da 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 tag <form> gerada. Repare nos atributos [data-ajax-attr], que refletem os valores dos campos no objeto [AjaxOptions] associado ao pedido Ajax. Estes atributos são geridos pela biblioteca Ajax. Sem eles, a tag <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>

Este é um formulário HTML padrão. Este código será executado se o utilizador desativar o JavaScript no seu navegador. As linhas 5–6 ficam então sem uso.

7.3.3. A ação [Action01Post]

A ação [Action01Post] que trata do pedido HTTP Ajax é a seguinte:


    [HttpPost]
    public PartialViewResult Action01Post(FormCollection postedData, SessionModel session)
    {
      // wait simulation
      Thread.Sleep(2000);
      // action model instantiation
      ViewModel01 modèle = new ViewModel01();
      // calculation time
      modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
      // model update
      TryUpdateModel(modèle, postedData);
      if (!ModelState.IsValid)
      {
        // returns an error
        modèle.Erreur = getErrorMessagesFor(ModelState);
        return PartialView("Action01Error", modèle);
      }
      // every other time, an error is simulated
      int val = session.Randomizer.Next(2);
      if (val == 0)
      {
        modèle.Erreur = "[erreur aléatoire]";
        return PartialView("Action01Error", modèle);
      }
      // calculations
      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);
      // view
      return PartialView("Action01Success", modèle);
}

  • linha 1: a ação apenas processa um [POST];
  • linha 2: aceita o seguinte modelo de ação:
    • [FormCollection postedData]: o conjunto de valores enviados pela solicitação POST do Ajax,
    • [SessionModel session]: os elementos da sessão. Aqui, utilizamos uma técnica descrita na secção 4.10;
  • linha 2: a ação irá devolver um fragmento HTML em vez de uma página HTML completa;
  • linha 5: artificialmente, fazemos 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: tentamos atualizar o modelo [ViewModel01] com os valores enviados. Recorde-se que existem dois: os valores dos números A e B;
  • linha 12: verificamos se esta atualização foi bem-sucedida;
  • linha 15: se ocorrer um erro, o campo [Error] do modelo é preenchido;
  • linha 16: é renderizada uma vista parcial [Action01Error.cshtml] utilizando o modelo [ViewModel01];
  • linhas 19–24: a cada duas vezes, é simulado um erro;
  • linha 19: é gerado um número inteiro aleatório no intervalo [0,1]. O gerador de números aleatórios é obtido da sessão;
  • linha 20: se o valor gerado for 0, é simulado um erro;
  • linha 22: a mensagem de erro é colocada no modelo;
  • linha 23: é renderizada uma vista parcial [Action01Error.cshtml] utilizando o modelo [ViewModel01];
  • linhas 26–29: são realizados cálculos aritméticos nos números A e B, e os resultados são colocados no modelo de visualização como cadeias de caracteres;
  • linha 31: uma vista parcial [Action01Success.cshtml] é renderizada utilizando 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>

Note que este fragmento de HTML será enviado em resposta ao pedido HTTP Ajax do tipo POST e inserido na página, na área com o ID [results]. Toda esta informação 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
  };    
}

Aqui está um exemplo de uma resposta com um erro:

Image

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 HTML parcial será enviado em resposta ao pedido HTTP Ajax do tipo POST e colocado na página na região com o id [results]:

Image

7.3.6. G estão de Sessões

Vimos que [Action01Post] utiliza a sessão. O modelo de sessão é do seguinte tipo [SessionModel]:


using System;
namespace Exemple_03.Models
{
  public class SessionModel
  {
    public Random Randomizer { get; set; }
  }
}

A sessão é inicializada em [Global.asax]:


    // Session
    protected void Session_Start()
    {
      SessionModel sessionModel=new SessionModel();
      sessionModel.Randomizer=new Random(DateTime.Now.Millisecond);
      Session["data"] = sessionModel;
}

A sessão está associada a um modelo em [Application_Start]:


    protected void Application_Start()
    {
...
      // model binders
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}

A classe [SessionModelBinder] foi descrita.

7.3.7. Gerir a imagem de espaço reservado


@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 o pedido Ajax é iniciado, a região com o id [loading] na linha 7 é apresentada após um segundo [linha 8]. Esta região corresponde à imagem da linha 21, que estava inicialmente oculta. Isto resulta na seguinte interface:

Vamos examinar o link [Calculate] na 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: clicar no link [Calcular] aciona 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() {
  // on fait un appel Ajax à la main avec JQuery
  var loading = $("#loading");
  var formulaire = $("#formulaire");
  var résultats = $('#results');
  $.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) {
    //Use the Globalization plugin to parse the value        
    var val = Globalize.parseFloat(value);
    return this.optional(element) || (
        val >= param[0] && val <= param[1]);
  }
});

As funções nas linhas 19–37 já foram abordadas na Secção 6.1. Elas tratam da internacionalização das páginas. Não vamos voltar a abordá-las aqui. Nas linhas 1–17, efetuamos manualmente a chamada Ajax, que, no caso do botão [Calcular], era anteriormente tratada pela biblioteca Ajax do projeto. Para isso, utilizamos a biblioteca jQuery do projeto.

  • Linha 3: uma referência ao componente com o ID [loading]. [$("#loading")] devolve o conjunto de elementos com o ID [loading]. Existe apenas um;
  • Linha 4: uma referência ao componente com o ID [form];
  • linha 5: uma referência ao componente com o ID [results];
  • Linha 6: A chamada Ajax com as suas opções;
  • Linha 7: o URL de destino da chamada Ajax;
  • Linha 8: o método HTTP utilizado;
  • linha 9: os dados enviados. [form.serialize] cria a string POST [A=val1&B=val2] para o formulário com o ID [form];
  • linha 10: o tipo de dados esperado na resposta. Sabemos que o servidor irá devolver um fluxo HTML;
  • linha 11: o método a executar quando o pedido for iniciado. Aqui, especificamos que o componente com o ID [loading] deve ser apresentado. Esta é a imagem animada de carregamento;
  • linha 12: o método a executar se a solicitação Ajax for bem-sucedida. O parâmetro [data] é a resposta completa do servidor. Sabemos que se trata de um fluxo HTML;
  • linha 13: ocultamos o indicador de carregamento;
  • linha 14: atualizamos o componente com o ID [results] com o HTML do parâmetro [data].

Convidamos o leitor a testar o link [Calculate]. Funciona exatamente como o botão [Calculate], com a exceção da mensagem de erro " ." Depois de utilizar este link, podem ser introduzidos valores inválidos para A e B:

  • em [1] e [2], introduzimos valores inválidos. Estes são sinalizados pelos validadores do lado do cliente;
  • em [3], clicámos no link [Calcular];
  • em [4], ocorreu um [POST], uma vez que recebemos a resposta [4].

Quando os valores são inválidos e clicamos no botão [Calcular], o pedido [POST] para o servidor não ocorre. No mesmo cenário, utilizar o link [Calcular] aciona a solicitação [POST] para o servidor. Existe, portanto, um comportamento do botão [Calcular] que não conseguimos reproduzir com o link [Calcular]. Em vez de tentar resolver este problema agora, vamos deixá-lo para um exemplo posterior que também ilustrará outra questão de validação do lado do cliente.

7.4. Atualizar uma página HTML com um feed JSON

No exemplo anterior, o servidor web respondeu à solicitação HTTP Ajax com um fluxo HTML. Esse fluxo continha dados acompanhados de formatação HTML. Vamos revisitar o exemplo anterior, desta vez usando respostas JSON (JavaScript Object Notation) que contêm apenas os dados. A vantagem é que menos bytes são transmitidos.

7.4.1. A ação [Action02Get]

A ação [Action02Get] será o ponto de entrada para a 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 de chamada Ajax;
  • linha 10: a função JS a executar quando o pedido for iniciado. Esta função está definida no ficheiro JS referenciado na linha 24;
  • linha 11: a função JS a executar se a solicitação falhar;
  • linha 12: a função JS a executar se a solicitação for bem-sucedida;
  • linha 13: a função JS a executar após a solicitação Ajax ter retornado o seu resultado (falha ou sucesso);
  • linhas 40–43: uma região com o ID [header];
  • linhas 44–49: uma região com o ID [results]. Apresentará os resultados das quatro operações aritméticas;
  • linhas 50–52: uma região com o ID [error]. Apresentará quaisquer mensagens de erro.

7.4.2. A ação [Action02Post]

O pedido Ajax é processado pela seguinte ação [Action02Post]:


[HttpPost]
    public JsonResult Action02Post(FormCollection postedData, SessionModel session)
    {
      // wait simulation
      Thread.Sleep(2000);
      // model validation
      ViewModel02 modèle = new ViewModel02();
      // loading and calculation times
      string HeureChargement = DateTime.Now.ToString("hh:mm:ss");
      string HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
      // model update
      TryUpdateModel(modèle, postedData);
      if (!ModelState.IsValid)
      {
        // returns an error
        return Json(new { Erreur = getErrorMessagesFor(ModelState), HeureCalcul = HeureCalcul });
      }
      // every other time, an error is simulated
      int val = session.Randomizer.Next(2);
      if (val == 0)
      {
        // returns an error
        return Json(new { Erreur = "[erreur aléatoire]", HeureCalcul = HeureCalcul });
      }
      // calculations
      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);
      // we return the results
      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, texto no formato JSON;
  • linha 16: a informação é devolvida como uma instância de classe anónima serializada para JSON. O método [getErrorMessagesFor] já foi apresentado. A cadeia JSON enviada para o navegador terá o seguinte formato:
{"Erreur":"[erreur aléatoire]","HeureCalcul":"05:31:37"}
  • linha 31: mesma abordagem para resultados aritméticos. Desta vez, a cadeia JSON enviada para o 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

Vamos rever a configuração da chamada Ajax na página HTML enviada para o 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]:


// global data
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() {
...
}
 
// document loading
$(document).ready(function () {
  formulaire = $("#formulaire");
  entete = $("#entete");
  loading = $("#loading");
  erreur = $("#erreur");
  résultats = $('#résultats');
  heureCalcul = $("#heureCalcul");
  msg = $("#msg");
  AplusB = $("#AplusB");
  AmoinsB = $("#AmoinsB");
  AmultipliéparB = $("#AmultipliéparB");
  AdiviséparB = $("#AdiviséparB");
 
  // hide certain page elements
  entete.hide();
  résultats.hide();
  erreur.hide();
});
 
// start
function OnBegin() {
....
}
 
// end of query
function OnComplete() {
...
}
 
// success
function OnSuccess(data) {
....
}
 
// error
function OnFailure(request, error) {
...
}
  • linha 19: a função JS executada quando a página termina de carregar no navegador;
  • linhas 20–30: recuperamos as referências de todos os componentes da página que nos interessam. A pesquisa de um componente numa página tem um custo, por isso é melhor fazer isto apenas uma vez;
  • linhas 33–35: os componentes [header], [results] e [loading] são ocultados;

Quando a solicitação Ajax é iniciada, a seguinte função é executada:


// start
function OnBegin() {
  // wait signal on
  loading.show();
  // hide certain page elements
  entete.hide();
  résultats.hide();
  erreur.hide();
}
  • linha 4: o componente [loading] é exibido. Esta é a imagem animada;
  • linhas 6–8: os componentes [header], [results] e [error] são ocultados;

Se a solicitação Ajax for bem-sucedida, o seguinte código JS é executado:


// réussite
function OnSuccess(data) {
  // affichage résultats
  heureCalcul.text(data.HeureCalcul);
  entete.show();
  if (data.Erreur != '') {
    msg.text(data.Erreur);
    erreur.show();
    return;
  }
  // pas d'erreur
  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 recordar as duas cadeias JSON que podem ser enviadas em resposta ao navegador:

{"Erreur":"[erreur aléatoire]","HeureCalcul":"05:31:37"}

em caso de erro; caso contrário, a string:

{"Erreur":"","AplusB":"4","AmoinsB":"-2","AmultipliéparB":"3","AdiviséparB":"0,333333333333333","HeureCalcul":"05:52:17"}

Se chamarmos a esta cadeia [data], o valor do campo [Error] é obtido utilizando a notação [data.Error] ou [data["Error"]], conforme desejado. O mesmo se aplica aos outros campos na cadeia JSON. Além disso, para atribuir texto não formatado a um componente com ID X, escrevemos [X.text(string)]. Voltemos ao código da função [OnSuccess]:

  • linha 2: [data] é a string JSON recebida;
  • linha 4: o componente [calculationTime] recebe o seu valor;
  • linha 5: o componente [entete] é exibido;
  • linha 6: verifica o campo [Error] da string JSON;
  • linha 7: o componente [msg] recebe o seu valor;
  • linha 8: o componente [error] é exibido;
  • linha 9: e pronto, para o caso de erro;
  • linha 12: o componente [AplusB] recebe o seu valor;
  • linha 13: o componente [AminusB] recebe o seu valor;
  • linha 14: o componente [AmultipliedbyB] recebe o seu valor;
  • linha 15: é atribuído um valor ao componente [AdividedbyB];
  • linha 16: o componente [results] é exibido.

A função [ OnFailure] será executada se a solicitação HTTP Ajax falhar. Esta falha é determinada pelo código de estado HTTP devolvido pelo servidor. Por exemplo, um código 500 [Erro interno do servidor] indica que o servidor não conseguiu processar a solicitação. A função [OnFailure] é a seguinte:


// error
function OnFailure(request, error) {
  alert("L'erreur suivante s'est produite :" + error);
}

Simplesmente exibimos uma caixa de diálogo com o erro que ocorreu. Na prática, devemos ser mais específicos. Em breve, iremos propor outra solução.

Por fim, a função [OnComplete] é executada quando o pedido é concluído, quer tenha sido bem-sucedido ou tenha falhado.


// end of query
function OnComplete() {
  // wait signal off
  loading.hide();
}

Note que é a configuração da chamada Ajax na vista [Action02Get.cshtml] que faz com que estas várias funções sejam chamadas:


  AjaxOptions ajaxOpts = new AjaxOptions
  {
...
    OnBegin = "OnBegin",
    OnFailure = "OnFailure",
    OnSuccess = "OnSuccess",
    OnComplete = "OnComplete"
};

O código HTML para o link [Calculate] 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() {
  // make a manual Ajax call with 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 código semelhante.

  • linha 4: URL de destino da chamada Ajax;
  • linha 5: método HTTP utilizado pela chamada Ajax;
  • linha 6: valores enviados. Estes são o resultado da serialização dos valores do formulário. O formulário, identificado pelo id [form], é referenciado pela variável [form]. [data] será uma string no formato [A=val1&B=val2];
  • linha 7: tipo de formato de resposta esperado. Trata-se de uma string JSON;
  • linha 8: função JS a executar quando a chamada Ajax for iniciada;
  • 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 executar assim que a resposta do servidor for recebida, independentemente de ser um sucesso ou um erro.

Vamos rever a função JavaScript que lida com 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:


// error
function OnFailure(request, error) {
  alert("L'erreur suivante s'est produite :" + error);
}

Geralmente, a exibição do objeto [error] não fornece nenhuma informação útil. Se estiver a utilizar uma chamada Ajax feita com jQuery, pode utilizar o seguinte método [OnFailure];


// error
function OnFailure(jqXHR) {
  alert("Erreur : " + jqXHR.status + " " + jqXHR.statusText);
  msg.html(jqXHR.responseText);
  erreur.show();
}

O objeto jQuery [jqXHR] possui as seguintes propriedades:

  • 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: exibimos o código de erro e a mensagem correspondente;
  • Linha 4: guardamos a resposta HTML do servidor no componente com o ID [msg];
  • Linha 5: exibimos a região com o ID [error].

Para testar este tratamento de erros, vamos criar artificialmente uma exceção na ação [Action02Post]:


    [HttpPost]
    public JsonResult Action02Post(FormCollection postedData, SessionModel session)
    {
      // an artificial exception to test the Ajax call error function
      throw new Exception();
      // wait simulation
      Thread.Sleep(2000);
      // model validation
...

A linha 5 lança uma exceção. Agora vamos testar a aplicação:

Recebemos as seguintes respostas [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 lidar com erros nas chamadas Ajax.

7.5. Aplicação Web de Página Única

A tecnologia Ajax permite-nos criar aplicações de página única:

  • a primeira página é carregada através de um pedido normal do navegador;
  • as páginas subsequentes são obtidas através de chamadas Ajax. Como resultado, o navegador nunca altera o seu URL e nunca carrega uma nova página. Este tipo de aplicação é designado por Aplicação de Página Única (SPA).

Aqui está um exemplo básico de uma aplicação deste tipo. A nova aplicação terá duas vistas:

  • em [1], a ação [Action03Get] apresenta a primeira página, a página 1;
  • em [2], um link permite-nos navegar para a página 2 através de uma chamada Ajax;
  • em [3], o URL não mudou. 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 mudou. A página apresentada é a página 1.

O código para a 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]. Este é o elemento onde as diferentes páginas serão exibidas;
  • linha 17: por predefinição, a página [Page1.cshtml] será apresentada 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 distinguir da página 2;
  • linha 3: um link Ajax com os seguintes parâmetros:
    • o rótulo do link [Página 2];
    • a ação de destino do link [Ação04];
    • os parâmetros do URL solicitado. Este será [/Premier/Action04?Page=2];
  • as opções de chamada Ajax. Aqui, apenas o ID da região a ser atualizada com a resposta do servidor. Para as outras opções, são utilizados valores padrão quando existem. O método HTTP padrão é GET.

Vamos ver o que acontece quando se clica no link. A URL [/Premier/Action04?Page=2] é solicitada através de uma solicitação 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 utiliza a string [page] como seu modelo. Sabemos que o URL contém esta informação: [/Premier/Action04?Page=2]. Note que o modelo não distingue maiúsculas de minúsculas;
  • linhas 4–8: [view] receberá o valor [Page2];
  • linha 9: a vista parcial [Page2.cshtml] é renderizada.

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 ao pedido GET Ajax [/Premier/Action04?Page=2]. Recorde-se que este pedido 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 apresentação [1]:

Seguindo a mesma lógica, vemos que clicar no link [Página 1] em [1] exibirá [2].

Voltemos ao diagrama geral de uma aplicação ASP.NET MVC:

Graças ao JavaScript incorporado nas páginas HTML e executado no navegador, podemos transferir código para o navegador e obter a seguinte arquitetura:

  • em [1], a camada web ASP.NET MVC tornou-se uma interface web para aceder a dados, normalmente armazenados numa base de dados. As vistas devolvidas contêm apenas dados e nenhuma marcação HTML, tais como feeds 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 vistas estáticas são então enriquecidas com 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 [de apresentação] lida com as interações do utilizador,
    • a camada [DAO] lida com o acesso aos dados através do servidor web [1],
    • a camada [lógica de negócio] corresponde à camada [lógica de negócio] que anteriormente se encontrava no servidor [1] e foi transferida para o navegador [2];

A vantagem desta arquitetura é que ela recorre a diferentes conjuntos de competências:

  • o código do servidor web [1] requer competências em .NET, mas não em JavaScript, HTML ou CSS;
  • o código incorporado no navegador [2] requer conhecimentos de JavaScript, HTML e CSS, mas é independente da tecnologia do servidor web [1].

Assim, esta arquitetura facilita o trabalho em paralelo por equipas com diferentes conjuntos de competências. É particularmente adequada para Aplicações de Página Única.

7.6. Aplicação Web de Página Única e Validação do Lado do Cliente

Mencionámos anteriormente uma anomalia no exemplo Ajax-01. Aqui está um resumo do contexto:

  • em [1] e [2], foram introduzidos valores inválidos. Estes são sinalizados pelos validadores do lado do cliente;
  • em [3], clicámos no link [Calcular];
  • em [4], ocorreu um [POST], uma vez que recebemos a resposta [4].

Quando os valores são inválidos e o botão [Calcular] é clicado, o pedido [POST] para o servidor não ocorre. No mesmo cenário, clicar no link [Calcular] desencadeia o pedido [POST] para o servidor. Portanto, existe um comportamento do botão [Calcular] que não conseguimos reproduzir utilizando o link [Calcular].

Vamos revisitar este exemplo num novo contexto: a aplicação terá várias vistas e será do tipo [Aplicação de Página Única] que acabámos de descrever.

7.6.1. As vistas no exemplo

O exemplo tem várias vistas:

  • em [1], a vista [Action05Get];
  • em [2], a vista parcial [Form05];
  • em [3], a vista parcial [Failure05];

  • em [4], a vista parcial [Success05].

A aplicação é uma aplicação de página única: a página é carregada pelo navegador durante o primeiro pedido. É depois 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>

Tenha em atenção os seguintes pontos:

  • linha 1: o modelo de visualização é do tipo [ViewModel05], que abordaremos em breve;
  • linhas 13–19: estas contêm os scripts JavaScript necessários para Ajax e validação do lado do cliente;
  • linha 20: iremos adicionar as nossas próprias funções JavaScript em [myScripts-05.js];
  • linha 27: a imagem de carregamento animada;
  • linhas 28–30: uma tag `id` [content]. É aqui que as vistas parciais [Form05, Success05, Failure05] serão inseridas;
  • linha 29: inserção da vista parcial [Form05].

A vista [Action05Get] é responsável por exibir a secção [1] da página inicial:

A vista parcial [Formulaire05] irá gerar a secção [2] acima. 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 aceita um tipo [ViewModel05] como seu modelo;
  • 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. Estamos a ignorar essa possibilidade aqui. O quarto parâmetro é importante. O formulário terá o ID [form];
  • linhas 5–22: o formulário para introduzir os números A e B;
  • linha 27: um link JavaScript que aciona a execução das quatro operações aritméticas em A e B;
  • linha 30: um link JavaScript que limpa as entradas e quaisquer mensagens de erro associadas.

Note que o formulário não tem um botão [submit]. Teremos de enviar manualmente [Post] os valores introduzidos de A e B.

Se não houver erros, os resultados são apresentados:

A secção [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 campos de entrada.

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 campos de entrada.

7.6.2. O modelo de visualização

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
  {
    // form
    [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; }
 
    // results
    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; }
  }
}

Este é o modelo [ViewModel01] já apresentado, com algumas pequenas alterações:

  • linhas 15 e 19: os campos A e B são agora do tipo [string], para que sejam apresentados campos de entrada vazios em vez de campos com o valor 0 quando o formulário de entrada é apresentado pela primeira vez;
  • linhas 14 e 18: isto não impede que o valor introduzido seja validado utilizando um validador [Range];
  • linha 26: uma lista de mensagens de erro apresentadas pela vista [Failure05].

7.6.3. Dados do âmbito [Session]

Na Secção 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
  {
    // the random number generator
    public Random Randomizer { get; set; }
  }
}

Este modelo de sessão é alargado para incluir os valores de A e B:


using System;
namespace Exemple_03.Models
{
  public class SessionModel
  {
    // the random number generator
    public Random Randomizer { get; set; }
    // the values of A and B
    public string A { get; set; }
    public string B { get; set; }
  }
}

É, de facto, necessário armazenar os valores de A e B na sessão, conforme mostrado na sequência seguinte:

Pedido 1

Pedido 2

Em [4], vemos as entradas feitas em [1]. No entanto, existem duas solicitações HTTP distintas. Sabemos que a sessão funciona como a memória entre duas solicitações HTTP. Para que a segunda solicitação recupere os valores enviados pela primeira, esses valores devem estar armazenados na sessão.

7.6.4. A ação do servidor [Action05Get]

A ação [Action05Get] é a ação que apresenta a página única inicial. 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], que já discutimos, é apresentada com um modelo do tipo [ViewModel05];

7.6.5. A ação do cliente [Calculate]

Vamos examinar as interações do utilizador com as vistas:

O link [1] é um link JavaScript:


<a href="javascript:calculer()">Calculer</a>

A função JavaScript [calculate] encontra-se no ficheiro [myScripts-05.js]:


  <script type="text/javascript" src="~/Scripts/myScripts-05.js"></script>

O código para a função JavaScript [calculate] é o seguinte:


// global data
var content;
var loading;
 
function calculer() {
  // first the references on DOM
  var formulaire = $("#formulaire");
  // then validate the form
  if (!formulaire.validate().form()) {
    // invalid form - terminated
    return;
  }
  // make a manual Ajax call
  $.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) {
      // display server response
      content.html(jqXHR.responseText);
    }
  })
}
 
function retourSaisies() {
 ...
}
 
function effacer() {
  ...
}
 
// document loading
$(document).ready(function () {
  // retrieve the references of the page's various components
  loading = $("#loading");
  content = $("#content");
  // we hide the moving image
  loading.hide();
});
  • Note que o código JavaScript é sempre executado no 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 id [loading];
  • linha 47: referência à região com id [content]. Esta região recebe as visualizações parciais [Form05, Success05, Failure05];
  • linhas 2-3: as variáveis das linhas 46-47 são declaradas globais para que outras funções possam aceder-lhes. Existe um custo associado à pesquisa de elementos numa página (linhas 46-47). Não há necessidade de repetir esta pesquisa se for possível evitá-la;
  • linha 5: a função [calculate];
  • linha 7: é recuperada uma referência ao formulário. A vista parcial [Form05] atribuiu-lhe o ID [form];
  • Linha 9: Esta instrução executa os validadores do formulário do lado do cliente. Era isto que faltava na questão identificada na página 176. Este método é fornecido pela biblioteca [jquery.unobtrusive-ajax] utilizada pela aplicação de página única:

  <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>

A instrução retorna [false] se o formulário for declarado inválido;

  • linha 11: a chamada Ajax para o servidor não é efetuada se o formulário for inválido;
  • linhas 14–32: a chamada Ajax é feita ao servidor;
  • linha 15: o URL de destino é a ação do servidor [Action05FaireCalcul];
  • linha 16: é solicitada através de um [POST];
  • linha 17: os valores enviados. Estes são os dados do formulário, neste caso os valores de A e B;
  • linhas 22–24: se a chamada Ajax for bem-sucedida, a função [calculate] atualiza a região com 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 para esta ação do lado do servidor é o seguinte:


    [HttpPost]
    public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
    {
      // model
      ViewModel05 modèle = new ViewModel05();
      // calculation time
      modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
      // model update
      TryUpdateModel(modèle, postedData);
      if (!ModelState.IsValid)
      {
        // returns an error
        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 os valores enviados (postedData) e o modelo de sessão (session) como parâmetros;
  • linha 5: o modelo da vista parcial é criado;
  • linha 7: é atualizado com a hora do cálculo;
  • linha 9: tentamos aplicar os valores enviados ao modelo. Os seus validadores serão então executados. Poder-se-á questionar 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 feito por código que não é nosso. Por isso, devemos sempre realizar validações do lado do servidor;
  • linha 10: verificamos se os validadores foram bem-sucedidos;
  • linha 13: se o modelo for inválido, atualizamo-lo com uma lista de erros. Não entraremos em detalhes sobre o método interno [getListOfMessagesFor], que é análogo ao método [GetErrorMessagesFor] descrito na página 60;
  • linha 14: a vista parcial [Failure05] é apresentada com o seu modelo. Aqui está o código para esta 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 formulário é apresentada utilizando uma tag <ul>.

Lembre-se de que a função JS [calculate], que aciona o [Post] para a ação do servidor [Action05FaireCalcul], colocará esta saída HTML na região com o id [content]. O resultado é algo semelhante a isto:

Vamos continuar a examinar o código da ação [Action05FaireCalcul]:


    [HttpPost]
    public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
    {
      // model
      ViewModel05 modèle = new ViewModel05();
...
      // we put the values of A and B in session
      session.A = modèle.A;
      session.B = modèle.B;
      // no errors so far
      List<string> erreurs = new List<string>();
      // every other time, an error is simulated
      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);
      }
      // calculations
      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);
      // view
      return PartialView("Success05", modèle);
}
  • linha 7: o modelo foi declarado válido;
  • linhas 8-9: os valores introduzidos A e B são armazenados na sessão. Pretendemos poder recuperá-los na consulta seguinte;
  • linhas 11–22: é gerado um erro aleatoriamente a cada duas vezes;
  • linhas 24–29: realizamos as quatro operações aritméticas nos números reais introduzidos;
  • linha 31: a vista parcial [Success05] é devolvida 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>

Lembre-se de que a função JS [calculate], que aciona a ação [Post] para o servidor [Action05FaireCalcul], irá colocar este fluxo HTML na região com o id [content]. O resultado será algo semelhante a isto:

7.6.6. O botão [Clear]

O link JavaScript [Clear] repõe o formulário ao seu estado inicial:

Image

Image

No formulário, o link JS [Limpar] é definido da seguinte forma:


<a href="javascript:effacer()">Effacer</a>

A função JS [clear] está definida no ficheiro [myScripts-05.js] da seguinte forma:


// global data
var content;
var loading;
 
function calculer() {
...
}
 
function retourSaisies() {
...
}
 
function effacer() {
  // first the references on DOM
  var formulaire = $("#formulaire");
  var A = $("#A");
  var B = $("#B");
  // valid values are assigned to entries
  A.val("0");
  B.val("0");
  // then validate the form to make
  // any error msg
  formulaire.validate().form();
  // then assign empty strings to the input fields
  A.val("");
  B.val("");
}
 
// document loading
$(document).ready(function () {
  // retrieve the references of the page's various components
  loading = $("#loading");
  content = $("#content");
  // we hide the moving image
  loading.hide();
});
  • linhas 15-17: recuperamos referências a vários elementos do DOM (Document Object Model);
  • linhas 19-20: definimos valores válidos nos campos de entrada para os números A e B;
  • linha 23: executa os validadores do lado do cliente. Uma vez que os valores de A e B são válidos, isto irá remover quaisquer mensagens de erro que possam ser apresentadas;
  • linhas 25–26: são inseridas cadeias de caracteres vazias nos campos de entrada para os números A e B;

7.6.7. A ação do lado do cliente [Voltar aos campos de entrada]

O link JavaScript [Voltar aos campos de entrada] permite-lhe regressar ao formulário após obter os resultados:

Image

Image

No formulário, o link JS [Voltar à entrada] é 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:


// global data
var content;
var loading;
 
function calculer() {
...
}
 
function retourSaisies() {
  // make a manual Ajax call
  $.ajax({
    url: '/Premier/Action05RetourSaisies',
    type: 'POST',
    dataType: 'html',
    beforeSend: function () {
      loading.show();
    },
    success: function (data) {
      content.html(data);
    },
    complete: function () {
      loading.hide();
      // IMPORTANT!!! validation
      $.validator.unobtrusive.parse($("#formulaire"));
    },
    error: function (jqXHR) {
      content.html(jqXHR.responseText);
    }
  })
}
 
function effacer() {
...
}
 
// document loading
$(document).ready(function () {
  // retrieve the references of the page's various components
  loading = $("#loading");
  content = $("#content");
  // we hide the moving image
  loading.hide();
});

  • linhas 11–29: uma chamada Ajax;
  • linha 12: o URL de destino;
  • linha 13: será solicitada através de uma solicitação HTTP POST. Esta é uma solicitação POST sem parâmetros. É por isso que não existe uma linha como:
    data: formulaire.serialize(),

na chamada Ajax;

  • linha 14: a resposta esperada do servidor é um fluxo HTML;
  • linhas 18–20: este fluxo HTML será utilizado para atualizar a região com o ID [content];

A ação do servidor [Action05RetourSaisies] é a seguinte:


    [HttpPost]
    public PartialViewResult Action05RetourSaisies(SessionModel session)
    {
      // view
      return PartialView("Formulaire05", new ViewModel05() { A = session.A, B = session.B });
}
  • linha 2: a ação recebe como parâmetro o modelo de sessão no qual armazenámos previamente os valores introduzidos de A e B;
  • linha 5: devolvemos a vista parcial [Form05] com um modelo do tipo [ViewModel05], no qual nos certificamos 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 [returnInput]:


function retourSaisies() {
  // make a manual Ajax call
  $.ajax({
    url: '/Premier/Action05RetourSaisies',
    type: 'POST',
    dataType: 'html',
    beforeSend: function () {
      loading.show();
    },
    success: function (data) {
      content.html(data);
    },
    complete: function () {
      loading.hide();
      // IMPORTANT!!! validation
      $.validator.unobtrusive.parse($("#formulaire"));
    },
    error: function (jqXHR) {
      content.html(jqXHR.responseText);
    }
  })
}
  • linha 13: o método executado quando a chamada Ajax é concluída;
  • linha 14: a imagem animada de carregamento é ocultada;
  • linha 16: uma instrução um tanto enigmática que encontrei online para resolver o seguinte problema: no formulário exibido pelo link [Voltar à Entrada], os validadores do lado do cliente já não estavam a funcionar. Enquanto procurava informações sobre a biblioteca JS [jquery.unobtrusive-ajax], encontrei a solução na linha 16. Ela analisa o formulário, talvez para ativar os validadores do lado do cliente.

7.7. Tornar uma aplicação ASP.NET acessível na Internet

Consulte a secção 9.26.

7.8. Gerar uma aplicação Android nativa a partir de uma aplicação APU de página única

Consulte a secção 9.27.