Compartir a través de



Septiembre de 2015

Volumen 30, número 9

Puntos de datos: repaso del enlace de datos de JavaScript, ahora con Aurelia

Por Julie Lerman

Julie LermanNunca me he considerado una desarrolladora de front-end, pero cada cierto tiempo, aparece un motivo para probar cosas con una interfaz de usuario. En mi columna de junio de 2012, después de ver una presentación de grupos de usuarios en Knockout.js, profundicé y escribí un artículo sobre enlazar datos OData en un sitio web con Knockout (msdn.microsoft.com/magazine/jj133816). Unos meses más tarde, escribí sobre cómo se puede agregar Breeze.js a la combinación para facilitar aún más el enlace de datos con Knockout.js (msdn.microsoft.com/magazine/jj863129). Cuando escribí sobre la modernización de una antigua aplicación de formularios Web Forms de ASP.NET 2.0 y el uso Knockout en 2014, algunos amigos se burlaron y me dijeron que Knockout era "algo del año 2012". Los marcos más recientes como Angular también conseguían el enlace de datos y mucho más. Pero el "mucho más" no me interesaba en especial, por lo que Knockout era suficiente para mí.

Ahora estamos en 2015 y aunque Knockout sigue siendo válido, relevante e increíble para el enlace de datos de JavaScript, quería dedicarle algo de tiempo a uno de los marcos nuevos y elegí Aurelia (Aurelia.io) porque conozco a muchos desarrolladores web que están encantados. Aurelia comenzó con Rob Eisenberg, que también es el artífice de Durandal, otro marco de cliente de JavaScript; aunque detuvo su producción cuando decidió trabajar en el equipo de Angular de Google. Finalmente, dejó Angular y, en lugar de retomar el proyecto de Durandal, creó Aurelia desde cero. Hay muchas cosas interesantes sobre Aurelia. Por supuesto, tengo mucho más que aprender, pero quiero compartir con todos algunos de los trucos de enlace de datos que aprendí, así como unos pequeños trucos con EcmaScript 6 (ES6), la versión más reciente de JavaScript, que se convirtió en un estándar en junio de 2015.

Una ASP.NET Web API para servir datos a un sitio web

Estoy usando una ASP.NET Web API que creé para exponer datos que quiero conservar con Entity Framework 6. La Web API tiene una serie de métodos sencillos a los que se puede llamar mediante HTTP.

El método Get, que se muestra en la Figura 1, recibe algunos parámetros de consulta y paginación, y los pasa a un método de repositorio que recupera una lista de objetos Ninja y sus objetos Clan relacionados mediante un elemento DbContext de Entity Framework. Cuando el método Get tiene los resultados a mano, los transforma en un conjunto de objetos de transferencia de datos (DTO) de ViewListNinja, definidos en otra parte. Esto es un paso importante debido a la manera en que se serializa JSON, que es un poco excesiva con las referencias circulares desde el elemento Clan a otros elementos Ninja. Con los DTO, evité que se transmitiera una excesiva cantidad de datos a través de la conexión y pude dar forma a los resultados para que coincidieran mejor con lo que hay en el cliente.

Figura 1. El método Get de la Web API

public IEnumerable<ViewListNinja> Get(string query = "",
  int page = 0, int pageSize = 20)
  {
    var ninjas = _repo.GetQueryableNinjasWithClan(query, page, pageSize);
    return ninjas.Select(n => new ViewListNinja
                              {
                                ClanName = n.Clan.ClanName,
                                DateOfBirth = n.DateOfBirth,
                                Id = n.Id,
                                Name = n.Name,
                                ServedInOniwaban = n.ServedInOniwaban
                              });
    }

En la Figura 2 se muestra una vista de los resultados JSON de ese método basada en una consulta que recuperó dos objetos Ninja.

Resultados JSON de la solicitud Web API de la lista de elementos Ninja
Figura 2. Resultados JSON de la solicitud Web API de la lista de elementos Ninja

Realización de consultas a la Web API con el marco Aurelia

El paradigma de Aurelia empareja un modelo de vista (una clase de JavaScript) con una vista (un archivo HTML) y realiza el enlace de datos entre ambos. Por lo tanto, tengo un archivo ninjas.js y un archivo ninjas.html. El modelo de vista Ninjas se define como una matriz de Ninjas, así como un objeto Ninja:

export class Ninja {
  searchEntry = '';
  ninjas = [];
  ninjaId = '';
  ninja = '';
  currentPage = 1;
  textShowAll = 'Show All';
  constructor(http) {
    this.http = http;
  }

El método más importante en ninjas.js es retrieveNinjas, que llama a la Web API:

retrieveNinjas() {
  return this.http.createRequest(
    "/ninjas/?page=" + this.currentPage +
    "&pageSize=100&query=" + this.searchEntry)
    .asGet().send().then(response => {
      this.ninjas = response.content;
    });
  }

En otro lugar de la aplicación web configuré la dirección URL base que Aurelia encontrará y la incorporé a la dirección URL de la solicitud:

x.withBaseUrl('http://localhost:46534/api');

Es destacable remarcar que el archivo ninjas.js contiene simplemente JavaScript. Si ha usado Knockout, puede que recuerde que tiene que configurar el modelo de vista con notación de Knockout, para que cuando se enlace el objeto al marcado, Knockout sabrá lo qué hacer con él. Con Aurelia no es así.

La respuesta ahora incluye la lista de Ninjas y la establecí en la matriz ninjas de mi modelo de vista, que se devuelve a la página ninjas.html que desencadenó la solicitud. No hay nada en el marcado que identifique el modelo; hay que ocuparse de ello porque el modelo está emparejado con el código HTML. De hecho, la mayor parte de la página es HTML estándar y algo de JavaScript, con tan solo unos comandos especiales que Aurelia encontrará y controlará.

Enlace de datos, interpolación y formato de cadenas

La parte más interesante del archivo ninjas.html es el bloque div, que se usa para mostrar la lista de ninjas:

<div class="row">
  <div  repeat.for="ninja of ninjas">
    <a href="#/ninjas/${ninja.Id}" class="btn btn-default btn-sm" >
      <span class="glyphicon glyphicon-pencil" />  </a>
    <a click.delegate="$parent.deleteView(ninja)" class="btn btn-default btn-sm">
      <span class="glyphicon glyphicon-trash" />  </a>
    ${ninja.Name}  ${ninja.ServedInOniwaban ? '[Oniwaban]':''}
    Birthdate:${ninja.DateOfBirth | dateFormat}
  </div>
</div>

El primer marcado específico de Aurelia de ese código es repeat.for "ninja of ninjas", que sigue un bucle for de paradigma ES6. Dado que Aurelia comprende el modelo de vista, entiende que "ninjas" es la propiedad definida como una matriz. La variable "ninja" puede ser cualquier nombre; por ejemplo, "foo". Simplemente representa cada elemento de la matriz de ninjas. Ahora es solo cuestión de recorrer en iteración los elementos de la matriz de ninjas. Vaya al marcado donde se muestran las propiedades; por ejemplo, "${ninja.Name}". Se trata de una característica de ES6 que Aurelia aprovecha y que se conoce como interpolación de cadenas. La interpolación de cadenas facilita la composición de cadenas con variables incrustadas en ellas en lugar de, por ejemplo, mediante concatenación. Por lo tanto, con una variable name="Julie", puedo escribir en JavaScript:

`Hi, ${name}!`

y se procesará como "Hi, Julie!". Aurelia se beneficia de la interpolación de cadenas ES6 y deduce un enlace de datos unidireccional cuando encuentra esa sintaxis. Por tanto, esa última línea de código, que comienza por ${ninja.Name} dará como resultado las propiedades ninja junto con el resto del texto HTML. Si codifica en C# o Visual Basic, es destacable que la interpolación de cadenas es una nueva característica de C# 6.0 y Visual Basic 14.

Tuve que aprender un poco más sobre la sintaxis de JavaScript durante el proceso; por ejemplo, la evaluación condicional de ServedInOniwaban Boolean, que tiene la misma sintaxis que C#—condition? true : false.

El formato de fecha que apliqué a la propiedad DateOfBirth es otra característica de Aurelia y puede resultarle familiar si ha usado XAML. Aurelia utiliza convertidores de valores. Me gusta usar la biblioteca de JavaScript moment como ayuda para el formato de fecha y hora, y la aprovecho en la clase date-format.js:

import moment from 'moment';
export class dateFormatValueConverter {
  toView(value) {
  return moment(value).format('M/D/YYYY');
  }
}

Tenga en cuenta que es necesario especificar "ValueConverter" en el nombre de la clase.

En la parte superior de la página HTML, justo debajo del elemento inicial <template>, hay una referencia al archivo:

<template>
  <require from="./date-format"></require>

Ahora, la interpolación de cadenas puede encontrar dateFormat­[ValueConverter] en el marcado y aplicarlo a la salida, como se muestra en la Figura 3.

Visualización de todos los ninjas con las propiedades enlazadas unidireccionalmente con Aurelia mediante interpolación de cadenas
Figura 3. Visualización de todos los ninjas con las propiedades enlazadas unidireccionalmente con Aurelia mediante interpolación de cadenas

Quiero destacar otra instancia del enlace en el bloque div, pero se trata de enlace de eventos, no de datos. Observe que en la primera etiqueta de hipervínculo utilizo una sintaxis común, con la incrustación de una dirección URL en el atributo href. Sin embargo, en el segundo no uso href. En su lugar, utilizo click.delegate. Delegate no es un comando nuevo, pero Aurelia lo controla de una forma especial que es mucho más potente que un controlador de eventos onclick estándar. Obtenga más información en bit.ly/1Jvj38Z. Aquí, seguiré centrándome en el enlace relacionado con datos.

Los iconos de edición llevan a una dirección URL que incluye el identificador del ninja He instruido al mecanismo de enrutamiento Aurelia para que enrute a una página llamada Edit.html. Esta está asociada a un modelo de vista que está en una clase denominada Edit.js.

Los métodos más importantes de Edit.js son para recuperar y guardar el ninja seleccionado. Comencemos con retrieveNinja:

retrieveNinja(id) {
  return this.http.createRequest("/ninjas/" + id)
    .asGet().send().then(response => {
      this.ninja = response.content;
    });
  }

Esto genera una solicitud similar a mi Web API como antes, pero esta vez anexa el identificador a la solicitud.

En la clase ninjas.js, enlazo los resultados a una propiedad de matriz de ninjas de mi modelo de vista. Aquí se ajustan los resultados, un único objeto, la propiedad ninja del modelo de vista actual.

Este es el método de Web API al que se llamará debido al identificador que se anexa al URI:

public Ninja Get(int id)
  {
    return _repo.GetNinjaWithEquipmentAndClan(id);
  }

Los resultados de este método son mucho más sofisticados que los devueltos para la lista de ninjas. En la Figura 4 se muestra el JSON devuelto por una de las solicitudes.

Resultados JSON de una solicitud de WebAPI para un único ninja
Figura 4. Resultados JSON de una solicitud de WebAPI para un único ninja

Una vez se insertan los resultados en el modelo de vista, es posible enlazar las propiedades del ninja a elementos de la página HTML. Esta vez uso el comando .bind. Aurelia determina si algo debe enlazarse de forma unidireccional, bidireccional o de otro modo. De hecho, como se vio en el archivo ninjas.html, usaba su flujo de trabajo de enlace subyacente cuando se producía la interpolación de cadenas. Ahí, se usaba un enlace unidireccional de un solo uso. En este caso, puesto que utilizo el comando .bind y estoy creando enlaces a elementos de entrada, Aurelia determina que quiero un enlace bidireccional. Esa es la opción predeterminada, que podría reemplazar con .one-way o cualquier otro comando en lugar de .bind.

Para una mayor brevedad, extraeré solamente el marcado relevante en lugar de los elementos adyacentes.

Este es un elemento de entrada enlazado a la propiedad Name de la propiedad ninja en el modelo que devolví desde la clase modelview:

<input value.bind="ninja.Name" />

Y este es otro, esta vez enlazado al campo DateOfBirth. Me encanta que es posible reutilizar fácilmente el convertidor de valores de formato de fecha incluso en este contexto, con la sintaxis que aprendí anteriormente:

<input  value.bind="ninja.DateOfBirth | dateFormat"  />

También quiero enumerar los equipos en la misma página, de manera similar a como se enumeraron los ninjas para que se pudieran editar y eliminar. Para la demostración, he llegado hasta la visualización de esa lista como cadenas, pero no he implementado las características de edición y eliminación ni una manera de agregar equipos:

<div repeat.for="equip of ninja.EquipmentOwned">
  ${equip.Name} ${equip.Type}
</div>

En la Figura 5 se muestra el formulario con el enlace de datos.

La página de edición donde se muestra la lista de equipos
Figura 5. La página de edición donde se muestra la lista de equipos

Aurelia también tiene una característica llamada enlace adaptativo, que le permite adaptar sus capacidades de enlace en función de las características disponibles del explorador o incluso los objetos que se pasan. Es bastante buena y está diseñada para poder trabajar con los exploradores y bibliotecas a medida que evolucionen con el tiempo. Puede leer más sobre el enlace adaptable en bit.ly/1GhDCDB.

Actualmente, solo es posible editar el nombre de ninja, la fecha de nacimiento y el indicador Oniwaban. Cuando un usuario desactiva Served in Oniwaban y presiona el botón Save, esta acción llama al método save de mi modelo de vista, que hace algo interesante antes de volver a insertar los datos en mi Web API. Actualmente, como se vio en la Figura 4, el objeto ninja es un gráfico de profundidad. No es necesario enviar todo eso de vuelta para guardarlo, solo las propiedades relevantes. Como estoy usando EF en el otro extremo, quiero asegurarme de que las propiedades que no edité también se devuelvan, de forma que no se reemplacen con valores NULL en la base de datos. Por tanto, crearé un DTO sobre la marcha llamado ninjaRoot. Ya declaré NinjaRoot como una propiedad de mi modelo de vista. Pero la definición de ninjaRoot se indicará según cómo la cree en mi método Save (consulte la Figura 6). He sido cuidadosa y he usado los mismos nombres de propiedades con las mismas mayúsculas y minúsculas que mi WebAPI espera, de forma que se pueda deserializar en el tipo de Ninja conocido de la API.

Figura 6. El método Save en la vista de modelo de edición

save() {
        this.ninjaRoot = {
          Id: this.ninja.Id,
          ServedInOniwaban: this.ninja.ServedInOniwaban,
          ClanId: this.ninja.ClanId,
          Name: this.ninja.Name,
          DateOfBirth: this.ninja.DateOfBirth,
          DateCreated: this.ninja.DateCreated,
          DateModified: this.ninja.DateModified
        };
        this.http.createRequest("/ninjas/")
          .asPost()
          .withHeader('Content-Type', 'application/json; charset=utf-8')
          .withContent(this.ninjaRoot).send()
          .then(response => {
            this.myRouter.navigate('ninjas');
          }).catch(err => {
            console.log(err);
          });
    }

Observe el método "asPost" de esta llamada. Esto garantiza que la solicitud se pasa al método Post en mi Web API:

public void Post([FromBody] object ninja)
{
  var asNinja =
    JsonConvert.DeserializeObject<Ninja>
    (ninja.ToString());
  _repo.SaveUpdatedNinja(asNinja);
}

El objeto JSON se deserializa en un objeto Ninja local y, después, se pasa a mi método de repositorio, que puede actualizar este objeto en la base de datos.

Cuando vuelvo a la lista Ninjas de mi sitio web, el cambio se refleja en la salida.

Más que un simple enlace de datos

Tenga en cuenta que Aurelia es un marco mucho más sofisticado que y tiene más usos que el enlace de datos, pero por mi naturaleza, me centré en los datos durante mis primeros pasos con esta herramienta. Puede aprender muchísimo más en el sitio web de Aurelia y descubrir una comunidad creciente en gitter.im/Aurelia/Discuss.

Quiero dar mi mayor agradecimiento al sitio web tutaurelia.net, una serie de entradas de blog sobre la combinación de ASP.NET Web API y Aurelia. Me ayudé de la parte 6 del autor Bart Van Hoey para mi primera versión de la visualización de la lista de ninjas. Es posible que en el momento de publicación de este artículo haya más entradas disponibles.

La descarga de este artículo se incluirá en la solución de Web API y en el sitio web de Aurelia. Puede usar la solución de Web API en Visual Studio. La creé en Visual Studio 2015 mediante Entity Framework 6 y Microsoft .NET Framework 4.6. Si quiere ejecutar el sitio web, deberá visitar el sitio Aurelia.io para aprender a instalar Aurelia y ejecutar el sitio web. También puede ver cómo muestro esta aplicación en el curso de Pluralsight, "Getting Started with Entity Framework 6", en bit.ly/PS-EF6Start.


Julie Lerman es una Microsoft MVP, mentora y consultora de .NET que vive en las colinas de Vermont. Puede encontrarla haciendo presentaciones sobre el acceso a datos y otros temas de .NET a grupos de usuarios y en conferencias en todo el mundo. Su blog es thedatafarm.com/blog y es la autora de “Programming Entity Framework” (2010) así como una edición de Code First (2011) y una edición de DbContext (2012), de O’Reilly Media. Sígala en Twitter en twitter.com/julielerman y vea sus cursos de Pluralsight en juliel.me/PS-Videos.


Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Rob Eisenberg (Durandal Inc.)
Rob Eisenberg cuenta con una década de experiencia en ingeniería y se centra en el diseño y arquitectura de interfaces de usuario. Es el creador de varios marcos de interfaces de usuario como Caliburn.Micro y Durandal, que miles de desarrolladores en todo el mundo han utilizado. Fue miembro del equipo de Angular 2.0, aunque actualmente Rob lidera el proyecto Aurelia, un marco del lado cliente de JavaScript de próxima generación para móvil, escritorio y web que aprovecha convenciones simples para fomentar su creatividad. Obtenga más información en http://aurelia.io/