ASP.NET MVC: Previsualizar imágenes subidas (1)

Buenas! Una pregunta que últimamente parece que se pregunta varias veces en los foros de ASP.NET MVC es como previsualizar una imagen que se quiere subir al servidor.

Antes que nada aclarar que, técnicamente, la pregunta está mal hecha: no es posible previsualizar la imagen antes de que sea subida. Antiguamente en algunos navegadores, y con un poco de javascript, eso era posible, pero ahora por suerte eso ya no funciona 🙂

Básicamente previsualizar una imagen consiste en:

  1. Recibir los datos de la imagen
  2. Guardarla en algún sitio “temporal”
  3. Mandarla de vuelta al navegador
  4. Borrar la imagen del sitio “temporal” si el usuario no la acepta

En este primer post vamos a ver como hacerlo sin usar Ajax. Luego habrá un segundo post y veremos como hacerlo usando Ajax (no es tan sencillo porque XMLHttpRequest no soporta el formato de datos multipart/form-data que es el que se usa cuando se mandan ficheros).

Bien, vamos a verlo rápidamente, ya veréis cuan sencillo es 🙂

Lo primero es tener la vista que tenga el formulario para enviar los datos. La siguiente servirá (si queréis más detalles de como hacer upload de ficheros en MVC mirad mi post al respecto):

<h2>Index</h2>

<form enctype="multipart/form-data" method="post" action="@Url.Action("SendImage")">
<label for="img">Seleccionar imagen:</label>
<input type="file" name="img" /> <br />
<input type="submit" />
</form>

Trivial: un form con multipart/form-data que enviará sus datos a la acción SendImage. La acción podría ser algo como:

[HttpPost]
public ActionResult SendImage(HttpPostedFileBase img)
{
var data = new byte[img.ContentLength];
img.InputStream.Read(data, 0, img.ContentLength);
var path = ControllerContext.HttpContext.Server.MapPath("/");
var filename = Path.Combine(path, Path.GetFileName(img.FileName));
System.IO.File.WriteAllBytes(Path.Combine(path, filename), data);
ViewBag.ImageUploaded = filename;
return View("Index");
}

Vale, fijaos que recibimos el HttpPostedFileBase (llamado img como el atributo name del input type=”file”). Lo que hacemos en la acción es muy simple:

  1. Guardamos la imagen en disco (en este caso por pura pereza uso el directorio raíz de la aplicación web. Por supuesto lo suyo es usar un directorio configurado en web.config y con los permisos NTFS correspondientes).
  2. Coloco en el ViewBag el nombre de la imagen que se ha guardado (luego vemos porque).
  3. Devuelvo la vista “Index” (la misma de la cual venimos)

Ok, tal y como lo tenemos ahora, si lo probáis veréis que podéis hacer un upload de la imagen, y la imagen se graba en el disco (en el directorio raíz de la aplicación web), pero no se previsualiza nada. Vamos a modificar la vista Index para que haya esa previsualización.

Para previsualizar la imagen basta con añadir el siguiente código, despues del </form> en la vista:

@if (!string.IsNullOrEmpty(ViewBag.ImageUploaded))
{
<img src="@Url.Action("Preview", new {file=ViewBag.ImageUploaded})" />
}

Simple, no? Si en el ViewBag existe la entrada ImageUploaded generamos un tag <img> cuya dirección es la acción “Preview” y le pasamos el parámetro file con el nombre de la imagen. Y como es la acción Preview? Pues super sencilla:

public ActionResult Preview(string file)
{
var path = ControllerContext.HttpContext.Server.MapPath("/");
if (System.IO.File.Exists(Path.Combine(path, file)))
{
return File(Path.Combine(path, file), "image/jpeg");
}
return new HttpNotFoundResult();
}

Simplemente leemos la imagen guardada y la devolvemos usando un FilePathResult. Simple, eh? 😉

Con eso, si subís una imagen, cuando le deis a submit, aparecerá de nuevo la vista, pero ahora previsualizando la imagen.

Ahora sólo nos queda el punto final: Que el usuario pueda aceptar esa imagen como correcta. Para ello lo más simple es hacer lo siguiente:

  1. Si el usuario está previsualizando una imagen y NO ha seleccionado otra, cuando hace el submit se entiende que acepta dicha imagen.
  2. Si el usuario NO está previsualizando una imagen, o bien está previsualizando una pero selecciona otra, al hacer el submit se entiende que descarta la imagen anterior y quiere previsualizar la nueva.

Para ello, tenemos que hacer que la vista le indique al controlador si se está previsualizando una imagen, y cual és. Por suerte eso es muy sencillo. Basta con modificar el <form> de la vista para que su atributo action quede como:

<form enctype="multipart/form-data" method="post" 
action="@Url.Action("SendImage", new {previewed=ViewBag.ImageUploaded})">

Fijaos que añadimos un parámetro previewed que la vista mandará a la acción y que será el nombre de la imagen que se está previsualizando.

Vamos a modificar la acción para que reciba ese parámetro y actúe en consecuencia:

[HttpPost]
public ActionResult SendImage(HttpPostedFileBase img, string previewed)
{
if (!string.IsNullOrEmpty(previewed) && img == null)
{
ViewBag.ImageName = previewed;
return View("Step2");
}

// Código tal cual estaba
}

Añadimos ese if al inicio: Si el usuario está previsualizando una imagen y no ha seleccionado otra, entendemos que acepta esta imagen. Entonces guardamos el nombre en el ViewBag y lo mandamos a la siguiente vista (Step2).

La vista es muy sencilla:

<h2>Step2</h2>

Siguiente paso. El usuario ha aceptado la imagen: <br />

<img src="@Url.Action("Preview", new {file=ViewBag.ImageName})" />

Simplemente mostramos la imagen. Fijaos que de nuevo usamos la acción Preview, puesto que la imagen ya la tenemos guardada en el servidor.

Notas finales

Faltarían, al menos, dos cosas para que dicho proyecto funcionase “de forma aceptable”:

  1. Borrar las imagenes descartadas por el usuario (en la acción SendImage si se recibe una imagen nueva y se estaba previsualizando una, borrar esta ya que el usuario la ha descartado).
  2. La acción  Preview siempre devuelve content-type a image/jpeg, eso debería hacerse según la extensión de la imagen, o guardarlo en algún sitio.

Ambas cosas son triviales y no las añado porque lo único que consiguiría es liar el código un poco más 😉

En el próximo post… lo mismo pero usando Ajax.

Saludos!

Rendering de vistas parciales en Razor y MVC3

Buenas! Una de las dudas que he visto que se van repitiendo por ahí tiene que ver con como renderizar vistas parciales en MVC3 usando Razor.

En MVC2 y anteriores (o en MVC3 usando el ViewEngine de WebForms) la forma de renderizar una vista parcial era sencilla:

<% Html.RenderPartial("VistaParcial", modelo); %>

Mucha gente traduce eso a Razor y usa lo siguiente para renderizar una vista parcial:

@Html.RenderPartial("VistaParcial")

Y se obtiene un error, quizá un poco críptico, que dice lo siguiente: CS1502: The best overloaded method match for ‘System.Web.WebPages.WebPageExecutingBase.Write(System.Web.WebPages.HelperResult)’ has some invalid arguments

El error no está en que Html.RenderPartial no pueda usarse con Razor, el error está en la sintaxis que estamos usando. Cuando en Razor usamos la @ para indicar el inicio de código de servidor, la expresión que viene a continuación debe devolver un valor, que será incluído en la respuesta a enviar al navegador. La excepción a esa norma es cuando lo que sigue a la @ es una palabra clave reservada de Razor (como @model) o una palabra clave reservada del lenguaje que estemos usando (como @foreach). Esos casos especiales Razor los sabe tratar y actúa en consecuencia. Pero en el resto de casos siempre, siempre, siempre la expresión debe devolver un valor.

Hablando en términos del engine de WebForms, el código Razor:

@Html.RenderPartial("VistaParcial", modelo)

Se corresponde a:

<%: Html.RenderPartial("VistaParcial",modelo) %>

Que es erróneo (y da el error CS1502: The best overloaded method match for ‘System.Web.HttpUtility.HtmlEncode(string)’ has some invalid arguments).

Entonces… como usar Html.RenderPartial en Razor? Fácil: usando llaves para indicarle al motor de Razor que eso es un código que debe ejecutar, en lugar de un valor que debe incrustar en la respuesta:

@{ Html.RenderPartial("VistaParcial"); }

Así pues: Html.RenderPartial puede usarse en Razor sin ningún problema… como el resto de Helpers que conozcáis. Si el método lo usábais con <% … %> en Razor es @{ … }, mientras que si usábais <%: … %> en Razor es simplemente @…

Otras maneras de incrustar vistas parciales

De todas formas hay un par de métodos más para incrustar vistas parciales.

El primer método es Html.Partial() un método de extensión adicional. Para llamarlo se usan los mismos parámetros que Html.RenderPartial. La diferencia es que Html.Partial devuelve una IHtmlString con los contenidos de la vista renderizada. Por lo tanto, para incrustar una vista usando Html.Partial() usamos:

// Razor
@Html.Partial("VistaParcial")
// Webforms viewengine
<%: Html.Partial("VistaParcial") %>

El segundo método es propio de Razor, ya que está definido dentro del framework que se conoce como “WebPages” y es usar el método RenderPage, definido en la clase WebPageBase de la cual heredan las vistas Razor.

Dicho método acepta dos parámetros:

  1. La localización de la vista. Ojo! No el nombre, sinó su localización (incluyendo directorios)
  2. Parámetros a pasar a la vista (params object[]).

P.ej. para renderizar la vista parcial usaríamos:

@RenderPage("~/Views/Home/VistaParcial.cshtml")

Fijaos en que se debe usar el nombre del archivo de la vista a incluir (incluyendo extensión .cshtml).

Si se pasan parámetros a la vista parcial, estos no están disponibles usando la propiedad Model en la vista, sinó que debe usarse la propiedad PageData. P.ej. podríamos pasar una cadena y un entero a la vista:

@RenderPage("~/Views/Home/VistaParcial.cshtml", "Parametro 1", 10)

Y mostrarlos desde la vista con el uso de PageData:

@foreach (var item in PageData)
{
<div>@item.Value</div>
}

Mi opinión sobre el método RenderPage (en MVC): Sobra totalmente, y espero que nunca, nunca, nunca lo uséis. Porque? Pues porque RenderPage rompe la encapsulación del framework (en lugar de especificar un nombre de vista debéis especificar un fichero). Es evidente que existe para dar soporte a WebPages pero WebPages y MVC se parecen sólo porque usan Razor como sintaxis, pero en concepción son dos cosas totalmente distintas… Aunque por razones (supongo que técnicas) Razor depende de WebPages y esa dependencia se arrastra a MVC3, cosa que personalmente no me gusta demasiado. Pero es lo hay… 😉

Conclusiones

  1. Html.RenderPartial funciona correctamente en Razor, al igual que el resto de métodos de siempre. Sólo debemos tener cuidado en usar la sintaxis correcta.
  2. Html.Partial es un método adicional para renderizar vistas parciales. La diferencia con Html.RenderPartial() es que este último escribe en la response directamente el contenido de la vista, mientras que Partial() lo devuelve dentro de una cadena. No tengo claras las implicaciones de rendimiento que puede tener empezar a crear mutltiud de cadenas que serán eliminadas por el GC casi de inmediato.
  3. RenderPage es el método de WebPages para renderizar vistas parciales. Desde el punto de vista de MVC es un método que sobra totalmente y que rompe la encapsulación del framework.

Espero que os sea útil!

Un saludo!

De los requerimientos…

Mi hermano y yo ambos somos arquitectos. Aunque en su caso él tiene un título universitario que lo acredita como tal y yo sólo una tarjeta de trabajo donde mi empresa ha decidido poner eso… y en inglés que se supone que queda mejor. Los dos nos dedicamos a pensar y diseñar cosas: él piensa y diseña espacios habitables (o sea pisos y casas) y yo pienso y diseño soluciones informáticas (o sea programas).

Ayer mi hermano (que no sólo es arquitecto, sinó que regenta una empresa de obras) me vino riéndose por una petición de presupuesto en broma que había recibido. El correo electrónico decía, más o menos, así…

Apreciado hermano de eiximenis,

Nos dirigimos a Ud. para que nos haga un presupuesto para la construcción de una nueva mansión para un concejal cualquiera (a partir de ahora “el cliente”) que, con un golpe de fortuna, ha ganado unos dinerillos que gastar. A continuación le detallamos como debe la mansión:

  1. La mansión contará con el número de habitaciones que el cliente estime cómo necesarias para el desarrollo de sus funciones de descanso y/o copulación. Además el cliente debe poder realizar otras funciones (a especificar en algún momento del futuro) sin menoscabo de la comodidad mínima exigida.
  2. Los materiales de construcción deben tener el nivel de calidad requerido por el cliente, nivel que por supuesto no aparece en ningún estándard y que sólo el cliente conoce y que puede variar en función de su estado de ánimo.
  3. Deberán integrarse, adaptarse o construirse distintos módulos de diversión, para aquellas actividades de ocio que el cliente considere necesarias. No es necesario especificar que las necesidades de ocio del cliente pueden variar en cualquier momento durante el periodo de tres años que se exige como mantenimiento.
  4. El número de baños deberá ser el justo y necesario para que pueda usarlos el cliente, así como los invitados a sus fiestas. El número de invitados no ha sido proporcionado por el cliente.
  5. Se valorará positivamente que los garajes estén listos al cabo de dos semanas y más positivamente si lo están al cabo de una.
  6. El cliente ha comprado un módulo de domótica que deberá ser instalado y configurado. El modelo es desconocido al igual que el fabricante.
  7. Existe un periodo de tres años de garantía, donde el cliente puede solicitar cambios en los materiales, disposición de los módulos, módulos de ocio nuevos, etc, etc

Dado que el cliente es un concejal debe asignar la obra de forma totalmente legal y por ello se deberán entregar tres sobres:

  1. El primero con la propuesta económica final. Dicha propuesta contendrá el precio exacto de la obra, incluyendo materiales y mano de obra y mantenimiento durante los 3 años. El mantenimiento implica adaptar la mansión a las nuevas necesidades que le puedan surgir al cliente. Hay total libertad de precio, siempre que no pase de 500000€
  2. El segundo con el calendario exacto de las obras: cuando estará el garaje, las distintas habitaciones, la fachada principal, los módulos de ocio, etc. El inclumpimiento del calendario acarreará sanciones económicas a determinar.
  3. El tercero con los detalles técnicos, planos de obra, materiales usados, etc

La valoración por concurso será tal y como sigue:

  1. Valoración económica: Un 50% (De 0 a 5 puntos)
  2. Calendario: Un 40% (de 0 a 4 puntos)
  3. Detalles técnicos: Un 10% (de 0 a 1 punto)

Se pide envío de los tres sobres antes de 2 días. El cliente no está disponible para contactar ya que está de vacaciones.

Mi hermano entre carcajadas y carcajada, me insistía en que le viera la gracia a la broma, que si “fíjate que aquí no concretan nada, que si eso es buenísimo porque ni dicen el número de las habitaciones, en que si tal y cual”…

… yo, la verdad es que sonreí un poco por compromiso mientras por dentro pensaba en las muchas veces que mi trabajo parece una broma… y de mal gusto.

Saludos!

[Material] WebCast ASP.NET MVC

Buenas.

Este post simplemente es para agradeceros a todos el interés que mostrasteis en el Webcast de ASP.NET MVC que tuve el placer de realizar para la gente del Lledia DotNetClub.

Antes que nada os dejo la página desde donde os podéis ver o descargaros el WebCast: https://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?culture=es-ES&EventID=1032476809&CountryCode=ES. En esa página veréis un enlace que pone “Regístrese sin cuenta de Windows Live ID” que es el que os llevará a la página de descarga.

Increíble, eh? Esta vez no la pifié con el livemeeting y quedó grabado! 🙂

Os dejo:

  1. El código fuente de Porrazo completo. Porrazo, el sistema de porras on-line, fue lo que más o menos fuimos desarrollando a lo largo de los dos webcasts. En el código veréis implementadas todas las técnicas de las que hablamos (con la excepción del helper WebGrid). Por supuesto que Porrazo es un conglomerado de técnicas de hacer las cosas: no significa que todas deban implementarse en una misma aplicación MVC. A vosotros os toca juzgar en cada caso cual es la mejor opción!
  2. Los dos ppts, aunqué no tienen apenas información, ya que son un mero índice.
  3. El script SQL para crear las tablas (sin datos… los datos deberéis ponerlos vosotros… :p).

Lo tenéis en: http://cid-6521c259e9b1bec6.office.live.com/browse.aspx/BurbujasNet/ZipsPosts/Porrazo

En breve me gustaría hacer un documento word (en esa misma carpeta) para que podáis realizar step-by-step Porrazo desde cero y podáis practicar las técnicas que vimos. Pero como ello me va a llevar cierto tiempo, prefiero dejaros los fuentes ya disponibles, y actualizar este post cuando tenga el documento realizado.

Si alquien quiere que le avise personalmente cuando tenga el documento, puede enviarme un mail (mi correo está en los ppts) o contactar conmigo a través del formulario de contacto del blog.

Muchas gracias!

Actualización 09/02: He dejado otro fichero .sql (Porrazo_tablas_con_datos.sql) en la carpeta de skydrive que crea las tablas (como el script Porrazo_tablas.sql) pero además añade los mismos datos que tengo yo en mi bbdd. De esa manera ya tendréis una bbdd lista para usar. Recordad que ninguno de los dos scripts crea la bbdd (Porrazo).

[WebCast] ¿Te quedaste con más ganas? ASP.NET MVC II

Muy buenas! Este jueves 03 de febrero (o sea mañana) gracias a la gente del Lleida dotnet club, tengo el gusto de dar un WebCast sobre ASP.NET MVC 🙂

La verdad es que el WebCast está pensado para ser la continuación del que dí el pasado 2 de diciembre. En aquel webcast vimos:

  1. Introducción a ASP.NET MVC
  2. Membership providers para autenticación y autorización
  3. Uso de Unity para desacoplar dependencias
  4. El patrón repositorio para acceder a la BBDD
  5. Uso de Ajax con Json
  6. Templates con jquery-tmpl
  7. Binding de datos en los controladores
  8. Guardando datos en la bbdd: Unit of Work

Todo ello lo vimos desarrollando una aplicación desde cero (al que mi departamento de marketing llamó Porrazo), y este nuevo webcast continuará donde se quedó aquel. Lamentablemente, debido a que soy un negado en eso del livemeeting el webcast no quedó grabado, así que al principio vamos a dar un rápido resumen a lo visto en aquel webcast, para que estemos todos en situación… Y luego proseguiremos con los puntos que se nos quedaron en el tintero:

  1. Uso de un Role Provider
  2. Añadiendo un módulo de administración (áreas)
  3. Componiendo presentación (uso de vistas parciales)
  4. Grids!! (Sí… esa es una feature que la gente pide mucho)
  5. Ventanas flotantes (popups)

Si me da tiempo (espero no volver a enrollarme) veremos algunos temas addicionales como:

  1. Validación de datos en cliente
  2. Unobtrusive javascript

Y al final una sesión de preguntas y respuestas (pero… no os paséis: que sean facilitas, eh? ;-))

Todo ello con ASP.NET MVC3, con VS2010 a tope (nada de power points!) y desarrollando Porrazo, el sistema de porras online que cuando esté finalizado colgaré aquí con la esperanza de que lo compre Google y me retire.

La página de registro esá en: https://msevents.microsoft.com/CUI/Register.aspx?culture=es-ES&EventID=1032476808&CountryCode=ES&IsRedirect=false

Un saludo a todos!

ASP.NET MVC3: Un helper Repeater

Muy buenas! En el post anterior comenté la característica de los templates de Razor y hoy vamos a ver como podríamos crear un helper que emule un poco el control Repeater que hay en webforms (salvando las distancias, claro).

Vamos a crear un helper externo, es decir que sea reutilizable en distintos proyectos: para ello nuestro helper va a residir en una clase (en mi ejemplo en el propio proyecto web, pero se podría situar en una librería de clases para ser reutilizable).

Esqueleto inicial

A nuestro helper se le pasará lo siguiente:

  1. Una cabecera: template Razor que se renderizará una sola vez al principio.
  2. Un cuerpo: template Razor que se renderizará una vez por cada elemento
  3. Un pie: template Razor que se renderizará una sola vez al final.
  4. Una colección de elementos (por cada elemento se renderizará el cuerpo).

Recordáis que ayer comentamos que los templates Razor son Func<T, HelperResult>?

Bien, pues vamos a declarar nuestro helper:

 

public static IHtmlString Repeat<TItem>(
IEnumerable<TItem> items,
Func<dynamic, HelperResult> header,
Func<TItem, HelperResult> body,
Func<dynamic, HelperResult> footer
)
{
var builder = new StringBuilder();
if (header != null)
{
builder.Append(header(null).ToHtmlString());
}

foreach (var item in items)
{
builder.Append(body(item).ToHtmlString());
}
if (footer != null)
{
builder.Append(footer(null).ToHtmlString());
}

return MvcHtmlString.Create(builder.ToString());
}

Los templates siempre están declarados como Func<T, HelperResult>, en este caso declaramos tres templates:

  1. header, cuyo parámetro @item es dynamic.
  2. body, cuyo parámetro @item es TItem, donde TItem es el tipo del enumerable que se le pasa al helper
  3. footer, cuyo parámetro @item es dynamic

Para invocar los templates y obtener el html asociado simplemente invocamos el Func (pasándole el parámetro del tipo correcto) y sobre el resultado llamamos a ToHtmlString: Este método nos devuelve la cadena HTML que ha parseado Razor.

Ahora p.ej. me creo una acción tal en el controlador:

public ActionResult List()
{
return View(new List<Product>
{
new Product() {Nombre="PS3", Precio=300},
new Product() {Nombre="XBox360", Precio=150},
new Product() {Nombre="Wii", Precio=100}
});
}

Y en la vista asociada:

@using MvcApplication14.Helpers
@model IEnumerable<MvcApplication14.Models.Product>

<table>
@Repeater.Repeat(Model,
@<thead><tr><td>Nombre</td><td>Precio</td></tr></thead>,
@<tr><td>@item.Nombre</td><td>@item.Precio</td></tr>,
null)
</table>

Fijaos en la llamada a Repeater.Repeat, donde le paso el modelo que recibe la vista (que es un IEnumerable de Product) y los tres templates Razor (en este caso no le paso footer y por ello pongo null).

En el segundo template (body) puedo acceder al elemento que se está renderizando mediante @item. Además, dado que en helper he declarado el template body de tipo Func<TItem, HelperResult>, tengo soporte de Intellisense:

image

Si teclease @item en el template header o footer no tendría intellisense porque los he declarado dynamic en el helper (además ojo, que en el helper le paso null, por lo que rebentaría si usamos @item en el template header o footer).

Y listos! El código HTML generado es:

<thead><tr><td>Nombre</td><td>Precio</td></tr></thead>
<tr><td>PS3</td><td>300</td></tr>
<tr><td>XBox360</td><td>150</td></tr>
<tr><td>Wii</td><td>100</td></tr>

Guay, no?

Podríamos “complicar” un poco el helper, para que desde los templates supiesemos si estamos en una fila par o impar y así aplicar clases… Para ello nos basta con declarar una clase adicional en nuestor helper:

public class RepeaterItem<TItem>
{
public int Index { get; private set; }
public bool IsEven { get { return Index % 2 == 0; } }
public TItem Item { get; private set; }

public RepeaterItem(TItem item, int index)
{
Index = index;
Item = item;
}
}

Esa clase simplemente contiene el índice del elemento actual, el propio elemento y una propiedad que indica si es par o no.

Ahora modificamos el helper para pasarle al template body un objeto RepeaterItem<TItem>:

public static class Repeater
{
public static IHtmlString Repeat<TItem>(
IEnumerable<TItem> items,
Func<dynamic, HelperResult> header,
Func<RepeaterItem<TItem>, HelperResult> body,
Func<dynamic, HelperResult> footer
)
{
var builder = new StringBuilder();
if (header != null)
{
builder.Append(header(null).ToHtmlString());
}

var count = 0;
foreach (var item in items)
{
var repeaterItem = new RepeaterItem<TItem>(item, count++);
builder.Append(body(repeaterItem).ToHtmlString());
}
if (footer != null)
{
builder.Append(footer(null).ToHtmlString());
}

return MvcHtmlString.Create(builder.ToString());
}
}

Los cambios básicamente son declarar el template domo Func<RepeaterItem<Titem>> y cuando invocamos el template, crear antes un objeto RepeaterItem y pasárselo como parámetro.

Finalmente ahora debemos modificar la vista, ya que el parámetro @item de nuestro template es ahora un RepeaterItem<TItem>:

@Repeater.Repeat(Model, 
@<thead><tr><td>Nombre</td><td>Precio</td></tr></thead>,
@<tr style="@(item.IsEven ? "background-color: white" : "background-color:pink")">
<td>@item.Item.Nombre</td><td>@item.Item.Precio</td></tr>,
null)

Fijaos como dentro del template body puedo preguntar si el elemento actual es par (@item.IsEven y acceder al propio elemento @item.Item). En este caso uso @item.IsEven para cambiar el color de fondo de la fila:

image

Y listos! Espero que esos dos posts sobre templates Razor os hayan parecido interesantes!

Un saludo a todos! 😉

ASP.NET MVC3: Razor Templates

Muy buenas!

En este post quiero comentaros una característica de Razor que yo considero que es una auténtica pasada: los templates.

Básicamente el meollo de todo está en la posibilidad de guardar el resultado de un parseo de Razor en un Func<T, HelperResult> siendo T el tipo del modelo que renderiza el template.

Veámoslo con código:

@{
Func<string, HelperResult> h =
@<h2>Esto es un template al que se le han pasado los datos: @item</h2>
;
}
<p>
Renderizamos el template: @h("datos")
</p>

Fijaos como nos guardamos en una Func<string, HelperResult> el resultado de renderizar un template razor (en este caso el template <h2>…</h2>). Fijaos en tres detalles:

  1. Dado que la variable h está declarada como Func<string, HelperResult> el tipo del modelo en este template es “string”
  2. Para acceder al modelo que se pasa al template se usa @item
  3. Al final del template Razor ponemos un ; (eso es una declaración C# y como tal debe terminar en punto y coma).

Luego más adelante renderizamos el template, con el código: @h("datos")

Eh!! Eso no es lo mismo que @helper?

Si conoces @helper te puede parecer que esto no es muy novedoso. Con @helper podemos crear helpers inline de la siguiente manera:

@helper h2(string s) {<h2>Esto es un helper: @s</h2>}

Y lo podíamos invocar con:

@h2("ufo")

Por lo que parece que viene a ser lo mismo. Y es que, en el fondo, ambas construcciones devuelven un HelperResult.

Nota: Si quieres más información sobre @helper, léete el post que hizo el maestro hace algún tiempecillo: http://www.variablenotfound.com/2010/11/y-mas-sobre-helpers-en-razor.html

Lo interesante no es que ambas construccione se parezcan, lo que quiero recalcar es que…

… Los templates son Func<T, HelperResult>!

Lo pongo así en negrita porque eso es importante, y a la vez me sirve de título. Si los templates Razor son Func<T, HelperResult>… cualquier método que reciba un Fun<T, HelperResult> puede recibir un template Razor

… Incluídos los helpers!

@helper Repeater(int count,Func<dynamic, HelperResult> template, string data) {
<ul>
@for (int idx=0; idx<count; idx++)
{
<li>@template(new { Text = data, Index = idx })</li>
}
</ul>
}

@Repeater(10, @<span>@item.Text (item: #@item.Index)</span>, "Ufo")

En este código:

  1. Declaramos un helper, llamado Repeater que acepta tres parámetros:
    1. Un entero
    2. Un Func<dynamic, HelperResult> que por lo tanto podrá ser un template Razor
    3. Una cadena
  2. El helper se limita a crear una lista y luego:
    1. Crea tantos <li> como indica el paràmetro count y en cada id
      1. Evalúa el segundo parámetro (que es el Func) y le pasa como parámetro un objeto anonimo con dos propiedades (Text e Index), donde Text es el tercer parámetro que recibe (y index el valor de la iteración actual).
  3. Cuando invocamos al helper, le pasamos como primer parámetro el número de repeticiones, como segundo parámetro un template de Razor. Fijaos que dentro de dicho template:
    1. Accedemos a las propiedades @item.Text y @item.Index. Eso podemos hacerlo porque hemos declarado el tipo del modelo de dicho template como dynamic y por eso nos compila (y nos funciona porque el helper cuando invoca el template crea esas propiedades en el objeto anónimo).

El código HTML generado por dicha llamada al helper Repeater es:

<ul>
<li> <span>Ufo (item: #0)</span></li>
<li> <span>Ufo (item: #1)</span></li>
<li> <span>Ufo (item: #2)</span></li>
<li> <span>Ufo (item: #3)</span></li>
<li> <span>Ufo (item: #4)</span></li>
<li> <span>Ufo (item: #5)</span></li>
<li> <span>Ufo (item: #6)</span></li>
<li> <span>Ufo (item: #7)</span></li>
<li> <span>Ufo (item: #8)</span></li>
<li> <span>Ufo (item: #9)</span></li>
</ul>

Espero que el post os haya resultado interesante!!! 😉

Saludos!

ASP.NET MVC: Como recuperar un dato de una cookie para cada petición… Una alternativa ¿igual?

Muy buenas! Hace algunos días escribí el post ASP.NET MVC: Como recuperar datos de una cookie en cada petición, donde mostraba el uso de un route handler propio para recuperar los datos de una cookie y colocarlos en el Route Data. En el ejemplo era una cookie de cultura de la aplicación, pero se puede aplicar a lo que queráis.

Lo que más me gusta de ASP.NET MVC es que muy expandible, que muchas cosas pueden hacerse de más de una forma. Pues bien, una de las novedades más interesantes de MVC3 (al margen de Razor) son los action filters globales.

En este post os propongo una solución alternativa (aunque ya veremos que tiene una ligerísima diferencia) al mismo problema. La diferencia es que no se debe alterar la tabla de rutas para nada. Y dicha solución pasa por usar un action filter global.

Una de las cosas que en MVC nos debe quedar claro es que cuando repitamos muchas veces un mismo código de un controlador debemos considerar de ponerlo en un Action Filter. El “problema” está que los action filters deben aplicarse controlador a controlador (o acción a acción). Si tenemos un filtro que debe aplicarse a todos los controladores podemos considerar crear una clase base que lo tenga y heredar todos los controladores de ella…

… o al menos eso era así antes de MVC3.

Con MVC3 y los filtros globales podemos aplicar un filtro a todas las acciones de todos los controladores. Y todo ello con una sola línea en global.asax. Es brutal!

El filtro global…

Lo bueno es que los filtros globales se implementan igual que los filtros no globales clásicos que teníamos en MVC2. En este caso la implementación es super sencilla:

public class CookieCultureFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var cultureCookieVal = GetCultureFromCookie(filterContext.HttpContext.Request.Cookies);
var culture = new CultureInfo(cultureCookieVal);
filterContext.RouteData.Values.Add("culture", cultureCookieVal);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}

private string GetCultureFromCookie(HttpCookieCollection cookies)
{
var retValue = "ca-ES";
if (cookies.AllKeys.Contains("userculture"))
{
retValue = cookies["userculture"].Value;
}
return retValue;
}
}

El código es trivial: derivo de ActionFilterAttribute (clase que ya existía) y redefino el método OnActionExecuting que se ejecuta antes de ejecutar la acción del controlador. En este método tengo el código para leer la cookie de cultura (exactamente lo mismo que tenía antes en el Route Handler).

Activar el filtro global

Os dije que era una sóla línea en global.asax, verdad? Pues concretamente esta:

GlobalFilters.Filters.Add(new CookieCultureFilterAttribute());

Otra opción es colocar esa línea dentro de la función RegisterGlobalFilters que crea el VS2010, aunque entonces no es necesario usar la clase GlobalFilters (usad en su lugar el parámetro filters):

filters.Add(new CookieCultureFilterAttribute());

Y ya está. Nada más. Antes de cada acción de cualquier controlador se ejecutará el código de nuestro filtro global. No es necesario modificar la tabla de rutas para añadir nuestro route handler.

¿Puedo usar el filtro en una aplicación MVC2?

Si, si que puedes, pero entonces debes:

  1. Aplicarlo en cada controlador (usando [CookieCultureFilter] antes de cada acción o cada controlador que quieras que use la cookie).
  2. Derivar todos tus controladores de un controlador base que tenga [CookieCultureFilter] aplicado.

Y lo más importante… ¿Ambas soluciones son equivalentes?

Pues NO. Ambas soluciones no son equivalentes… En el caso del post anterior, si recordáis, si declaraba un parámetro culture en una acción, recibía el valor de la cookie, ya que el route handler me añadía este parámetro en el RouteData. Pues bien, eso dejará de funcionar. Es decir, en este caso la acción:

public ActionResult Foo(string culture)
{
}

que en el post anterior recibía el valor de la cookie en culture, con esa nueva aproximación siempre recibirá null.

¿Y por que si mi filtro también añade en el RouteData el valor de la cookie? Pues muy sencillo: el Model Binder (que se encarga de hacer binding a los parámetros de las acciones) se ejecuta antes que el filtro. Simplificando, el flujo de ejecuciones sería:

  1. Route Handler
  2. Model Binder
  3. Action Filters
  4. Acción del controlador

En este caso, estamos añadiendo un valor en el RouteData después de que el Model Binder haya actuado. Por eso el parámetro no tendrá el valor. Eso no quita que desde el controlador lo podáis consultar (tenéis acceso al RouteData).

Y con esto termino… espero que el post os haya ayudado un poco más a entender como funciona MVC y ver distintas alternativas de cómo hacer las cosas!

Git para dummies (pero dummies, dummies eh?)

Disclaimer: Ese post ni es, ni lo pretende, ser un tutorial de Git. Es simplemente las impresiones de alguien (yo) que ayer empezó a usar, por primera vez, Git. Seguro que hay gente que lee ese blog y que sabe mucho, pero mucho más de Git que yo… Así que sus comentarios y correciones serán bienvenidas! 🙂

Esos días (ayer, vamos :p) he empezado a usar Git. Para quien no lo conozca Git es un sistema de control de código fuente distribuído. A diferencia de TFS, VSS o SVN que son centralizados, en Git cada desarrollador tiene su propia copia entera del repositorio en local. Los cambios son propagados entre repositorios locales y pueden (o no) sincronizarse con un repositorio central.

La diferencia fundamental con TFS ó VSS es que en esos está claro quien es el repositorio (el servidor). Eso no existe en Git. En Git cada usuario es el repositorio y se sincronizan cambios entre repositorios (usuarios). Opcionalmente puede usarse un repositorio central pero parece que no es obligatorio.

Los cambios en Git se propagan a través de esas operaciones:

  1. commit: Envia los datos del working directory al repositorio local
  2. push: Sincroniza los cambios del repositorio local a otro repositorio remoto.
  3. fetch: Sincroniza los cambios de un repositorio remoto al repositorio local.
  4. pull: Sincroniza los cambios de un repositorio remoto al working directory.
  5. checkout: Actualiza el workind directory con los datos del repositorio local.

La siguiente imagen lo clarifica bastante:Comandos de transporte de Git

Operaciones de Git (imagen original de Oliver Steele en http://www.gitready.com/beginner/2009/01/21/pushing-and-pulling.html).

Usar Git desde Visual Studio

Para usar Git desde VS2010 he encontrado las Git Extensions que instalan, no sólo un plugin para VS2010, sinó también el propio Git y clientes adicionales que pueden necesitarse (SSH o PuTTY si queremos conexiones seguras con nuestros repositorios).

Una vez instaladas nos aparecerá un menú nuevo llamado “Git” en el VS2010.

Crear un repositorio local y rellenarlo

Para empezar a trabajar con Git, lo primero es crear un repositorio. Recordad que los repositorios son locales, por lo que un repositorio es simplemente un directorio de nuestro disco. En mi caso, tenía ya una solución de VS2010 en local que es la que quería empezar a compartir con Git. Por ello lo que hice fue crear un nuevo repositorio local. Hay otra opción, que es crear un repositorio local a partir de los datos de un repositorio remoto (git-clone) pero todavía no lo he hecho. Si todo va como está planeado, el viernes tocará hacerlo, y ya escribiré al respecto!

Usando las Git Extensions crearnos nuestro propio repositorio es tan sencillo como usar la opción “Initialize new repository” y nos saldrá una ventana como la siguiente:

image

La opción normal es “Personal repository”. Un Central repository es un repositorio sin Working directory, que sólo sirve para sincronizar datos.

Una vez entrada la carpeta (en este caso D:Gittest) esa pasa a ser el directorio de trabajo (working directory) para ese repositorio. Si abrimos la carpeta con el explorador de windows veremos que hay una carpeta en su interior llamada .git: ese es el repositorio local.

Nota: Dado que el directorio donde inicializamos el repositorio local pasa a ser el directorio de tabajo, lo suyo es inicializar el repositorio local en el mismo directorio donde tenemos la solución de VS. Es decir, si la solución de VS la tenemos en el directorio C:projectssourcemyproject, ese sería el directorio que usariamos (recordad que el repositorio se crea en una subcarpeta llamada .git, por lo que no modificarà ni borrará nada de la carpeta que le digáis).

En mi caso en D:Gittest ya tenía una solución, por lo que el árbol de directorios me ha quedado:

image

Ahora vamos a hacer commit de la solución al repositorio local. Antes que nada, pero debemos tener presente de que no queremos que todos los archivos que cuelgan del working directory (D:gittest) se guarden en el repositorio local. Git no entiende de tipos de archivo, no sabe que es código fuente y que no. Existe un fichero en el working directory llamado .gitignore que sirve para indicar que ficheros no deben guardarse en el repositorio local al hacer un commit.

Por suerte editar este fichero con las Git Exensions, es trivial. Nos vamos al menú Git y seleccionamos “Edit .gitignore”. Nos aparecerá una ventana parecida a:

image

A la izquierda el contenido del .gitignore (en este caso vacío, normal ya que ni existe el fichero). A la derecha un ejemplo de .gitignore adaptado a VS. Si pulsais el botón “add default” os copiará las entradas de la derecha a la izquierda:

image

Fijaos que el .gitignore se ha confiugrado para evitar archivos como *.exe, *.pdb, etc, pero también directorios como TestResult* (que usa VS para guardar los resultados de las pruebas unitarias) o _Resharper* (que usa resharper para guardar sus configuraciones). Nosotros podríamos añadir más entradas y finalmente darle a “Save”. Eso nos creará el archivo .gitignore en nuestro working directory (D:gittest).

Ahora estamos listos para hacer el commit y rellenar por primera vez el repositorio local. Para ello, de nuevo nos vamos al menú Git y seleccionamos “Commit”. Nos aparecerá una ventana parecida a:

image

Se divide en cuatro secciones:

  1. Izquierda superior: listado de operaciones pendientes de realizar (cambios entre el working directory y el repositorio local)
  2. Izquierda inferior: listado de operaciones que se van a realizar (es decir cuales de los cambios del working directory queremos propagar al repositorio local).
  3. Derecha superior: preview del archivo seleccionado en la parte (1).
  4. Derecha inferior: comandos de Git.

En este caso vamos a propagar todos los cambios, para ello pulsamos el botón “Staged Files” y en el menú desplegable marcamos “Stage all”. Con eso todos los ficheros de la izquierda superior pasarán a la izquiera inferior. Ahora vamos a realizar el commit (si quisieramos podríamos hacer también un push a un repositorio remoto pero todavía no hemos configurado ninguno).  Así que entramos un mensaje de commit en la parte derecha inferior (p.ej. commit inicial) y le damos al botón “Commit”. Git Extensions nos mostrará al final un diálogo con el resumen de lo hecho:

image

Añadir cambios

Bien, una vez hemos hecho el commit podemos seguir trabajando con VS2010 sin necesidad de hacer nada especial. Olvidaros de conceptos como “proteger” o “desproteger” de TFS o VSS. Simplemente trabajáis y modificáis el contenido del directorio de trabajo.

Cuando decidáis propagar los cambios del directorio de trabajo al repositorio local, repetís la operación de antes: Git –> Commit.

Repositorio remoto

Bien, ha llegado la hora de configurar un repositorio remoto. Para ello, lo primero que necesitamos es tener acceso a un repositorio remoto. Hay varios proveedores de repositorios Git en internet, entre los que destaca GitHub. GitHub pero está limitado a proyectos open source. Si neceistáis un hosting de Git para proyectos no open source hay varios de pago (con algunas versiones gratuitas). En esta consulta de StackOverflow hablan de ello: Best git repository hosting for commercial project.

Nota: A título informativo os diré que yo me dí de alta en el plan gratuito de Assembla, que te da 2GB de espacio Git.

La configuración de un repositorio remoto dependerá de donde lo tengamos, pero básicamente son dos pasos muy simples:

  1. Generar el par clave pública-privada para ssh o PuTTY y subir la clave pública al repositorio (si queremos acceso seguro).
  2. Ponerle a Git Extensions la URL del repositorio remoto.

Vamos a ver una demostración. Para independizarnos de cualquier proveedor, vamos a crear otro repositorio de Git en nuestra máquina (podríamos hacerlo en una carpeta compartida p.ej.) y lo usaremos como repositorio remoto.

Para ello de nuevo nos vamos al menú Git y le damos a “Initialize new repository” y ahora marcamos la opción de Central Repository. Y ponemos cualquier path nuevo que creemos (en mi caso he creado una carpeta D:Remote):

image

Si ahora vamos a D:Remote veremos que allí tenemos el repositorio. En este caso el repositorio no está en un subdirectorio .git, porque hemos elegido la opción de Central repository que no tiene directorio de trabajo.

Bien, ahora vamos a hacer un push de nuestro repositorio local al repositorio remoto. Para ello, primero, debemos dar de alta este repositorio remoto en las Git Extensions. Para ello nos vamos al menú Git y seleccionamos la opción de “Manage Remotes”. Nos aparecerá una ventana y ponemos los datos:

image

El nombre es un nombre identificativo, mientras que la URL en este caso es la carpeta donde está el repositorio. Finalmente le damos a “Save” para guardarlo.

Ahora ya podemos hacer un push para pasar los datos del repositorio local al remoto. Para ello, de nuevo nos vamos al menú Git y marcamos la opción Push. En la ventana que nos aparece marcamos la opción “Remote” y de la combo seleccionamos el repositorio remoto que dimos antes de alta:

image

Luego pulsamos el botón Push. Como antes nos mostrará un diálogo cuando haya finalizado:

image

Navegar por repositorios

Podemos navegar por un repositorio, usando la opción “Browse” del menú Git. Seleccionamos el repositorio y podemos navegar, ver los commits/push que se han realizado y ver los archivos y cambios contenidos en cada commit/push. De todos modos sólo he visto como hacer esto en repositorios (locales o remotos) que estén en disco. No sé como navegar por mi repositorio en Assembla p.ej.

image

Y aquí lo dejamos por el momento… A medida que trabaje y que vaya aprendiendo como funciona Git iré poniendo más información al respecto!

Espero que este post os haya sido útil y que en todo caso haya servido para que veáis que es Git y las diferencias con otros sistemas de control de código fuente como TFS.

Un saludo!

ASP.NET MVC: Como recuperar un dato de una cookie para cada petición…

EEhhhmm… bueno, no se me ocurre un título mejor. Este post nace gracias a un tweet de Lluis Franco. En el tweet Lluís preguntaba dónde guardar la cultura de una aplicación MVC si no se podía poner en la URL. Después de varios tweets comentando algunas cosillas yo he respondido diciendo que veía dos opciones: o en una cookie o en la base de datos. Una de las cosas que más me gustan de HTTP es que es simple: no hay muchas maneras de pasar estado entre cliente y servidor 😉

En este post vamos a ver como podemos solucionar fácilmente el problema asumiendo que se guarda la cultura del usuario en una cookie.

De mi tweet, la parte importante es la segunda: independizar a los controladores de donde esta la cultura de la aplicación. Ya lo he comentado en varios posts: evitad acceder desde los controladores a objetos que dependen de HTTP: sesión, aplicación, cache y… cookies.

En un post anterior ya comenté como usar un value provider para hacer binding de datos de una cookie a un controlador. Esa es una buena solución si los datos de la cookie se usan en algunas pocas acciones de un controlador. Pero ese no es nuestro caso ahora: ahora queremos que la cultura se establezca siempre, para todas las acciones de todos los controladores.

La solución en este caso pasa por un Route Handler nuevo. Los route handlers son los objetos que se encargan de procesar las rutas (crear los controladores y cederles el control). Son pues objetos de bajo nivel. Cuando la tabla de rutas enruta una URL para ser procesada por una ruta concreta, se usa el RouteHandler asociado a dicha ruta para crear toda la infrastructura que MVC necesita para procesar la petición.

Recordad que la tabla de rutas se define en Global.asax y que por defecto tiene el siguiente código:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}

Aquí no estamos especificando ningún route handler, por lo que se usará el que tiene MVC por defecto… Pero como (casi) todo en MVC lo podemos cambiar 🙂

En lugar de usar el método MapRoute (que por si alguien no lo sabe es un método de extensión) podemos crear un objeto Route y añadirlo directamente a la tabla de rutas. El constructor de Route tiene un parámetro que es de tipo IRouteHandler y que es el route handler para esta ruta. Así que puedo transformar la tabla de rutas anterior en esta:

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

routes.Add("Default", new Route("{controller}/{action}/{id}",
new CultureRouteHandler())
{
Defaults = new RouteValueDictionary(
new
{
controller = "Home", action = "Index", id = UrlParameter.Optional
})
});
}

Ambas son equivalentes, salvo que esta usará un objeto de tipo CultureRouteHandler para procesar las peticiones.

Ahora vamos a ver como es el CultureRouteHandler:

public class CultureRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
var cultureCookieVal = GetCultureFromCookie(requestContext.HttpContext.Request.Cookies);
var culture = new CultureInfo(cultureCookieVal);
requestContext.RouteData.Values.Add("culture", cultureCookieVal);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
return base.GetHttpHandler(requestContext);
}

private string GetCultureFromCookie(HttpCookieCollection cookies)
{
var retValue = "ca-ES";
if (cookies.AllKeys.Contains("userculture"))
{
retValue = cookies["userculture"].Value;
}
return retValue;
}
}

En este caso derivo de MvcRouteHandler (como casi siempre en MVC es mucho más sencillo derivar de alguna clase base que implementar la interfaz entera), y en el método GetHttpHandler lo que hago es llamar al método de la clase base pero antes:

  1. Recupero el valor de la cookie de cultura
  2. Guardo este valor en el route data con el nombre culture (por si alguien quiere consultarlo)
  3. Creo un CultureInfo a partir de los datos de la cookie y establezco la cultura del thread actual a este valor: así cualquier mecanismo que tenga de “localización” debería funcionar igualmente.

Finalmente para probar el tema me he creado un pequeño controlador:

public class HomeController : Controller
{
[OutputCache(NoStore = true, Location = OutputCacheLocation.None)]
public ActionResult Index()
{
return View();
}

public ActionResult SetCookie(string id)
{
if (!string.IsNullOrEmpty(id))
{
this.ControllerContext.HttpContext.Response.Cookies.Add(new HttpCookie("userculture", id));
}
return RedirectToAction("Index");
}
}

La acción /Home/Index simplemente retorna una vista. La acción /Home/SetCookie/id establece la cookie de cultura (se supone que el id es válido, algo así como /Home/SetCookie/es-ES p.ej.).

La vista que devuelve /Home/Index simplemente muestra la cultura actual:

<p>
Cultura actual: @System.Threading.Thread.CurrentThread.CurrentUICulture.ToString();
</p>

Bonus track: Y si quiero que algún controlador reciba la cultura actual como parámetro de alguna de sus acciones?

Bien, recordad que hemos hecho que el route handler guardase el valor de cultura en los route values. MVC tiene un value provider que permite realizar bindings desde los route values hacia los controladores. Guardábamos el valor con el nombre “culture” así que nos basta con:

public ActionResult Foo(string culture)
{
// Código...
}

El parámetro culture tiene el valor de la cultura.

Si quieres saber exactamente cómo reciben los datos los controladores, hace algún tiempecillo escribí un post al respecto.

De esa manera conseguimos lo que yo decía en mi tweet: agnostizar los controladores de dónde se guarda la cultura!

Un saludo!