Y el combate se decidió por KO (i)

Hace algunas semanas salió un post de Shaun Walker titulado “Microsoft Declares the future of ASP.NET is Web API”. La verdad es que el post es interesante. Yo no sé cuales serán las intenciones de Microsoft (creo que ni ellos las saben realmente) pero lo que si es cierto es que las aplicaciones web están realmente cambiando a un modelo donde cada vez se procesa más en cliente y menos en servidor. Es un modelo que deja totalmente obsoleto no solo a Webforms si no incluso a ASP.NET MVC.

En este modelo las páginas o vistas de una aplicación web, ya no son servidas desde el servidor. En todo caso, se sirve un “bootstrapper” inicial, que contiene el código javascript que realiza la petición inicial y luego todo es modificar el DOM en cliente a partir de datos recibidos por servicios REST, usando javascript. Por lo tanto dejamos de servir contenido de presentación (HTML generado dinámicamente) desde el servidor, para servir tan solo datos. Y la conversión de datos a algo visible (DOM) se realiza en el propio cliente, usando javascript.

A nivel tecnológico, salvando quizá cuestiones de rendimiento (que no son en absoluto despreciables, ahí está la experiencia de Twitter) estamos ya bastante preparados para dar este salto. Y aquí entra lo que os quería comentar en el post de hoy. Vamos a iniciar una serie, de longitud desconocida, hablando de Knockout, una librería javascript que resumiendo lo que hace es implementar el patrón MVVM en javascript.

Como siempre no soy el primero en hablar de Knockout en geeks. Concretamente el Maestro se me ha avanzado con un post (http://geeks.ms/blogs/jmaguilar/archive/2012/05/07/knockout-i-pongamos-orden-en-el-lado-cliente.aspx).

También está Marc Rubiño que tiene una presentación en slideshare: http://www.slideshare.net/webcatbcn/javascript-con-mvvm-knockout-por-marcrubino 

Pero por encima de todos Sergio Marrero que tiene una serie impresionante sobre Knockout en su blog: http://smarrerof.blogspot.com.es/search/label/knockout

Pero bueno… Yo intentaré aportar mi grano de arena y intentaré acercaros un poco Knockout para que los que no lo conozcáis os echeis las manos a la cabeza y os preguntéis como narices habeis podido hacer aplicaciones web todo este tiempo 🙂

¡Espero que esta sea una larga serie de posts, porque realmente hay mucho que contar!

Introducción a Knockout

Knockout es una librería javascript que básicamente nos da soporte para el patrón MVVM. Este patrón, bien conocido para los que desarrollan en WPF, Silverlight o WinRT, es en realidad un MVP Supervising Controller con dos peculiaridades:

  1. Se crea un modelo específicamente para presentación. Es decir, no se usa el “Modelo” real para tareas de presentación, si no que se crea otro modelo específicamente para ello. En muchos casos este otro modelo está adaptado y optimizado para cada vista. En MVVM conocemos a este otro modelo con el nombre de ViewModel.
  2. Se promueve que la comunicación entre la vista y su viewmodel sea a través de bindings (bidireccionales).

Lo que Knockout nos permite hacer de forma muy rápida es, realizar estos bindings entre elementos del DOM y nuestro viewmodel así como crear elementos del DOM a partir del propio viewmodel (lo que antes hacíamos con jquery_tmpl p.ej.)

Hello Knockout

Vamos a empezar con el primer proyecto usando Knockout. En este caso vamos a montar una tabla que muestre información de cervezas. Para ello, nos creamos un nuevo proyecto de tipo WebApi, donde vamos a añadir un controlador:

   1: public class ApiBeersController : ApiController

   2: {

   3:     public IEnumerable<Beer> GetAll()

   4:     {

   5:         yield return new Beer { Name = "Estrella Damm", Abv = 5.4M, Ibu = 21 };

   6:         yield return new Beer { Name = "Heineken", Abv = 5.0M, Ibu = 10 };

   7:     }

   8: }

La clase Beer simplemente contiene las propiedades Name, Abv e Ibu.

Si ahora abrimos una URL y navegamos a /api/apibeers deberíamos recibir un json:

[{"Name":"Estrella Damm","Abv":5.4,"Ibu":21},{"Name":"Heineken","Abv":5.0,"Ibu":10}]

Nota: Si recibes un XML en lugar de un JSON eso es debido a la cabecera Accept que envía el navegador. Una solución para ello es indicarle a WebApi que nunca devuelva resultados en XML, lo que se consigue colocando la siguiente línea en el Application_Start: GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);

 

Ahora vamos a incluir a Knockout en nuestra página master.

Para ello damos de alta un nuevo bundle para incluir Knockout (en el método RegisterBundles de la clase BundleConfig):

   1: bundles.Add(new ScriptBundle("~/bundles/ko").Include(

   2:                         "~/Scripts/knockout-2*"));

Ok, ahora vamos a crear un controlador “normal” que nos devuelva la vista que debe mostrar las cervezas:

   1: <h2>Index de cervezas</h2>

   2:  

   3: <script type="text/javascript">

   4:     $(document).ready(function () {

   5:         var url="@Url.RouteUrl("DefaultApi", new {httproute="", controller="ApiBeers"})";

   6:         $.getJSON(url, function(data) {

   7:             ko.applyBindings({beers: data});

   8:         });

   9:     });

  10: </script>

  11:  

  12:  

  13: <div id="beers">

  14:     <table>

  15:         <tbody data-bind="foreach:beers">

  16:             <tr>

  17:                 <td data-bind="text: Name" />

  18:                 <td data-bind="text: Abv" />

  19:                 <td data-bind="text: Ibu" />

  20:             </tr>

  21:         </tbody>

  22:     </table>

  23: </div>

Analicemos el código:

  1. Al cargarse la página usamos $.getJSON para obtener los datos de las cervezas
  2. Una vez hemos obtenido el objeto JSON con las cervezas (recordad que era un IEnumerable<Beer> en C# y un array puro en javascript) creamos un objeto con una propiedad llamada “beers” cuyo contenido es precisamente este array de cervezas.
  3. Llamamos al método applyBindings, para Knockout haga su magia. A applyBindings se le pasa el viewmodel a usar.

¿Y como hace el enlace Knockout? Pues, y ahí radica su gracia, usa un atributo propio llamado data-bind. Recordad que HTML5 nos permite definir nuestros propios atributos siempre y cuando empiecen por data-. Knockout buscará los elementos que estén marcados con este atributo data-bind y realizará distintas tareas según el valor de dicho atributo y del viewmodel definido. En este primer ejemplo vemos dos usos de data-bind:

  • foreach: propiedad –> Itera sobre todos los elementos de la propiedad especificada. En nuestro caso usamos foreach:beers, ya que beers es el nombre de la propiedad de nuestro viewmodel que contiene el array de cervezas. Por cada elemento de la propiedad beers, knockout repetirá todos los elementos que estén dentro del elemento que contiene el foreach. En nuestro caso creará un <tr> por cada elemento.
  • text: propiedad –> Muestra el texto de la propiedad especificada. Como estamos dentro de un foreach muestra la propiedad especificada del elemento que se está “renderizando”.

El resultado de crear esta vista es, tal y como seguro que esperabais:

image

Bueno… ¿no está nada mal por un par de líneas, no? ¿A que ya os está picando la curiosidad?

En los próximos posts iremos ampliando y viendo muchas otras cosas que Knockout nos ofrece! 😀

11 comentarios en “Y el combate se decidió por KO (i)”

  1. Buen apunte, leí el articulo que enlazas al principio y deja pensando, así que decidí mezclar tu ejemplo con este ejemplo de Web Forms http://www.asp.net/web-api/overview/hosting-aspnet-web-api/using-web-api-with-aspnet-web-forms

    Funciona, solo que la parte de los Bundles aun no logre que funcione.

    A pesar de que funcione, lo que aun “no veo” es como en cliente va con minúsculas a pesar de que Beer se definio con mayusculas en el controlador….

  2. @ecardenas
    Te refieres a la URL? Que en cliente es “beers”? En ASP.NET MVC el nombre de los controladores (y de las acciones dicho sea de paso) es case-insensitive. Eso significa que http://host/beers y http://host/BEERS seran tomados como la misma Url, que apuntarà al controlador BeersController. Y sí, eso significa que NO podemos tener un BeersController y un BEERSController (al menos no sin hacer “cosas rares”).

    Saludos!

    PD: Que te ocurre con los Bundles?

    @Javi
    Jejejeeee… a ver por donde van a salir al final 😛

  3. Ahh no lo sabia, es que eso de que algunas cosas pasen por “magia” (lease convencion sobre configuracion) sin ser explicitas aun me raya…

    Lo de los Bundles ya lo arregle (gracias al link que habia puesto Rodrigo en tu otro post) coloque esto:
    var b = new Bundle(“~/CustomKo”).Include(“~/Scripts/knockout-2*”);
    BundleTable.Bundles.Add(b);

    y en cliente a falta de @Styles.Render coloque directamente un , eso si no percibo ninguna diferencia entre usar EnableOptimizations o no, al menos en este caso, supongo que sera porque solo he registrado una librería.

  4. Y una cosa que me llama la atencion, es que tu en tu controller expones un metodo llamado GetAll, el cual nuevamente es invocado por “magia” desde el cliente, perooo al cruzar con el otro ejemplo que te comentaba me di cuenta que podia colocar GetAllLoQueEtomasSeLeOcurra y seguir funcionando….Plop! noguta

  5. @ecardenas (la “e” es de Ernesto???)

    El Routing de WepApi funciona, por defecto, según el verbo http. Es decir, si la petición es via GET el método que se invoca del controlador se debe llamar GetXXX donde XXX puede ser el nombre que tu quieras.
    Lo bueno de eso es que puedes tenir varios métodos GetXXX distintos, con parámetros distintos y WebApi lo enrutará bien.

    Asi puedes tener en el controlador un método GetAll() y otro método GetById(string id). Pues bien, si navegas (usando GET) a http://api/controlador te llamará al método GetAll y si navegas a http://api/controlador/120 te llamará al método GetById (con el parámetro id a 120). Eso NO es possible en ASP.NET MVC tradicional.
    En este post lo cuento con más detalle: http://geeks.ms/blogs/etomas/archive/2012/02/19/explorando-asp-net-mvc4-webapi-2-enrutamiento-y-verbos-http-propios.aspx

    Un saludo y gracias por comentar!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *