Visual Studio 2012: diagramas parciales para Entity Framework

Los que hayáis tratado con modelos extensos en Entity Framework seguro que habéis sufrido bastante con Visual Studio 2010, puesto que las entidades y relaciones las representábamos obligatoriamente en un único diagrama y esto dificultaba enormemente su legibilidad y mantenimiento.

De hecho, modelo conceptual y diagrama eran una misma cosa: las entidades y relaciones mostradas a nivel visual en un diagrama eran todas las que formaban parte del modelo de datos, y viceversa.

Pues bien, esto se va a acabar con la próxima versión del entorno de desarrollo: Visual Studio 2012 incluye la posibilidad de crear más de un diagrama por cada modelo de datos (.edmx).

O en otras palabras, sobre el mismo modelo conceptual podemos crear tantos diagramas como necesitemos, y en cada uno de ellos incluir las entidades y relaciones que nos interesen. Por ejemplo, podemos tener un modelo conceptual con las entidades A, B, C, y D y crear dos diagramas, mostrando en el primero de ellos las entidades A, B y C y en el segundo las entidades A y D.
Pero ojo, que no añade ninguna ventaja desde el punto de vista de la codificación; simplemente es una ayuda para organizar los diagramas, seguiremos trabajando con un único contexto de datos.

Para no introducir ningún cambio rompedor, Visual Studio se comportará como hasta ahora cuando incluimos un modelo de datos en nuestra aplicación. Las herramientas para hacerlo son prácticamente idénticas, y al finalizar veremos el diagrama generado por defecto, que por supuesto incluye todas las entidades si hemos decidido hacerlo partiendo de una base de datos:

Creación de diagrama por defecto

De esta forma, en sistemas simples que no valga la pena “trocear” el modelo, o en proyectos migrados desde versiones anteriores de Visual Studio todo seguirá funcionando igual que hasta ahora y no apreciaremos cambios.

Model browserDonde notaréis la desvinculación del modelo conceptual y los diagramas es al acceder al Model Browser, en el que la sección “Diagrams” aparece claramente separada del resto de elementos, como podéis observar en la captura de pantalla adjunta. Esto no existía en Visual Studio 2010.

Las entidades que aparecen vinculadas a un diagrama concreto son aquellas representadas en el mismo, un subconjunto de las disponibles en el modelo completo, que podéis observar en la sección “Model > Entity Types”.

Tenemos varias fórmulas para añadir diagramas al modelo. La más sencilla es seleccionar una o varias entidades desde un diagrama existente (por ejemplo, el generado inicialmente al añadir un modelo de datos) y seleccionar la opción “Move to new diagram”:

Mover entidades a nuevo diagrama

También podemos, desde el mismo Model browser, abrir el menú contextual sobre la carpeta “Diagrams” y seleccionar la opción “Add new diagram”. Una vez creado, podemos arrastrar sobre la superficie de diseño entidades disponibles en la sección “Entity Types”:

Arrastrando entidades sobre un diagrama

Tras arrastrar las entidades del modelo al diagrama, ya aparecerán vinculadas al mismo en el Model Browser, colgando de la entrada “Diagram 2” de la sección de diagramas.

Fijaos que las entidades Role y User del ejemplo están incluidas en ambos diagramas, aunque en realidad se trata de simples referencias hacia la entidad definida a nivel de modelo. Por tanto, si desde cualquier punto modificamos una entidad, los cambios serán reflejados en todos los diagramas que las incluyan.

Otro punto en el que notaremos diferencias respecto a la versión anterior de Visual Studio (y EF) es al eliminar entidades de un diagrama. De hecho, como podéis observar en la siguiente captura de pantalla, ahora tenemos dos opciones para eliminar una entidad: quitarla del diagrama actual pero manteniéndola en el modelo (atajo: tecla Supr), o quitarla del modelo conceptual y de todos los diagramas que la incluyan (Mays+Supr):

Eliminación de entidades de un diagrama
En fin, una característica muy esperada en Entity Framework, y que seguro que nos ayuda cuando más lo necesitamos, en sistemas con modelos de cierto volumen y complejidad.

Publicado en Variable not found.

¡Microsoft MVP 2012!

Acabo de llevarme una gran alegría, y tengo la urgente necesidad de compartirla con todos vosotros: por segundo año consecutivo he tenido la inmensa fortuna de ser reconocido como Microsoft MVP en el área ASP.NET/IIS 🙂

Es para mí todo un honor y un privilegio seguir formando parte de este grupo de apasionados de las tecnologías Microsoft, gente increíble con la que da gusto tratar, y con los que espero poder compartir más buenos ratos.

Por si alguno no sabe de qué se trata, el galardón MVP (Most Valuable Professional) es un prestigioso reconocimiento que otorga Microsoft a personas de la comunidad técnica que dedican parte de su tiempo a compartir con otros usuarios sus conocimientos y experiencias sobre productos y tecnologías de esta compañía. Si estás interesado en saber más sobre ello, aquí puedes leer bastante sobre el reconocimiento MVP.

Muchas gracias a todos los que lo habéis hecho posible de nuevo 🙂

Ah, y ¡felicidades a todos los nuevos galardonados, y a los que repiten también este año!

Publicado en Variable not found.

AllowAnonymous en ASP.NET MVC 4

 

ASP.NET MVCDesde el principio de los tiempos, ASP.NET MVC dispone de un mecanismo muy sencillo para controlar el acceso a acciones, basado en los sistemas de autenticación por formulario estándar de ASP.NET.

A grandes rasgos, el asunto consiste en decorar acciones, o incluso los controladores completos, con el atributo [Authorize], de forma que si el usuario no ha superado el procedimiento de autenticación, no se podrá acceder a ellas. Además, gracias a los parámetros admitidos por este filtro, es posible indicar qué usuarios concretos pueden ejecutar la acción, o qué roles son los permitidos:

public class CustomersController : Controller
{
    [Authorize(Roles = "manager")]
    public ActionResult Delete(int id)
    {
        // TODO: Delete a customer
    }

    //...
}

Sin embargo, había algunos escenarios que eran algo molestos de implementar usando esta técnica. Imaginad que estáis desarrollando una web totalmente privada, en la que es necesario incluir el atributo [Authorize] a todos los controladores: el ideal sería utilizar filtros globales, ¿no? En vez de modificar cada uno de los controladores, haríamos lo siguiente en el global.asax.cs:

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        filters.Add(new AuthorizeAttribute());
    }

Y como casi todo en la vida, no es tan sencillo. Este atributo afectaría a las acciones que retornan las vistas del propio formulario de login, por lo que simplemente no sería posible acceder a la aplicación. Lógicamente hay varias soluciones para este problema; por ejemplo, podríamos introducir a mano el filtro en los controladores, o incluso mejor aún, crear un controlador base con el atributo y heredar de él todos los controladores menos los destinados a facilitar la identificación del usuario.

ASP.NET MVC 4 introduce un nuevo filtro, denominado [AllowAnonymous] cuya utilidad seguro que podéis deducir en este momento: aplicado a un controlador o acción, hace que se ignore en éste cualquier posible atributo [Authorize] que pudiera haberse aplicado al mismo. De hecho, en la misma plantilla de proyectos MVC 4 ya vemos que se utiliza bastante en el controlador AccountController , tradicionalmente destinado a realizar las tareas de conexión, registro y desconexión de usuarios:

    [Authorize]
    public class AccountController : Controller
    {

        //
        // GET: /Account/Login

        [AllowAnonymous]
        public ActionResult Login()
        {
            // ... 
        }
    }

Y este post podría acabar aquí, pero hay una curiosidad que quería comentar sobre la forma en que está implementado este filtro. Si observamos su código fuente, vemos que es, poco más o menos, así:

namespace System.Web.Mvc 
{  
   [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, 
                   AllowMultiple = false, Inherited = true)] 

   public sealed class AllowAnonymousAttribute : Attribute 
   { 

   } 
}

Salvo la metainformación contenida en el AttributeUsage, el resto es una clase vacía (!). Aunque al principio puede parecer raro, es muy lógico: la magia la pone el atributo [Authorize] en cuya implementación encontramos la lógica que posibilitará la ejecución de una acción si se encuentra decorada (ella o su controlador) con [AllowAnonymous], saltándose todas las comprobaciones de autorización:

bool skipAuthorization = 
    context.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
    || context.ActionDescriptor.ControllerDescriptor.IsDefined(
                typeof(AllowAnonymousAttribute), inherit: true);

if (skipAuthorization)
{
    return; 
}

Publicado en Variable not found.

Login único para subdominios en ASP.NET

 ASP.NETImaginad que tenemos un sistema web de cierto volumen y decidimos estructurarlo en aplicaciones independientes, cada una publicada en un subdominio propio:

  • www.acme.org, que sería el sitio principal.
  • crm.acme.org, con el sistema CRM de la empresa.
  • erp.acme.org, con un sistema de gestión empresarial.
  • administration.acme.org con las herramientas de administración del sistema.
  • etc.

Desde un punto de vista operativo, es probable que nos interese suministrar un mecanismo de autenticación de usuarios compartido entre todas estas aplicaciones, de forma que el usuario, una vez identificado, pueda pasar de una a otra sin necesidad de introducir de nuevo sus credenciales.

No es una tarea complicada en ASP.NET, aunque hay que hacer algunos ajustillos para que todo funcione correctamente. Veámoslos.

Primero: ampliar el alcance de la cookie

Como sabemos, el procedimiento estándar de autenticación consiste en comprobar que las credenciales suministradas por un usuario son correctas (con membership o cualquier otro mecanismo), y en caso afirmativo, generar una cookie con información encriptada sobre el mismo. Esta cookie, llamada por defecto “.ASPXAUTH”, viaja en las sucesivas peticiones hacia el servidor, de forma que éste puede comprobar que el usuario ha sido autenticado satisfactoriamente con anterioridad.

El problema es que por defecto esta cookie es específica para cada host, por lo que sólo estará disponible para el dominio desde el que ha sido generada, con todos sus subdirectorios. Así, cuando se supera el procedimiento de autenticación y se crea la cookie (por ejemplo llamando a FormsAuthentication.SetAuthCookie()), lo que se enviará al servidor en los encabezados de la respuesta es lo siguiente:

Set-Cookie: .ASPXAUTH=F1E37685DF9CBED74094D02958BA239B4AEAA0BDEF2FF379A2E2C5A
4B7F9AC271B7F14BCFFE3E18799434EE8886CF4A0227E6BE92BC91E
34601D0FCC18D3F1786D5060329DB578DF2BB5148F6AB2972D72C3D
B17A437CE977660E552B92A6E5F981F3E6CE6037065244E1F0AB0BD
A570D61DEB02; path=/; HttpOnly

Pues bien, para conseguir nuestros objetivos, lo primero que nos interesa es asegurar que esta cookie estará disponible en los subdominios del dominio desde la cual se ha generado.

Afortunadamente podemos configurar bastantes aspectos del sistema de autenticación basado en formularios simplemente tocando un poco el web.config, y en este caso simplemente debemos indicar el dominio (de segundo nivel) para el cual queremos que la cookie sea válida, incluyendo sus subdominios. en el parámetro domain:

  <authentication mode="Forms">
    <forms loginUrl="~/Account/LogOn" timeout="2880" domain="acme.org" />      
  </authentication>

El valor de este parámetro será el nombre del dominio raíz del que colgarán todos los subdominios a los que haremos la cookie visible. Una vez introducido este valor en el web.config, la cookie de autorización será la siguiente:

Set-Cookie: .ASPXAUTH=4A67B460978D78217D52248318EB68717E857D7C08012057D0B6731
896E0E4EB9023AD2C02024CFB7D190617CCCD97FBA1E6ED484CE3FF
3581E7C8BE31FA204508F5ABB0DF994ADD698369132A5AF932AFF40
A267422C9ABCA86E620B9041ABB2E97C516880960F3D8193B209616
5978108AB500; domain=acme.org; path=/; HttpOnly

Este cambio tendremos que hacerlo únicamente en la aplicación que genere la cookie, es decir, aquella que incluya la implementación del procedimiento de autenticación del usuario. En nuestro caso, por ejemplo, podría ser la aplicación “raíz”, www.acme.org.

Segundo: encriptar la cookie usando una clave común

Esa secuencia de letras y números que veis en la cookie no es más que el resultado de encriptar la información que necesita ASP.NET para mantener información sobre el usuario autenticado. Esta encriptación se realiza utilizando un algoritmo y unas claves específicas para cada sitio web, que por defecto son generadas de forma automática.

Así, si queremos que distintos sitios web puedan desencriptar la cookie y acceder a su contenido, lo cual es fundamental para mantener el usuario conectado, debemos hacer que todos ellos compartan el algoritmo y clave de encriptación. Esto se hace desde el web.config estableciendo estos aspectos en la entrada <machineKey>:

...
< system.web>
<machineKey <!-- ¡Ojo no copiar y pegar! -->
validationKey="6171035B16CD1EE0E401BA3E7348DE49FA9FB9C043B3CDE3BA4FF
EDDC84B167C68B83916FAD8AEE4CFEE001AD5CEA8A4B3E28D51F9
D5EA55CD5F276E67B71FC6"
decryptionKey="3F12339F897F687F4456FEC2446167C621BDE5F664178CBEF9AF0
40DB82EC806"
validation="SHA1"
decryption="AES"
/>
...
< /system.web>

Puedes generar este elemento usando el generador de MachineKey online, copiar el código y pegarlo en la sección <system.web> del web.config de todos los sitios web, raíz y subdominios, de la aplicación.

A partir de este momento ya podemos probar el sistema completo en funcionamiento. Si el mecanismo de autenticación se encuentra en www.acme.org, la autorización viajará al navegar por todos sus subdominios (crm.acme.org, erp.acme.org…), éstos serán capaces de desencriptar las credenciales y, por tanto, el usuario permanecerá logado en el sistema.

Tercero: ajustar Redirecciones

Dado que hemos considerado que todas las aplicaciones son privadas, con toda seguridad estarán protegidas contra accesos de usuarios no autenticados.Por ejemplo, en ASP.NET MVC probablemente tengamos todas las acciones protegidas con un filtro [Authorize], o en WebForms tendremos secciones <authorization> en el web.config para denegar el acceso a todas sus funcionalidades.

En cualquier caso, nos interesa que los usuarios no autenticados sean redirigidos a la aplicación desde la cual pueda autenticarse, por lo que podemos debemos indicar la URL de la página de login en el parámetro loginUrl como sigue:

    <authentication mode="Forms">
      <forms loginUrl="http://www.acme.org/account/logon" timeout="2880">
      </forms>
    </authentication>

Este cambio sería necesario hacerlo en todas las aplicaciones a las que no vamos a permitir el acceso anónimo (crm.acme.org, erp.acme.org, …), con lo que aseguramos que cualquier intento de entrada de usuarios no autenticados será redirigida al sitio principal, desde el cual podrá identificarse.

Cuarto: retocar la URL de retorno y procesarla tras la autenticación

Casi hemos acabado, pero aún hay un detalle cuya solución no es tan inmediata como los puntos anteriores.
El parámetro ReturnUrl
Cuando se produce la redirección hacia la página de login, automáticamente se añade a la petición un parámetro llamado ReturnUrl donde se almacena la URL a la que estaba intentado acceder el usuario. Esto permite devolverlo a ella una vez se haya autenticado en el sistema.

El problema es que este parámetro no incluye el host, por lo que en una aplicación distribuida en distintos dominios esta información se perderá. Es decir, si un usuario intenta acceder directamente a erp.acme.org/customers/index, será redirigido a la URL http://www.acme.org/account/logon?ReturnUrl=customers/index para ser autenticado, y cuando esto ocurra, el sistema no tiene información suficiente como para devolverlo a la página a la que deseaba ir en un principio (en erp.acme.org).

Lamentablemente no podemos “influir” en la forma en que ASP.NET genera el contenido para este parámetro al redirigir al usuario, por lo que o bien hacemos nosotros la redirección de forma manual al detectad que el usuario no se ha autenticado, o bien nos introducimos en algún punto avanzado del ciclo de ejecución de la petición para modificar el valor original del parámetro.

Un primer acercamiento de la implementación de esta última opción podría ser la siguiente, implementando el evento Application_EndRequest (en el global.asax.cs, válido tanto en MVC como en Webforms):

protected void Application_EndRequest(object sender, EventArgs e)
{
    if (Response.StatusCode == (int)HttpStatusCode.Found && !Request.IsAuthenticated)
    {
        string redirectUrl = this.Response.RedirectLocation;
        if (redirectUrl.Contains("ReturnUrl="))
        {
            var host = HttpUtility.UrlEncode(
                            "http://" +
                            this.Request.Url.Host +
                            (Request.Url.IsDefaultPort ? "" : ":" + Request.Url.Port));
            Response.RedirectLocation = redirectUrl.Replace("ReturnUrl=", "ReturnUrl=" + host);
        }
    }
}

Como podéis observar, simplemente intentamos detectar cuándo la respuesta enviada al cliente es una redirección, y si el usuario no está autenticado y existe un parámetro ReturnUrl, lo modificamos para que presente también el host (y puerto). De esta forma, ya llegará a la página de login la dirección completa a la que hay que enviar el usuario cuando supere la autenticación.

Ya llega la URL completa!

Y por último, ya lo único que quedaría sería implementar la redirección a la URL suministrada en el parámetro ReturnUrl una vez confirmada la identidad del usuario, es decir, tras establecer la cookie.

Eso sí, tened un poco de cuidado antes de redirigir y comprobad que la dirección a la que vais a enviarlo forma parte de vuestro sitio (por ejemplo, comprobando que el host de destino pertenece a acme.org) para evitar la vulnerabilidad Open Redirect.

Publicado en Variable not found.

ASP.NET MVC 4 Release Candidate ya disponible

ASP.NET MVC¡Bueno, pues parece que esto se mueve! Hace unos días ha sido publicada la Release Candidate de ASP.NET MVC 4 coincidiendo con la liberación de Windows 8 Release Preview y Visual Studio 2012 Release Candidate (que, de hecho, incluye de serie MVC 4 RC).

Y como viene siendo costumbre, vamos a dar un repaso a todo lo que encontramos en esta nueva entrega, que presumiblemente será la última (bueno, o penúltima, nunca se sabe) antes de la versión definitiva. Eso sí, para no hacer el post demasiado largo nos centraremos exclusivamente en los cambios más destacables y visibles que se han introducido respecto a la versión beta.

1. Cambios en plantillas de proyecto

Plantillas de proyecto en MVC 4En la versión RC de MVC 4, al crear un nuevo proyecto encontramos plantillas ya conocidas de versiones y revisiones anteriores del framework:

  • Empty
  • Internet Application
  • Intranet Application
  • Mobile Application
  • Web API

Adicionalmente, aparece una nueva plantilla, llamada Basic, que podríamos decir que es un término medio entre la plantilla vacía y la aplicación para internet. En ella se incluyen sólo unas vistas shared (el archivo _viewstart, un _layout y la página estándar de error), los scripts, recursos gráficos y estilos utilizados en ellas, y la inicialización de bundles, rutas y filtros. Nada de los célebres HomeController o AccountController y sus vistas asociadas.

Otra cosa curiosa: recordaréis que la versión Beta traía “soporte experimental” para Single Page Applications, o Aplicaciones de Página Única. Pues bien, se ve que ahora han optado por no continuar con este experimento y en la RC de MVC 4 han eliminado la plantilla para proyectos SPA, que seguirá evolucionando pero de forma independiente a MVC 4. Casi que mejor así 😉

También ha sido eliminado de la plantilla Internet Application el sistema de autenticación basado en Ajax que quedaba tan molón, pero que complicaba bastante el código del AccountController.

2. Nueva convención de ubicación de archivos (App_Start)

El código de inicialización de las aplicaciones cada vez va tomando más volumen; incluso en las aplicaciones más simples nos encontramos con la necesidad de definir rutas, bundles, filtros globales, inicialización del acceso a datos, mapeos para inyección de dependencias, y un largo etcétera.

imageIntroducir todo este código en el global.asax.cs no es una buena idea, y como el framework no aportaba ninguna sugerencia al respecto, al final acabábamos poniéndolo cada uno donde nos parecía oportuno.

Pues bien, a partir de hora, todo el código de inicialización de las aplicaciones se encontrará por defecto en la carpeta App_Start. De hecho, en las plantillas no vacías ya vemos cómo se incluye la definición de rutas, filtros o bundling. Por otra parte, en el global.asax.cs sólo encontraremos las correspondientes llamadas a dada uno de estos inicializadores.

3. Nuevas referencias

Examinando las referencias de los distintos tipos de proyecto encontramos nuevos ensamblados, que corresponden con los siguientes componentes:

  • Json.NET (en el ensamblado Newtonsoft.json) que es ahora utilizado como formateador JSON por WebAPI, sustituyendo las funcionalidades proporcionadas tradicionalmente por System.Json.dll.
  • WebGrease, que es el nuevo sistema de optimización de scripts, css e incluso imágenes, que es ya utilizado por el sistema de bundling del paquete System.Web.Optimization.
  • Antl3.Runtime es la adaptación para .NET del runtime del analizador de gramáticas ANTLR (Another Tool for Language Recognition). Es una dependencia de WebGrease.
  • Entity Framework 5.0 RC, que aporta toda la infraestructura de acceso a datos que necesitamos para nuestras aplicaciones. Esta revisión incorpora múltiples novedades: tipos enumerados, tipos espaciales, TVFs, mejoras en el diseñador (VS2012), etc. Puedes leer más sobre las novedades incorporadas en EF 5 RC aquí.

4. Avances en WebAPI

WebAPI, el nuevo framework que tanto entusiasmo está provocando, sigue evolucionando y mejorando con esta versión. De hecho, diría que es uno de los puntos que más cambios han sufrido, al menos visiblemente, desde su aparición en la versión beta de ASP.NET MVC 4. Las novedades de mayor calado son las siguientes:

  • Los métodos de nuestro API que retornen un IQueryable<T> deben decorarse con el atributo [Queryable] para que esté permitido componer consultas sobre ellos.
  • Es posible registrar de forma global una clase que implemente ITraceWriter para utilizarla como punto único de trazado y diagnóstico, en la que podemos registrar los eventos que vayan produciéndose en el sistema.
  • También es posible, usando el método extensor RegisterForDispose(), asociar a un Request un objeto IDisposable, de forma que será liberado automáticamente cuando termine de ser procesada la petición. Muy interesante para gestionar de forma correcta el ciclo de vida de los objetos.
  • Podemos utilizar el servicio ApiExplorer de nuestros Api Controllers para obtener metainformación en tiempo de ejecución sobre los servicios, lo que da opción para crear sistemas de ayuda o clientes dinámicos (por ejemplo para pruebas).
  • Ha sido simplificada la forma de solicitar y responder objetos con negociación de contenidos. Ahora, por ejemplo, usaremos el método extensor CreateHttpResponse<T> para retornar objetos desde nuestros métodos con control total sobre la respuesta. Las clases que usábamos antes, HttpRequestMessage<T> y HttpResponseMessage<T>, han sido eliminadas.
  • Se han introducido métodos específicos en la propiedad Headers para obtener las cookies asociadas a una petición y para añadirlas a una respuesta.
  • Y muchos otros cambios internos que podéis consultar en el documento de notas de la revisión.

La verdad es que algunos de los avances que han sido incorporados tienen también sentido para controladores MVC convencionales y no sólo para WebAPI. Espero que estas dos líneas paralelas serán unificadas vistas a la versión definitiva, porque la verdad es que no le veo demasiado sentido a mantenerlas separadas. De hecho, en estos momentos resulta un poco caótico que existan clases duplicadas en ensamblados distintos (System.Web.Http para WebAPI y System.Web.Mvc para ASP.NET MVC).

Por poner un ejemplo, el filtro HttpGet podemos encontrarlo en ambos namespaces, y dependiendo del contexto en que lo necesitemos (WebAPI o MVC) debemos usar uno u otro, y lo mismo ocurre con muchos otros elementos, tanto internos como externos. Esto puede provocar muchos fallos difíciles de detectar dado que las clases usadas dependerán en muchos casos del namespace usado en nuestro código.

En fin, espero que esto quede unificado en algún momento…

5. Cambios en el sistema de bundling

Hasta ahora, el mismo sistema de bundling venía de serie con lógica para incluir de forma automática los scripts de uso más habitual en aplicaciones web usando métodos como EnableDefaultBundles(). Sin embargo, los desarrolladores no podíamos modificarla porque se incluía en los propios componentes de la biblioteca de optimización.

A partir de la RC, la configuración de los bundles por defecto se realiza de forma explícita en nuestra aplicación. En la nueva carpeta /App_Start encontramos la clase BundleConfig, en cuyo método estático RegisterBundles() se encarga de ello, y obviamente podemos modificarlo a nuestro antojo:

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new ScriptBundle("~/bundles/jquery").Include("~/Scripts/jquery-1.*"));
 
        bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include("~/Scripts/jquery-ui*"));
 
        bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));
 
        [...]

Respecto a revisiones anteriores de MVC 4, se ha simplificado un poco la sintaxis para crear bundles, asociarlos al componente encargado de realizar su transformación, y especificar los recursos a incluir en el paquete.

Por otro lado, las referencias desde las vistas a los distintos bundles también se han simplificado y se generan de forma automática utilizando clases estáticas provistas desde el ensamblado System.Web.Optimization:

        @Styles.Render("~/Content/css")
        @Scripts.Render("~/bundles/jquery")

Otro aspecto muy interesante es que dependiendo de si estamos ejecutando la aplicación en modo release o debug los bundles serán compactados y minimizados o no, para facilitar la depuración.

6. Cambios en scaffolding

También se han introducido cambios relativos a la creación de controladores:

  • Ahora es posible añadir un controlador sobre cualquier carpeta del proyecto pulsando el botón derecho sobre ella. Esto facilitará la organización de aplicaciones donde convivan controladores MVC y controladores WebAPI.
  • Se ha eliminado la plantilla de controlador Ajax grid que encontrábamos en la beta del producto. El motivo, según indican en el documento de notas de la revisión, no es otro que dejar al mínimo la lista de plantillas disponibles.
  • Se ha creado una nueva plantilla para controladores WebAPI basados en una entidad y contexto de datos de Entity Framework.

Por otra parte, las famosas recetas (recipes) han sido finalmente eliminadas de MVC 4 y serán incorporadas como parte de Nuget. De hecho, si lo pensamos un poco, la idea inicial tiene bastante más que ver con Nuget que con MVC, así la decisión creo que es bastante correcta.

Y hasta aquí llegamos. Tenemos más detalles descritos en el documento de notas de la revisión, que os recomiendo que leáis, y seguro iremos descubriendo muchos otras novedades durante las próximas semanas.

Enlaces:

Publicado en Variable not found.

[Auges] Vídeo y materiales del webcast sobre SignalR. ¡uau!

SignalR, ¡uau!Como sabéis, el pasado jueves tuve el placer de hablar sobre SignalR en un Webcast organizado por Auges, el grupo de usuarios de ASP.NET de España.

Ante todo, agradeceros a los asistentes que decidierais pasar esa tarde con nosotros. Una audiencia bastante numerosa para estos tiempos que corren, y un gran número de amigos que os quedasteis hasta el final las dos horas que estuvimos hablando sobre esta pequeña maravilla. Incluso hubo quien comentó que se le habían hecho cortas 😉 Muchas gracias a todos.

La charla la empezamos hablando sobre HTTP, y las limitaciones de éste en escenarios en los que el tiempo real era un requisito imprescindible. Sobrevolamos soluciones tradicionales como el polling, presentamos el concepto Push, revisamos los estándares que están en la cocina por parte de IETF y W3C, y explicamos técnicas que podemos usar hoy en día para implementar aplicaciones en las que el servidor debe tomar la iniciativa en la comunicación con el cliente.

Seguidamente, tras una breve presentación de SignalR, entramos de lleno en terreno práctico, implementamos desde cero varias demostraciones de las capacidades del framework y revisamos otro buen número de ellas, mediante las cuales intenté mostrar las bases de la programación con esta joya: conexiones persistentes, hubs, acceso a clientes desde procesos externos, e incluso llegamos a implementar un cliente con Windows Forms.

El cliente siempre manda, incluso en HTTPUn total de ocho demos que espero que hayan conseguido transmitir mi entusiasmo por este framework, y las posibilidades que brinda a la hora de dotar a nuestros sistemas de la espectacularidad que sólo los sistemas colaborativos, multiusuarios y en tiempo real son capaces de proporcionar.

Bueno, a lo que iba en el post… el caso es que ya está disponible el vídeo y los materiales de la charla:

¡Hasta la próxima!

Publicado en Variable not found.

[Auges] Webcast sobre SignalR: ¡uau!

AUGES: ASP.NET User Group EspañaSimplemente informaros de que el próximo jueves 24 de mayo a las 19:00h (hora peninsular española), participaré en una charla con AUGES sobre SignalR, esa pequeña maravilla de la que ya os he hablado por aquí en varias ocasiones, una ayuda imprescindible a la hora de crear sistemas interactivos, multiusuario y en tiempo real para la web.

El “cartel” oficial del evento es el siguiente:

SIGNALR: APLICACIONES MULTIUSUARIO, ASÍNCRONAS, Y EN TIEMPO REAL. ¡UAU!

¿Te has preguntado alguna vez cómo es posible que en una aplicación web como Google Docs varios usuarios puedan interactuar de forma simultánea? ¿Te interesa la tecnología que usan los grandes como Facebook o Twitter para informarte en tiempo real de que algo ha ocurrido? ¿Piensas que crear un chat basado puramente en ASP.NET no es tarea sencilla? ¿Te gustaría que el servidor pudiera enviar información a los clientes en cualquier momento, sin esperar a que éstos soliciten una página o recurso?

imageSi has respondido que sí a alguna de estas preguntas, SignalR te entusiasmará. Es un marco de trabajo open source que permite saltarse las barreras impuestas por HTTP y su clásico modelo de petición/respuesta para conseguir aplicaciones más interactivas, dinámicas y colaborativas, en las que el servidor toma la iniciativa.

No te pierdas este evento y aprende a poner un factor “¡uau!” en tus desarrollos.

Os recuerdo que se trata de un Webcast, o sea, que podéis asistir a este evento desde vuestro sillón favorito, acompañados de una buena cerveza y un saco de palomitas. Y por supuesto, es totalmente gratuito.

Lo único que debéis hacer para poder asistir es registraros:

No me faltéis, que paso lista 😉

Más características de Razor con MVC 4 y Web Pages 2

 

ASP.NET MVCHace poco comentamos algunos detalles sobre las novedades que incluirá Razor 2, la versión que será incluida en ASP.NET MVC 4, y siguen apareciendo más novedades destinadas a mejorar la calidad del código y nuestra productividad al crear las vistas MVC o Web Pages.

En este post vamos a ver otros comportamientos de Razor en distintos escenarios.

Cerrado implícito de etiquetas

La versión anterior de Razor daba problemas con un código como el siguiente:

@if(passwordNeeded) 
{
    <input type="password" name="password">
}

La página no compila y genera una excepción en tiempo de ejecución debido a que la etiqueta <input> no se ha cerrado explícitamente usando la sintaxis de autocierre <input … /> o bien mediante el cierre de toda la vida </input>.

En Razor 2 se ha creado un parser mucho más complejo, en el que, entre otras cosas, se tienen en cuenta los llamados void elements por la W3C, que son aquellas etiquetas que en ningún caso pueden tener contenido: area, base, br, col, command, embed, hr, img, input, keygen, link, meta, param, source, track y wbr.

Cuando Razor se encuentra con alguna de estos elementos, si la etiqueta inmediatamente posterior no es la que lo cierra, asumirá que éste está cerrado de forma automática, por lo que el código mostrado anteriormente será válido.

Rutas con partes variables

Ya vimos que el nuevo Razor será capaz de interpretar el gusanillo (~) directamente, de forma que ya no será necesario utilizar esos verbosos @Url.Content() a la hora de referenciar recursos como scripts, imágenes u hojas de estilo:

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

Pues bien, el caso es que las rutas especificadas de esta forma no tienen por qué ser una cadena constante, sino que pueden incluir variables, como en el siguiente ejemplo:

@{
    var scriptsFolder = "scripts";
}
<script type="text/javascript" src="~/@scriptsFolder/jquery-1.7.1.js"></script>

Lo que hace Razor en este caso es interpretar y obtener la dirección teniendo en cuenta desde el gusanillo hasta la primera barra, insertar el contenido de la variable, y seguirlo del resto de la cadena constante. El equivalente con la versión anterior (MVC3) de Razor sería algo así:

<script type="text/javascript" src="@Url.Content("~/")@scriptsFolder/jquery-1.7.1.js"></script>

Atributos condicionales con varios valores

También vimos en el post anterior que cuando Razor 2 tenía que generar el contenido de un atributo cuyo valor era null, no generaba ni si quiera el nombre del atributo, lo que al final se traducía en un código más sencillo y compacto:

@{
    string nullValue = null;
    string stringValue = "hello";
}
<span class="@nullValue" id="@stringValue"></span>
 
GENERA:
<span id="hello"></span>

Pues bien, este comportamiento funciona incluso cuando en el atributo aparecen valores procedentes de más de una expresión. Aquellas cuyos valores sean nulos no serán generadas (ni siquiera el espacio en blanco que la separa de las demás), y si todas ellas son nulas el atributo no será generado:

@{
    string nullValue = null;
    string stringValue = "hello";
    string otherNull = null;
}
<span class="@nullValue @stringValue @otherNull"></span>
<span class="@nullValue @otherNull"></span>
 
GENERA:
<span class="hello"></span>
<span></span>

Tratamiento de atributos booleanos

Otro detalle muy interesante lo tenemos al escribir valores para atributos booleanos, como disabled, checked o similares. Lo habitual en la versión 1 de Razor para generar apropiadamente estos atributos era hacer algo así:

@{
    string disabled = "";
    if(condition)
    {
        disabled = "disabled='disabled'";
    }
}
<button @Html.Raw(disabled)>Click!</button>

GENERA (dependiendo del valor de condition):
<button >Click!</button>                
<button disabled="disabled">Click!</button>                

La nueva versión de Razor permite ser mucho más directo y explícito en estos casos. Introducimos directamente el booleano como valor del atributo; si es true, el valor del atributo será su mismo nombre, mientras que si es false el atributo no será renderizado:

<button disabled="@condition">Click!</button>

GENERA (dependiendo del valor de condition):
<button>Click!</button>                
<button disabled="disabled">Click!</button>                

Y eso es todo de momento. Seguiremos atentos a las novedades e iremos contándolas por aquí, como de costumbre 🙂

Publicado en Variable not found.

Knockout (I): pongamos orden en el lado cliente

La programación web tradicional, basada en el modelo de petición-respuesta de página completa, no requería demasiado código en cliente más allá que el necesario para realizar validaciones y lógica de presentación simple: ¿para qué complicarnos en cliente si podíamos hacerlo todo en servidor?

De hecho, durante la era Webforms, la célebre pareja formada por viewstate y postback llevaban a servidor prácticamente la totalidad de la lógica de presentación mediante el artificio de la programación por eventos, y el código cliente necesario para que todo funcionara era generado de forma automática por ASP.NET en su mayor parte.
Modelo monolítico tradicional
Esta lógica de presentación podía estar (y de hecho muchas veces estaba) mezclada con tareas de control e incluso con lógica de negocio, y los desarrolladores empezamos a hacernos conscientes de los monstruos que estábamos creando. Esta preocupación provocó la aparición de soluciones que pretendían estructurar los sistemas de forma razonable, separando las responsabilidades y, en definitiva, poniendo un poco de orden en ese caos.

Arquitectura MVCImplementaciones del patrón MVP, MVVM, o incluso el framework ASP.NET MVC son ejemplos del resultado de esta inquietud. Aunque con matices, todos estos enfoques permiten separar la lógica de negocio, control y presentación en “capas” relativamente independientes.

Sin embargo, la programación web está cambiando, o mejor dicho, lo ha hecho ya ;-). El pesado modelo tradicional petición-respuesta de página completa está siendo relegado en favor de interfaces más ágiles y dinámicos que se basan en el aprovechamiento de la capacidad del cliente para realizar una gran cantidad de tareas a través del uso intensivo de scripting y Ajax.

Arquitectura MVC con MVVM en cliente¿Y a dónde nos lleva este cambio de paradigma? Pues, entre otras cosas, a que el volumen de código script en nuestras páginas ha crecido de forma desmesurada y, debido a ello, se incremente exponencialmente la complejidad en el lado cliente.

Pero como ocurrió en el lado servidor, enseguida han comenzado a aparecer soluciones que nos ayudan a estructurar el código cliente usando patrones ya usados en el otro lado, como MVVM, MVP, o MVC.

En esta serie de posts estudiaremos el uso de Knockout.js, una biblioteca javascript que facilita la creación de interfaces ricas y dinámicas basándose en el patrón MVVM.

¿El patrón MVVM?

MVVM, Model-View-ViewModel, es un patrón de diseño de la capa presentación definido en 2005 por John Gossman, de Microsoft, adaptando el Presentation Model de Fowler a las particularidades de las tecnologías en las que estaba trabajando (WPF).

De forma muy similar a MVC, esta arquitectura divide los componentes en tres grupos.

El Modelo básicamente contiene datos de forma totalmente independiente de cómo vayan a ser presentados en el UI, y casi siempre sin comportamiento. Por ejemplo, podrían ser clases o entidades planas del dominio de la aplicación.

La Vista contiene los elementos del interfaz necesarios para comunicarse con el usuario, como etiquetas, cuadros de texto, botones, desplegables, etc. Por ir centrándonos en el contexto que nos interesa, la vista es puro lenguaje de marcado (HTML, XAML…), y no contiene lógica de ningún tipo. Tanto los datos que necesita para componer la UI como el comportamiento de los elementos en pantalla los toma del View-Model usando mecanismos como el binding (después entraremos en esto).

El Modelo-Vista, o View-Model, es una abstracción sobre la Vista que contiene tanto los datos como el comportamiento de ésta, pero sin conocer los detalles sobre cómo está implementada. En cierto sentido podríamos considerar que es similar a un controlador en MVC. Es el encargado de transformar y formatear la información del Modelo para que pueda ser representada en la vista y, de la misma forma, se encarga de recibir y gestionar las acciones del usuario y actualizar el Modelo cuando es necesario.

Para los desarrolladores ASP.NET MVC, el concepto podría asimilarse con una clase ViewModel, a la que además se han incorporado métodos para gestionar los eventos desencadenados por el usuario durante la interacción con la aplicación.

Pero sin duda, una de las piezas clave en este patrón es el binding, o enlace, tanto de datos como de acciones entre las capas View y View-Model.

En la práctica, este mecanismo permite indicar sobre la Vista, de forma declarativa, qué propiedades del Modelo-Vista se corresponden con cada uno de los componentes del interfaz de usuario, estableciendo un vínculo uni o bidireccional sobre ambos.

Por ejemplo, podríamos tener un cuadro de texto en la Vista vinculado a una propiedad llamada “Nombre” en el View-Model, de forma que cuando el valor de la propiedad cambiara automáticamente se reflejaría en el valor del cuadro de texto, y cuando el usuario modificara el cuadro de texto el valor de la propiedad también sería actualizado de forma automática. Con esto conseguimos mantener Vista y View-Model perfectamente sincronizados sin ningún tipo de esfuerzo por parte del desarrollador.

Y al igual que es posible enlazar elementos del interfaz con propiedades del View-Model, también podemos enlazar de forma declarativa acciones o eventos del usuario a comandos, o métodos definidos sobre el mismo View-Model. Por tanto, y valga como ejemplo, el tratamiento del clic sobre un botón lo implementaríamos en un método del Modelo-Vista, mientras que en la Vista lo único que haríamos es bindear (enlazar) el evento click del botón a dicho método.

¿Qué es Knockout?

Knockout.jsKnockout.js es una biblioteca de scripts desarrollada por Steve Sanderson que implementa el patrón MVVM con el objetivo de facilitarnos la creación de interfaces de usuario de edición y visualización de datos, usando una estructura simple, limpia y mantenible en nuestro código.

Es bastante compacta (~40Kb sin comprimir), no depende de ninguna otra biblioteca para funcionar y funciona con todos los navegadores razonablemente actualizados. Es open source, se distribuye bajo licencia MIT y está muy bien documentada en su sitio web (incluyendo unos tutoriales interactivos bastante buenos).

Sus características principales, que conforman las ventajas que nos aporta a los desarrolladores, son:

  • Sistema de bindings declarativos, expresados sobre los propios tags HTML usando atributos data-bind, que permiten crear un vínculo uni o bidireccional entre elementos del interfaz de usuario y propiedades o acciones del View-Model, que se actualizarán de forma automática ante cambios.
  • Seguimiento de dependencias, capaz de detectar los cambios realizados tanto en la Vista como en el Modelo-Vista y propagarlos hacia todos los objetos o elementos dependientes.
  • Por supuesto, el interfaz de usuario se actualiza automáticamente para reflejar los cambios en el View-Model.
  • Incluye un sistema de plantillas que facilita la creación porciones de vistas reutilizables.

Y por si lo que trae de serie Knockout se nos queda corto, podemos extenderlo muy fácilmente.

¿En qué tipo de proyectos puedo utilizarlo?

En todos: PHP, Python, Perl, JSP, Ruby, ASP.NET MVC, Webforms, ASP clásico, HTML y en los que se os puedan ocurrir.

Realmente se trata de una biblioteca pura de scripts que funciona en cliente sin dependencias hacia ninguna tecnología concreta en el lado servidor.

¡Un poco de código, por favor!

PantallaVeamos un ejemplo rápido que demuestre una pequeña parte de la potencia de Knockout. Nuestro objetivo es implementar una funcionalidad sencilla como la siguiente:

  • presentaremos un simple cuadro de texto en el que el usuario podrá introducir su nombre, junto con un botón.
  • bajo ellos, un texto en el que se podrá ver el nombre del usuario y el número de veces que ha sido pulsado el botón; las porciones “dinámicas” de este texto (nombre y número de clics) se actualizarán desde script conforme el usuario actúe sobre el interfaz. Es decir, si el usuario modifica el nombre o pulsa el botón, se actualizará el mensaje para representar el nuevo estado.
  • además, para complicar un poco más el escenario, como máximo se podrá hacer clic cinco veces sobre el botón. Al llegar a ese punto, el botón deberá deshabilitarse.

Pero veamos primero una posible implementación utilizando métodos “tradicionales”, puro scripting con un poco de jQuery para simplificar:

HTML SCRIPT
<div>
  <input type="text" id="name" />
  <button id="button">Click me!</button>
  <p>
    <span id="nameLabel"></span>, 
    you've clicked 
    <span id="clicksLabel"></span> 
    times
  </p>
</div>
$(function () {
  var numClicks = 0;
    
  $("#name").val("John");
  $("#clicksLabel").text(numClicks);
  $("#nameLabel").text($("#name").val());
  
  $("#name")
    .change(function () {
       $("#nameLabel").text($("#name").val());
    });
  
  $("#button").click(function () {
    numClicks++;
    if (numClicks >= 5) {
       $("#button").attr("disabled", true);
    }
    $("#clicksLabel").text(numClicks);
  });
});

Observad en el código anterior que, aunque logramos una separación razonable entre la visualización (HTML) y la lógica de presentación (script), el código script es bastante farragoso y presenta una fuerte dependencia hacia la forma en que está implementada la vista. Estamos mezclando inicialización de valores, el registro de manejadores de eventos, lógica, accesos al DOM, mapeo entre valores y controles… demasiadas responsabilidades para un único punto.

Pero lo peor de todo es que esta forma de implementarlo no deja claro qué datos maneja la página, ni qué comportamiento se espera de ella, pues ambos aspectos están dispersos a lo largo del script. Esta forma de desarrollar, cuando nos encontremos ante un escenario más complejo, nos llevaría irremediablemente al caos que comentaba al principio.

¿Y cómo implementaríamos estas mismas funcionalidades con Knockout.js? Veamos primero el script que podríamos emplear, que conformaría la capa View-Model:

    var viewModel = {
        numClicks: ko.observable(0),
        name: ko.observable("Peter"),
        canClick: function () {
            return this.numClicks() < 5;
        },
        addClick: function () {
            if (this.canClick()) {
                var clicks = this.numClicks();
                this.numClicks(clicks + 1);
            }
        }
    };
    ko.applyBindings(viewModel);

Bastante más simple y ordenado, ¿verdad? En el código podemos ver claramente los datos que necesita la vista para funcionar (numClicks y name), y el comportamiento esperado en ella (los métodos canClick() y addClick()); ojo, y no existe ninguna referencia hacia la implementación de la vista ni accesos al DOM.

Pasemos ahora a los detalles.

ko es el objeto básico de Knockout; o en otras palabras, ko es a Knockout lo que $ a jQuery.

Las propiedades nombre (name) y número de clicks (numClicks) las estamos inicializando a sus valores por defecto, “Peter” y “0” respectivamente. Sin embargo, como podéis observar, usamos para ello el método ko.Observable(). Esto quiere decir básicamente que estas propiedades serán “vigiladas”, de forma que cuando cambie su valor Knockout deberá analizar qué elementos dependen de ellas y actualizarlos en consecuencia (volveremos a esto cuando veamos el código de la Vista).

También podemos ver que sobre el propio View-Model se define el método canClick(), que determina si debemos permitir al usuario hacer más clicks sobre el botón, y addClick(), que será la lógica a ejecutar cuando el usuario lo pulse: simplemente incrementamos el número de clicks, aunque dado que es un objeto “observable” tenemos que hacerlo usándolo como una función tanto a la hora de consultar su valor como de asignárselo.

Por último, la llamada a ko.applyBindings() lo que hace es poner en marcha Knockout con el objeto View-Model que hemos definido.

Lo interesante de este código es que, aparte de dejar bastante claro qué esperamos de la página, no hay referencia alguna desde el script hacia los elementos de la vista, ni accesos al DOM, simplemente se mantiene actualizado el View-Model. Knockout se encargará de actualizar el UI conforme a los cambios realizados gracias al binding y a los “observables” que hemos comentado anteriormente.

La implementación de la Vista, puro marcado HTML, podría ser la siguiente:

<div>
    <input  data-bind="value: name" type="text" />
    <button data-bind="click: addClick, enable: canClick()" type="button">Click me!</button>
    <p>
        <span data-bind="text: name"></span>, you've clicked 
        <span data-bind="text: numClicks"></span> times
    </p>
</div>

El atributo data-bind es el utilizado por Knockout para definir los enlaces, o bindings, desde los elementos del interfaz hacia las propiedades o métodos del View-Model.

  • en el primer caso, el cuadro de edición, estamos enlazando su valor al de la propiedad name del View-Model. Cuando cambie ésta, el cuadro de texto de texto cambiará y viceversa: Vista y View-Model estarán siempre sincronizados de forma automática.
  • en el segundo caso, el botón, enlazamos el evento click del ratón con el método addClick del View-Model, y su atributo enable con canClick() para deshabilitar el botón cuando hayamos llegado al máximo de veces permitidas.
  • el tercer y cuatro enlace se realiza entre los <span> y las propiedades del View-Model que queremos mostrar. De nuevo la magia de los “observables” hará que cuando éstas cambien el texto interior de estas etiquetas cambien de forma automática para mostrar su valor.

Fijaos que en la vista no hay lógica, sólo enlaces hacia datos y comportamiento definido en el View-Model.

Bueno, y con esto creo que podemos dejarlo por hoy 🙂 Obviamente, Knockout es mucho más que esto, y así lo iremos viendo en futuros posts, en los que iremos desmenuzando poco a poco este interesante framework y viendo cómo podemos utilizarlo para mejorar el código de nuestra capa de presentación.

Ah, he colgado en Skydrive el ejemplo desarrollado en este post. Es un zip de 15kb, con un único archivo .html y otro .js (knockout) con el que podéis jugar para entender mejor su funcionamiento.

Publicado en Variable not found.

Caching de bundles en MVC 4 (o MVC 3, o Webforms…)

Hace unos meses ya estuvimos comentando el interesante paquete System.Web.Optimizations que se distribuía con la developer preview de MVC 4, aunque también decíamos que este paquete era igualmente descargable a través de Nuget, y esto hacía posible su uso con MVC 3 o incluso con WebForms.

Como vimos en su momento, su uso era bastante sencillo. En resumidas cuentas, si no queríamos complicarnos demasiado la vida, era suficiente con introducir el siguiente código en la inicialización de la página (en el Application_Start del Global.asax):

    BundleTable.Bundles.RegisterTemplateBundles();

(Bueno, en la preview el método a llamar era EnableDefaultBundles(), pero el resultado era el mismo)

Simplemente con ello, ya podíamos acceder a dos recursos que eran generados automáticamente por el sistema:

  • /scripts/js, que contiene un único archivo .js con todos los scripts existentes en la carpeta /scripts del proyecto, convenientemente minimizados.
  • /content/css, donde encontramos en un único archivo todos los .css presentes en /content, también minimizados para aligerar su descarga.
  • /content/themes/base/css, nuevo en la última versión del componente, que incluye los estilos relativos al tema básico de jQuery UI, utilizado en la plantilla del proyectos de MVC 4.

Además de las ventajas que ofrece en tiempo de ejecución, este mecanismo es muy cómodo a la hora de mantener los scripts actualizados con Nuget. Es decir, dado que descargamos todos los scripts en una única llamada y bajo una única denominación, ya no será necesario andar actualizando las referencias a las bibliotecas en el _Layout.cshtml, algo que resulta molestillo en estos momentos cada vez que actualizamos.

Con la beta de MVC 4 recientemente liberada, este componente ha incluido también un interesante sistema de cacheo que hace aún más eficiente y automático su uso. Si observamos el layout de la plantilla de proyectos ASP.NET MVC 4 veremos que incluye las siguientes líneas:

<link href="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Content/css")" 
      rel="stylesheet" type="text/css" />
<link href="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Content/themes/base/css")" 
      rel="stylesheet" type="text/css" />
<script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/js")">
</script>

Observad que las referencias a los distintos recursos se generan a través del método ResolveBundleUrl(). Pues bien, el código generado por las líneas anteriores es, más o menos así de sorprendente:

<link href="/Content/css?v=oI5uNwN5NWmYrn8EXEybCIbINNBbTM_DnIdXDUL5RwE1" 
      rel="stylesheet" type="text/css" />
<link href="/Content/themes/base/css?v=UM624qf1uFt8dYtiIV9PCmYhsyeewBIwY4Ob0i8OdW81" 
      rel="stylesheet" type="text/css" />
<script src="/Scripts/js?v=GP89PKpk2iEmdQxZTRyBnKWSLjO7XdNG4QC1rv6LPxw1"></script>

En las referencias se está añadiendo un parámetro que contiene un valor hash correspondiente al contenido de la carpeta empaquetada en el momento de generación de la vista. La primera petición que se realice enviará de vuelta los recursos empaquetados y minimizados con una validez a nivel de caché de un año; las siguientes peticiones, por tanto, se resolverán muy rápidamente mediante un HTTP 304 (No modificado):

Respuestas HTTP 304 en peticiones

A partir de ahí, cualquier cambio que se realice en alguno de los archivos o directorios incluidos en los bundles provocará un cambio de hash, por lo que la petición será diferente y, por tanto, el resultado no se tomará desde la caché.

Publicado en: Variable not found.