Skip to content

7. Implementação de Ajax em um aplicativo ASP.NET MVC

7.1. O papel do AJAX em um aplicativo web

Por enquanto, os exemplos de aprendizado estudados apresentam a seguinte arquitetura:

Para passar de uma visualização [Vue1] para uma visualização [Vue2], o navegador:

  • envia uma solicitação para a aplicação web;
  • recebe a visualização [Vue2] e a exibe no lugar da visualização [Vue1].

Esse é o esquema clássico:

  • solicitação do navegador;
  • geração de uma visualização pelo servidor web em resposta ao cliente;
  • exibição dessa nova visualização 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 visualização exibida pelo navegador e o servidor web. O navegador continua fazendo o que sabe fazer, ou seja, exibir uma visualização HTML, mas agora é controlado por JavaScript incorporado na visualização HTML exibida. O esquema é o seguinte:

  • em [1], ocorre um evento na página exibida no navegador (clique em um botão, alteração de um texto, etc.). Esse evento é interceptado pelo JavaScript (JS) incorporado à página;
  • em [2], o código JavaScript faz uma solicitação HTTP, da mesma forma que o navegador teria feito. A solicitação é assíncrona: o usuário pode continuar interagindo 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], uma resposta é enviada ao cliente JS. Em vez de uma visualização HTML completa, é enviada uma visualização HTML parcial, um fluxo XML ou JSON (Notação de Objeto JavaScript) que é enviado;
  • em [4], o JavaScript recupera essa resposta e a utiliza para atualizar uma região da página HTML exibida.

Para o usuário, há uma mudança na visualização, pois o que ele vê mudou. No entanto, não há recarregamento total da página, mas simplesmente uma modificação parcial da página exibida. Isso contribui para dar fluidez e interatividade à página: como não há recarregamento total da página, é possível gerenciar eventos que antes não eram gerenciados. Por exemplo, oferecer ao usuário uma lista de opções à medida que ele digita caracteres em um campo de entrada. A cada novo caractere digitado, uma solicitação AJAX é enviada ao servidor, que então retorna outras sugestões. Sem o Ajax, esse tipo de auxílio à digitação era impossível anteriormente. Não era possível recarregar uma nova página a cada caractere digitado.

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

Frequentemente, incorporamos a biblioteca JavaScript JQuery às nossas páginas. Nela, encontramos a linha:


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

Observação: adapte a versão do jQuery à versão do seu Visual Studio.

A tecnologia Ajax do ASP.NET MVC utiliza o JQuery. Nós mesmos escreveremos alguns scripts JQuery. Por isso, apresentaremos agora os conceitos básicos de JQuery que você precisa conhecer para entender os scripts deste capítulo.

Criamos um novo projeto [Exemple-04] dentro de nossa solução [Exemples]:

Para utilizar o Ajax com o ASP.NET e o MVC, é necessário incluir uma linha no arquivo de configuração [Web.config] e [1]:


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

A linha 3 permite o uso do Ajax nas visualizações ASP.NET. Ela está presente por padrão.

Criamos um arquivo HTML [JQuery-01.html] na pasta [Content] do novo projeto [2]:

Esse arquivo 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 à sua versão do Visual Studio);
  • linhas 10-12: um elemento da página com o ID [element1]. Vamos trabalhar com esse elemento.

Visualizamos esses arquivos no navegador Google Chrome: [4] e [5]:

No Google Chrome, digite [Ctrl-Maj-I] para exibir as ferramentas de desenvolvimento [6]. A aba [Console] [7] permite executar código JavaScript. A seguir, apresentamos comandos JavaScript a serem digitados, acompanhados de uma explicação.

JS
resultado
$("#element1")
: retorna a coleção de todos os elementos com o id [element1]; portanto, normalmente uma coleção de 0 ou 1 elemento, pois não é possível ter dois ids idênticos em uma página HTML.
$("#element1").text("blabla")
: aplica o texto [blabla] a todos os elementos da coleção. Isso faz com que o conteúdo exibido pela página seja alterado
$("#element1").hide()
oculta os elementos da coleção. O texto [blabla] não é mais exibido.
$("#element1")
: exibe a coleção novamente. Isso nos permite ver que o elemento com o id [element1] possui 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;' que garante essa exibição.
$("#element1").attr('style','color: red')
: define um atributo para todos os elementos da coleção. O atributo aqui é [style] e seu valor é [color: red]. O texto [blabla] fica em vermelho.
Tableau
Dictionnaire

Observe-se que o URL do navegador não sofreu alterações durante todas essas operações. Não houve troca de dados com o servidor web. Tudo ocorre internamente no 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. Ele não reflete de forma alguma as alterações que fizemos no elemento nas linhas 10 a 12. É importante lembrar disso ao depurar JavaScript. Portanto, muitas vezes é inútil visualizar o código-fonte da página exibida. Para conhecer o código-fonte da página atualmente exibida, proceda da seguinte maneira:

 

Já sabemos o suficiente para compreender os scripts JS que virão a seguir.

7.3. Atualização de uma página com um fluxo HTML

7.3.1. As visualizações

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 com dois números reais A e B;
  • em [3], a resposta do servidor é exibida em uma área da página;
  • em [4], a hora do cálculo. Essa hora é diferente da hora de carregamento da página [5]. Esta última é igual a [1], o que mostra 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 visualização

Criamos um controlador chamado [Premier]:

Para exibir 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 do tempo de carregamento da visualização;
  • linha 6: exibição da vista [Action10Get.cshtml] e de 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 em A e B;
  • linha 25: o texto de um possível erro;
  • linha 26: a hora em que a visualização foi carregada no navegador;
  • linha 27: a hora do cálculo dos campos das linhas 21-24;
  • linha 7: este modelo de visualização também é um modelo de ação. Excluem-se deste último os campos que não são enviados pelo navegador.

A visualização [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 quanto 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 visualização;
  • linha 35: um formulário Ajax — voltaremos a isso mais tarde;
  • linhas 40-41: rótulos para a inserção dos números A e B;
  • linhas 46-47: campos de entrada dos números A e B;
  • linhas 50-51: mensagens de erro para as entradas dos números A e B;
  • linha 56: o botão que envia o formulário. Ele será enviado por meio 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 por meio de uma solicitação Ajax;
  • linha 62: uma tag <div> com o id [résultats]. É aqui que colocaremos o fluxo HTML enviado pelo servidor web.

Esta visualização exibe a seguinte página:

 

Vamos agora examinar 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 usar [@Html.BeginForm], usamos [@Ajax.BeginForm]. Esse 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 processará o POST do formulário,

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

ajaxOpts: as opções da chamada Ajax. Elas 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 Ajax HTTP;
  • linha 7: método da solicitação Ajax HTTP;
  • linha 6: id da região da página que será atualizada pela resposta à solicitação Ajax;
  • linha 9: id da região da página que será exibida durante a solicitação Ajax – geralmente uma imagem de espera. Aqui, será exibida a linha 20. Ela contém uma imagem animada que simboliza uma espera. Inicialmente, essa imagem fica oculta pelo estilo [display : none];
  • linha 10: tempo de espera em milissegundos antes que a imagem animada seja 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. Observe os atributos [data-ajax-attr], que refletem os valores dos campos do objeto do tipo [AjaxOptions] associado à solicitação Ajax. Esses atributos são gerenciados 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>

Estamos, então, diante de um formulário HTML clássico. É esse código que será executado se o usuário desativar o JavaScript em seu navegador. As linhas 5 e 6 ficam, então, sem uso.

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)
      {
        // é retornado um erro
        modèle.Erreur = getErrorMessagesFor(ModelState);
        return PartialView("Action01Error", modèle);
      }
      // a 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: ela 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. Utiliza-se aqui uma técnica descrita no parágrafo 4.10;
  • linha 2: a ação retornará um fragmento HTML e não uma página HTML completa;
  • linha 5: artificialmente, fazemos uma pausa de dois segundos para simular uma ação Ajax demorada;
  • linha 7: um modelo do tipo [ViewModel01] é instanciado;
  • linha 9: o tempo de cálculo é inicializado;
  • linha 11: tenta-se atualizar o modelo do tipo [ViewModel01] com os valores enviados. Vale lembrar que são dois: os valores dos números A e B;
  • linha 12: verifica-se se essa atualização foi bem-sucedida ou não;
  • linha 15: em caso de erro, preenche-se o campo [Erreur] do modelo;
  • linha 16: retorna-se uma visualização parcial [Action01Error.cshtml] utilizando o modelo [ViewModel01];
  • linhas 19-24: a 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: retorna-se uma visualização parcial [Action01Error.cshtml] utilizando o modelo [ViewModel01];
  • linhas 26-29: os cálculos aritméticos sobre os números A e B são realizados e os resultados inseridos no modelo na forma de cadeias de caracteres;
  • linha 31: é gerada uma visualização parcial [Action01Success.cshtml] utilizando o modelo [ViewModel01];

7.3.4. A visualização [Action01Error]

A visualização [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>

Vale lembrar que esse fluxo parcial HTML será enviado em resposta à solicitação Ajax HTTP do tipo POST e será inserido na página na região com o ID [résultats]. Todas essas 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
  };    
}

Aqui está um exemplo de resposta com erro:

 

7.3.5. A visualização [Action01Success]

A visualização [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, esse 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 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 é feita no [Application_Start]:


    protected void Application_Start()
    {
...
      // ligadores de modelo
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}

A classe [SessionModelBinder] foi descrita.

7.3.7. Gerenciamento 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]). Essa região corresponde à imagem da linha 21, inicialmente oculta. Isso resulta na seguinte interface:

Vamos examinar 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] faz com que a função JS [postForm] seja executada. Essa função está definida no arquivo [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) {
    //Use o plug-in 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 a 37 já foram abordadas no parágrafo 6.1. Elas gerenciam a internacionalização das páginas. Não voltaremos a abordá-las. Nas linhas 1 a 17, fazemos manualmente a chamada Ajax que, no caso do botão [Calculer], era feita pela biblioteca Ajax associada ao projeto. Para isso, utilizamos a biblioteca JQuery associada ao projeto.

  • linha 3: uma referência ao componente com id [loading]. [$("#loading")] retorna a coleção de elementos com id [loading]. Há 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 suas opções;
  • linha 7: o URL, destino da chamada Ajax;
  • linha 8: o método HTTP utilizado;
  • linha 9: os dados enviados. [formulaire.serialize] cria a string [A=val1&B=val2] a partir do campo POST do formulário com o ID [formulaire];
  • linha 10: o tipo de dados esperado como retorno. Sabe-se que o servidor retornará um fluxo HTML;
  • linha 11: o método a ser executado quando a solicitação for iniciada. Aqui, indica-se que é necessário exibir o componente com o ID [loading]. Trata-se da imagem animada de espera;
  • linha 12: o método a ser executado 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]. Ele funciona como o botão [Calculer], com exceção de uma anomalia. Depois de usar esse link, é possível enviar valores inválidos para A e B:

  • nos links [1] e [2], foram inseridos valores inválidos. Eles são sinalizados pelos validadores do lado do cliente;
  • em [3], clicou-se no link [Calculer];
  • no [4], houve um [POST], já que obtém-se a resposta [4].

Quando os valores são inválidos e clicamos 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. Portanto, há um comportamento do botão [Calculer] que não conseguimos reproduzir com o link [Calculer]. Em vez de tentar resolver esse problema agora, vamos deixá-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 à solicitação Ajax HTTP com um fluxo HTML. Nesse fluxo, havia dados acompanhados pela formatação HTML. Propomos retomar o exemplo anterior, mas desta vez com respostas JSON (JavaScript Object Notation) contendo apenas os dados. A vantagem é que, dessa forma, são transmitidos menos bytes.

7.4.1. A ação [Action02Get]

A ação [Action02Get] será o ponto de entrada do novo aplicativo. 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 ser executada no início da solicitação. Essa função está definida no arquivo JS, referenciado na linha 24;
  • linha 11: a função JS a ser executada em caso de falha da solicitação;
  • linha 12: a função JS a ser executada caso a solicitação seja bem-sucedida;
  • linha 13: a função JS a ser executada após a solicitação Ajax ter obtido 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]. Ela exibirá os resultados das quatro operações aritméticas;
  • linhas 50-52: uma região com o ID [erreur]. Ela exibirá 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)
      {
        // retornando um erro
        return Json(new { Erreur = getErrorMessagesFor(ModelState), HeureCalcul = HeureCalcul });
      }
      // a cada duas vezes, simula-se um erro
      int val = session.Randomizer.Next(2);
      if (val == 0)
      {
        // retornamos 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);
      // os resultados são retornados
      return Json(new { Erreur = "", AplusB = AplusB, AmoinsB = AmoinsB, AmultipliéparB = AmultipliéparB, AdiviséparB = AdiviséparB, HeureCalcul = HeureCalcul });
    }
  • linha 2: o método retorna um tipo [JsonResult], ou seja, um texto no formato JSON;
  • linha 16: as informações são apresentadas na forma de uma instância de classe anônima serializada em JSON. O método [getErrorMessagesFor] já foi apresentado. A string JSON enviada ao navegador terá o seguinte formato:
{"Erreur":"[erreur aléatoire]","HeureCalcul":"05:31:37"}
  • linha 31: mesmo procedimento para os resultados aritméticos. Desta vez, a string 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

Vale lembrar 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 a 10 (à direita do sinal =) estão definidas no seguinte arquivo [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");

  // ocultamos alguns elementos da página
  entete.hide();
  résultats.hide();
  erreur.hide();
});

// início
function OnBegin() {
....
}

// fim da solicitação
function OnComplete() {
...
}

// sucesso
function OnSuccess(data) {
....
}

// erro
function OnFailure(request, error) {
...
}
  • linha 19: a função JS é executada ao 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 busca por um componente em uma página tem um custo e é preferível realizá-la apenas uma vez;
  • linhas 33-35: os componentes [entete], [résultats] e [loading] são ocultados;

Ao iniciar a solicitação Ajax, a seguinte função é executada:


// início
function OnBegin() {
  // luz de espera acesa
  loading.show();
  // ocultando alguns elementos da página
  entete.hide();
  résultats.hide();
  erreur.hide();
}
  • linha 4: o componente [loading] é exibido. 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 erro
  AplusB.text(data.AplusB);
  AmoinsB.text(data.AmoinsB);
  AmultipliéparB.text(data.AmultipliéparB);
  AdiviséparB.text(data.AdiviséparB);
  résultats.show();
}

Para entender esse código, é preciso lembrar os dois textos JSON que podem ser enviados como resposta ao navegador:

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

em caso de erro; caso contrário, a sequência:

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

Se chamarmos essa sequência de [data], o valor do campo [Erreur] é obtido pela notação [data.Erreur] ou [data["Erreur"]], conforme a preferência. O mesmo vale para os demais campos da cadeia JSON. Além disso, para atribuir um texto sem formatação a um componente com 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 seu valor;
  • linha 5: o componente [entete] é exibido;
  • linha 6: teste do campo [Erreur] da cadeia JSON;
  • linha 7: o componente [msg] recebe seu valor;
  • linha 8: o componente [erreur] é exibido;
  • linha 9: o processo de erro está concluído;
  • linha 12: o componente [AplusB] recebe seu valor;
  • linha 13: o componente [AmoinsB] recebe seu valor;
  • linha 14: o componente [AmultipliéparB] recebe seu valor;
  • linha 15: o componente [AdiviséparB] recebe seu valor;
  • linha 16: o componente [résultats] é exibido.

A função [OnFailure] será executada caso a solicitação Ajax HTTP falhe. Essa falha é identificada pelo código HTTP retornado 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 exibir uma caixa de diálogo com o erro que ocorreu. Na prática, seria necessário ser mais preciso. Em breve, proporemos outra solução.

Por fim, a função [OnComplete] é executada quando a consulta é concluída, seja com sucesso ou com falha.


// fim da consulta
function OnComplete() {
  // sinal de espera desativado
  loading.hide();
}

Vale lembrar que é a configuração da chamada Ajax na visualização [Action02Get.cshtml] que faz com que essas diferentes funções sejam chamadas:


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

O código HTML do link [Calculer] na visualização [Action02Get.cshtml] é o seguinte:


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

A função JS [postForm] foi encontrada no arquivo importado [myScripts-02.js]:


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

Seu código é o seguinte:


function postForm() {
  // está sendo feita 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á encontramos um código semelhante.

  • linha 4: URL, destino da chamada Ajax;
  • linha 5: comando HTTP utilizado pela chamada Ajax;
  • linha 6: valores enviados. Eles são o resultado da serialização dos valores do formulário. Esse formulário, identificado pelo id [formulaire], é referenciado pela variável [formulaire]. [data] será uma sequência de caracteres no formato [A=val1&B=val2];
  • linha 7: tipo de formatação da resposta esperada. Trata-se de uma sequência de caracteres JSON;
  • linha 8: função JS a ser executada no início da chamada Ajax;
  • linha 9: função JS a ser executada caso a chamada Ajax seja bem-sucedida;
  • linha 10: função JS a ser executada caso a chamada Ajax falhe;
  • linha 11: função JS a ser executada assim que a resposta do servidor for recebida, independentemente do resultado (sucesso ou erro).

Voltemos à função JavaScript que lida com o caso em que a chamada Ajax falha (linha 10). A chamada Ajax falha em diversas situações, por exemplo, quando o servidor retorna 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);
}

Geralmente, a exibição do objeto [error] não fornece nenhuma informação relevante. Se for utilizada uma chamada Ajax feita com JQuery, é possível 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 suas propriedades, as seguintes:

  • responseText: o texto da resposta do servidor;
  • status: o código de erro retornado pelo servidor;
  • statusText: o texto associado a esse 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 de id [msg];
  • linha 5: exibe-se a região com o ID [erreur].

Para testar essa 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 simulada 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 o aplicativo:

Recebemos a seguinte resposta: [1] e [2]:

A resposta do servidor nos permite identificar em qual linha do código do servidor ocorreu o erro. Muitas vezes, é uma informação útil de se saber. Daqui em diante, utilizaremos essa técnica para gerenciar os erros das chamadas Ajax.

7.5. Aplicativo web de página única

A tecnologia Ajax permite construir aplicativos de página única:

  • a primeira página é resultante de uma solicitação convencional do navegador;
  • as páginas seguintes são obtidas por meio de chamadas Ajax. Assim, no final, o navegador nunca muda de URL e nunca carrega uma nova página. Esse tipo de aplicativo é chamado de Aplicativo de Página Única (APU) ou, em inglês, Single Page Application (SPA).

Aqui está um exemplo básico desse tipo de aplicação. A nova aplicação terá duas visualizações:

  • em [1], a ação [Action03Get] nos permite acessar a primeira página, a página 1;
  • em [2], um link nos permite passar para a página 2 por meio de uma chamada Ajax;
  • em [3], o URL não mudou. A página exibida é a página 2;
  • em [4], um link permite que voltemos à página 1 por meio de uma chamada Ajax;
  • em [5], o URL não sofreu alterações. A página exibida é a página 1.

O código da ação [Action03Get] é o seguinte:


    [HttpGet]
    public ViewResult Action03Get()
    {
      return View();
}
  • linha 4: a visualização [Action03Get.cshtml] é exibida.

A visualização [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]. É nesse elemento que as diferentes páginas serão exibidas;
  • linha 17: por padrão, a página [Page1.cshtml] será exibida primeiro.

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 diferenciá-la 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. Aqui, apenas o ID da região a ser atualizada com a resposta do servidor. Para as demais opções, são utilizados valores padrão, quando existentes. O método padrão de HTTP é GET.

Vamos ver o que acontece quando o link é clicado. A ação URL é solicitada com um [/Premier/Action04?Page=2]. 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 retorna um fluxo HTML parcial;
  • linha 2: a ação tem como modelo a sequência [page]. Sabe-se, porém, que o URL contém esta informação: [/Premier/Action04?Page=2]. Vale lembrar que o modelo não diferencia maiúsculas de minúsculas;
  • linhas 4-8: [vue] receberá o valor [Page2];
  • linha 9: a visão parcial [Page2.cshtml] é retornada.

A visão 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, portanto, retorna o fluxo HTML acima como resposta à chamada Ajax GET [/Premier/Action04?Page=2]. Vale lembrar que essa chamada Ajax utiliza essa 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>

Isso resulta na exibição da seguinte nova página [1]:

Seguindo o mesmo raciocínio, percebe-se que clicar no link [Page 1] de [1] fará com que seja exibido [2].

Voltemos ao esquema geral de um aplicativo 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 chegar à seguinte arquitetura:

  • em [1], a camada web ASP.NET MVC tornou-se uma interface web de acesso aos dados, geralmente armazenados em um banco de dados. As visualizações fornecidas contêm apenas dados e nenhum elemento de apresentação HTML, como, por exemplo, os fluxos XML ou JSON;
  • no [2]: o navegador exibe visualizações 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]. Essas visualizações estáticas são, em seguida, 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] lida com as interações com o usuário,
    • a camada [DAO] é responsável pelo acesso aos dados por meio do servidor web [1],
    • a camada [métier] corresponde à camada [métier], que anteriormente estava no servidor [1] e foi transferida para o navegador [2];

A vantagem dessa arquitetura é que ela envolve diferentes competências:

  • 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, essa arquitetura facilita o trabalho em paralelo de equipes com competências diferentes. Ela se aplica particularmente bem a aplicativos de página única.

7.6. Aplicativo web de página única e validação no lado do cliente

Mencionamos anteriormente uma anomalia no exemplo Ajax-01. Recordamos o contexto:

  • em [1] e [2], foram inseridos valores inválidos. Eles são sinalizados pelos validadores do lado do cliente;
  • no [3], clicou-se no link [Calculer];
  • em [4], ocorreu um [POST], já que obtém-se a resposta [4].

Quando os valores são inválidos e clicamos 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. Portanto, há um comportamento do botão [Calculer] que não conseguimos reproduzir com o link [Calculer].

Vamos retomar esse exemplo em um novo contexto: o aplicativo terá várias visualizações e será do tipo [Application à Page Unique] que acabamos de descrever.

7.6.1. As visualizações do exemplo

O exemplo possui várias visualizações:

  • em [1], a visualização [Action05Get];
  • em [2], a visualização parcial [Formulaire05];
  • em [3], a vista parcial [Failure05];
  • em [4], a visualização parcial [Success05].

O aplicativo consiste em uma única página: ela é carregada pelo navegador na primeira solicitação. Em seguida, é atualizada por meio de chamadas Ajax.

As páginas anteriores são geradas pelas seguintes visualizações [cshtml]:

A visualização carregada inicialmente é a seguinte: [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>

Observe os seguintes pontos:

  • linha 1: o modelo da visualização é do tipo [ViewModel05], que apresentaremos em breve;
  • linhas 13-19: encontram-se os scripts JavaScript necessários para implementar Ajax e validação do lado do cliente;
  • linha 20: vamos adicionar nossas próprias funções JavaScript no [myScripts-05.js];
  • linha 27: a imagem animada de espera;
  • linhas 28-30: uma tag com o ID [content]. É nessa tag que as visualizações parciais [Formulaire05, Success05, Failure05] serão inseridas;
  • linha 29: inserção da vista parcial [Formulaire05].

A visualização [Action05Get] é responsável pela exibição da parte [1] da página inicial:

A vista parcial [Formulaire05] irá gerar a parte [2] acima. 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 esse formulário será enviado por uma chamada Ajax, os três primeiros parâmetros do método serão ignorados. A menos que o usuário tenha desativado o JavaScript em seu navegador. Ignoramos essa possibilidade aqui. O quarto parâmetro é importante. O formulário terá o id [formulaire];
  • linhas 5-22: o formulário para inserção dos números A e B;
  • linha 27: um link em JavaScript que inicia a execução das quatro operações aritméticas em A e B;
  • linha 30: um link em JavaScript que apaga os dados inseridos e as eventuais mensagens de erro a eles associadas.

Observe-se que o formulário não possui um botão do tipo [submit]. Teremos que calcular manualmente o [Post] a partir dos valores A e B inseridos.

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

A parte [4] acima é gerada pela seguinte visão 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 visão parcial [Success05.cshtml] recebe um modelo do tipo [ViewModel05];
  • linha 12: um link em JavaScript para retornar às entradas.

Em caso de erro, é exibida outra vista parcial [3]:

Essa visualização é gerada pelo código [Failure05.cshtml] a seguir:


@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 visão parcial [Failure05.cshtml] recebe um modelo do tipo [ViewModel05];
  • linha 14: um link em JavaScript para retornar às entradas.

7.6.2. O modelo das visualizações

Todas as visualizações anteriores compartilham 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 agora são do tipo [string] para exibir campos de preenchimento vazios, em vez de campos com o valor 0, na exibição inicial do formulário de preenchimento;
  • linhas 14 e 18: isso não impede a verificação do valor inserido com um validador [Range];
  • linha 26: uma lista de mensagens de erro exibida pela visualização [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 modelo [SessionModel] a seguir:


using System;
namespace Exemple_03.Models
{
  public class SessionModel
  {
    // o gerador de números aleatórios
    public Random Randomizer { get; set; }
  }
}

Esse modelo de sessão é ampliado para incluir 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 fato, é necessário armazenar os valores de A e B na sessão, conforme mostra a sequência a seguir:

Solicitação 1

Solicitação 2

Em [4], encontramos as entradas feitas em [1]. No entanto, há duas solicitações 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, é necessário que esses valores sejam armazenados na sessão.

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

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

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

Vamos examinar as interações do usuário com as visualizações:

O link [1] é um link em JavaScript:


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

A função JavaScript [calculer] está no arquivo [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");
  // em seguida, validação do formulário
  if (!formulaire.validate().form()) {
    // formulário inválido – concluído
    return;
  }
  // fazemos 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 () {
  // recuperando as referências dos diferentes componentes da página
  loading = $("#loading");
  content = $("#content");
  // armazenamos a imagem animada em cache
  loading.hide();
});
  • vale lembrar 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 é concluído;
  • linha 46: referência à imagem animada com id [loading];
  • linha 47: referência à região com id [content]. É essa região que recebe as visualizações 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 a elas. Há um custo associado à busca de elementos em uma página (linhas 46-47). Não há motivo para repetir essa busca se for possível evitá-la;
  • linha 5: a função [calculer];
  • linha 7: obtém-se uma referência ao formulário. A visão parcial [Formulaire05] atribuiu a ele o ID [formulaire];
  • linha 9: esta instrução executa os validadores do formulário no lado do cliente. Era isso que faltava na anomalia constatada na página 183. Esse 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 retorna [false] se o formulário for declarado inválido;

  • linha 11: a chamada Ajax ao servidor não é feita se o formulário for inválido;
  • linhas 14-32: a chamada Ajax é feita ao servidor;
  • linha 15: o URL tem como destino a ação do servidor [Action05FaireCalcul];
  • linha 16: ela é solicitada por meio de um [POST];
  • linha 17: os valores enviados. São os dados inseridos 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 de ID [content] com o fluxo HTML enviado pelo servidor.

Esse fluxo HTML é o enviado pela ação [Action05FaireCalcul], à qual a chamada Ajax se destina. O código dessa ação no 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: ela retorna uma visualização parcial;
  • linha 2: ela recebe como parâmetros os valores enviados (postedData) e o modelo da sessão (session);
  • linha 5: o modelo da visualização parcial é criado;
  • linha 7: ele é atualizado com a hora do cálculo;
  • linha 9: tenta-se aplicar os valores enviados ao modelo. Os validadores deste serão então executados. Pode-se questionar por que se dá esse trabalho, já que os validadores do lado do cliente impedem o POST caso os dados inseridos sejam inválidos. Na verdade, não temos certeza da origem do POST. Ele pode ter sido gerado por um código que não é o nosso. Por isso, devemos sempre realizar as verificações no lado do servidor;
  • linha 10: verificamos se os validadores foram bem-sucedidos;
  • linha 13: se o modelo for inválido, ele é atualizado com uma lista de erros. Não entraremos em detalhes sobre o método interno [getListOfMessagesFor], que é semelhante ao método [GetErrorMessagesFor] descrito na página 65;
  • linha 14: a visão parcial [Failure05] é exibida com seu modelo. Repetimos o código dessa visão;

@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 é exibida por meio da tag <ul>.

Lembre-se de que a função JS [calculer], naorigem do [Post] na ação do servidor [Action05FaireCalcul] irá inserir esse fluxo HTML na região de ID [content]. O resultado fica mais ou menos assim:

Vamos continuar analisando 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 inseridos na sessão
      session.A = modèle.A;
      session.B = modèle.B;
      // sem erros até o momento
      List<string> erreurs = new List<string>();
      // a 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 inseridos A e B são armazenados na sessão. O objetivo é poder recuperá-los na consulta que se seguirá;
  • linhas 11-22: gera-se aleatoriamente um erro a cada duas vezes;
  • linhas 24-29: realizam-se as quatro operações aritméticas nos números reais inseridos;
  • linha 31: retorna-se a visão parcial [Success05] com seu modelo. Essa visão 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>

Lembramos que a função JS [calculer],origem do [Post] na ação do servidor [Action05FaireCalcul] irá inserir esse fluxo HTML na região de ID [content]. O resultado é algo como isto:

7.6.6. A ação do cliente [Effacer]

O link JavaScript [Effacer] permite restaurar 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 arquivo [myScripts-05.js] da seguinte maneira:


// 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");
  // atribuímos valores válidos aos dados inseridos
  A.val("0");
  B.val("0");
  // em seguida, valida-se o formulário para que desapareçam
  // as eventuais mensagens de erro
  formulaire.validate().form();
  // em seguida, atribui-se strings vazias aos campos de entrada
  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 diversos elementos do DOM (Document Object Model);
  • linhas 19-20: insere-se valores válidos nos campos de entrada 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, isso fará com que eventuais mensagens de erro que pudessem ser exibidas desapareçam;
  • linhas 25-26: insere-se cadeias vazias nos campos de entrada dos números A e B;

7.6.7. A ação do cliente [Retour aux Saisies]

O link JavaScript [Retour aux Saisies] permite retornar 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 arquivo [myScripts-05.js] da seguinte maneira:


// dados globais
var content;
var loading;

function calculer() {
...
}

function retourSaisies() {
  // é feita uma chamada Ajax manualmente
  $.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 () {
  // recuperamos 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, não encontramos uma linha do tipo:
    data: formulaire.serialize(),

na chamada Ajax;

  • linha 14: o fluxo esperado do servidor é um fluxo HTML;
  • linhas 18-20: esse 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 armazenamos anteriormente os valores de A e B inseridos;
  • linha 5: retorna-se a visualização 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 obtidos da sessão;

Agora, voltemos ao código da função JavaScript [retourSaisies]:


function retourSaisies() {
  // faz-se uma chamada Ajax manualmente
  $.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 é 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 exibido pelo link [Retour aux saisies], os validadores do lado do cliente não estavam mais funcionando. Ao pesquisar informações sobre a biblioteca JS [jquery.unobtrusive-ajax], encontrei a solução da linha 16. Ela analisa o formulário, talvez para ativar os validadores do lado do cliente.

7.7. Tornar acessível na Internet um aplicativo ASP.NET

Consulte o parágrafo 9.26.

7.8. Geração de um aplicativo nativo para Android a partir de um aplicativo de página única APU

Consulte o parágrafo 9.27.