Skip to content

7. Implementación de Ajax en una aplicación ASP.NET MVC

7.1. El papel de AJAX en una aplicación web

Por el momento, los ejemplos de aprendizaje analizados tienen la siguiente arquitectura:

Para pasar de una vista [Vue1] a una vista [Vue2], el navegador:

  • envía una solicitud a la aplicación web;
  • recibe la vista [Vue2] y la muestra en lugar de la vista [Vue1].

Este es el esquema clásico:

  • solicitud del navegador;
  • el servidor web genera una vista en respuesta al cliente;
  • el navegador muestra esta nueva vista.

Existe otro modo de interacción entre el navegador y el servidor web: AJAX (Asynchronous JavaScript and XML). De hecho, se trata de interacciones entre la vista mostrada por el navegador y el servidor web. El navegador sigue haciendo lo que sabe hacer, es decir, mostrar una vista HTML, pero ahora es controlado por JavaScript integrado en la vista HTML que se muestra. El esquema es el siguiente:

  • en [1], ocurre un evento en la página que se muestra en el navegador (clic en un botón, cambio de texto, etc.). Este evento es interceptado por JavaScript (JS) integrado en la página;
  • en [2], el código de JavaScript realiza una solicitud HTTP tal como lo habría hecho el navegador. La solicitud es asíncrona: el usuario puede seguir interactuando con la página sin que se bloquee mientras espera la respuesta a la solicitud HTTP. La solicitud sigue el proceso clásico de procesamiento. Nada (o casi nada) la distingue de una solicitud convencional;
  • en [3], se envía una respuesta al cliente JS. En lugar de una vista HTML completa, lo que se envía es más bien una vista HTML parcial, un flujo XML o JSON (notación de objeto JavaScript) lo que se envía;
  • en [4], el JavaScript recupera esta respuesta y la utiliza para actualizar una sección de la página HTML que se muestra.

Para el usuario, se produce un cambio en la vista porque lo que ve ha cambiado. Sin embargo, no hay una recarga total de la página, sino simplemente una modificación parcial de la página que se muestra. Esto contribuye a dar fluidez e interactividad a la página: como no hay una recarga total de la página, podemos gestionar eventos que antes no se podían manejar. Por ejemplo, ofrecerle al usuario una lista de opciones a medida que va ingresando caracteres en un campo de entrada. Con cada nuevo carácter que se escribe, se envía una solicitud AJAX al servidor, el cual devuelve otras sugerencias. Sin Ajax, este tipo de ayuda para la entrada de datos antes era imposible. No se podía recargar una nueva página con cada carácter escrito.

7.2. Conceptos básicos de JQuery y JavaScript

A menudo hemos incorporado la biblioteca de JavaScript JQuery a nuestras páginas. En ella se encuentra la línea:


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

Nota: adapta la versión de jQuery a la de tu versión de Visual Studio.

La tecnología Ajax de ASP.NET MVC utiliza JQuery. Vamos a escribir nosotros mismos algunos scripts JQuery. Por eso, a continuación te presentamos los conceptos básicos de JQuery que debes conocer para entender los scripts de este capítulo.

Creamos un nuevo proyecto [Exemple-04] dentro de nuestra solución [Exemples]:

Para utilizar Ajax con ASP.NET MVC, debe haber una línea en el archivo de configuración [Web.config] [1]:


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

La línea 3 permite el uso de Ajax en las vistas ASP.NET. Está presente de forma predeterminada.

Creamos un archivo HTML [JQuery-01.html] en la carpeta [Content] del nuevo proyecto [2]:

Este archivo tendrá el siguiente contenido:


<!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>
  • línea 6: importación de JQuery (adapta la versión a la de tu versión de Visual Studio);
  • líneas 10-12: un elemento de la página con el ID [element1]. Vamos a trabajar con este elemento.

Visualizamos este archivo en el navegador Google Chrome: [4] y [5]:

En Google Chrome, presiona [Ctrl-Maj-I] para que aparezcan las herramientas de desarrollo [6]. La pestaña [Console] [7] permite ejecutar código JavaScript. A continuación, te presentamos algunos comandos de JavaScript que puedes escribir, junto con una explicación de cada uno.

JS
resultado
$("#element1")
: devuelve la colección de todos los elementos con el identificador [element1]; por lo tanto, normalmente será una colección de 0 o 1 elemento, ya que no pueden existir dos identificadores idénticos en una página HTML.
$("#element1").text("blabla")
: aplica el texto [blabla] a todos los elementos de la colección. Esto tiene como efecto cambiar el contenido que muestra la página
$("#element1").hide()
oculta los elementos de la colección. El texto [blabla] ya no se muestra.
$("#element1")
: vuelve a mostrar la colección. Esto nos permite ver que el elemento con el ID [element1] tiene el atributo CSS style='display: none;', lo que hace que el elemento quede oculto.
$("#element1").show()
: muestra los elementos de la colección. El texto [blabla] vuelve a aparecer. Es el atributo CSS style='display: block;' el que garantiza esta visualización.
$("#element1").attr('style','color: red')
: asigna un atributo a todos los elementos de la colección. El atributo aquí es [style] y su valor es [color: red]. El texto [blabla] se vuelve rojo.
Tableau
Dictionnaire

Cabe destacar que el URL del navegador no ha cambiado durante todas estas operaciones. No ha habido intercambio alguno con el servidor web. Todo ocurre dentro del navegador. Ahora, veamos el código fuente de la 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 es el texto inicial. No refleja en absoluto las modificaciones que hemos realizado en el elemento de las líneas 10 a 12. Es importante recordar esto al depurar JavaScript. Por lo tanto, a menudo no tiene sentido ver el código fuente de la página que se muestra. Para conocer el código fuente de la página que se muestra actualmente, procederemos de la siguiente manera:

 

Ya sabemos lo suficiente para entender los scripts JS que vendrán a continuación.

7.3. Actualización de una página con un flujo HTML

7.3.1. Las vistas

Nos proponemos estudiar la siguiente aplicación:

  • en [1], la hora de carga de la página;
  • en [2], se realizan las cuatro operaciones aritméticas con dos números reales A y B;
  • en [3], la respuesta del servidor se inserta en una sección de la página;
  • en [4], la hora del cálculo. Esta es diferente de la hora de carga de la página [5]. Esta última es igual a [1], lo que indica que la región [6] no se ha recargado. Además, el URL y el [7] de la página no han cambiado.

7.3.2. El controlador, las acciones, el modelo, la vista

Creamos un controlador llamado [Premier]:

Para mostrar la vista inicial, creamos la siguiente acción [Action01Get]:


    [HttpGet]
    public ViewResult Action01Get()
    {
      ViewModel01 modèle = new ViewModel01();
      modèle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
      return View(modèle);
}
  • línea 4: instanciación del modelo de la vista;
  • línea 5: inicialización de la hora de carga de la vista;
  • línea 6: visualización de la vista [Action10Get.cshtml] y de su modelo.

El modelo [ViewModel01] es el siguiente:


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
  {
    // formulario
    [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; }
  }
}
  • líneas 11-14: el valor A del formulario;
  • líneas 15-18: el valor B del formulario;
  • líneas 21-24: los resultados de las cuatro operaciones aritméticas con A y B;
  • línea 25: el texto de un posible error;
  • línea 26: la hora en que se cargó la vista en el navegador;
  • línea 27: la hora en que se calcularon los campos de las líneas 21-24;
  • línea 7: esta plantilla de vista también es una plantilla de acción. De esta última se excluyen los campos que el navegador no envía.

La vista [Action01Get.cshtml] es la siguiente:


@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>
  • línea 1: la vista tiene como modelo un tipo [ViewModel01];
  • línea 21: se necesita JQuery tanto para las validaciones como para Ajax;
  • líneas 22-23: las bibliotecas de validación;
  • líneas 24-26: las bibliotecas de internacionalización;
  • línea 27: la biblioteca de Ajax;
  • línea 28: una biblioteca de JavaScript local;
  • línea 33: visualización de la hora de carga de la vista;
  • línea 35: un formulario Ajax —volveremos sobre esto más adelante—;
  • líneas 40-41: etiquetas de los campos de entrada de los números A y B;
  • líneas 46-47: campos de entrada de los números A y B;
  • líneas 50-51: mensajes de error para los campos de los números A y B;
  • línea 56: el botón que envía el formulario. Este se enviará mediante una solicitud Ajax;
  • línea 57: una imagen de espera que se muestra durante la solicitud Ajax;
  • línea 58: un enlace para enviar el formulario mediante una solicitud Ajax;
  • línea 62: una etiqueta <div> con el identificador [résultats]. Ahí es donde colocaremos el flujo HTML devuelto por el servidor web.

Esta vista muestra la siguiente página:

 

Ahora veamos el código que implementa Ajax en el formulario:


...
@{
  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" />
  • línea 15: en lugar de usar [@Html.BeginForm], se usa [@Ajax.BeginForm]. Este método admite numerosas sobrecargas. La que se utiliza tiene la siguiente firma:
Ajax.BeginForm(string ActionName, RouteValueDictionary routeValues, AjaxOptions ajaxOptions, IDictionary<string,object> htmlAttributes)

Aquí utilizamos los siguientes parámetros efectivos:

Action01Post: el nombre de la acción que procesará el POST del formulario,

null: no hay información de ruta que proporcionar,

ajaxOpts: las opciones de la llamada Ajax. Se definieron en las líneas 6 a 10,

new { id = "formulario" }: para asignar el atributo [id='formulaire'] a la etiqueta <form> generada;

Las opciones de Ajax utilizadas son las siguientes:

  • línea 8: el destino de la solicitud Ajax;
  • línea 7: método de la solicitud Ajax HTTP;
  • línea 6: el identificador de la sección de la página que se actualizará con la respuesta a la solicitud Ajax;
  • línea 9: identificador de la sección de la página que se mostrará durante la solicitud Ajax —por lo general, una imagen de espera—. En este caso, se mostrará la línea 20, que contiene una imagen animada que simboliza la espera. Inicialmente, esta imagen está oculta por el estilo [display : none];
  • línea 10: tiempo de espera en milisegundos antes de que se muestre la imagen animada; en este caso, 1 segundo.

El código HTML generado por el formulario Ajax es el siguiente:


<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" />
  • línea 1: la etiqueta <form> generada. Cabe destacar los atributos [data-ajax-attr], que reflejan los valores de los campos del objeto de tipo [AjaxOptions] que se asoció a la solicitud Ajax. Estos atributos son administrados por la biblioteca Ajax. Sin ellos, la etiqueta <form> queda así:

<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>

En ese caso, se trata de un formulario HTML convencional. Este es el código que se ejecutará si el usuario desactiva JavaScript en su navegador. Las líneas 5 y 6 quedan entonces sin usar.

7.3.3. La acción [Action01Post]

La acción [Action01Post], que procesa la solicitud Ajax HTTP, es la siguiente:


    [HttpPost]
    public PartialViewResult Action01Post(FormCollection postedData, SessionModel session)
    {
      // simulación de espera
      Thread.Sleep(2000);
      // instanciación del modelo de la acción
      ViewModel01 modèle = new ViewModel01();
      // hora de cálculo
      modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
      // actualización del modelo
      TryUpdateModel(modèle, postedData);
      if (!ModelState.IsValid)
      {
        // se devuelve un error
        modèle.Erreur = getErrorMessagesFor(ModelState);
        return PartialView("Action01Error", modèle);
      }
      // una de cada dos veces, se simula un error
      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);
      // vista
      return PartialView("Action01Success", modèle);
}
  • línea 1: la acción solo procesa un [POST];
  • línea 2: admite como modelo de acción:
    • [FormCollection postedData]: el conjunto de valores enviados por la solicitud Ajax POST,
    • [SessionModel session]: los elementos de la sesión. Aquí se utiliza una técnica descrita en el párrafo 4.10;
  • línea 2: la acción devolverá un fragmento HTML y no una página completa HTML;
  • línea 5: de manera artificial, nos detenemos dos segundos para simular una acción Ajax prolongada;
  • línea 7: se instancia un modelo de tipo [ViewModel01];
  • línea 9: se inicializa el tiempo de cálculo;
  • línea 11: se intenta actualizar el modelo de tipo [ViewModel01] con los valores enviados. Recordemos que hay dos: los valores de los números A y B;
  • línea 12: se verifica si esta actualización se realizó con éxito o no;
  • línea 15: en caso de error, se completa el campo [Erreur] del modelo;
  • línea 16: se devuelve una vista parcial [Action01Error.cshtml] que utiliza el modelo [ViewModel01];
  • líneas 19-24: una de cada dos veces, se simula un error;
  • línea 19: se genera un número entero aleatorio en el intervalo [0,1]. El generador de números se toma de la sesión;
  • línea 20: si el valor generado es 0, se simula un error;
  • línea 22: el mensaje de error se inserta en el modelo;
  • línea 23: se devuelve una vista parcial [Action01Error.cshtml] que utiliza el modelo [ViewModel01];
  • líneas 26-29: se realizan los cálculos aritméticos con los números A y B y los resultados se colocan en el modelo en forma de cadenas de caracteres;
  • línea 31: se devuelve una vista parcial [Action01Success.cshtml] que utiliza el modelo [ViewModel01];

7.3.4. La vista [Action01Error]

La vista [Action01Error.cshtml] es la siguiente:


@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>

Cabe recordar que este flujo parcial HTML se enviará en respuesta a la solicitud Ajax HTTP de tipo POST y se colocará en la página en la región con id [résultats]. Toda esta información proviene de la configuración de Ajax utilizada en la 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
  };    
}

A continuación se muestra un ejemplo de respuesta con error:

 

7.3.5. La vista [Action01Success]

La vista [Action01Success.cshtml] es la siguiente:


@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>

Una vez más, este flujo parcial HTML se enviará como respuesta a la solicitud Ajax HTTP de tipo POST y se colocará en la página en la región con id [résultats]:

 

7.3.6. G estión de la sesión

Hemos visto que [Action01Post] utiliza la sesión. El modelo de la sesión es el siguiente tipo [SessionModel]:


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

La sesión se inicializa en [Global.asax]:


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

La asociación de la sesión con un modelo se realiza en [Application_Start]:


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

Se ha descrito la clase [SessionModelBinder].

7.3.7. Gestión de la imagen 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>
  }
...

Cuando se inicia la solicitud Ajax, la región con id [loading], línea 7, se muestra después de un segundo en [ligne 8]. Esta región es la imagen de la línea 21, que inicialmente estaba oculta. Esto da como resultado la siguiente interfaz:

7.3.8. Manejo del enlace [Calculer]

Analicemos el enlace [Calculer] de la 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" />
  • línea 18: al hacer clic en el enlace [Calculer] se ejecuta la función JS [postForm]. Esta función está definida en el archivo [myScripts-01.js], en la línea 5. Este script es el siguiente:

function postForm() {
  // se realiza una llamada Ajax manualmente con 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) {
    //Usa el complemento de globalización para analizar el valor        
    var val = Globalize.parseFloat(value);
    return this.optional(element) || (
        val >= param[0] && val <= param[1]);
  }
});

Las funciones de las líneas 19 a 37 ya se mencionaron en el párrafo 6.1. Se encargan de la internacionalización de las páginas. No volveremos sobre ellas. En las líneas 1 a 17, realizamos manualmente la llamada Ajax que, en el caso del botón [Calculer], la realizaba la biblioteca Ajax asociada al proyecto. Para ello, utilizamos la biblioteca JQuery asociada al proyecto.

  • línea 3: una referencia al componente con id [loading]. [$("#loading")] devuelve la colección de elementos con id [loading]. Solo hay uno;
  • línea 4: una referencia al componente con id [formulaire];
  • línea 5: una referencia al componente con id [résultats];
  • línea 6: la llamada Ajax con sus opciones;
  • línea 7: el URL, destino de la llamada Ajax;
  • línea 8: el método HTTP utilizado;
  • línea 9: los datos enviados. [formulaire.serialize] crea la cadena [A=val1&B=val2] del campo POST del formulario con id [formulaire];
  • línea 10: el tipo de datos que se espera recibir como respuesta. Sabemos que el servidor devolverá un flujo HTML;
  • línea 11: el método que se debe ejecutar al iniciar la solicitud. Aquí se indica que se debe mostrar el componente con el ID [loading]. Se trata de la imagen animada de espera;
  • línea 12: el método que se debe ejecutar si la solicitud Ajax tiene éxito. El parámetro [data] es la respuesta completa del servidor. Sabemos que se trata de un flujo HTML;
  • línea 13: se oculta la señal de espera;
  • línea 14: se actualiza el componente con id [résultats] con el valor HTML del parámetro [data].

Se invita al lector a probar el enlace [Calculer]. Funciona igual que el botón [Calculer], salvo por una an . Una vez que se ha utilizado este enlace, se pueden enviar valores inválidos de A y B:

  • en [1] y [2], se ingresaron valores no válidos. Estos son señalados por los validadores del lado del cliente;
  • en [3], se hizo clic en el enlace [Calculer];
  • en [4], se produjo un [POST], ya que se obtiene la respuesta [4].

Cuando los valores no son válidos y se hace clic en el botón [Calculer], no se envía el [POST] al servidor. En el mismo caso, con el enlace [Calculer], sí se envía la solicitud [POST] al servidor. Por lo tanto, existe un comportamiento del botón [Calculer] que no hemos podido reproducir con el enlace [Calculer]. En lugar de intentar resolver este problema ahora, lo dejamos para un ejemplo posterior que también ilustrará otro problema de validación del lado del cliente.

7.4. Actualización de una página HTML con un flujo JSON

En el ejemplo anterior, el servidor web respondía a la solicitud Ajax HTTP con un flujo HTML. En este flujo, había datos acompañados de formato HTML. Nos proponemos retomar el ejemplo anterior, pero esta vez con respuestas JSON (JavaScript, notación de objeto) que contengan únicamente los datos. La ventaja es que de esta manera se transmiten menos bytes.

7.4.1. La acción [Action02Get]

La acción [Action02Get] será el punto de entrada de la nueva aplicación. Su código es el siguiente:


@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>
  • líneas 4-14: las opciones de la llamada Ajax;
  • línea 10: la función JS que se ejecutará al iniciar la solicitud. Esta función está definida en el archivo JS al que se hace referencia en la línea 24;
  • línea 11: la función JS que se ejecutará en caso de que falle la solicitud;
  • línea 12: la función JS que se ejecutará si la consulta tiene éxito;
  • línea 13: la función JS que se ejecutará después de que la solicitud Ajax haya obtenido su resultado (fallo o éxito);
  • líneas 40-43: una región con el ID [entete];
  • líneas 44-49: una región con el identificador [résultats]. Mostrará los resultados de las cuatro operaciones aritméticas;
  • líneas 50-52: una región con el identificador [erreur]. Mostrará cualquier mensaje de error que pueda surgir.

7.4.2. La acción [Action02Post]

La solicitud Ajax es procesada por la siguiente acción [Action02Post]:


[HttpPost]
    public JsonResult Action02Post(FormCollection postedData, SessionModel session)
    {
      // simulación en espera
      Thread.Sleep(2000);
      // validación del modelo
      ViewModel02 modèle = new ViewModel02();
      // tiempo de carga y cálculo
      string HeureChargement = DateTime.Now.ToString("hh:mm:ss");
      string HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
      // actualización del modelo
      TryUpdateModel(modèle, postedData);
      if (!ModelState.IsValid)
      {
        // se devuelve un error
        return Json(new { Erreur = getErrorMessagesFor(ModelState), HeureCalcul = HeureCalcul });
      }
      // una de cada dos veces, se simula un error
      int val = session.Randomizer.Next(2);
      if (val == 0)
      {
        // se devuelve un error
        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);
      // se devuelven los resultados
      return Json(new { Erreur = "", AplusB = AplusB, AmoinsB = AmoinsB, AmultipliéparB = AmultipliéparB, AdiviséparB = AdiviséparB, HeureCalcul = HeureCalcul });
    }
  • línea 2: el método devuelve un tipo [JsonResult], es decir, un texto en formato JSON;
  • línea 16: la información se presenta en forma de una instancia de clase anónima serializada en JSON. El método [getErrorMessagesFor] ya se ha presentado. La cadena JSON enviada al navegador tendrá la siguiente forma:
{"Erreur":"[erreur aléatoire]","HeureCalcul":"05:31:37"}
  • línea 31: mismo procedimiento para los resultados aritméticos. En esta ocasión, la cadena JSON enviada al navegador tendrá el siguiente formato:
{"Erreur":"","AplusB":"4","AmoinsB":"-2","AmultipliéparB":"3","AdiviséparB":"0,333333333333333","HeureCalcul":"05:52:17"}

7.4.3. El código JavaScript del lado del cliente

Recordemos la configuración de la llamada Ajax en la página HTML enviada al navegador del cliente:


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

Las funciones JS a las que se hace referencia en las líneas 7 a 10 (a la derecha del signo =) se definen en el siguiente archivo [myScripts-02.js]:


// datos globales
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() {
...
}

// al cargar el 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");

  // se ocultan ciertos elementos de la página
  entete.hide();
  résultats.hide();
  erreur.hide();
});

// inicio
function OnBegin() {
....
}

// fin de la consulta
function OnComplete() {
...
}

// éxito
function OnSuccess(data) {
....
}

// error
function OnFailure(request, error) {
...
}
  • línea 19: la función JS se ejecuta al finalizar la carga de la página en el navegador;
  • líneas 20-30: se recuperan las referencias de todos los componentes de la página que nos interesan. La búsqueda de un componente en una página tiene un costo y es preferible realizarla solo una vez;
  • líneas 33-35: se ocultan los componentes [entete], [résultats] y [loading];

Al iniciar la solicitud Ajax, se ejecuta la siguiente función:


// inicio
function OnBegin() {
  // señal de espera encendida
  loading.show();
  // se ocultan algunos elementos de la página
  entete.hide();
  résultats.hide();
  erreur.hide();
}
  • línea 4: se muestra el componente [loading]. Se trata de la imagen animada;
  • líneas 6-8: los componentes [entete], [résultats] y [erreur] están ocultos;

Si la solicitud Ajax tiene éxito, se ejecuta el siguiente código JS:


// éxito
function OnSuccess(data) {
  // visualización de resultados
  heureCalcul.text(data.HeureCalcul);
  entete.show();
  if (data.Erreur != '') {
    msg.text(data.Erreur);
    erreur.show();
    return;
  }
  // sin error
  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 este código, hay que recordar los dos textos JSON que podrían enviarse como respuesta al navegador:

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

en caso de error; de lo contrario, la cadena:

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

Si llamamos a esta cadena [data], el valor del campo [Erreur] se obtiene mediante la notación [data.Erreur] o [data["Erreur"]], según se prefiera. Lo mismo ocurre con los demás campos de la cadena JSON. Por otra parte, para asignar un texto sin formato a un componente con id X, se escribe [X.text(chaine)]. Volvamos al código de la función [OnSuccess]:

  • línea 2: [data] es la cadena JSON recibida;
  • línea 4: el componente [heureCalcul] recibe su valor;
  • línea 5: se muestra el componente [entete];
  • línea 6: se comprueba el campo [Erreur] de la cadena JSON;
  • línea 7: el componente [msg] recibe su valor;
  • línea 8: se muestra el componente [erreur];
  • línea 9: se termina el caso de error;
  • línea 12: el componente [AplusB] recibe su valor;
  • línea 13: el componente [AmoinsB] recibe su valor;
  • línea 14: el componente [AmultipliéparB] recibe su valor;
  • línea 15: el componente [AdiviséparB] recibe su valor;
  • línea 16: se muestra el componente [résultats].

La función [OnFailure] se ejecutará en caso de que falle la solicitud Ajax HTTP. Este fallo se detecta mediante el código HTTP devuelto por el servidor. El código 500 [Internal Server Error], por ejemplo, indica que el servidor no pudo ejecutar la solicitud. La función [OnFailure] es la siguiente:


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

Simplemente se muestra un cuadro de diálogo con el error que se produjo. En la práctica, sería necesario ser más preciso. Pronto ofreceremos otra solución.

Por último, la función [OnComplete] se ejecuta cuando la consulta ha finalizado, ya sea con éxito o con un error.


// fin de la consulta
function OnComplete() {
  // señal de espera desactivada
  loading.hide();
}

Cabe recordar aquí que es la configuración de la llamada Ajax en la vista [Action02Get.cshtml] la que hace que se llamen estas diferentes funciones:


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

7.4.4. El enlace [Calculer]

El código HTML del enlace [Calculer] en la vista [Action02Get.cshtml] es el siguiente:


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

La función JS [postForm] se encuentra en el archivo importado [myScripts-02.js]:


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

Su código es el siguiente:


function postForm() {
  // se realiza una llamada Ajax manualmente con JQuery
  $.ajax({
    url: '/Premier/Action02Post',
    type: 'POST',
    data: formulaire.serialize(),
    dataType: 'json',
    beforeSend: OnBegin,
    success: OnSuccess,
    error: OnFailure,
    complete: OnComplete
  })
}

Ya hemos encontrado un código similar.

  • línea 4: URL, destino de la llamada Ajax;
  • línea 5: comando HTTP utilizado por la llamada Ajax;
  • línea 6: valores enviados. Son el resultado de la serialización de los valores del formulario. Este, identificado con el id [formulaire], es referenciado por la variable [formulaire]. [data] será una cadena de caracteres con el formato [A=val1&B=val2];
  • línea 7: tipo de formato de la respuesta esperada. Es una cadena JSON;
  • línea 8: función JS que se ejecutará al iniciar la llamada Ajax;
  • línea 9: función JS que se ejecutará si la llamada Ajax tiene éxito;
  • línea 10: función JS que se ejecutará si la llamada Ajax falla;
  • línea 11: función JS que se ejecutará una vez recibida la respuesta del servidor, independientemente de si se trata de un éxito o un error.

Volvamos a la función de JavaScript que maneja el caso en que la llamada Ajax falla (línea 10). La llamada Ajax falla en diversas situaciones, por ejemplo, cuando el servidor devuelve un código de error como [403 Forbidden], [404 Not Found], [500 Internal Server Error], [301 Moved Permanently], ...

En el ejemplo anterior, la función [OnFailure] es la siguiente:


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

Por lo general, la visualización del objeto [error] no aporta ninguna información relevante. Si se utiliza una llamada Ajax realizada con JQuery, se puede emplear el siguiente método [OnFailure]:


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

El objeto JQuery [jqXHR] cuenta entre sus propiedades con las siguientes:

  • responseText: el texto de la respuesta del servidor;
  • status: el código de error devuelto por el servidor;
  • statusText: el texto asociado a este código de error.
  • línea 3: se muestra el código de error y la descripción correspondiente;
  • línea 4: se coloca la respuesta HTML del servidor en el componente de id [msg];
  • línea 5: se muestra la región con el identificador [erreur].

Para probar esta función de error, vamos a generar artificialmente una excepción en la acción [Action02Post]:


    [HttpPost]
    public JsonResult Action02Post(FormCollection postedData, SessionModel session)
    {
      // una excepción artificial para probar la función de error de la llamada Ajax
      throw new Exception();
      // simulación de espera
      Thread.Sleep(2000);
      // validación del modelo
...

La línea 5 genera una excepción. Ahora probemos la aplicación:

Obtenemos la siguiente respuesta: [1] y [2]:

La respuesta del servidor nos permite ver en qué línea del código del servidor ocurrió el error. A menudo es útil conocer esta información. De ahora en adelante utilizaremos esta técnica para manejar los errores de las llamadas Ajax.

7.5. Aplicación web de página única

La tecnología Ajax permite crear aplicaciones de página única:

  • la primera página se obtiene mediante una solicitud convencional del navegador;
  • las páginas siguientes se obtienen mediante llamadas Ajax. Por lo tanto, al final, el navegador nunca cambia de URL y nunca carga una nueva página. A este tipo de aplicación se le llama «aplicación de página única» (APU) o, en inglés, «Single Page Application» (SPA).

A continuación se muestra un ejemplo básico de este tipo de aplicación. La nueva aplicación tendrá dos vistas:

  • en [1], la acción [Action03Get] nos permite acceder a la primera página, la página 1;
  • en [2], un enlace nos permite pasar a la página 2 mediante una llamada Ajax;
  • en [3], el URL no ha cambiado. La página que se muestra es la página 2;
  • en [4], un enlace nos permite regresar a la página 1 mediante una llamada Ajax;
  • en [5], la página URL no ha cambiado. La página que se muestra es la página 1.

El código de la acción [Action03Get] es el siguiente:


    [HttpGet]
    public ViewResult Action03Get()
    {
      return View();
}
  • línea 4: se muestra la vista [Action03Get.cshtml].

La vista [Action03Get.cshtml] es la siguiente:


@{
  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>
  • líneas 16-18: un elemento con el identificador [content]. Es en este elemento donde se mostrarán las diferentes páginas;
  • línea 17: por defecto, la página [Page1.cshtml] será la primera en mostrarse.

La página [Page1.cshtml] es la siguiente:


<h4>Page 1</h4>
  <p>
    @Ajax.ActionLink("Page 2", "Action04", new { Page = 2 }, new AjaxOptions() { UpdateTargetId = "content" })
</p>
  • línea 1: el título de la página para diferenciarla de la página 2;
  • línea 3: un enlace Ajax con los siguientes parámetros:
    • el texto del enlace [Page 2];
    • la acción de destino del enlace [Action04];
    • los parámetros de la solicitud URL. Este será [/Premier/Action04?Page=2];
  • las opciones de la llamada Ajax. En este caso, solo el ID de la región que se actualizará con la respuesta del servidor. Para las demás opciones, se utilizan los valores predeterminados cuando existen. El método predeterminado de HTTP es GET.

Veamos qué sucede cuando se hace clic en el enlace. Se solicita el URL [/Premier/Action04?Page=2] con un GET. A continuación, se ejecuta la acción [Action04]:


    [HttpGet]
    public PartialViewResult Action04(string page = "1")
    {
      string vue = "Page1";
      if (page == "2")
      {
        vue = "Page2";
      }
      return PartialView(vue);
}
  • línea 2: la acción devuelve un flujo HTML parcial;
  • línea 2: la acción tiene como patrón la cadena [page]. Ahora bien, sabemos que URL contiene esta información: [/Premier/Action04?Page=2]. Recordemos que el patrón no distingue entre mayúsculas y minúsculas;
  • líneas 4-8: [vue] recibirá el valor [Page2];
  • línea 9: se devuelve la vista parcial [Page2.cshtml].

La vista parcial [Page2.cshtml] es la siguiente:


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

Por lo tanto, el servidor devuelve el flujo HTML anterior como respuesta a la llamada Ajax GET [/Premier/Action04?Page=2]. Recordemos que esta llamada Ajax utiliza esta respuesta para actualizar la región con id [content] (línea 3 a continuación):


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

Esto da lugar a la siguiente visualización nueva: [1]:

Siguiendo el mismo razonamiento, vemos que al hacer clic en el enlace [Page 1] desde [1] se mostrará [2].

Volvamos al esquema general de una aplicación ASP.NET MVC:

Gracias al JavaScript integrado en las páginas HTML y ejecutado en el navegador, se puede trasladar código al navegador y lograr la siguiente arquitectura:

  • En [1], la capa web ASP.NET MVC se ha convertido en una interfaz web de acceso a los datos, que por lo general se almacenan en una base de datos. Las vistas generadas contienen únicamente datos y ningún elemento de diseño HTML, por ejemplo, flujos XML o JSON;
  • en [2]: el navegador muestra vistas estáticas (es decir, no generadas dinámicamente) proporcionadas por un servidor web que puede estar o no en la misma máquina que el servidor [1]. Estas vistas estáticas se enriquecen posteriormente con los datos obtenidos por JavaScript a través de la interfaz web [1];
  • el código JavaScript integrado en las páginas HTML puede estructurarse en capas:
    • la capa [présentation] se encarga de las interacciones con el usuario,
    • la capa [DAO] se encarga del acceso a los datos a través del servidor web [1],
    • la capa [métier] corresponde a la capa [métier], que anteriormente se encontraba en el servidor [1] y que se ha trasladado al navegador [2];

La ventaja de esta arquitectura es que requiere diferentes habilidades:

  • el código del servidor web [1] requiere conocimientos de .NET, pero no de JavaScript, HTML ni CSS;
  • el código integrado en el navegador [2] requiere conocimientos de JavaScript, HTML, CSS, pero es independiente de la tecnología del servidor web [1].

De este modo, esta arquitectura facilita el trabajo en paralelo de equipos con diferentes habilidades. Se adapta particularmente bien a las aplicaciones de página única.

7.6. Aplicación web de página única y validación del lado del cliente

Anteriormente mencionamos una anomalía en el ejemplo Ajax-01. Recordemos el contexto:

  • en [1] y [2], se ingresaron valores inválidos. Estos son señalados por los validadores del lado del cliente;
  • en [3], se hizo clic en el enlace [Calculer];
  • en [4], se produjo un [POST], ya que se obtiene la respuesta [4].

Cuando los valores no son válidos y se hace clic en el botón [Calculer], no se envía el [POST] al servidor. En el mismo caso, con el enlace [Calculer], sí se envía la solicitud [POST] al servidor. Por lo tanto, existe un comportamiento del botón [Calculer] que no hemos podido reproducir con el enlace [Calculer].

Retomaremos este ejemplo en un nuevo contexto: la aplicación tendrá varias vistas y será del tipo [Application à Page Unique] que acabamos de describir.

7.6.1. Las vistas del ejemplo

El ejemplo tiene varias vistas:

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

La aplicación consta de una sola página: el navegador la carga durante la primera solicitud. Posteriormente, se actualiza mediante llamadas Ajax.

Las páginas anteriores son generadas por las siguientes vistas [cshtml]:

La vista que se carga inicialmente es la siguiente 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>

Cabe destacar los siguientes puntos:

  • línea 1: el modelo de la vista es del tipo [ViewModel05], que presentaremos en breve;
  • líneas 13-19: aquí se encuentran los scripts de JavaScript necesarios para implementar Ajax y la validación del lado del cliente;
  • línea 20: vamos a agregar nuestras propias funciones de JavaScript en [myScripts-05.js];
  • línea 27: la imagen animada de espera;
  • líneas 28-30: una etiqueta con el identificador [content]. Es en esta etiqueta donde se insertarán las vistas parciales [Formulaire05, Success05, Failure05];
  • línea 29: inserción de la vista parcial [Formulaire05].

La vista [Action05Get] se encarga de mostrar la parte [1] de la página inicial:

La vista parcial [Formulaire05] generará la parte [2] anterior. Su código es el siguiente:


@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>
}
  • línea 1: la vista parcial admite como modelo un tipo [ViewModel05];
  • línea 3: el formulario generado por el método [Html.BeginForm]. Dado que este formulario se enviará mediante una llamada Ajax, se ignorarán los tres primeros parámetros del método. A menos que el usuario haya desactivado JavaScript en su navegador. Aquí no tomamos en cuenta esa posibilidad. El cuarto parámetro es importante. El formulario tendrá el identificador [formulaire];
  • líneas 5-22: el formulario para ingresar los números A y B;
  • línea 27: un enlace de JavaScript que inicia la ejecución de las cuatro operaciones aritméticas sobre A y B;
  • línea 30: un enlace de JavaScript que borra los datos ingresados y los posibles mensajes de error relacionados con ellos.

Cabe señalar que el formulario no cuenta con un botón de tipo [submit]. Tendremos que calcular manualmente el [Post] a partir de los valores A y B ingresados.

Si no hay errores, se muestran los resultados:

La parte [4] anterior es generada por la vista parcial [Success05.cshtml] siguiente:


@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>
  • línea 1: la vista parcial [Success05.cshtml] recibe una plantilla de tipo [ViewModel05];
  • línea 12: un enlace de JavaScript para regresar a los datos ingresados.

En caso de error, se muestra otra vista parcial [3]:

Esta vista es generada por el siguiente 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>
  • línea 1: la vista parcial [Failure05.cshtml] recibe un modelo de tipo [ViewModel05];
  • línea 14: un enlace de JavaScript para regresar a los campos de entrada.

7.6.2. La plantilla de las vistas

Todas las vistas anteriores comparten la misma plantilla [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
  {
    // formulario
    [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; }
  }
}

Se trata del modelo [ViewModel01] ya presentado, con algunas diferencias:

  • líneas 15 y 19: los campos A y B ahora son de tipo [string] para mostrar campos de entrada vacíos en lugar de campos con el valor 0, al mostrar inicialmente el formulario de entrada;
  • líneas 14 y 18: esto no impide verificar el valor ingresado con un validador [Range];
  • línea 26: una lista de mensajes de error mostrada por la vista [Failure05].

7.6.3. Los datos de ámbito [Session]

En el párrafo 7.3.6, vimos que los datos de la sesión estaban encapsulados en el siguiente modelo [SessionModel]:


using System;
namespace Exemple_03.Models
{
  public class SessionModel
  {
    // el generador de números aleatorios
    public Random Randomizer { get; set; }
  }
}

Este modelo de sesión se amplía para incorporar los valores de A y B:


using System;
namespace Exemple_03.Models
{
  public class SessionModel
  {
    // el generador de números aleatorios
    public Random Randomizer { get; set; }
    // los valores de A y B
    public string A { get; set; }
    public string B { get; set; }
  }
}

De hecho, es necesario almacenar los valores de A y B en la sesión, tal como lo muestra la siguiente secuencia:

Solicitud 1

Solicitud 2

En [4], se encuentran las entradas realizadas en [1]. Sin embargo, hay dos solicitudes distintas: HTTP. Sabemos que la memoria entre dos solicitudes HTTP es la sesión. Para que la segunda pueda recuperar los valores enviados por la primera, es necesario que estos últimos se guarden en la sesión.

7.6.4. La acción del servidor [Action05Get]

La acción [Action05Get] es la que muestra la página única inicial. Su código es el siguiente:


    [HttpGet]
    public ViewResult Action05Get()
    {
      ViewModel05 modèle = new ViewModel05();
      modèle.HeureChargement = DateTime.Now.ToString("hh:mm:ss");
      return View(modèle);
}
  • línea 6: la vista [Action05Get.cshtml], ya analizada, se muestra con una plantilla de tipo [ViewModel05];

7.6.5. La acción de cliente [Calculer]

Analicemos las interacciones del usuario con las vistas:

El enlace [1] es un enlace de JavaScript:


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

La función de JavaScript [calculer] se encuentra en el archivo [myScripts-05.js]:


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

El código de la función de JavaScript [calculer] es el siguiente:


// datos globales
var content;
var loading;

function calculer() {
  // primero las referencias en el DOM
  var formulaire = $("#formulaire");
  // luego, validación del formulario
  if (!formulaire.validate().form()) {
    // formulario inválido - finalizado
    return;
  }
  // se realiza una llamada 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) {
      // Visualización de la respuesta del servidor
      content.html(jqXHR.responseText);
    }
  })
}

function retourSaisies() {
 ...
}

function effacer() {
  ...
}

// al cargar el documento
$(document).ready(function () {
  // se recuperan las referencias de los distintos componentes de la página
  loading = $("#loading");
  content = $("#content");
  // se almacena en caché la imagen animada
  loading.hide();
});
  • Cabe recordar que el código de JavaScript siempre se ejecuta del lado del cliente, en el navegador;
  • línea 44: la función JS se ejecuta cuando finaliza la carga inicial de la página única;
  • línea 46: referencia a la imagen animada con el identificador [loading];
  • línea 47: referencia a la región con id [content]. Es esta región la que recibe las vistas parciales [Formulaire05, Success05, Failure05];
  • líneas 2-3: las variables de las líneas 46-47 se declaran globales para que las demás funciones puedan acceder a ellas. Buscar elementos en una página (líneas 46-47) conlleva un costo. No hay necesidad de repetir esta búsqueda si se puede evitar;
  • línea 5: la función [calculer];
  • línea 7: se obtiene una referencia del formulario. La vista parcial [Formulaire05] le asignó el identificador [formulaire];
  • línea 9: esta instrucción ejecuta los validadores del formulario del lado del cliente. Esto es lo que faltaba en la anomalía detectada en la página 183. Este método lo proporciona la biblioteca [jquery.unobstrusive-ajax] utilizada por la página única:

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

La instrucción devuelve [false] si el formulario se declara inválido;

  • línea 11: no se realiza la llamada Ajax al servidor si el formulario no es válido;
  • líneas 14-32: se realiza la llamada Ajax al servidor;
  • línea 15: el destino de URL es la acción del servidor [Action05FaireCalcul];
  • línea 16: se solicita mediante un [POST];
  • línea 17: los valores enviados. Son los datos ingresados en el formulario, en este caso los valores de A y B;
  • líneas 22-24: si la llamada Ajax tiene éxito, la función [calculer] actualiza la región con id [content] con el flujo HTML enviado por el servidor.

Este flujo HTML es el que envía la acción [Action05FaireCalcul] a la que apunta la llamada Ajax. El código de esta acción del lado del servidor es el siguiente:


    [HttpPost]
    public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
    {
      // plantilla
      ViewModel05 modèle = new ViewModel05();
      // la hora de cálculo
      modèle.HeureCalcul = DateTime.Now.ToString("hh:mm:ss");
      // actualización de la plantilla
      TryUpdateModel(modèle, postedData);
      if (!ModelState.IsValid)
      {
        // se devuelve un error
        modèle.Erreurs = getListOfMessagesFor(ModelState);
        return PartialView("Failure05", modèle);
      }
...
}
  • línea 1: la acción solo acepta un [post];
  • línea 2: devuelve una vista parcial;
  • línea 2: recibe como parámetros los valores enviados (postedData) y el modelo de la sesión (session);
  • línea 5: se crea el modelo de la vista parcial;
  • línea 7: se actualiza con la hora de cálculo;
  • línea 9: se intenta aplicar los valores enviados al modelo. A continuación, se ejecutarán los validadores de este. Cabe preguntarse por qué nos tomamos esta molestia, cuando los validadores del lado del cliente impiden el POST si los datos ingresados son inválidos. De hecho, no estamos seguros del origen del POST. Es posible que haya sido generado por un código que no es el nuestro. Por eso, siempre debemos realizar las verificaciones del lado del servidor;
  • línea 10: se comprueba si los validadores han tenido éxito;
  • línea 13: si el modelo no es válido, se actualiza con una lista de errores. No se detallará el método interno [getListOfMessagesFor], que es similar al método [GetErrorMessagesFor] descrito en la página 65;
  • línea 14: se muestra la vista parcial [Failure05] junto con su plantilla. Recordemos el código de esta vista;

@model Exemple_04.Models.ViewModel05
<hr />
<p><strong>Heure de calcul : @Model.HeureCalcul</strong></p>
<p>A=@Model.A</p>
<p>B=@Model.B</p>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
  @foreach (string msg in Model.Erreurs)
  {
    <li>@msg</li>
  }
</ul>
<p>
  <a href="javascript:retourSaisies()">Retour aux saisies</a>
</p>
  • líneas 7-12: se muestra la lista de errores de la plantilla mediante la etiqueta <ul>.

Recordemos que la función JS [calculer], en elorigen de [Post] en la acción de servidor [Action05FaireCalcul] colocará este flujo HTML en la región de id [content]. El resultado es algo así:

Sigamos analizando el código de la acción [Action05FaireCalcul]:


    [HttpPost]
    public PartialViewResult Action05FaireCalcul(FormCollection postedData, SessionModel session)
    {
      // plantilla
      ViewModel05 modèle = new ViewModel05();
...
      // se almacenan los valores de A y B en la sesión
      session.A = modèle.A;
      session.B = modèle.B;
      // sin errores por el momento
      List<string> erreurs = new List<string>();
      // una de cada dos veces, se simula un error
      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);
      // vista
      return PartialView("Success05", modèle);
}
  • línea 7: se ha declarado válido el modelo;
  • líneas 8-9: se almacenan en la sesión los valores ingresados A y B. El objetivo es poder recuperarlos en la consulta que seguirá;
  • líneas 11-22: se genera un error al azar una de cada dos veces;
  • líneas 24-29: se realizan las cuatro operaciones aritméticas con los números reales ingresados;
  • línea 31: se devuelve la vista parcial [Success05] con su modelo. Esta vista parcial es la siguiente:

@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>

Recordemos que la función JS [calculer],origen de [Post] en la acción del servidor [Action05FaireCalcul] colocará este flujo HTML en la región de id [content]. El resultado es algo así:

7.6.6. La acción de cliente [Effacer]

El enlace de JavaScript [Effacer] permite restablecer el formulario a su estado inicial:

En el formulario, el enlace JS [Effacer] se define de la siguiente manera:


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

La función JS [effacer] se define en el archivo [myScripts-05.js] de la siguiente manera:


// datos globales
var content;
var loading;

function calculer() {
...
}

function retourSaisies() {
...
}

function effacer() {
  // primero las referencias en el DOM
  var formulaire = $("#formulaire");
  var A = $("#A");
  var B = $("#B");
  // se asignan valores válidos a los datos ingresados
  A.val("0");
  B.val("0");
  // luego se valida el formulario para que desaparezcan
  // los posibles mensajes de error
  formulaire.validate().form();
  // luego se asignan cadenas vacías a los campos de entrada
  A.val("");
  B.val("");
}

// al cargar el documento
$(document).ready(function () {
  // se recuperan las referencias de los distintos componentes de la página
  loading = $("#loading");
  content = $("#content");
  // se oculta la imagen animada
  loading.hide();
});
  • líneas 15-17: se obtienen referencias a diversos elementos del DOM (Modelo de Objetos de Documento);
  • líneas 19-20: se asignan valores válidos a los campos de entrada de los números A y B;
  • línea 23: se ejecutan los validadores del lado del cliente. Como los valores de A y B son válidos, esto hará que desaparezcan los posibles mensajes de error que pudieran aparecer;
  • líneas 25-26: se ingresan cadenas vacías en los campos de entrada de los números A y B;

7.6.7. La acción del cliente [Retour aux Saisies]

El enlace de JavaScript [Retour aux Saisies] permite regresar al formulario después de obtener los resultados:

En el formulario, el enlace JS [Retour aux Saisies] se define de la siguiente manera:


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

La función JS [retourSaisies] está definida en el archivo [myScripts-05.js] de la siguiente manera:


// datos globales
var content;
var loading;

function calculer() {
...
}

function retourSaisies() {
  // se realiza una llamada 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 !! validación
      $.validator.unobtrusive.parse($("#formulaire"));
    },
    error: function (jqXHR) {
      content.html(jqXHR.responseText);
    }
  })
}

function effacer() {
...
}

// al cargar el documento
$(document).ready(function () {
  // se recuperan las referencias de los distintos componentes de la página
  loading = $("#loading");
  content = $("#content");
  // se almacena en caché la imagen animada
  loading.hide();
});
  • líneas 11-29: una llamada Ajax;
  • línea 12: el URL de destino;
  • línea 13: será solicitada mediante un comando HTTP POST. Se trata de un POST sin parámetros enviados. Por eso, no se encuentra una línea del tipo:
    data: formulaire.serialize(),

en la llamada Ajax;

  • línea 14: el flujo esperado del servidor es un flujo HTML;
  • líneas 18-20: este flujo HTML servirá para actualizar la región con id [content];

La acción del servidor [Action05RetourSaisies] es la siguiente:


    [HttpPost]
    public PartialViewResult Action05RetourSaisies(SessionModel session)
    {
      // vista
      return PartialView("Formulaire05", new ViewModel05() { A = session.A, B = session.B });
}
  • línea 2: la acción recibe como parámetro la plantilla de la sesión en la que previamente hemos almacenado los valores de A y B ingresados;
  • línea 5: se devuelve la vista parcial [Formulaire05] con un modelo de tipo [ViewModel05], en el que nos aseguramos de inicializar los campos A y B con los valores de A y B tomados de la sesión;

Ahora regresemos al código de la función JavaScript [retourSaisies]:


function retourSaisies() {
  // se realiza una llamada 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 !! validación
      $.validator.unobtrusive.parse($("#formulaire"));
    },
    error: function (jqXHR) {
      content.html(jqXHR.responseText);
    }
  })
}
  • línea 13: el método que se ejecuta cuando finaliza la llamada Ajax;
  • línea 14: se oculta la imagen animada de espera;
  • línea 16: una instrucción que me resulta un poco confusa, encontrada en Internet para resolver el siguiente problema: en el formulario que se muestra mediante el enlace [Retour aux saisies], los validadores del lado del cliente ya no funcionaban. Al buscar información sobre la biblioteca JS [jquery.unobtrusive-ajax], encontré la solución de la línea 16. Esta analiza el formulario, tal vez para activar los validadores del lado del cliente.

7.7. Hacer accesible en Internet una aplicación ASP.NET

Consulte el párrafo 9.26.

7.8. Generación de una aplicación nativa para Android a partir de una aplicación de página única APU

Consulte el párrafo 9.27.