Build 2014: Imágenes detrás de escena

Pasó el día 1 de Microsoft Build en San Francisco, con novedades centradas principalmente alrededor de la futura nueva versión de Windows Phone (Windows Phone 8.1) y el nuevo update para Windows (Windows 8.1 Update 1, ya disponible para suscriptores MSDN). Con más tiempo intentaré escribir sobre todo esto y sobre mis impresiones de la conferencia. En este post quería simplemente compartir unas pocas imágenes que muestran algunas de las cosas que suceden fuera de los keynotes y sesiones y aquellos que no tienen la suerte de poder asistir no pueden ver.

DJ musicalizando antes que empiece el Keynote

Sesiones 1:1 con expertos en diferentes tecnologías

Test de dispositivos

Stands de partners

Demo de impresoras 3D

Demo de Windows en "otros" dispositivos (IoT)

Comida

Bebida

Regalos para los asistentes

En los próximos días espero subir más (y escribir sobre lo que realmente importa :-) . Enjoy!

Desarrollo de videojuegos con HTML5–Parte 9

En los anteriores post (8, 7, 6, 5, 4, 3, 2, 1) hablamos sobre las bases del desarrollo de videojuegos con HTML5 y JavaScript. Especialmente sobre algunas técnicas de desarrollo y conceptos de programación dentro de este paradigma.

En todo caso, todo lo anterior nos sirvió para conocer el funcionamiento interno de los videojuegos, pero, como en la mayoría de los casos de desarrollo tenderá a que tengamos que escribir mucho código. La solución para este caso, usar algún framework de desarrollo que nos abstraiga de tener que escribir cada punto y coma.

Por supuesto, tenemos cientos de Frameworks. Pagos, gratuitos, open source, licenciados, etc.

Básicamente aquí dependerá de gustos ya que casi todos (Casi), tienden a contar con las mismas funcionalidades, pero con diferentes acercamientos hacia el desarrollador. Algunos tratan de tomar el control absoluto del funcionamiento del juego, otros dan mayor responsabilidad al desarrollador, y así la lista sigue.

Para la serie de ejemplos que veremos desde ahora en adelante, usaremos jsGFwk. Un Framework creado (Y aún en proceso de desarrollo) por mí, y que nace luego de varios años de experiencias en esta rama del desarrollo.

Pero sin muchos preámbulos sobre el Framework, construyamos el primer ejemplo.

El canvas

Como en todos los casos, necesitaremos de un tag <canvas> en nuestra página, el que será usando por el Framework para dibujar los diferentes elementos del juego.

<canvas id="canvas" width="640" height="480"></canvas> 

El Framework

jsGFwk está pensado para que las diferentes funcionalidades se comporten como plug-ins. Donde cada característica no sea parte de todo un paquete obligatorio a referenciar (Aunque no lo usemos), sino que solo incluyamos aquello que nos haga falta.

En este sentido, para obtener un contexto para un videojuego, solo necesitaremos agregar a la página una referencia al framework.

<script language="Javascript" src="Framework/jsGFwk.js"></script> 

De igual manera, necesitaremos especificar que lienzo deberá usar el motor del juego.


//Seteamos el ID del objeto canvas a usar

jsGFwk.settings.canvas = "canvas"; 

Creando objetos de juego

Una vez contamos con el framework, podremos crear diferentes objetos de juego. A diferencia de los ejemplos en las anteriores entregas, aquí, cada elemento del juego es un objeto que encapsulará la funcionalidad del mismo.

jsGFwk.createObject({        id: "ball",         x: 10,        y: 10,        zOrder: 2,        visible: true,        update: function(delta) {            this.x++;        },        draw: function (context) {            context.fillStyle = "rgb(255,255,255)";            context.fillRect(this.x,this.y,10,10);        }    });

La función createObject espera un objeto con algunas propiedades o funciones públicas específicas:

  • id: Representa el nombre del objeto. Este nombre es útil para acceder a propiedades de otros objetos de juego desde cualquier punto del código.
  • zOrder: Un valor que representa la profundidad del objeto. Debido a que los objetos se dibujan en capas, mientras más alto sea el valor numérico de esta propiedad, más arriba en las capas se dibujará.
  • visible: Un objeto que no cuente con la propiedad “visible” o esta esté configurada a falso, no se dibujará en la pantalla.
  • update: Por cada tick del juego, la función update se llamará una vez. Esto quiere decir que si esperamos que nuestro juego se dibuje a 24 cuadros por segundo, esta función se disparará por lo menos 24 veces por segundo. Además, recibirá un valor delta, que representa el tiempo que ha pasado entre el cuadro anterior y el cuadro actual de dibujo.
  • draw: Al igual que update, se disparará tantas veces como cuadros por segundo hayamos configurado, siempre después de que la función update haya concluido. El parámetro context, hace referencia al contexto de dibujo en el que deberemos dibujar nuestras imágenes.

Iniciando el juego

El último paso es inicializar el motor de juego.

jsGFwk.start();

Por supuesto, podemos crear objetos de juego, así como destruirlos, en cualquier momento, no es necesario que creemos todos los objetos y luego iniciemos el motor.

Como resultado, veremos que un cuadrado de color se desplaza sobre el eje X en el objeto canvas.

Untitled

En las siguientes entregas veremos más sobre este juego y cómo podemos contruir juegos de forma rápida.

Tracking de proyectos con GitHub

Existen muchas herramientas que ayudan a la gestión de proyectos de software.

Es común encontrar herramientas para el control del código fuente, reporte de horas, creación de tickets o tareas de trabajo, bugs, documentación, entre otras. Algunas más usadas, otras usadas solo por un grupo de personas dentro de un área productiva.

En todo caso, en el día a día, cuando creamos código necesitamos algunas que nos resuelvan problemas específicos, y que sin ellas, el trabajo sería difícil y lento; el control de código fuente, donde todos los desarrolladores puedan dejar su trabajo, se mantenga un historial de cambios, y que luego pueda ser usado para criterios como integración continua o verificación y validación; el control de requerimientos, bugs o características que conformen el proyecto, que nos dará visibilidad sobre el progreso del desarrollo, tareas pendientes, bugs nuevos o no resueltos, entre otros.

Por supuesto, y cómo comentaba al principio, existen cientos de herramientas para hacer esto, desde TFS, ClearCase, ClearQuest, SVN, Mercurial, SourceSafe y otros. Pero actualmente GitHub se ha transformado en un lugar común para el desarrollo, en muchos casos como repositorio de código, y por supuesto, como administrador de tareas para el proyecto.

Creando tareas

Desde el sitio Web de GitHub es posible crear diferentes “issues” que describan las tareas a realizarse para ese proyecto.

Un punto interesante en este modelo es que es posible definir una fecha esperada de cierre, la persona que se encargará de realizarla, si pertenece a un grupo de tareas más grande y que tipo de tarea es (Bug, mejora, o una que definamos por nuestra cuenta).

image GitHub nos muestra la consola de issues para el proyecto “101 Javascript Samples

Al crear una nueva tarea, es posible agregarle una descripción, imágenes y demás características que comentábamos antes.

image Un nuevo issue asignado para ser trabajado.

Una vez creado, cada issue obtendrá un número. Este número será de vital importancia a la hora de trabajar con el mismo.

image Un nuevo issue es creado y se le asigna el número 1.

Cerrando tareas

Con la lista de tareas creadas y definidas, solo necesitaremos realizar el código indicado y publicarlo.

Solo a modo de ejemplo, escribimos el siguiente código:

<script>
//Having the following object
//iterate it and execute all the functions
var o = {
    sum: function () {
        //Do math
    },
    analyze: function () {
        //Do task
    },
    task_1: function () {
        //Do task
    }}; 

//Your code here
for (var p in o) {
    console.log(p);
}</script>

El código anterior muestra cómo podemos interactuar con un objeto en Javascript tomando el nombre de sus propiedades y escribiéndolas en la consola.

Con los cambios en su sitio, solo necesitaremos publicarlos y cerrar el issue. Por supuesto, esto sería una tarea que nos consuma tiempo si por cada uno de los cambios necesitáramos ingresar al sitio Web de GitHub a marcar que tareas hemos o no cerrado. Por lo que necesitamos una forma más óptima.

image Publicamos y cerramos el issue en un solo paso.

Al momento de publicar, es posible usar los mismos comentarios para indicar que tareas estamos cerrando junto con el código.

GitHub admite una lista de palabras que, acompañadas con el número de issue, sirven para cerrar el mismo.

Por ejemplo, podríamos decir: “This resolves #200. File not found for new request”. O: “Fix #10, Fix #11 and Fix #12

En todos los casos, GitHub entenderá que queremos, con este código, declarar cerrados los issues 200, 10, 11 y 12.

La lista de palabras disponibles es la siguiente: Fix, fixes, fixed, close, closes, closed, resolve, resolves, resolved. Todas acompañadas del número de issue.

Además de ser una forma rápida de poder cerrar las tareas, una ventaja adicional es que el código quedará asociado a dicha tarea, por lo que si necesitáramos ver exactamente que código dio solución a que tarea, podremos encontrar esa información de forma muy simple.

image El código queda asociado a la tarea.

En resumen, GitHub puede resultar una herramienta que solucione, no solo, problemas con el manejo del código fuente, si no, con el conocimiento general del estado de las tareas del proyecto.

Desarrollo de videojuegos con HTML5 – Parte 8

En esta octava entrega veremos cómo interactuar con el mouse para poder mover nuestro personaje dentro del juego y dejaremos para la próxima entrega la interacción por medio del teclado.

Algo que debemos tener en cuenta es que, a diferencia de otros lenguajes o plataformas para desarrollador videojuegos, aquí estamos restringidos de forma directa por las políticas de seguridad del navegador que interpreta nuestro código. Por tal motivo, si el navegador no permitiera el uso del teclado, el mouse, o cualquier otro periférico, nosotros como desarrolladores no podríamos forzar el uso de los mismos. Diferente situación para el desarrollo nativo, ya que estos lenguajes poseen pleno acceso al dispositivo en el que se ejecutan. Con esto queremos decir que si conectáramos un joystick o algún otro dispositivo, salvo que contemos con un plugin que lea este dispositivo, no podríamos acceder al mismo de forma nativa por medio del código JavaScript.

Por otro lado, es importante entender que tanto el manejo del teclado como el manejo del mouse no son diferentes, en líneas de código, a las líneas de código que pudiéramos hacer para detectar los mismos en un front-end Web.

El mouse

El primer paso es registrar el evento para capturar las diferentes acciones del mouse. En nuestro caso, el evento Click, sobre el lienzo de dibujo. Debido a que el juego y sus acciones se realizarán sobre este, entonces será necesario capturar las acciones del mouse sobre el mismo.

canvas.addEventListener("click", mouseClick, false);

La función mouseClick se disparará cada vez que se realice un click sobre el lienzo y junto con esta llamada, una variable con las coordenadas representando el lugar donde se realizó el click.

function mouseClick(e) {
	var tx;
	var ty;

	if (e.pageX || e.pageY) {
	  tx = e.pageX;
	  ty = e.pageY;
	} else {
	  tx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
	  ty = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
	} 

	tx -= canvas.offsetLeft;
	ty -= canvas.offsetTop;

	destX = tx;
	destY = ty;
}

Las funciones pueden ser más o menos complejas, dependiendo si nuestro lienzo se encuentra dentro de otros contenedores o si posee algún estilo para modificar su posición dentro de la página Web, de cualquier manera, estos ajustes pueden ser realizados a medida que experimentemos con el funcionamiento del juego.

En el código anterior, las variables destX y destY son variables globales que contendrán las coordenadas del click del mouse, luego usaremos estas variables para interactuar con el personaje.

El personaje

Teniendo las coordenadas del click del mouse, y siguiendo con el código que hemos creado en post anteriores, usaremos estas coordenadas para que Yoda (Nuestro personaje), se mueva hacia ellas, y ya no en línea recta hasta golpear contra la caja negra. De cualquier manera, seguiremos detectando el contacto con la caja para detener su recorrido.

boxYoda.startupRectangle(x, y, 42, 39);
if (!boxYoda.intersects(boxBlock)) {
	if (x !== destX) {
		x += x < destX ? 1 : -1;
	}

	if (y !== destY) {
		y += y < destY ? 1 : -1;
	}
}

Dentro del bucle principal, en vez de simplemente incrementar la posición en el eje X, ahora realizamos un pequeño cálculo para determinar hacia donde deberá moverse el personaje. De igual forma, si el rectángulo del personaje choca con el de la caja, este cálculo se detiene y por ende, el personaje dejará de moverse.

La última modificación está en la línea que dibuja el personaje, ya que ahora deberemos incluír el eje Y.

contextBuffer.drawImage(yoda,
	walkFrame * 42, 1, 42, 39,
	x, y, 42, 39);

Como hemos podido ver, interactuar con el mouse no resulta nada complejo, solo es necesario contar con una buena rutina para detectar el punto exacto donde el mouse es presionado.

Insertar valores explícitamente en una columna IDENTITY

Alguna vez puede que nos encontraremos con la situación de querer migrar datos de una base de datos a otra. Y Cuando sucede es muy probable que columnas con la propiedad IDENTITY nos traigan un dolor de cabeza ya que los valores auto generados en el origen pueden diferir de los datos en la base destino.

¿Entonces como hacemos para insertar nuestros nuevos valores en el destino? Pues bien, una forma de resolver esto es “apagando” la restricción impuesta por el IDENTITY. Cosa que en realidad es muy sencillo.
Supongamos por un momento que disponemos una tabla “Ciudad” cuyos campos son “Id” y “Nombre”. Donde “Id” es la Primary Key definida con la propiedad IDENTITY. Supongamos también que ya tenemos varias ciudades insertadas cuyo máximo id es 7 y ahora queremos insertar los siguientes datos:

INSERT INTO [Ciudad] (Id,Nombre)
VALUES (9,'Posadas')

INSERT INTO [Ciudad] (Id,Nombre)
VALUES (11,'Claromeco')

INSERT INTO [Ciudad] (Id,Nombre)
VALUES (13,'Bariloche')

Normalmente esto fallaría por varias razones (sin contar que no se puede insertar valores en forma explicita en la columna Id). Si nos fijamos queremos insertar ciudades cuyos id no son ni consecutivos con el ultimo que tenemos almacenado ni entre ellos mismos. Para poder salvar estos problema entonces haremos lo siguiente:

SET IDENTITY_INSERT Ciudad ON

INSERT INTO [Ciudad] (Id,Nombre)
VALUES (9,'Posadas')

INSERT INTO [Ciudad] (Id,Nombre)
VALUES (11,'Claromeco')

INSERT INTO [Ciudad] (Id,Nombre)
VALUES (13,'Bariloche')

SET IDENTITY_INSERT Ciudad OFF

La diferencia esta en las sentencias “SET IDENTITY_INSERT Ciudad ON” y en “SET IDENTITY_INSERT Ciudad OFF”. La primer sentencia se encarga de deshabilitar temporalmente la restricción de no poder insertar valores explícitamente en la tabla Ciudad. La segunda se encarga de volver a habilitar la inserción de valores autogenerados.

Luego de ejecutar la sentencia “SET IDENTITY_INSERT Ciudad OFF” el próximo valor de la columna Id corresponderá al siguiente del mayor insertado, en nuestro caso 14.

Advertencia

Si ya existe una tabla con esta propiedad establecida en ON y se ejecuta una instrucción SET IDENTITY_INSERT ON para otra tabla, SQL Server devuelve un mensaje de error que indica que SET IDENTITY_INSERT ya está establecido en ON y la tabla para la que se ha establecido.

 

Saludos!

Obtener un valor de una fila recién insertada

Es muy común trabajar con tablas en las bases de datos cuya Primary Key fueron creadas con la propiedad IDENTITY para que su valor sea autoincrementable. Entonces cuando hacemos inserts sobre dicha tabla, en principio, no tenemos forma de saber el valor de dicho campo.

Supongamos por un momento que tenemos una tabla “Ciudad” cuyos campos son “Id” y “Nombre”. Donde “Id” es la Primary Key definida con la propiedad IDENTITY. Supongamos ahora que necesitamos ejecutar una sentencia para insertar una “Ciudad” en la tabla y queremos saber con que Id se inserto.

Una forma sencilla de hacer esto, es la siguiente:

INSERT INTO Ciudad
OUTPUT  Inserted.Id
SELECT 'Nombre'

Donde Inserted es una tabla temporal especial que Sql Server administra y nos permite ver los valores de todas las columnas que acabamos de insertar.

Saludos!

Cuando un padre y su hijo no logran comunicarse

Buenas!

Antes que nada, voy a hacer una aclaración necesaria: no! no es un artículo de relaciones familiares, ni de psicología ni mucho menos… Me refiero a la problemática de lograr que un iframe pueda enviar y recibir mensajes de la ventana padre que lo contiene (incluso pudiendo ser de dominios distintos).

Cuándo a un desprevenido del tema como yo le toca lidiar con esto, algo que suena fácil y que parece no tener más complicaciones que incluir el correspondiente javascript que haga referencia al elemento hijo desde el padre o al padre desde el hijo, según sea el caso, termina siendo un dolor de cabeza. Entonces uno empieza a investigar y se encuentra con varios conceptos que hasta el momento ignoraba, como CORS (Cross-Origin Resource Sharing), same origin policy, etc.. Y lo siguiente que uno piensa es: “ok, es lógico que esto funcione así, sería muy inseguro que, por ejemplo, yo pudiera hacer una página con un iframe a gmail.com y que cuando un usuario ingrese sus datos yo pudiera escuchar en mi ventanita el evento keypress y robarme sus datos!”

No voy a desarrollar los conceptos que mencioné porque hay gente que ya lo hace mucho más claro. Acá dejo algunos links que a mi me sirvieron como para empezar a “empaparme” del tema:
http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
http://en.wikipedia.org/wiki/Same_origin_policy
http://softwareas.com/cross-domain-communication-with-iframes

Lo bueno es que hay algunos mecanismos para relajar estas políticas de seguridad, empezando por el caso más simple en que el dominio/origen es el mismo hasta llegar al caso complejo en que son dos scripts de distintas ventanas y de distintos dominios.

Yo necesitaba algo que me salve para el caso complejo. Para ser más específico, necesitaba que mi iframe pueda avisarle a la ventana que lo contiene sobre cierto evento. Y la respuesta era PostMessage. Esta herramienta provee una forma de enviar mensajes de manera muy simple y también segura. Acá va el código:

Receptor:

var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";

eventer(messageEvent, function (e) {
    alert('Recibí el mensaje ' + e.data);
}, false);

El código para manejar el evento considera el distinto mecanismo del que hace uso el IExplorer.

Emisor:

parent.postMessage(un_mensaje_cualquiera, la_url_del_emisor);

Esta es la versión más simple y reducida. Se puede, por ejemplo:
- Consultar el origen (window o iframe) del emisor del mensaje mediante e.source
- Consultar la URL del emisor del mensaje mediante e.origin (y filtrar si es de un desconocido)
- Consultar el mensaje en sí con e.data

Quedando algo así:

window.addEventListener('message', function(event) {
if(event.origin !== 'http://blogs.kinetica-solutions.com') return;
    console.log('message received: ' + event.data, event);
    event.source.postMessage('Aca te respondo!', event.origin);
},false);

También se puede agregar en el emisor el mismo manejo de eventos que el receptor y permitirle así recibir mensajes de respuesta, tal como el que está mandando el del ejemplo de arriba.

Todo era risas hasta que la letra chica decía que esto funciona en HTML5… Y yo estaba trabajando con un navegador precario que no soportaba esta funcionalidad!

Lo que me terminó salvando es un hack muy piola: cuando cambias el fragmento identificador de una URL (que es el que viene despues del “#”), la página no se recarga y si combinamos esto con qué el location del parent es una propiedad a la que sí puede acceder el iframe hijo entonces… Bingo! Tenemos un lugar donde podemos dejar mensajes.

La contra de ésto es que vamos a necesitar un mecanismo para consultar periódicamente si hay un nuevo mensaje (el conocido setInterval es el candidato número 1) lo cuál representa un costo en performance, pero es un costo a pagar más que aceptable si el navegador no te da una mano.

Espero que les haya parecido interesante. Mi intención no era explicar todo el marco teórico sino simplemente contarles las soluciones prácticas que manejé ante un problema concreto y que si les pica la curiosidad puedan arrancar a investigar desde donde yo lo hice.

Saludos!

 

 

 

HTML, Razor y JS

Quizás conozcan Razor y ya sean fanáticos de los @variable, @foreach, @if, etc. A continuación algunos tips para no volvernos locos.

Este es el caso más simple:

@if (lista.Count == 0)
{
	<div>No se encontraron resultados</div>
}

Pero si quisiéramos hacer algo así:

@if (lista.Count == 0)
{
	No se encontraron resultados
}

Nos encontraríamos con un problema. Razor trata de seguir interpretando el texto como código C# y nos lanza un error de compilación.

En realidad la solución es bastante simple, usar el tag “falso” text:

@if (lista.Count == 0)
{
	<text>No se encontraron resultados</text>
}

De esta forma Razor interpreta esa parte como HTML en lugar de C#.

Este caso es bastante sencillo pero se complica un poco más cuando uno se encuentra generando código JS con código C#. Hay que tener este concepto bien claro para entender un código como el que sigue:

<script>
var arrayDeJs = [];
@foreach (var resultado in Model)
{
	<text>arrayDeJs.push({ nombre: @resultado.Nombre });</text>
}
</script>

¡Saludos!

Un poco de TDD en aplicaciones Windows 8 Modern UI

En una aplicación que estamos haciendo, necesitamos clasificar un texto según 3 tipos de promociones: %, cuotas u otro. En este caso usamos qunit como framework de test unitario para javascript.

Estos son los test:

https://gist.github.com/ferclaverino/5594014#file-test-js

Y el código que pasa los test:

https://gist.github.com/ferclaverino/5594014#file-benefit-js

Para mantener el código organizado (ya que en las aplicaciones Windows 8 suele haber mucho código javascript), necesitamos definir un namespace usando WinJS:

WinJS.Namespace.define("Benefit", {
  parseBenefit: parseBenefit
});

Gracias a la magia de javascript, podemos crear en los test un arnés para definir un namespace fuera de una aplicación windows 8 y ejecutar los test en una aplicación html + javascript estándar.

var Benefit;
var WinJS = {};
WinJS.Namespace = {};
WinJS.Namespace.define = function(name, object) {
  if (name == "Benefit") {
    Benefit = object;
  }
}

De esta forma, y separado las responsabilidades, podemos fácilmente hacer TDD en aplicaciones Windows 8 Modern UI.

Cómo renderizar imágenes obteniéndolas de BD y cachearlas a nivel navegador con MVC3?

Muy buenas a todos!

Este post se podría dividir en dos partes: la primera sería, tal como el título indica, cómo mostrar en una vista una imagen que no se encuentra alojada en el servidor como archivo de formato jpg/png/etc. sino que está almacenada en una base de datos como array de bytes. Lo anterior, si bien no deja de ser “piola”, es algo dentro de todo sencillo. La segunda parte es una vuelta de tuerca más de la primera: “okey, ya puedo ver la imagen… cómo hago para que el navegador no la trafique cada vez que recargo la página?”.

1) Partimos de código HTML en el cual aparece una imagen:

<img src="../content/img/categories/5.png" alt="Una foto">

2) Editamos el archivo Global.asax.cs. Lo que vamos a hacer es agregarle una ruta para que justamente rutee las direcciones del tipo “content/img/categories” a una acción de un controlador nuestro:

public static void RegisterRoutes(RouteCollection routes)
{
   routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

   routes.MapRoute(
      "Default", // Route name
      "{controller}/{action}/{id}", // URL with parameters
      new { controller = "Account",
            action = "LogOn",
            id = UrlParameter.Optional });

   //Ruta que agrego
   routes.MapRoute(
      "ImagenCategory",
      "content/img/Categories/{filename}",
      new { controller = "Image",
            action = "LoadCategory" });
}

Notar que pusimos un parámetro en la ruta llamado “filename”. Este parámetro va a ser el id de la imagen en la base de datos.

3) También agregamos nuestro controlador (llamado “ImageController”) y nuestra acción (“LoadCategory”), que son estos:

namespace Ejemplo
{
    using System.IO;
    using System.Web.Mvc;

    public class ImageController : Controller
    {
        public ActionResult LoadCategory(string filename)
        {
            //El catálogo me devuelve un objeto que tiene dos
            //atributos: el byte[] de la imagen y la fecha de modificación
            ImageAssociation image = new CategoryRepository().GetImage(filename);

            return this.File(new MemoryStream(image.Image), "image/jpg");
        }
     }
}

Es importante como devolvemos el resultado!

Entonces, a partir de ahora, cada vez  que aparezca una referencia con ese formato en el html, se va a invocar a esta acción en vez de buscar la imagen en el path virtual del servidor. Y con esto cubrimos la primera parte!

Ahora vamos a enfocarnos en el manejo del cacheo de la imagen…

Si vamos a Fiddler o a la solapa red del Firebug, tal como estamos hasta el momento y cada vez que recarguemos la página, vamos a ver que la llamada correspondiente a la imagen devuelve un código 200 y que trafica preciosos KB. Lo que queremos es que si la imagen ya la descargamos previamente y no se modificó devuelva un código 304 y la obtenga de la caché del navegador sin traficar nada más.

Para esto vamos a modificar la acción de esta manera:

public ActionResult LoadCategory(string filename)
{
   ImageAssociation image = new CategoryRepository().GetImage(filename);

   var headerValue = Request.Headers["If-Modified-Since"];
   if (headerValue != null)
   {
      var modifiedSince = DateTime.Parse(headerValue).ToUniversalTime();
      if (Math.Abs(modifiedSince.Subtract(image.Date).TotalMinutes) < 1)
      {
         Response.SuppressContent = true;
         Response.StatusCode = 304;
         Response.StatusDescription = "Not Modified";
         Response.AddHeader("Content-Length", "0");

         return new EmptyResult();
      }
   }

   Response.AddHeader("Last-Modified", image.Date.ToString("R"));

   return File(new MemoryStream(image.Image), "image/png");
}

Las claves de esto son los headers “If-Modified-Since” y “Last-Modified” que nos permiten comparar si la versión de la imagen cacheada en el navegador cambió o no (para esto es que teníamos la clase ImageAssociation con el byte[] y la fecha de modificación) respecto a la que está en base de datos. Si bien estamos yendo a la base para obtener la imagen y su fecha de modificación (mismo se podría separar estos datos para que la consulta sea más liviana), esto es una operación mucho menos costosa -despreciable- en tiempo que traficar la imagen por la red.
Finalmente, si la imagen no cambió devolvemos un EmptyResult con el código 304 y conseguimos lo que buscábamos!

Espero que les sirva!
Saludos