Skip to content

7. Implementação de Ajax numa aplicação ASP.NET MVC

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

Por enquanto, os exemplos de aprendizagem analisados têm a seguinte arquitetura:

Para passar de uma vista [Vue1] para uma vista [Vue2], o navegador:

  • envia um pedido à aplicação web;
  • recebe a vista [Vue2] e apresenta-a no lugar da vista [Vue1].

Este é o esquema clássico:

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

Existe outro modo de interação entre o navegador e o servidor web: AJAX (Asynchronous JavaScript and XML). Trata-se, na verdade, de interações entre a vista apresentada pelo navegador e o servidor web. O navegador continua a fazer o que sabe fazer, ou seja, apresentar uma vista HTML, mas agora é controlado por JavaScript incorporado na vista HTML apresentada. O esquema é o seguinte:

  • em [1], ocorre um evento na página apresentada no navegador (clique num botão, alteração de um texto, etc.). Este evento é interceptado pelo JavaScript (JS) incorporado na página;
  • em [2], o código JavaScript efetua uma solicitação HTTP, tal como o navegador teria feito. A solicitação é assíncrona: o utilizador pode continuar a interagir com a página sem ficar bloqueado pela espera pela resposta à solicitação HTTP. A solicitação segue o processo clássico de processamento. Nada (ou quase nada) a distingue de uma solicitação clássica;
  • em [3], é enviada uma resposta ao cliente JS. Em vez de uma vista HTML completa, é enviada uma vista HTML parcial, um fluxo XML ou JSON (Notação de Objeto JavaScript) que é enviado;
  • em [4], o JavaScript recupera essa resposta e utiliza-a para atualizar uma região da página HTML apresentada.

Para o utilizador, verifica-se uma alteração na visualização, pois o que vê mudou. No entanto, não há um recarregamento total da página, mas sim uma simples modificação parcial da página apresentada. Isto contribui para conferir fluidez e interatividade à página: como não há uma recarga total da página, é possível gerir eventos que anteriormente não eram geridos. Por exemplo, apresentar ao utilizador uma lista de opções à medida que este introduz caracteres numa caixa de entrada. A cada novo caractere digitado, é enviada uma solicitação AJAX ao servidor, que, por sua vez, devolve outras sugestões. Sem o Ajax, este tipo de ajuda à digitação era anteriormente impossível. Não era possível recarregar uma nova página a cada caractere digitado.

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

Frequentemente, incluímos a biblioteca JavaScript JQuery nas nossas páginas. Nela, encontramos a seguinte linha:


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

Nota: adapte a versão do jQuery à da sua versão do Visual Studio.

A tecnologia Ajax da ASP.NET MVC utiliza a JQuery. Vamos escrever nós próprios alguns scripts JQuery. Por isso, apresentamos agora os conceitos básicos de JQuery que é necessário conhecer para compreender os scripts deste capítulo.

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

Para utilizar o Ajax com o ASP.NET e o MVC, deve existir uma linha no ficheiro de configuração [Web.config] e [1]:


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

A linha 3 permite a utilização do Ajax nas vistas ASP.NET. Está presente por predefinição.

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

Este ficheiro terá o seguinte conteúdo:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>JQuery-01</title>
  <script type="text/javascript" src="/Scripts/jquery-1.8.2.min.js"></script>
</head>
<body>
  <h3>Rudiments de JQuery</h3>
  <div id="element1">
    Elément 1
  </div>
</body>
</html>
  • linha 6: importação de JQuery (adapte a versão à da sua versão do Visual Studio);
  • linhas 10-12: um elemento da página com o ID [element1]. Vamos experimentar com este elemento.

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

No Google Chrome, digite [Ctrl-Maj-I] para aceder às ferramentas de desenvolvimento [6]. O separador [Console] [7] permite executar código JavaScript. Apresentamos a seguir alguns comandos JavaScript a introduzir, acompanhados de uma explicação.

JS
resultado
$("#element1")
: devolve a coleção de todos os elementos com o id [element1]; por isso, normalmente, uma coleção de 0 ou 1 elemento, uma vez que não é possível ter dois ids idênticos numa página HTML.
$("#element1").text("blabla")
: aplica o texto [blabla] a todos os elementos da coleção. Isto tem como efeito alterar o conteúdo apresentado pela página
$("#element1").hide()
oculta os elementos da coleção. O texto [blabla] já não é apresentado.
$("#element1")
: volta a exibir a coleção. Isto permite-nos ver que o elemento com o ID [element1] tem o atributo CSS style='display: none;', o que faz com que o elemento fique oculto.
$("#element1").show()
: apresenta os elementos da coleção. O texto [blabla] volta a aparecer. É o atributo CSS style='display : block;' que garante esta visualização.
$("#element1").attr('style','color: red')
: define um atributo para todos os elementos da coleção. O atributo é, neste caso, [style] e o seu valor é [color: red]. O texto [blabla] fica a vermelho.
Tableau
Dictionnaire

Note-se que o URL do navegador não se alterou durante todas estas operações. Não houve qualquer comunicação com o servidor web. Tudo decorre no interior do navegador. Agora, vamos visualizar o código-fonte da página:

 

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>JQuery-01</title>
  <script type="text/javascript" src="/Scripts/jquery-1.8.2.min.js"></script>
</head>
<body>
  <h3>Rudiments de JQuery</h3>
  <div id="element1">
    Elément 1
  </div>
</body>
</html>

Este é o texto inicial. Não reflete de forma alguma as alterações que fizemos no elemento nas linhas 10 a 12. É importante ter isto em conta ao depurar JavaScript. Por isso, muitas vezes é inútil visualizar o código-fonte da página apresentada. Para conhecer o código-fonte da página atualmente apresentada, devemos proceder da seguinte forma:

 

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

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

7.3.1. As vistas

Propomos estudar a seguinte aplicação:

  • em [1], a hora de carregamento da página;
  • em [2], realizam-se as quatro operações aritméticas sobre dois números reais A e B;
  • em [3], a resposta do servidor é inserida numa área da página;
  • em [4], a hora do cálculo. Esta é diferente da hora de carregamento da página [5]. Esta última é igual a [1], o que demonstra que a região [6] não foi recarregada. Além disso, o URL e o [7] da página não se alteraram.

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

Criamos um controlador denominado [Premier]:

Para apresentar a vista inicial, criamos a seguinte ação [Action01Get]:


    [HttpGet]
    public ViewResult Action01Get()
    {
      ViewModel01 modèle = new ViewModel01();
      modèle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
      return View(modèle);
}
  • linha 4: instanciação do modelo da vista;
  • linha 5: inicialização da hora de carregamento da vista;
  • linha 6: exibição da vista [Action10Get.cshtml] e do seu modelo.

O modelo [ViewModel01] é o seguinte:


using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace Exemple_04.Models
{
  [Bind(Exclude = "AplusB, AmoinsB, AmultipliéparB, AdiviséparB, Erreur, HeureChargement, HeureCalcul")]
  public class ViewModel01
  {
    // formulário
    [Required(ErrorMessage="Donnée requise")]
    [Display(Name="Valeur de A")]
    [Range(0, Double.MaxValue, ErrorMessage = "Tapez un nombre positif ou nul")]
    public double A { get; set; }
    [Required(ErrorMessage = "Donnée requise")]
    [Display(Name = "Valeur de B")]
    [Range(0, Double.MaxValue, ErrorMessage="Tapez un nombre positif ou nul")]
    public double B { get; set; }

    // resultados
    public string AplusB { get; set; }
    public string AmoinsB { get; set; }
    public string AmultipliéparB { get; set; }
    public string AdiviséparB { get; set; }
    public string Erreur { get; set; }
    public string HeureChargement { get; set; }
    public string HeureCalcul { get; set; }
  }
}
  • linhas 11-14: o valor A do formulário;
  • linhas 15-18: o valor B do formulário;
  • linhas 21-24: os resultados das quatro operações aritméticas sobre A e B;
  • linha 25: o texto de um eventual erro;
  • linha 26: a hora em que a vista foi carregada no navegador;
  • linha 27: a hora do cálculo dos campos das linhas 21-24;
  • linha 7: este modelo de vista é também um modelo de ação. Excluem-se deste último os campos que não são enviados pelo navegador.

A vista [Action01Get.cshtml] é a seguinte:


@model Exemple_04.Models.ViewModel01
@{
  Layout = null;
  AjaxOptions ajaxOpts = new AjaxOptions
  {
    UpdateTargetId = "résultats",
    HttpMethod = "post",
    Url = Url.Action("Action01Post"),
    LoadingElementId = "loading",
    LoadingElementDuration = 1000
  };    
}

<!DOCTYPE html>

<html lang="fr-FR">
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Ajax-01</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.en-US.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
  <script type="text/javascript" src="~/Scripts/myScripts-01.js"></script>
</head>
<body>

  <h2>Ajax - 01</h2>
  <p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
  <h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
  @using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
  {
    <table>
      <thead>
        <tr>
          <th>@Html.LabelFor(m => m.A)</th>
          <th>@Html.LabelFor(m => m.B)</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>@Html.TextBoxFor(m => m.A)</td>
          <td>@Html.TextBoxFor(m => m.B)</td>
        </tr>
        <tr>
          <td>@Html.ValidationMessageFor(m => m.A)</td>
          <td>@Html.ValidationMessageFor(m => m.B)</td>
        </tr>
      </tbody>
    </table>
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
  }
  <hr />
  <div id="résultats" />
</body>
</html>
  • linha 1: a vista tem como modelo um tipo [ViewModel01];
  • linha 21: é necessário o JQuery tanto para as validações como para o Ajax;
  • linhas 22-23: as bibliotecas de validação;
  • linhas 24-26: as bibliotecas de internacionalização;
  • linha 27: a biblioteca Ajax;
  • linha 28: uma biblioteca JavaScript local;
  • linha 33: exibição da hora de carregamento da vista;
  • linha 35: um formulário Ajax — voltaremos a este assunto;
  • linhas 40-41: rótulos para a introdução dos números A e B;
  • linhas 46-47: campos de introdução dos números A e B;
  • linhas 50-51: mensagens de erro para os campos de introdução dos números A e B;
  • linha 56: o botão que envia o formulário. Este será enviado através de uma solicitação Ajax;
  • linha 57: uma imagem de espera exibida durante a solicitação Ajax;
  • linha 58: um link para enviar o formulário através de uma solicitação Ajax;
  • linha 62: uma baliza <div> com o ID [résultats]. É aqui que iremos inserir o fluxo HTML devolvido pelo servidor web.

Esta vista apresenta a seguinte página:

 

Vamos agora analisar o código que implementa o Ajax no formulário:


...
@{
  Layout = null;
  AjaxOptions ajaxOpts = new AjaxOptions
  {
    UpdateTargetId = "résultats",
    HttpMethod = "post",
    Url = Url.Action("Action01Post"),
    LoadingElementId = "loading",
    LoadingElementDuration = 1000
  };    
}

...
  @using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
  {
....
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
}
...
 <div id="résultats" />
  • linha 15: em vez de utilizar [@Html.BeginForm], utiliza-se [@Ajax.BeginForm]. Este método admite várias sobrecargas. A utilizada tem a seguinte assinatura:
Ajax.BeginForm(string ActionName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string,object> htmlAttributes)

Utilizamos aqui os seguintes parâmetros efetivos:

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

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

ajaxOpts: as opções da chamada Ajax. Estas foram definidas nas linhas 6 a 10,

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

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

  • linha 8: o URL destino da solicitação HTTP Ajax;
  • linha 7: método da solicitação Ajax HTTP;
  • linha 6: ID da área da página que será atualizada pela resposta à solicitação Ajax;
  • linha 9: id da área da página que será exibida durante a solicitação Ajax – geralmente uma imagem de espera. Neste caso, será exibida a linha 20. Esta contém uma imagem animada que simboliza a espera. Inicialmente, esta imagem está oculta pelo estilo [display : none];
  • linha 10: tempo de espera, em milissegundos, antes de a imagem animada ser exibida; neste caso, 1 segundo.

O código HTML gerado pelo formulário Ajax é o seguinte:


<form action="/Premier/Action01Post" data-ajax="true" data-ajax-loading="#loading" data-ajax-loading-duration="1000" data-ajax-method="post" data-ajax-mode="replace" data-ajax-update="#résultats" data-ajax-url="/Premier/Action01Post" id="formulaire" method="post">    <table>
...
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
</form>
<hr />
<div id="résultats" />
  • linha 1: a baliza <form> gerada. Note-se os atributos [data-ajax-attr], que refletem os valores dos campos do objeto do tipo [AjaxOptions] que foi associado à solicitação Ajax. Estes atributos são geridos pela biblioteca Ajax. Sem eles, a baliza <form> passa a ser:

<form action="/Premier/Action01Post" id="formulaire" method="post">
...
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
</form>

Estamos então perante um formulário HTML clássico. É este código que será executado se o utilizador desativar o JavaScript no seu navegador. As linhas 5 e 6 ficam, então, sem utilização.

7.3.3. A ação [Action01Post]

A ação [Action01Post], que processa a solicitação Ajax HTTP, é a seguinte:


    [HttpPost]
    public PartialViewResult Action01Post(FormCollection postedData, SessionModel session)
    {
      // simulação de espera
      Thread.Sleep(2000);
      // instanciação do modelo da ação
      ViewModel01 modèle = new ViewModel01();
      // hora do cálculo
      modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
      // atualização do modelo
      TryUpdateModel(modèle, postedData);
      if (!ModelState.IsValid)
      {
        // é devolvido um erro
        modèle.Erreur = getErrorMessagesFor(ModelState);
        return PartialView("Action01Error", modèle);
      }
      // de cada duas vezes, simula-se um erro
      int val = session.Randomizer.Next(2);
      if (val == 0)
      {
        modèle.Erreur = "[erreur aléatoire]";
        return PartialView("Action01Error", modèle);
      }
      // cálculos
      modèle.AplusB = string.Format("{0}", modèle.A + modèle.B);
      modèle.AmoinsB = string.Format("{0}", modèle.A - modèle.B);
      modèle.AmultipliéparB = string.Format("{0}", modèle.A * modèle.B);
      modèle.AdiviséparB = string.Format("{0}", modèle.A / modèle.B);
      // visualização
      return PartialView("Action01Success", modèle);
}
  • linha 1: a ação processa apenas um [POST];
  • linha 2: aceita como modelo de ação:
    • [FormCollection postedData]: o conjunto de valores enviados pela solicitação Ajax POST,
    • [SessionModel session]: os elementos da sessão. Aqui, utiliza-se uma técnica descrita no parágrafo 4.10;
  • linha 2: a ação irá devolver um fragmento HTML e não uma página HTML completa;
  • linha 5: artificialmente, faz-se uma pausa de dois segundos para simular uma ação Ajax demorada;
  • linha 7: é instanciado um modelo do tipo [ViewModel01];
  • linha 9: o tempo de cálculo é inicializado;
  • linha 11: tenta-se atualizar o modelo do tipo [ViewModel01] com os valores enviados. Recorde-se que existem dois: os valores dos números A e B;
  • linha 12: verifica-se se esta atualização foi bem-sucedida ou não;
  • linha 15: em caso de erro, preenche-se o campo [Erreur] do modelo;
  • linha 16: devolve-se uma vista parcial [Action01Error.cshtml] que utiliza o modelo [ViewModel01];
  • linhas 19-24: uma em cada duas vezes, simula-se um erro;
  • linha 19: gera-se um número inteiro aleatório no intervalo [0,1]. O gerador de números é obtido na sessão;
  • linha 20: se o valor gerado for 0, simula-se um erro;
  • linha 22: a mensagem de erro é inserida no modelo;
  • linha 23: é devolvida uma vista parcial [Action01Error.cshtml] que utiliza o modelo [ViewModel01];
  • linhas 26-29: são efetuados os cálculos aritméticos sobre os números A e B e os resultados são inseridos no modelo sob a forma de cadeias de caracteres;
  • linha 31: é devolvida uma vista parcial [Action01Success.cshtml] que utiliza o modelo [ViewModel01];

7.3.4. A vista [Action01Error]

A vista [Action01Error.cshtml] é a seguinte:


@model Exemple_04.Models.ViewModel01
<h4>Résultats</h4>
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p style="color: red;">Une erreur s'est produite : @Model.Erreur</p>

Recorde-se que este fluxo parcial HTML será enviado em resposta à solicitação Ajax HTTP do tipo POST e colocado na página na região com o ID [résultats]. Todas estas informações provêm da configuração Ajax utilizada na página principal [Action01Get.cshtml]:


@model Exemple_04.Models.ViewModel01
@{
  Layout = null;
  AjaxOptions ajaxOpts = new AjaxOptions
  {
    UpdateTargetId = "résultats",
    HttpMethod = "post",
    Url = Url.Action("Action01Post"),
    LoadingElementId = "loading",
    LoadingElementDuration = 1000
  };    
}

Eis um exemplo de resposta com erro:

 

7.3.5. A vista [Action01Success]

A vista [Action01Success.cshtml] é a seguinte:


@model Exemple_04.Models.ViewModel01
<h4>Résultats</h4>
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>

Mais uma vez, este fluxo parcial HTML será enviado em resposta à solicitação Ajax HTTP do tipo POST e inserido na página na região com o ID [résultats]:

 

7.3.6. G estão da sessão

Vimos que o [Action01Post] utilizava a sessão. O modelo da sessão é do tipo [SessionModel], conforme se segue:


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

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


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

A associação da sessão a um modelo é efetuada no [Application_Start]:


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

A classe [SessionModelBinder] foi descrita.

7.3.7. Gestão da imagem de espera


@model Exemple_04.Models.ViewModel01
@{
  Layout = null;
  AjaxOptions ajaxOpts = new AjaxOptions
  {
...
    LoadingElementId = "loading",
    LoadingElementDuration = 1000
  };    
}

...
<body>

...
  @using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
  {
...
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
  }
...

Quando a solicitação Ajax é iniciada, a região com o ID [loading], linha 7, é exibida após um segundo [ligne 8]. Esta região corresponde à imagem da linha 21, inicialmente oculta. Isto resulta na seguinte interface:

Analisemos o link [Calculer] da página principal [Action01Get.cshtml]:


<head>
  <meta name="viewport" content="width=device-width" />
  <title>Ajax-01</title>
  ...
  <script type="text/javascript" src="~/Scripts/myScripts-01.js"></script>
</head>
<body>

  <h2>Ajax - 01</h2>
  <p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
  <h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
  @using (Ajax.BeginForm("Action01Post", null, ajaxOpts, new { id = "formulaire" }))
  {
...
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
  }
  <hr />
<div id="résultats" />
  • linha 18: um clique no link [Calculer] provoca a execução da função JS [postForm]. Esta função está definida no ficheiro [myScripts-01.js], na linha 5. O script é o seguinte:

function postForm() {
  // faz-se uma chamada Ajax manualmente com JQuery
  var loading = $("#loading");
  var formulaire = $("#formulaire");
  var résultats = $('#resultados');
  $.ajax({
    url: '/Premier/Action01Post',
    type: 'POST',
    data: formulaire.serialize(),
    dataType: 'html',
    begin: loading.show(),
    success: function (data) {
      loading.hide()
      résultats.html(data);
    }
  })
}

// http://blog.instance-factory.com/?p=268
$.validator.methods.number = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseFloat(value));
}

$.validator.methods.date = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseDate(value));
}

jQuery.extend(jQuery.validator.methods, {
  range: function (value, element, param) {
    //Utilize o plugin de globalização para analisar o valor        
    var val = Globalize.parseFloat(value);
    return this.optional(element) || (
        val >= param[0] && val <= param[1]);
  }
});

As funções das linhas 19-37 já foram abordadas no parágrafo 6.1. Estas funções gerem a internacionalização das páginas. Não voltaremos a abordá-las. Nas linhas 1 a 17, efetuamos manualmente a chamada Ajax que, no caso do botão [Calculer], era feita pela biblioteca Ajax associada ao projeto. Para tal, utilizamos a biblioteca JQuery associada ao projeto.

  • linha 3: uma referência ao componente com o ID [loading]. [$("#loading")] devolve a coleção de elementos com o ID [loading]. Existe apenas um;
  • linha 4: uma referência ao componente com o ID [formulaire];
  • linha 5: uma referência ao componente com o ID [résultats];
  • linha 6: a chamada Ajax com as respetivas opções;
  • linha 7: o URL, destino da chamada Ajax;
  • linha 8: o método HTTP utilizado;
  • linha 9: os dados enviados. Cria a cadeia de caracteres do formulário com o ID [formulaire];
  • linha 10: o tipo de dados esperado em resposta. Sabe-se que o servidor irá devolver um fluxo HTML;
  • linha 11: o método a executar quando a solicitação for iniciada. Aqui, indica-se que é necessário apresentar o componente com o ID [loading]. Trata-se da imagem animada de espera;
  • linha 12: o método a executar caso a solicitação Ajax seja bem-sucedida. O parâmetro [data] é a resposta completa do servidor. Sabemos que se trata de um fluxo HTML;
  • linha 13: ocultamos o sinal de espera;
  • linha 14: atualiza-se o componente com o ID [résultats] com o valor HTML do parâmetro [data].

Sugere-se ao leitor que teste o link [Calculer]. Funciona como o botão [Calculer], com uma única an . Depois de utilizar este link, é possível enviar valores inválidos para A e B:

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

Quando os valores são inválidos e se clica no botão [Calculer], o [POST] para o servidor não ocorre. No mesmo caso, com o link [Calculer], o [POST] para o servidor é enviado. Existe, portanto, um comportamento do botão [Calculer] que não conseguimos reproduzir com o link [Calculer]. Em vez de tentarmos resolver este problema agora, deixamo-lo para um exemplo posterior que também ilustrará outro problema de validação do lado do cliente.

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

No exemplo anterior, o servidor web respondia ao pedido Ajax HTTP com um fluxo HTML. Neste fluxo, havia dados acompanhados de formatação HTML. Propomos retomar o exemplo anterior, mas desta vez com respostas JSON (JavaScript Object Notation) que contêm apenas os dados. A vantagem é que, desta forma, são transmitidos menos bytes.

7.4.1. A ação [Action02Get]

A ação [Action02Get] será o ponto de entrada da nova aplicação. O seu código é o seguinte:


@model Exemple_04.Models.ViewModel02
@{
  Layout = null;
  AjaxOptions ajaxOpts = new AjaxOptions
  {
    HttpMethod = "post",
    Url = Url.Action("Action02Post"),
    LoadingElementId = "loading",
    LoadingElementDuration = 1000,
    OnBegin = "OnBegin",
    OnFailure = "OnFailure",
    OnSuccess = "OnSuccess",
    OnComplete = "OnComplete"
  };    
}

<!DOCTYPE html>

<html lang="fr-FR">
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Ajax-02</title>
....
  <script type="text/javascript" src="~/Scripts/myScripts-02.js"></script>
</head>
<body>
  <h2>Ajax - 02</h2>
  <p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
  <h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
  @using (Ajax.BeginForm("Action02Post", null, ajaxOpts, new { id = "formulaire" }))
  {
...
    <p>
      <input type="submit" value="Calculer" />
      <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
      <a href="javascript:postForm()">Calculer</a>
    </p>
  }
  <hr />
  <div id="entete">
    <h4>Résultats</h4>
    <p><strong>Heure de calcul : <span id="heureCalcul"/></strong></p>
  </div>
  <div id="résultats">
    <p>A+B=<span id="AplusB"/></p>
    <p>A-B=<span id="AmoinsB"/></p>
    <p>A*B=<span id="AmultipliéparB"/></p>
    <p>A/B=<span id="AdiviséparB"/></p>
  </div>
  <div id="erreur">
    <p style="color: red;">Une erreur s'est produite : <span id="msg"/></p>
  </div>
</body>
</html>
  • linhas 4-14: as opções da chamada Ajax;
  • linha 10: a função JS a executar no início da solicitação. Esta função está definida no ficheiro JS referenciado na linha 24;
  • linha 11: a função JS a executar em caso de falha da solicitação;
  • linha 12: a função JS a ser executada em caso de sucesso da solicitação;
  • linha 13: a função JS a executar após a requisição Ajax ter obtido o seu resultado (falha ou sucesso);
  • linhas 40-43: uma região com o ID [entete];
  • linhas 44-49: uma região com o ID [résultats]. Esta irá apresentar os resultados das quatro operações aritméticas;
  • linhas 50-52: uma região com o ID [erreur]. Esta irá apresentar uma eventual mensagem de erro.

7.4.2. A ação [Action02Post]

A solicitação Ajax é processada pela seguinte ação [Action02Post]:


[HttpPost]
    public JsonResult Action02Post(FormCollection postedData, SessionModel session)
    {
      // simulação em espera
      Thread.Sleep(2000);
      // validação do modelo
      ViewModel02 modèle = new ViewModel02();
      // tempo de carregamento e cálculo
      string HeureChargement = DateTime.Now.ToString("hh:mm:ss");
      string HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
      // atualização do modelo
      TryUpdateModel(modèle, postedData);
      if (!ModelState.IsValid)
      {
        // é devolvido um erro
        return Json(new { Erreur = getErrorMessagesFor(ModelState), HeureCalcul = HeureCalcul });
      }
      // de cada duas vezes, simula-se um erro
      int val = session.Randomizer.Next(2);
      if (val == 0)
      {
        // é devolvido um erro
        return Json(new { Erreur = "[erreur aléatoire]", HeureCalcul = HeureCalcul });
      }
      // cálculos
      string AplusB = string.Format("{0}", modèle.A + modèle.B);
      string AmoinsB = string.Format("{0}", modèle.A - modèle.B);
      string AmultipliéparB = string.Format("{0}", modèle.A * modèle.B);
      string AdiviséparB = string.Format("{0}", modèle.A / modèle.B);
      // são devolvidos os resultados
      return Json(new { Erreur = "", AplusB = AplusB, AmoinsB = AmoinsB, AmultipliéparB = AmultipliéparB, AdiviséparB = AdiviséparB, HeureCalcul = HeureCalcul });
    }
  • linha 2: o método devolve um tipo [JsonResult], ou seja, um texto no formato JSON;
  • linha 16: as informações são apresentadas sob a forma de uma instância de classe anónima serializada em JSON. O método [getErrorMessagesFor] já foi apresentado. A cadeia JSON enviada ao navegador terá o seguinte formato:
{"Erreur":"[erreur aléatoire]","HeureCalcul":"05:31:37"}
  • linha 31: o mesmo procedimento aplica-se aos resultados aritméticos. Desta vez, a cadeia JSON enviada ao navegador terá o seguinte formato:
{"Erreur":"","AplusB":"4","AmoinsB":"-2","AmultipliéparB":"3","AdiviséparB":"0,333333333333333","HeureCalcul":"05:52:17"}

7.4.3. O código JavaScript do lado do cliente

Recorde-se a configuração da chamada Ajax na página HTML enviada ao navegador do cliente:


  AjaxOptions ajaxOpts = new AjaxOptions
  {
    HttpMethod = "post",
    Url = Url.Action("Action02Post"),
    LoadingElementId = "loading",
    LoadingElementDuration = 1000,
    OnBegin = "OnBegin",
    OnFailure = "OnFailure",
    OnSuccess = "OnSuccess",
    OnComplete = "OnComplete"
};    

As funções JS referenciadas nas linhas 7-10 (à direita do sinal =) estão definidas no seguinte ficheiro [myScripts-02.js]:


// dados globais
var entete;
var loading;
var résultats;
var erreur;
var heureCalcul;
var msg;
var AplusB;
var AmoinsB;
var AmultipliéparB;
var AdiviséparB;
var formulaire;
...
function postForm() {
...
}

// ao carregar o documento
$(document).ready(function () {
  formulaire = $("#formulaire");
  entete = $("#entete");
  loading = $("#loading");
  erreur = $("#erreur");
  résultats = $('#resultados');
  heureCalcul = $("#heureCalcul");
  msg = $("#msg");
  AplusB = $("#AplusB");
  AmoinsB = $("#AmoinsB");
  AmultipliéparB = $("#AmultipliéparB");
  AdiviséparB = $("#AdiviséparB");

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

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

// fim da consulta
function OnComplete() {
...
}

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

// erro
function OnFailure(request, error) {
...
}
  • linha 19: a função JS é executada no final do carregamento da página no navegador;
  • linhas 20-30: recuperam-se as referências de todos os componentes da página que nos interessam. A pesquisa de um componente numa página tem um custo e é preferível fazê-la apenas uma vez;
  • linhas 33-35: os componentes [entete], [résultats] e [loading] são ocultados;

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


// início
function OnBegin() {
  // luz de espera acesa
  loading.show();
  // ocultar determinados elementos da página
  entete.hide();
  résultats.hide();
  erreur.hide();
}
  • linha 4: o componente [loading] é apresentado. Trata-se da imagem animada;
  • linhas 6-8: os componentes [entete], [résultats] e [erreur] ficam ocultos;

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


// sucesso
function OnSuccess(data) {
  // exibição dos resultados
  heureCalcul.text(data.HeureCalcul);
  entete.show();
  if (data.Erreur != '') {
    msg.text(data.Erreur);
    erreur.show();
    return;
  }
  // sem erros
  AplusB.text(data.AplusB);
  AmoinsB.text(data.AmoinsB);
  AmultipliéparB.text(data.AmultipliéparB);
  AdiviséparB.text(data.AdiviséparB);
  résultats.show();
}

Para compreender este código, é necessário ter em conta os dois textos JSON que podem ser enviados como resposta ao navegador:

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

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

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

Se designarmos esta cadeia por [data], o valor do campo [Erreur] é obtido através da notação [data.Erreur] ou [data["Erreur"]], à escolha. O mesmo se aplica aos outros campos da cadeia JSON. Além disso, para atribuir um texto não formatado a um componente com o ID X, escreve-se [X.text(chaine)]. Voltemos ao código da função [OnSuccess]:

  • linha 2: [data] é a cadeia JSON recebida;
  • linha 4: o componente [heureCalcul] recebe o seu valor;
  • linha 5: o componente [entete] é apresentado;
  • linha 6: teste do campo [Erreur] da cadeia JSON;
  • linha 7: o componente [msg] recebe o seu valor;
  • linha 8: o componente [erreur] é apresentado;
  • linha 9: o processo de erro está concluído;
  • linha 12: o componente [AplusB] recebe o seu valor;
  • linha 13: o componente [AmoinsB] recebe o seu valor;
  • linha 14: o componente [AmultipliéparB] recebe o seu valor;
  • linha 15: o componente [AdiviséparB] recebe o seu valor;
  • linha 16: o componente [résultats] é apresentado.

A função [OnFailure] será executada caso a solicitação Ajax HTTP falhe. Esta falha é identificada pelo código HTTP devolvido pelo servidor. O código 500 [Internal Server Error], por exemplo, indica que o servidor não conseguiu executar a solicitação. A função [OnFailure] é a seguinte:


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

Limita-se a apresentar uma caixa de diálogo com o erro que ocorreu. Na prática, seria necessário ser mais preciso. Em breve iremos propor outra solução.

Por fim, a função [OnComplete] é executada quando a consulta termina, quer tenha sido bem-sucedida quer tenha falhado.


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

Recorde-se aqui que é a configuração da chamada Ajax na vista [Action02Get.cshtml] que faz com que estas diferentes funções sejam chamadas:


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

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


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

A função JS [postForm] encontra-se no ficheiro importado [myScripts-02.js]:


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

O seu código é o seguinte:


function postForm() {
  // é efetuada uma chamada Ajax manualmente com JQuery
  $.ajax({
    url: '/Premier/Action02Post',
    type: 'POST',
    data: formulaire.serialize(),
    dataType: 'json',
    beforeSend: OnBegin,
    success: OnSuccess,
    error: OnFailure,
    complete: OnComplete
  })
}

Já nos deparámos com um código semelhante.

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

Voltemos à função JavaScript que gere o caso em que a chamada Ajax falha (linha 10). A chamada Ajax falha em várias situações, por exemplo, quando o servidor devolve um código de erro como [403 Forbidden], [404 Not Found], [500 Internal Server Error], [301 Moved Permanently], ...

No exemplo anterior, a função [OnFailure] é a seguinte:


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

Normalmente, a visualização do objeto [error] não fornece nenhuma informação relevante. Se for utilizada uma chamada Ajax efetuada com JQuery, pode-se utilizar o seguinte método [OnFailure];


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

O objeto JQuery [jqXHR] possui, entre as suas propriedades, as seguintes:

  • responseText: o texto da resposta do servidor;
  • status: o código de erro devolvido pelo servidor;
  • statusText: o texto associado a este código de erro.
  • linha 3: exibe-se o código de erro e a descrição correspondente;
  • linha 4: insere-se a resposta HTML do servidor no componente com o ID [msg];
  • linha 5: exibe-se a região com o ID [erreur].

Para testar esta função de erro, vamos criar artificialmente uma exceção na ação [Action02Post]:


    [HttpPost]
    public JsonResult Action02Post(FormCollection postedData, SessionModel session)
    {
      // uma exceção artificial para testar a função de erro da chamada Ajax
      throw new Exception();
      // simulação de espera
      Thread.Sleep(2000);
      // validação do modelo
...

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

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

A resposta do servidor permite-nos ver em que linha do código do servidor ocorreu o erro. Esta é frequentemente uma informação útil de se saber. A partir de agora, utilizaremos esta técnica para gerir os erros das chamadas Ajax.

7.5. Aplicação web de página única

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

  • a primeira página resulta de um pedido clássico de um navegador;
  • as páginas seguintes são obtidas através de chamadas Ajax. Assim, no final, o navegador nunca muda de URL e nunca carrega uma nova página. A este tipo de aplicação chama-se «Aplicação de Página Única» (APU) ou, em inglês, «Single Page Application» (SPA).

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

  • em [1], a ação [Action03Get] permite-nos aceder à primeira página, a página 1;
  • em [2], um link permite-nos passar para a página 2 através de uma chamada Ajax;
  • em [3], o URL não sofreu alterações. A página apresentada é a página 2;
  • em [4], um link permite-nos regressar à página 1 através de uma chamada Ajax;
  • em [5], o URL não sofreu alterações. A página apresentada é a página 1.

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


    [HttpGet]
    public ViewResult Action03Get()
    {
      return View();
}
  • linha 4: a vista [Action03Get.cshtml] é apresentada.

A vista [Action03Get.cshtml] é a seguinte:


@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action03Get</title>
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
</head>
<body>
  <h3>Ajax - 03 - Single Page Application</h3>
  <div id="content">
    @Html.Partial("Page1")
  </div>
</body>
</html>
  • linhas 16-18: um elemento com o ID [content]. É neste elemento que as diferentes páginas serão apresentadas;
  • linha 17: por predefinição, é a página [Page1.cshtml] que será exibida em primeiro lugar.

A página [Page1.cshtml] é a seguinte:


<h4>Page 1</h4>
  <p>
    @Ajax.ActionLink("Page 2", "Action04", new { Page = 2 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>
  • linha 1: o título da página para a diferenciar da página 2;
  • linha 3: um link Ajax com os seguintes parâmetros:
    • o texto do link [Page 2];
    • a ação de destino do link [Action04];
    • os parâmetros da solicitação URL. Este será [/Premier/Action04?Page=2];
  • as opções da chamada Ajax. Neste caso, apenas o ID da região a atualizar com a resposta do servidor. Para as restantes opções, são utilizados valores por predefinição, sempre que existam. O método HTTP por predefinição é GET.

Vamos ver o que acontece quando se clica no link. O URL [/Premier/Action04?Page=2] é solicitado com um GET. A ação [Action04] é então executada:


    [HttpGet]
    public PartialViewResult Action04(string page = "1")
    {
      string vue = "Page1";
      if (page == "2")
      {
        vue = "Page2";
      }
      return PartialView(vue);
}
  • linha 2: a ação devolve um fluxo HTML parcial;
  • linha 2: a ação tem como modelo a cadeia [page]. Ora, sabe-se que o URL contém esta informação: [/Premier/Action04?Page=2]. Recorde-se que o modelo não distingue maiúsculas de minúsculas;
  • linhas 4-8: [vue] irá receber o valor [Page2];
  • linha 9: a vista parcial [Page2.cshtml] é apresentada.

A vista parcial [Page2.cshtml] é a seguinte:


<h4>Page 2</h4>
  <p>
    @Ajax.ActionLink("Page 1", "Action04", new { Page = 1 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>

O servidor devolve, portanto, o fluxo HTML acima como resposta à chamada Ajax GET [/Premier/Action04?Page=2]. Recorde-se que esta chamada Ajax utiliza esta resposta para atualizar a região com o ID [content] (linha 3 abaixo):


<h4>Page 1</h4>
  <p>
    @Ajax.ActionLink("Page 2", "Action04", new { Page = 2 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>

Isto resulta na seguinte nova exibição: [1]:

Seguindo o mesmo raciocínio, verifica-se que clicar na ligação [Page 1] a partir de [1] irá provocar a exibição de [2].

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

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

  • em [1], a camada Web ASP.NET MVC tornou-se uma interface Web de acesso aos dados, geralmente alojados numa base de dados. As vistas apresentadas contêm apenas dados e nenhum elemento de apresentação HTML, por exemplo, fluxos XML ou JSON;
  • em [2]: o navegador apresenta vistas estáticas (ou seja, não geradas dinamicamente) fornecidas por um servidor web que pode ou não estar na mesma máquina que o servidor [1]. Estas visualizações estáticas são posteriormente enriquecidas com os dados obtidos pelo JavaScript a partir da interface web [1];
  • o código JavaScript incorporado nas páginas HTML pode ser estruturado em camadas:
    • a camada [présentation] trata das interações com o utilizador,
    • a camada [DAO] trata do acesso aos dados através do servidor web [1],
    • a camada [métier] corresponde à camada [métier], que anteriormente se encontrava no servidor [1] e que foi transferida para o navegador [2];

A vantagem desta arquitetura é que envolve competências diferentes:

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

Assim, esta arquitetura facilita o trabalho em paralelo de equipas com competências diferentes. Aplica-se particularmente bem a aplicações de página única.

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

Já mencionámos anteriormente uma anomalia no exemplo Ajax-01. Recordamos o contexto:

  • em [1] e [2], foram introduzidos valores inválidos. Estes são sinalizados pelos validadores do lado do cliente;
  • no [3], clicou-se na ligação [Calculer];
  • em [4], ocorreu um [POST], uma vez que se obtém a resposta [4].

Quando os valores são inválidos e se clica no botão [Calculer], o [POST] para o servidor não ocorre. No mesmo caso, com o link [Calculer], o [POST] para o servidor ocorre. Existe, portanto, um comportamento do botão [Calculer] que não conseguimos reproduzir com o link [Calculer].

Vamos retomar este exemplo num novo contexto: a aplicação terá várias vistas e será do tipo [Application à Page Unique] que acabámos de descrever.

7.6.1. As vistas do exemplo

O exemplo tem várias vistas:

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

A aplicação é de página única: esta é carregada pelo navegador aquando do primeiro pedido. É posteriormente atualizada através de chamadas Ajax.

As páginas anteriores são geradas pelas seguintes vistas [cshtml]:

A vista carregada inicialmente é a seguinte vista [Action05Get.cshtml]:


@model Exemple_04.Models.ViewModel05
@{
  Layout = null;
}

<!DOCTYPE html>

<html lang="fr-FR">
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Ajax-05</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.en-US.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
  <script type="text/javascript" src="~/Scripts/myScripts-05.js"></script>
</head>
<body>

  <h2>Ajax - 05, Page unique - Validation formulaire côté client</h2>
  <p><strong>Heure de chargement : @Model.HeureChargement</strong></p>
  <h4>Opérations arithmétiques sur deux nombres réels A et B positifs ou nuls</h4>
  <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
  <div id="content">
    @Html.Partial("Formulaire05", Model)
  </div>
</body>
</html>

É importante destacar os seguintes pontos:

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

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

A vista parcial [Formulaire05] irá gerar a parte [2] acima referida. O seu código é o seguinte:


@model Exemple_04.Models.ViewModel05

@using (Html.BeginForm("Action05Post", "Premier", FormMethod.Post, new { id = "formulaire" }))
{
  <table>
    <thead>
      <tr>
        <th>@Html.LabelFor(m => m.A)</th>
        <th>@Html.LabelFor(m => m.B)</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>@Html.TextBoxFor(m => m.A)</td>
        <td>@Html.TextBoxFor(m => m.B)</td>
      </tr>
      <tr>
        <td>@Html.ValidationMessageFor(m => m.A)</td>
        <td>@Html.ValidationMessageFor(m => m.B)</td>
      </tr>
    </tbody>
  </table>
  <p>
    <table>
      <tbody>
        <tr>
          <td><a href="javascript:calculer()">Calculer</a>
          </td>
          <td style="width: 20px" />
          <td><a href="javascript:effacer()">Effacer</a>
          </td>
        </tr>
      </tbody>
    </table>
  </p>
}
  • linha 1: a vista parcial utiliza como modelo um tipo [ViewModel05];
  • linha 3: o formulário gerado pelo método [Html.BeginForm]. Como este formulário será enviado através de uma chamada Ajax, os três primeiros parâmetros do método serão ignorados. A menos que o utilizador tenha desativado o JavaScript no seu navegador. Ignoramos aqui essa possibilidade. O quarto parâmetro é importante. O formulário terá o ID [formulaire];
  • linhas 5-22: o formulário para introduzir os números A e B;
  • linha 27: um link JavaScript que inicia a execução das quatro operações aritméticas em A e B;
  • linha 30: um link JavaScript que apaga os valores introduzidos e as eventuais mensagens de erro a eles associadas.

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

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

A parte [4] acima é gerada pela seguinte vista parcial [Success05.cshtml]:


@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h4>Résultats</h4>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>
<p>
  <a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
  • linha 1: a vista parcial [Success05.cshtml] recebe um modelo do tipo [ViewModel05];
  • linha 12: um link JavaScript para regressar aos dados introduzidos.

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

Esta vista é gerada pelo seguinte código [Failure05.cshtml]:


@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
  @foreach (string msg in Model.Erreurs)
  {
    <li>@msg</li>
  }
</ul>
<p>
  <a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
  • linha 1: a vista parcial [Failure05.cshtml] recebe um modelo do tipo [ViewModel05];
  • linha 14: um link JavaScript para regressar aos dados introduzidos.

7.6.2. O modelo das vistas

Todas as vistas anteriores partilham o mesmo modelo [ViewModel05]:


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace Exemple_04.Models
{
  [Bind(Exclude = "AplusB, AmoinsB, AmultipliéparB, AdiviséparB, Erreurs, HeureChargement, HeureCalcul")]
  public class ViewModel05
  {
    // formulário
    [Required(ErrorMessage="Donnée A requise")]
    [Display(Name="Valeur de A")]
    [Range(0, Double.MaxValue, ErrorMessage = "Tapez un nombre A positif ou nul")]
    public string A { get; set; }
    [Required(ErrorMessage = "Donnée B requise")]
    [Display(Name = "Valeur de B")]
    [Range(0, Double.MaxValue, ErrorMessage="Tapez un nombre B positif ou nul")]
    public string B { get; set; }

    // resultados
    public string AplusB { get; set; }
    public string AmoinsB { get; set; }
    public string AmultipliéparB { get; set; }
    public string AdiviséparB { get; set; }
    public List<string> Erreurs { get; set; }
    public string HeureChargement { get; set; }
    public string HeureCalcul { get; set; }
  }
}

Trata-se do modelo [ViewModel01] já apresentado, com algumas pequenas diferenças:

  • linhas 15 e 19: os campos A e B são agora do tipo [string], de modo a apresentar campos de introdução de dados vazios, em vez de campos com o valor 0, aquando da exibição inicial do formulário de introdução de dados;
  • linhas 14 e 18: isto não impede a verificação do valor introduzido com um validador [Range];
  • linha 26: uma lista de mensagens de erro apresentada pela vista [Failure05].

7.6.3. Os dados do escopo [Session]

No parágrafo 7.3.6, vimos que os dados da sessão estavam encapsulados no seguinte modelo [SessionModel]:


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

Este modelo de sessão é ampliado para integrar os valores de A e B:


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

É, de facto, necessário memorizar os valores de A e B na sessão, tal como mostra a sequência seguinte:

Consulta 1

Solicitação 2

Em [4], encontram-se os dados introduzidos em [1]. No entanto, existem duas consultas distintas: HTTP. Sabe-se que a memória entre duas solicitações HTTP é a sessão. Para que a segunda solicitação possa recuperar os valores enviados pela primeira, estes têm de ser guardados na sessão.

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

A ação [Action05Get] é a ação que faz com que a página inicial única seja apresentada. O seu código é o seguinte:


    [HttpGet]
    public ViewResult Action05Get()
    {
      ViewModel05 modèle = new ViewModel05();
      modèle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
      return View(modèle);
}
  • linha 6: a vista [Action05Get.cshtml], já analisada, é apresentada com um modelo do tipo [ViewModel05];

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

Analisemos as interações do utilizador com as vistas:

O link [1] é um link JavaScript:


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

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


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

O código da função JavaScript [calculer] é o seguinte:


// dados globais
var content;
var loading;

function calculer() {
  // primeiro, as referências no DOM
  var formulaire = $("#formulaire");
  // depois, validação do formulário
  if (!formulaire.validate().form()) {
    // formulário inválido - concluído
    return;
  }
  // faz-se uma chamada Ajax manualmente
  $.ajax({
    url: '/Premier/Action05FaireCalcul',
    type: 'POST',
    data: formulaire.serialize(),
    dataType: 'html',
    beforeSend: function () {
      loading.show();
    },
    success: function (data) {
      content.html(data);
    },
    complete: function () {
      loading.hide();
    },
    error: function (jqXHR) {
      // exibição da resposta do servidor
      content.html(jqXHR.responseText);
    }
  })
}

function retourSaisies() {
 ...
}

function effacer() {
  ...
}

// ao carregar o documento
$(document).ready(function () {
  // recuperam-se as referências dos diferentes componentes da página
  loading = $("#loading");
  content = $("#content");
  // armazenamos a imagem animada na cache
  loading.hide();
});
  • Recorde-se que o código JavaScript é sempre executado do lado do cliente, no navegador;
  • linha 44: a função JS é executada quando o carregamento inicial da página única estiver concluído;
  • linha 46: referência à imagem animada com o id [loading];
  • linha 47: referência à região com o id [content]. É esta região que recebe as vistas parciais [Formulaire05, Success05, Failure05];
  • linhas 2-3: as variáveis das linhas 46-47 são declaradas globais para que as outras funções tenham acesso às mesmas. A pesquisa de elementos numa página (linhas 46-47) tem um custo. Não há motivo para repetir essa pesquisa se for possível evitá-la;
  • linha 5: a função [calculer];
  • linha 7: obtém-se uma referência ao formulário. A vista parcial [Formulaire05] atribuiu-lhe o ID [formulaire];
  • linha 9: esta instrução executa os validadores do formulário do lado do cliente. Era isto que faltava na anomalia constatada na página 183. Este método é fornecido pela biblioteca [jquery.unobstrusive-ajax] utilizada pela página única:

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

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

  • linha 11: a chamada Ajax ao servidor não é efetuada se o formulário for inválido;
  • linhas 14-32: é efetuada a chamada Ajax ao servidor;
  • linha 15: o URL tem como destino a ação do servidor [Action05FaireCalcul];
  • linha 16: é solicitada através de um [POST];
  • linha 17: os valores enviados. Trata-se dos dados introduzidos no formulário, neste caso os valores de A e B;
  • linhas 22-24: caso a chamada Ajax seja bem-sucedida, a função [calculer] atualiza a região com o ID [content] com o fluxo HTML enviado pelo servidor.

Este fluxo HTML é o enviado pela ação [Action05FaireCalcul] a que a chamada Ajax se destina. O código desta ação do lado do servidor é o seguinte:


    [HttpPost]
    public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
    {
      // modelo
      ViewModel05 modèle = new ViewModel05();
      // hora do cálculo
      modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
      // atualização do modelo
      TryUpdateModel(modèle, postedData);
      if (!ModelState.IsValid)
      {
        // retorna um erro
        modèle.Erreurs = getListOfMessagesFor(ModelState);
        return PartialView("Failure05", modèle);
      }
...
}
  • linha 1: a ação aceita apenas um [post];
  • linha 2: devolve uma vista parcial;
  • linha 2: recebe como parâmetros os valores enviados (postedData) e o modelo da sessão (session);
  • linha 5: o modelo da vista parcial é criado;
  • linha 7: é atualizado com a hora de cálculo;
  • linha 9: tenta-se aplicar os valores enviados ao modelo. Os validadores deste serão então executados. Podemos questionar-nos por que razão nos damos a este trabalho, uma vez que os validadores do lado do cliente impedem o POST se os dados introduzidos forem inválidos. Na verdade, não temos a certeza da origem do POST. Pode ter sido gerado por um código que não é o nosso. Por isso, devemos sempre efetuar as verificações do lado do servidor;
  • linha 10: verificamos se os validadores foram bem-sucedidos;
  • linha 13: se o modelo for inválido, este é atualizado com uma lista de erros. Não iremos detalhar o método interno [getListOfMessagesFor], semelhante ao método [GetErrorMessagesFor] descrito na página 65;
  • linha 14: a vista parcial [Failure05] é apresentada com o seu modelo. Recordamos o código desta vista;

@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
  @foreach (string msg in Model.Erreurs)
  {
    <li>@msg</li>
  }
</ul>
<p>
  <a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
  • linhas 7-12: a lista de erros do modelo é apresentada através de uma baliza <ul>.

Recorde-se que a função JS [calculer], naorigem do [Post] na ação do servidor [Action05FaireCalcul] irá inserir este fluxo HTML na região com o ID [content]. O resultado é algo semelhante a isto:

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


    [HttpPost]
    public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
    {
      // modelo
      ViewModel05 modèle = new ViewModel05();
...
      // os valores de A e B são colocados na sessão
      session.A = modèle.A;
      session.B = modèle.B;
      // sem erros por enquanto
      List<string> erreurs = new List<string>();
      // de cada duas vezes, simula-se um erro
      int val = session.Randomizer.Next(2);
      if (val == 0)
      {
        erreurs.Add("[erreur aléatoire]");
      }
      if (erreurs.Count != 0)
      {
        modèle.Erreurs = erreurs;
        return PartialView("Failure05", modèle);
      }
      // cálculos
      double A = double.Parse(modèle.A);
      double B = double.Parse(modèle.B);
      modèle.AplusB = string.Format("{0}", A + B);
      modèle.AmoinsB = string.Format("{0}", A - B);
      modèle.AmultipliéparB = string.Format("{0}", A * B);
      modèle.AdiviséparB = string.Format("{0}", A / B);
      // visualização
      return PartialView("Success05", modèle);
}
  • linha 7: o modelo foi declarado válido;
  • linhas 8-9: os valores introduzidos A e B são guardados na sessão. Pretende-se poder recuperá-los na consulta que se seguirá;
  • linhas 11-22: gera-se aleatoriamente um erro uma vez em cada duas;
  • linhas 24-29: realizam-se as quatro operações aritméticas sobre os números reais introduzidos;
  • linha 31: devolve-se a vista parcial [Success05] com o seu modelo. Esta vista parcial é a seguinte:

@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h4>Résultats</h4>
<p>A+B=@Model.AplusB</p>
<p>A-B=@Model.AmoinsB</p>
<p>A*B=@Model.AmultipliéparB</p>
<p>A/B=@Model.AdiviséparB</p>
<p>
  <a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>

Recorde-se que a função JS [calculer], naorigem do [Post] na ação do servidor [Action05FaireCalcul] irá colocar este fluxo HTML na região de ID [content]. O resultado é algo semelhante a isto:

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

O link JavaScript [Effacer] permite repor o formulário ao seu estado inicial:

No formulário, o link JS [Effacer] está definido da seguinte forma:


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

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


// dados globais
var content;
var loading;

function calculer() {
...
}

function retourSaisies() {
...
}

function effacer() {
  // primeiro as referências no DOM
  var formulaire = $("#formulaire");
  var A = $("#A");
  var B = $("#B");
  // atribuem-se valores válidos aos dados introduzidos
  A.val("0");
  B.val("0");
  // depois valida-se o formulário para que desapareçam
  // as eventuais mensagens de erro
  formulaire.validate().form();
  // e, em seguida, atribuem-se cadeias de caracteres vazias aos campos de introdução de dados
  A.val("");
  B.val("");
}

// ao carregar o documento
$(document).ready(function () {
  // recuperam-se as referências dos diferentes componentes da página
  loading = $("#loading");
  content = $("#content");
  // ocultamos a imagem animada
  loading.hide();
});
  • linhas 15-17: recuperam-se referências a vários elementos do DOM (Document Object Model);
  • linhas 19-20: inserem-se valores válidos nos campos de introdução dos números A e B;
  • linha 23: executam-se os validadores do lado do cliente. Como os valores de A e B são válidos, isto fará com que desapareçam eventuais mensagens de erro que pudessem ser apresentadas;
  • linhas 25-26: inserem-se cadeias vazias nos campos de introdução dos números A e B;

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

O link JavaScript [Retour aux Saisies] permite regressar ao formulário após obter os resultados:

No formulário, o link JS [Retour aux Saisies] está definido da seguinte forma:


  <a href="javascript:retourSaisies()">Retour aux saisies</a>

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


// dados globais
var content;
var loading;

function calculer() {
...
}

function retourSaisies() {
  // efetua-se manualmente uma chamada Ajax
  $.ajax({
    url: '/Premier/Action05RetourSaisies',
    type: 'POST',
    dataType: 'html',
    beforeSend: function () {
      loading.show();
    },
    success: function (data) {
      content.html(data);
    },
    complete: function () {
      loading.hide();
      // IMPORTANT !! validação
      $.validator.unobtrusive.parse($("#formulaire"));
    },
    error: function (jqXHR) {
      content.html(jqXHR.responseText);
    }
  })
}

function effacer() {
...
}

// ao carregar o documento
$(document).ready(function () {
  // recuperam-se as referências dos diferentes componentes da página
  loading = $("#loading");
  content = $("#content");
  // ocultamos a imagem animada
  loading.hide();
});
  • linhas 11-29: uma chamada Ajax;
  • linha 12: o URL de destino;
  • linha 13: será solicitada por um comando HTTP POST. Trata-se de um POST sem parâmetros enviados. É por isso que não se encontra uma linha do tipo:
    data: formulaire.serialize(),

na chamada Ajax;

  • linha 14: o fluxo esperado do servidor é um fluxo HTML;
  • linhas 18-20: este fluxo HTML servirá para atualizar a região com o ID [content];

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


    [HttpPost]
    public PartialViewResult Action05RetourSaisies(SessionModel session)
    {
      // visualização
      return PartialView("Formulaire05", new ViewModel05() { A = session.A, B = session.B });
}
  • linha 2: a ação recebe como parâmetro o modelo da sessão no qual armazenámos anteriormente os valores de A e B introduzidos;
  • linha 5: devolve-se a vista parcial [Formulaire05] com um modelo do tipo [ViewModel05], no qual se tem o cuidado de inicializar os campos A e B com os valores de A e B retirados da sessão;

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


function retourSaisies() {
  // efetua-se manualmente uma chamada Ajax
  $.ajax({
    url: '/Premier/Action05RetourSaisies',
    type: 'POST',
    dataType: 'html',
    beforeSend: function () {
      loading.show();
    },
    success: function (data) {
      content.html(data);
    },
    complete: function () {
      loading.hide();
      // IMPORTANT !! validação
      $.validator.unobtrusive.parse($("#formulaire"));
    },
    error: function (jqXHR) {
      content.html(jqXHR.responseText);
    }
  })
}
  • linha 13: o método executado quando a chamada Ajax estiver concluída;
  • linha 14: a imagem animada de espera é ocultada;
  • linha 16: uma instrução um pouco obscura para mim, encontrada na Internet, para resolver o seguinte problema: no formulário apresentado pelo link [Retour aux saisies], os validadores do lado do cliente já não funcionavam. Ao procurar informações sobre a biblioteca JS [jquery.unobtrusive-ajax], encontrei a solução da linha 16. Esta analisa o formulário, talvez para ativar os validadores do lado do cliente.

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

Consulte o parágrafo 9.26.

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

Consulte o parágrafo 9.27.