Mostrando entradas con la etiqueta asp.net MVC. Mostrar todas las entradas
Mostrando entradas con la etiqueta asp.net MVC. Mostrar todas las entradas

miércoles, 11 de mayo de 2011

[Webcast–AUGES] Introducción a ASP.NET MVC

Seguramente la mayoría ya sabréis que gracias al empuje del maestro Luis Ruiz Pavón (que nos ha ido convenciendo a varios), se ha creado AUGES, el grupo de usuarios de ASP.NET de España.

Para mi es un honor y un placer poder formar parte de este grupo, pero todavía es un placer más grande inaugurar la agenda de eventos del grupo. Y como no podía ser de otro modo el evento será un Webcast de ASP.NET MVC. :)

La fecha? El Miércoles 18. La hora? A las 19:30 (hora española peninsular).

La idea es hacer un evento 100% introductorio: explicar que es esto de ASP.NET MVC, ver ejemplos y comentar cosillas básicas. Más adelante ya tendremos tiempo de hacer eventos más hardcore sobre el tema! ;-)

Así que, ya sabes… si has oído a hablar de MVC y quieres ver de que se trata, o si conoces implementaciones de MVC en otros sistemas y quieres ver la de Microsoft, o si simplmente quieres escucharme un rato (mmmm…. :p) pásate por la web de registro y… nos conectamos el miércoles!!

Saludos… y nos vemos! ;-)

PD: Como siempre… crosspost desde mi blog de geeks.ms

domingo, 3 de abril de 2011

[ASP.NET MVC] Pasar parámetros a través del PathInfo

¡Muy buenas! Bueno, el título del post no queda demasiado claro, pero a ver si consigo explicar un poco la idea. ;-)

Los que habéis usado ASP.NET MVC estáis muy acostumbradas a las URLs del estilo /controlador/accion/id, es decir algo como:

  • /Home/Index/10
  • /Articles/View/Eiximenis
  • /Blog/View/10293

Sabemos que gracias a la tabla de rutas podemos pasar tantos parámetros como queramos, y así podríamos tener URLs del tipo:

  • /Articles/View/Eiximenis/MVC/2011

Que podría devolverme los articulos de “Eiximenis” con el tag “MVC” y del año 2011.

El único punto a tener presente es que el orden de los parámetros importa, es decir no es lo mismo /Articles/View/Eiximenis/MVC/2011 que /Articles/View/2011/MVC/Eiximenis. En el primer caso buscamos los artículos de Eiximenis sobre MVC en el 2011 y en el segundo caso buscaríamos los artículos del blogger 2011, sobre MVC en el año de Eiximenis. Y sin duda Fra Francesc Eiximenis, fue un gran escritor, pero que yo sepa todavía no se le ha dedicado un año (algo totalmente injusto, por supuesto :p).

En este artículo quiero enseñaros una manera para que podáis gestionar URLs del tipo:

  • /Articles/View/Author/Eiximenis/Tag/MVC/Year/2011
  • /Articles/View/Tag/MVC/Year/2011/Author/Eiximenis

Y que ambas URLs sean tratadas de forma idéntica. En este caso estaríamos pasando tres parámetros: Author, Tag y Year.

Para conseguir este efecto nos bastan dos acciones muy simples: definir un route handler nuevo y una entrada a la tabla de rutas.

El route handler lo único que debe hacer es recoger la información de la URL y parsearla en “tokens” (usando el ‘/’ como separador). Y por cada par de tokens añadir una entrada en los valores de ruta (route values). El código es muy simple:

public class UrlRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var path = requestContext.RouteData.Values["pathInfo"];
if (path != null)
{
var tokens = path.ToString().Split('/');
for (var idx =0; idx<tokens.Length; idx+=2)
{
if (idx+1 < tokens.Length)
{
requestContext.RouteData.Values.Add(tokens[idx], tokens[idx+1]);
}
}
}

return base.GetHttpHandler(requestContext);
}
}





Una pequeña nota es que la cadena que separamos en tokens, no es toda la URL sino “pathInfo” un parámetro de ruta que ya nos vendrá dado. Este parámetro de ruta contendrá todo aquello que no es ni el controlador ni la acción. Es decir en la URL /Articles/View/Author/Eiximenis/Tag/MVC/Year/2011 el valor de pathInfo será Author/Eiximenis/Tag/MVC/Year/2011 (que son justo los parámetros).



Ahora nos queda añadir la entrada a la tabla de rutas. En mi ejemplo yo he eliminado la entrada “Default” que genera VS2010 y la he sustituído por:




routes.Add("Default", new Route(url: "{controller}/{action}/{*pathInfo}",
routeHandler: new UrlRouteHandler(),
defaults: new RouteValueDictionary(new {controller = "Home", action = "Index"})));





La clave aquí está en el {*pathInfo}. Aquí le digo al sistema de rutas que coja todo lo que venga después de /{controller}/{action} y me lo añada a un parámetro de ruta llamado pathInfo. Además de eso, en esta ruta le indico que su routeHandler será una instancia de la clase UrlRouteHandler que hemos creado antes.



Y listos! Una vez los datos están en el route value ya puede entrar en acción el sistema de binding de ASP.NET MVC lo que quiere decir que puedo crear un controlador como el siguiente:




public class ArticlesController : Controller
{
public ActionResult View(string author, string tag, int? year)
{
dynamic data = new ExpandoObject();
data.Author = author ?? "Sin formato";
data.Tag = tag ?? "Sin confirmación";
data.Year = year.HasValue ? year.ToString() : "Sin año";
return View(data);
}
}





Que recibiría los parámetros de las URLs que hemos visto anteriormente.



Un saludo a todos!



PD: De nuevo eso es un crosspost desde mi blog en geeks.ms!

martes, 15 de marzo de 2011

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

Buenas! Donde dije digo, digo Diego… Sí, ya sé que dije que el segundo post sería como hacerlo con Ajax, pero bueno… la culpa es de twitter, concretamente de @pablonete con el que hemos empezado a hablar sobre si es posible evitar el guardar la imágen físicamente en el servidor. Hay un mecanismo obvio, que es usar la sesión (guardar el array de bytes que conforman la imágen en la sesión). Pero… hay otra? Pues sí: usar data urls!

Data urls

Lo que mucha gente no conoce es que el formato de URL permite incrustar datos que son interpretados por el navegador como si se los hubiese descargado externamente. P.ej. la siguiente url es válida:

data:image/jpeg;base64,xxxxxxx

donde xxxxxxx es la codificación en base64 de la imágen.

P.ej. eso es totalmente correcto:

<img src=”data:image/jpeg;base64,xxxxxx/>





Veamos como podríamos modificar nuestro proyecto anterior, para usar data urls en lugar de un fichero temporal en el servidor… Fijaos que eso sólo evita guardar el fichero, la imagen debe ser subida al servidor.



Las modificaciones son muy simples, por un lado primero vamos a modificar la acción del controlador, para que quede así:




[HttpPost]
public ActionResult SendImage(HttpPostedFileBase img, string base64, string contenttype)
{
if (base64 != null && contenttype != null && img==null)
{
// Aquí podríamos guardar la imagen (en base64 tenemos los datos)
return View("Step2");
}
var data = new byte[img.ContentLength];
img.InputStream.Read(data, 0, img.ContentLength);
var base64Data = Convert.ToBase64String(data);
ViewBag.ImageData = base64Data;
ViewBag.ImageContentType = img.ContentType;
return View("Index");
}





La acción recibe tres parámetros:




  1. img: El fichero seleccionado (contenido del <input type=”file”).


  2. base64: Codificación en base64 de la imagen que se está previsualizándo.


  3. contenttype: Content-type de la imagen que se está previsualizando.



Por supuesto base64 y contenttype sólo se envían si se está previsualizando una imagen. En caso contrario valen null.



Fijémonos lo que hace la acción, si base64 y contenttype valen null, o bien el usuario ha seleccionado una imagen nueva (img != null):




  1. Obtenemos los datos en base64 de la imagen enviada por el usuario (base64Data).


  2. Pasamos esos datos (junto con el content-type) a la vista, usando el ViewBag.



Y la vista que hace? Pues poca cosa:




<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" />

@if (ViewBag.ImageData != null)
{
<img src="data:@ViewBag.ImageContentType;base64,@ViewBag.ImageData" />
<input type="hidden" value="@ViewBag.ImageData" name="base64" />
<input type="hidden" value="@ViewBag.ImageContentType" name="contenttype" />
}
</form>





Tenemos el formulario con el input type=”submit”, y luego si en el ViewBag vienen los datos de la imagen que se debe previsualizar, genera 3 campos más:




  1. Una imagen, cuyo src es una data url (formato data:content-type;base64,[datos en base64])


  2. Dos campos hidden (para guardar el content-type y los datos para mandarlos de vuelta al servidor).



Y listos! Con eso tenemos la previsualización de las imágenes sin necesidad de generar fichero temporal alguno.



Finalmente, tres aclaraciones:




  1. Fijaos que la codificación en base64 se incrusta en la página (en este caso se incrusta dos veces, aunque con un poco de javascript podría incrustarse solo una), por lo que esto puede generar páginas muy grandes si la imagen ocupa mucho.


  2. Si no voy errado, los navegadores sólo están obligados a soportar URLs de hasta 1024 bytes. Todo lo que exceda de aquí… depende del navegador. Firefox p.ej. acepta URLs muy largas, pero recordad que Base64 genera fácilmente URLs no muy largas, sinó descomunalmente largas (si la imágen ocupa 100Ks, la URL en Base64 ocupará más de 100Ks). Así que para imágenes pequeñas igual tiene sentido, pero para imágenes largas, honestamente no creo que sea una buena solución.


  3. La mayoría de navegadores modernos soportan data urls (IE lo empezó a hacer con su versión 8).



En fin… bueno, como curiosidad no está mal, eh? ;-)



Gracias a Pablo Nuñez por la divertida e interesante conversación en twitter!



Saludos!



PD: Enlace al post anterior de esa serie: http://geeks.ms/blogs/etomas/archive/2011/03/15/asp-net-mvc-previsualizar-im-225-genes-subidas-1.aspx



PD2: Otro crosspost des de mi blog en geeks.ms

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!



PD: Pues sí… otro crosspost de mi blog de geeks.ms! Que raro, no? :P

viernes, 25 de febrero de 2011

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!

martes, 8 de febrero de 2011

[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!

PD: Eso es un crosspost desde mi blog en geeks.ms!

miércoles, 2 de febrero de 2011

[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!

miércoles, 26 de enero de 2011

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! ;-)



PD: Como siempre… eso es un crosspost desde mi blog en geeks.ms!

martes, 25 de enero de 2011

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!



PD: Como siempre… otro crosspost desde mi blog en geeks.ms!

miércoles, 19 de enero de 2011

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!



PD: Eso es otroooooooooooooooooo crosspost desde mi blog de geeks.ms