ASP.NET MVC 4 – Minification and Bundling

Una de las nuevas features de ASP.NET MVC 4 es la de minification and bundling (también denominada web optimization), la cual nos permite compactar y agrupar archivos de Javascript y CSS en nuestra aplicación o sitio web. Esta característica es proporcionada por la librería System.Web.Optimization, que tenemos disponible por default en nuestros proyectos a partir de la versión RC de ASP.NET MVC 4.

Un punto importante, es que a partir de esta feature surge un nuevo concepto, los bundles:

Bundle es simplemente una agrupación lógica de archivos que se pueden referenciar mediante un único nombre, y que se cargan en una única petición HTTP.

La agrupación (bundling) generalmente incluye uno o más archivos de scripts JS o CSS relacionados entre sí. Estos archivos son compactados y optimizados permitiéndonos descargarlos por única vez del servidor ahorrándonos varios request.

La compactación (monification) se realiza en el server y ocurre una única vez, este proceso elimina todos los espacios y saltos de líneas de los archivos incluidos en el paquete (bundle), ademas de renombrar todas las variables por nombres muchos más simples y pequeños. El resultado final: archivos muchos más livianos que ofrecen una mejor performance en el cliente.

Ahora veamos de que se trata esto de Bundles. Si generamos una nueva Web Application ASP.NET MVC 4 podremos ver en ciertas vistas algo similar a lo siguiente:

Utilizando bundles

Utilizando bundles

En este punto, le estamos diciendo a la vista es que utilice bundles, previamente definidos, para los estilos @Style y para los scripts JS @Scripts (veremos más adelante donde definir los paquetes). De esta forma ya no es necesario registrar estos recursos utilizando los tags <scripts> o <styles>.

Si prestan atención al ejemplo, le estamos pasando como parámetros  al método Render() la ubicación y nombre del bundle. Dicha definición del paquete (nombre, path y archivos que lo componen), se realiza en la clase BundleConfig (App_Start\BundelConfig.cs):

BundleConfig

BundleConfig

Los bundles serán generados por única vez cuando se inicie la aplicación. En el método Application_Start() del global.asax encontrarán la inicialización de los mismos:

BundleConfig.RegisterBundles(BundleTable.Bundles);

La definición de un nuevo bundle es bastante simple: creamos un objeto ScriptBundle y le pasamos como parámetro el “path virtual” (ubicación y nombre del paquete), el cual nos permitirá identificarlo desde las vistas. Acto seguido, indicamos que archivos vamos a incluir en el paquete. Existen ciertos atajos para poder especificar los archivos que componen un paquete, podemos utilizar por ejemplo el * para indicar el conjunto de archivos que comienzan con el mismo nombre: jquery-1.*.

Algunos puntos importantes, del mecanismo de bundles. Uno es que tanto los versiones de scripts JS para documentación o previamente minificadas (*.min.js, *-vsdoc.js, *.debug.js) son “descartados” al momento de la compactación. Otro es que el orden en que registramos los archivos dentro de un bundle son respetados al momento de generar el paquete, lo que evita problemas de referencia entre scripts.

Sin embargo, cuando ejecutemos nuestra aplicación nos vamos a encontrar con que ninguna de las características mencionadas anteriormente se hicieron efectivas:

Request realizados al servidor

Request realizados al servidor

Pero a no preocuparse!. Para poder implementar esta feature, simplemente debemos des-habilitar el modo “debug”. Para eso simplemente modificamos la entrada compilation en el archivo web.config:

Modificando el modo de compilación

Modificando el modo de compilación

Esto es así, ya que en el ambiente de desarrollo generalmente necesitamos depurar nuestros archivos de scripts o css, y no tiene sentido el uso de bundles. Sin embargo, si quisiéramos forzar su uso sin afectar el modo de compilación, podemos hacerlo de la siguiente manera (en el método Application_Start() del global.asax):

BundleTable.EnableOptimizations = true;

Ya sea modificando el web.config o habilitando la propiedad BundleTable.EnabledOptimization, si ejecutamos nuevamente la aplicación nos encontraremos con lo siguiente:

Request realizados al servidor

Request realizados al servidor

Como podemos ver, ya no fueron necesarios 15 request para obtener todos los scripts y css. Con solo 4 request trajimos los paquetes compactados y optimizados.

Si revisamos en detalle los archivos que fueron descargados en cada request, podemos ver que tanto el nombre como la ubicación de los paquetes son los que definimos como “path virtual”:

Ubicación y nombres de los bundles

Ubicación y nombres de los bundles

Pero esto no es todo, si volvemos a solicitar la página, vamos a encontrarnos con que los paquetes se encuentran en cache – HTTP Status Code 304 – Not Modified -, por lo que ya no es necesario volver a solicitarlos! 🙂

Cache de Bundles

Cache de Bundles

Inspeccionando un poco más la petición del bundle, se puede observar que a la URL se añade un parámetro “v” con un valor hash. Este es utilizado para identificar cuando un paquete fue modificado, y por lo tanto es necesario volver a buscarlo nuevamente en el servidor.

Como podemos ver, el mecanismo de bundles, no solo se trata de agrupar o compactar archivos. También nos permite manejar de una manera transparente cuestiones como la cache o cambios de versión de los recursos, que a veces son pasadas por alto.

Por último les dejo un par de post excelentes sobre el tema de José M. Aguilar, Scott Hanselman y Rick Anderson.

Espero que les sea de utilidad!

Implementando características de OData con ASP.NET WebAPI

En esta nueva entrega sobre ASP.NET Web API vamos a hablar sobre las características que este soporta del protocolo Open Data Protocol (de ahora en mas OData).

Open Data Protocol

Open Data Protocol

Antes de comenzar, vamos a realizar una pequeñísima introducción a Open Data Protocol.

OData es un protocolo abierto – open protocol – creado por Microsoft para exponer datos como servicio. Este se basa en estándares conocidos de Internet como HTTP, Atom (AtomPub) y JSON. Como todo protocolo de servicios, uno de los fines principales es poder independizar los datos de la aplicación o sitio web que los utiliza. Los clientes que consumen servicios a través el protocolo OData pueden hacerlo bajo formatos como Atom, JSON o XML plano, pero además incluyendo características como paginación, ordenación y filtrosquerys -.

Otra característica interesante de OData es que nos permite exponer y acceder a información de una gran variedad de fuentes, incluyendo, bases de datos relacionales, sistemas de archivos, sistemas de gestión de contenidos y sitios web tradicionales.

Escenarios de despliegue de OData

Escenarios de despliegue de OData

Ahora bien, de todas las características que ofrece OData, la que nos interesa en este momento es la utilización de convenciones URI que nos permitirán, entre otras cosas, realizar operaciones como navegación, filtrado, orden y paginación de datos en la solicitud de un recurso.

URI Components

URI Components

La utilización de estas convenciones nos permiten, desde la misma URI del recurso, especificar query options que serán aplicadas al momento de obtener un recurso. Podemos ver en el gráfico anterior – URI Components – que las opciones de consultas se especifican al final de la URI.

Ejemplos de query options son:

  • $filter : permite aplicar filtros sobre el resultado.
  • $orderby : permite ordenar por alguna condición el resultado
  • $top : permite recuperar un cierto número de resultados.
  • $skip : permite saltear un cierto número de resultados.

Vayamos a un ejemplo, si quisiera obtener la lista de clientes ordenadas por nombre, debería invocar al servicio utilizando la siguiente URI:

http://localhost:[port]/api/clientes?$orderby=Nombre

Ahora bien, ASP.NET Web API trae soporte para un subconjunto de características del protocolo OData. Una de ellas es que podemos trabajar con las convenciones URI que trabajaran en la interacción con los controladores de nuestra API.

Para trabajar con ellas simplemente debemos modificar el tipo de datos de la respuesta de nuestro método. Recordaran que en post anteriores el método retornaba un objeto IEnumerable:

Método de acción Get() retornando un IEnumerable

Método de acción Get() retornando un IEnumerable

En este caso debemos vamos a modificar la firma y el cuerpo del método para que retorne un objeto IQueryable:

Método de acción Get() retornando un IQueryable

Método de acción Get() retornando un IQueryable

Tal como menciona MSDN, el motivo de esta cambio es que la interfaz IQueryable hereda la interfaz IEnumerable, por lo que si representa una consulta, se pueden enumerar los resultados de esa consulta (la enumeración provoca la ejecución del árbol de expresión asociado a un objeto IQueryable). Es tarea del framework armar la consultas correctamente a partir de las query options enviadas en la URI.

Realizado el cambio, vamos a consumir el servicio como lo veníamos haciendo normalmente utilizando la siguiente URI:

http://localhost:[port]/api/clientes
Datos obtenidos del servicio

Datos obtenidos del servicio

Podemos observar que los resultados vienen en el mismo orden que los habíamos agregamos en el array (es decir, sin estar ordenados por alguna condición). Ahora solicitemos el mismo recurso, especificando que vengan ordenados por el atributo nombre:

http://localhost:[port]/api/clientes?$orderby=Nombre
Datos obtenidos del servicio utilizando las convenciones de URL

Datos obtenidos del servicio utilizando las convenciones de URL

Como vemos, utilizando las convenciones URI, es muy simple establecer condiciones-acciones-funciones en la obtención de los recursos! 🙂

Pero eso no es todo, también podríamos trabajar con paginación y filtros. Simplemente debemos agregar en la URI la query correspondiente de acuerdo a nuestras necesidades, algunos ejemplos:

http://localhost:[port]/api/clientes?$filter=Nombre eq ‘Jous’
  • Filtrar utilizando operadores lógicos:
http://localhost:[port]/api/clientes?$filter=Nombre eq ‘Sebis’ or Nombre eq ‘Jous’
http://localhost:[port]/api/clientes?$top=3&$skip=0

También tenemos disponibles un gran conjunto de opciones de query dentro de las cuales podemos encontrar: Select, Top, OrderBy, Expand, Format, DateTime Functions, Math Functions, Type Functions y muchísimas otras más.

Para finalizar, quería comentarles que OData dispone de un conjunto de API’s de creación y consumo de Servicios OData para trabajar desde el lado del cliente con dispositivos mobiles (WP7, Android, iOS), app webs (Silverligth, ASP.NET, HTML 5 + Javascript, Java, PHP, Ruby) y web CMS (Joomla, Drupal). Y también desde el lado del servidor con custom servers (.NET Server, Java, PHP, Node.JS),  databases (SQL Server, MySql, Azure Data) y cloud app (App Engine, Azure).

Espero que les sea de utilidad.

Nos vemos pronto!