ASP.NET MVC – Helper Html.LabelFor<T> parametrizable

Buenas! La verdad es que llevo algunos días sin actualizar mucho el blog… Ya se sabe trabajo y tal 🙂

Hoy quiero comentaros algo rapidito y que se ha preguntado varias veces en los foros y que es como poder asignar un ID al <label /> generado por el helper Html.LabelFor<T>. En este caso vamos a hacer que se le puedan añadir todos los atributos que se quieran a la etiqueta <label />

Aunque use este helper, la técnica aplicada debería serviros para ver como ampliar los helpers, en caso que lo necesitéis.

Por ejemplo, dado un viewmodel que tenga una propiedad Address el siguiente código:

@Html.LabelFor(x => x.Address);

Nos genera el HTML siguiente:

<label for="Address">Address</label>

Si quisiéramos parametrizar el <label /> generado nos encontramos conque ninguna de las dos sobrecargas que ofrece el helper nos es de ayuda (la otra simplemente nos permite especificar el texto de la cadena).

En nuestro caso queremos poder hacer una llamada como la siguiente:

@Html.LabelFor(x => x.Address, new { id = "lblEiximenis", width = "100" })<br />

y que el código HTML generado sea:

<label for="Address" id="lblEiximenis" width="100">Address</label>

Bien! Por suerte es muy sencillito, basta con crearnos un método extensor sobre HtmlHelper como el que sigue:

public static class HtmlExtensions

{

        public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes)

        {

            ModelMetadata meta = ModelMetadata.FromLambdaExpression (expression, html.ViewData);

            var htmlFullName = ExpressionHelper.GetExpressionText(expression);

            var labelText = meta.DisplayName ?? meta.PropertyName;

            if (String.IsNullOrEmpty(labelText))

            {

                return MvcHtmlString.Empty;

            }

            var tag = new TagBuilder(«label»);

            tag.Attributes.Add(«for», html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFullName));

            if (htmlAttributes != null)

            {

                tag.MergeAttributes(new RouteValueDictionary(htmlAttributes));

            }

            tag.SetInnerText(labelText);

            return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));

        }

    }

La verdad es que el código es muy sencillito, hay 3 puntos a destacar:

  1. El uso de ModelMetadata para de esta manera poder cojer el valor de los meta datos del modelo (usualmente las Data Annotations) y de este modo poder hacer caso de ellas. En este caso respetaríamos el atrbuto [DisplayName] si el usuario lo usara.
  2. El uso de ExpressionHelper.GetExpressionText. Este método lo que devuelve es el texto de una expresión lambda. Es decir, si yo tengo x=>x.Customer.Name, este método me devolverá “Customer.Name”. Esto lo necesitamos porque el valor del atributo for es todo el texto del cuerpo de la lambda expression. Si al atributo for le pasase el nombre de la propiedad en lugar de todo el texto de la expresión lambda no podría usar propiedades anidadas (es decir me funcionaría para x=>x.Name pero NO para x => x.Customer.Name).
  3. El método MergeAttributes del TagBuilder, que lo que hace es añadir a la etiqueta que se está construyendo todos aquellos atributos basándose en el IDictionary<string, object> que recibe como parámetro. Como en nuestro método los atributos los pasamos como objeto anónimo, nos aprovechamos de la clase RouteValueDictionary (que tiene un constructor que acepta un object). Eso nos evita tener que usar reflection directamente 😉

Y listos! Ya podemos personalizar al máximo las <label>s generadas!

Un saludo a todos!

PD: El código de este méotdo está copiado casi directamente del código fuente de ASP.NET MVC. Así que el consejo final de este post es: mirad el código fuente de ASP.NET MVC aprendereis mucho de él.

PD2: Una búsqueda en Google me rebela que Imran Baloch se me ha avanzado por algunos días: http://weblogs.asp.net/imranbaloch/archive/2010/07/03/asp-net-mvc-labelfor-helper-with-htmlattributes.aspx

[WebCast] Material del webcast de ASP.NET MVC para AUGES

Buenas!

Recién realizado el WebCast para AUGES, os comento que he subido el material (el código del proyecto que hemos hecho) en mi skydrive.

Al final diría que no ha estado mal, aunque ahora en retrospectiva se sacan algunas conclusiones…

  1. Sigo sin aclararme con lo de compartir elementos en Live meeting… ahora se ven, ahora no, ahora cambia él, ahora no… Al final tuve que ir compartiendo el escritorio entero, que es lo único que al parecer funciona bien (o yo se hacer, que todo podría ser :p).
  2. A ver si Microsoft mejora el Live meeting… No por lo de compartir sino porque me dejó sin sonido. A ver si ahora que tienen Skype mejora eso un poco, porque no entiendo como el producto puede ser tan, tan y tan malo. No es la primera vez que “me peta” el sonido o bien me desconecta o cualquier cosa rara. Lo peor es que no avisa ni nada de que el sonido “se ha ido”: tienes que desplegar el menú de sonido y entonces sí que te dice que algo ha ido mal y no tienes sonido, en fin…
  3. Sobre la exposición, pues en general creo que fue bien, aunque ahora un día después, habría alguna cosas que cambiaría, y de hecho seguramente haré algun post para aclarar algo que igual no comenté del todo, o pasé demasiado de puntillas por él.

Bueno! Basta de quejas, jejejeee… os dejo el enlace al código fuente. Es un zip con el powerpoint y dos soluciones de VS2010, una con el proyecto “al inicio” y otra con el proyecto finalizado. Destripadlas a gusto, y recordad que es un código de demo! 🙂 (esto es un disclaimer para que me perdonéis cualquier barbaridad que veais, jajajajaaa…)

Si después de ver el webcast os queda alguna duda, os animo a que la pongáis aquí, o bien contacteis conmigo (mandadme un mail o un twitter) y trataré de responderos lo buenamente que pueda!

Y por supuesto: animaros a participar en AUGES, a través la página web, de facebook, de twitter o de Linkedin! Se trata de que todos compartamos, discutamos, hablemos (y algún dia nos tomemos algunas cervecitas)!

Finalmente, muchas gracias a todos por asistir al Webcast, para mi ha sido un auténtico placer!

Un saludo!

PD: Ah sí! El enlace: http://cid-6521c259e9b1bec6.office.live.com/self.aspx/BurbujasNet/ZipsPosts/AUGES%20-%20Un%20paseo%20por%20MVC.zip

¿Webforms y ASP.NET MVC juntos? Pues claro!

En el grupo de linkedin de AUGES, en uno de los debates que tenemos abierto, Javier Giners pregunta estrategias de migración de Webforms hacia ASP.NET MVC. Yo le responde que depende de como esté arquitecturada la aplicación pero que tenga presente que ASP.NET MVC y webforms pueden convivir juntos en una misma aplicación web. No se trata de que una aplicación web hecha en webforms se comunique fácilmente con otra hecha en ASP.NET MVC no. Se trata de que ambas tecnologías pueden combinarse para crear una sola aplicación web.

Y ese es el objetivo de este post 😉

1. En el inicio sólo existía webforms

Para este post he empezado por desarrollar una aplicación webforms. Es muy chorra, lo único que tiene es una página con una grid que a través de un LinqDataSource se conecta a una base de datos y muestra los datos de una tabla (llamada en un alarde de originalidad “Datos”).

No voy a comentar nada del proyecto Webforms, al final del post hay el enlace para que os lo descarguéis, pero ya veréis que es muy simplón. Esta es una captura de pantalla:

image

Fijaos que la URL termina en .aspx (es un Webform) y también que hay usuario registrado. La configuración de seguridad en web.config está:

<location path="VerDatos.aspx">
<system.web>
<authorization>
<deny users ="?" />
</authorization>
</system.web>
</location>

Vamos, que todo permitido excepto VerDatos.aspx que sólo lo pueden ver usuarios registrados.

Y listos, ya tenemos una aplicación webforms!

2. Preparando la entrada de MVC

Empieza el show. Ahora queremos implementar el método de dar de alta un usuario, y para ello queremos que la funcionalidad de introducir un dato nuevo, esté hecho en ASP.NET MVC.

Lo primero es modificar el web.config para “añadir soporte” a ASP.NET MVC. Si estáis en VS2010 no necesitás hacer mucha cosa, copiar apenas tres pedacitos.

El primer pedacito es para añadir las referencias a los assemblies propios de ASP.NET MVC. Se debe poner dentro del tag <compilation>:

<!-- 1: Añadimos assemblies que se usan en ASP.NET MVC -->
<assemblies>
<add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add assembly="System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</assemblies>

El segundo pedazo de código es para registrar los namespaces propios de ASP.NET MVC. Para ello dentro de la etiqueta <system.web> que cuelga directamente de <configuration> ponemos:

<!-- 2: Registramos namespaces de ASP.NET MVC -->
<pages>
<namespaces>
<add namespace="System.Web.Helpers" />
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="System.Web.WebPages"/>
</namespaces>
</pages>

Y finalmente sólo nos queda añadir las referencias a ASP.NET MVC en el proyecto. Para ello le dais a Add Reference –> Browse y donde tengáis ASP.NET MVC3 instalado y añadís la referencia a System.Web.Mvc.dll (Si quereis usar Razor también podemos añadir la referencia a System.Web.WebPages.dll).

3. Inicializando ASP.NET MVC

Para inicializar ASP.NET MVC es muy sencillo, basta con añadir algunas líneas en Global.asax.cs:

private void MvcInit()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}

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

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("Default", "{controller}/{action}/{id}",
new {action = "index", id = UrlParameter.Optional});
}

Y añadir la llamada a ese método MvcInit() dentro del Application_Start.

Con eso ya tenemos ASP.NET MVC inicializado dentro de nuestro proyecto webforms Y hemos dado de alta las rutas estándard de ASP.NET MVC. Si ahora ejecutamos de nuevo vemos que todo sigue funcionando.

Nota: ¿Te sorprende que siga funcionando todo? Ahora ya tenemos ASP.NET MVC y las URLs de tipo /controlador/acción ya están habilitadas gracias a la tabla de rutas. Si conoces algo de ASP.NET MVC igual te preguntas: si ya tenemos URLs bonitas… cómo es que siguen funcionando las URLs que terminan con .aspx?

La respuesta es muy sencilla: Las rutas, en principio, no se aplican si existe un fichero físico que coincida con la URL. La razón no es facilitar la interoperabilidad con webforms (aunque ayuda), la razón es mucho más simple: Si no fuese así, deberíamos crear controladores para devolver imágenes, css, ficheros javascript y multitud de elementos estáticos más.

Así pues recuerda: Si existe un fichero en la ruta física que indica la URL, las rutas no se tienen en cuenta (a menos que se indique lo contrario). Es por eso que ahora una URL /VerDatos.aspx nos funciona, porque tenemos un fichero físico llamado VerDatos.aspx en la raíz de la aplicación web.

4. Creando un controlador y una vista

Bueno… ahora ya podemos crear el controlador. Dado que partimos de un proyecto de Webforms no tenemos el soporte de tooling de Visual Studio disponible (no hay el menú “Add Controller” p.ej.). Por suerte añadirlo es muy simple.

Para ello abrimos el fichero .csproj con un editor de texto y busca el tag <ProjectGuids>. En mi caso tenía ese valor:

<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

Estos GUIDs son los que indican el tipo (o tipos) del proyecto y por lo tanto indican que herramientas aplica Visual Studio. Como no sabía el GUID de un proyecto de ASP.NET MVC3, cree uno de vacío y miré esa misma línea. En el caso de ASP.NET MVC3 el valor de ProjectTypeGuids es:

<ProjectTypeGuids>{E53F8FEA-EAE0-44A6-8774-FFD645390401};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

Veo que de los tres GUIDs que hay en la segunda línea, dos son los mismos que antes y que hay uno de nuevo ({E53F8FEA-EAE0-44A6-8774-FFD645390401}), así que simplemente copio el GUID nuevo en la etiqueta ProjectTypeGuids del .csproj de Webforms.

¡Y voilá! Ya tenemos el tooling de VS2010 en nuestro proyecto webforms!

Nota importante: El orden de los GUIDs dentro de ProjectTypeGuids parece que importa! El GUID “nuevo” (el que hemos copiado del proyecto MVC3 al de Webforms) tiene que estar en la primera posición. Si no está en la primera posición no podréis abrir el proyecto en VS2010!

Ahora añadimos manualmente las carpetas Controllers y Views. Una vez creada ya podemos añadir el controlador. En este caso vamos a añadir un controlador para añadir datos nuevos:

public class DatosController : Controller
{
public ActionResult Add()
{
return View();
}

[HttpPost]
public ActionResult Add(DatosViewModel datos)
{
if (ModelState.IsValid)
{
using (var database = new DatosDataContext())
{
var nuevoDato = new Dato();
nuevoDato.id = datos.Id;
nuevoDato.nombre = datos.Nombre;
nuevoDato.twitter = datos.Twitter;
database.Datos.InsertOnSubmit(nuevoDato);
database.SubmitChanges();
}
return Redirect("/");
}
else
{
return View();
}
}
}

DatosViewModel es una clase que me he creado yo, que me servirá para hacer binding de los datos que entre el usuario:

public class DatosViewModel
{
public int Id { get; set; }
public string Nombre { get; set; }
public string Twitter { get; set; }
}

Ahora toca crear la vista de alta. Antes que nada nos toca crear la vista Layout (la equivalente de la master en Razor).

Nota: Podríamos usar el View Engine de aspx para reutilizar la master. Pero vamos a ver como hacerlo en Razor, porque (en mi opinión) Razor es una mejora sustancial respecto el View Engine de aspx.

Podemos crear un página de Layout y establecerla en cada vista (lo mismo que hacemos en el caso de webforms o el view engine de aspx) o bien podemos no establecer el Layout en cada vista y hacerlo a través del _ViewStart.cshtml (que se ejecuta antes de ejecutar cada vista).

Creamos un archivo llamado _ViewStart.cshtml que esté en /Views y que tenga el código:

@{
Layout = "~/Views/Shared/Layout.cshtml";
}

Con eso establecemos /Views/Shared/Layout.cshtml como la página de layout de todas nuestras vistas.

Ahora podemos crear la página (Add New Item –> MVC3 Layout Page (Razor)). El código que VS2010 nos genera por defecto ya es suficiente:

<!DOCTYPE html>

<html>
<head>
<title>@ViewBag.Title</title>
</head>
<body>
<div>
@RenderBody()
</div>
</body>
</html>

Recordad de guardar esa vista en /Views/Shared con el nombre de Layout.cshtml.

Ahora ya podemos añadir nuestra vista. Añadimos una vista llamada “Add” que esté en /Views/Datos. En mi caso he creado la vista con esos parámetros:

image

Y el código que por defecto genera VS2010 ya es suficiente 😉

Ahora ya podemos probarlo, a ver que tal… Para ello, el Webform VerDatos.aspx tiene al final un <asp:Hyperlink> al que le añado la propiedad NavigateUrl con valor “/Datos/Add”. Ahora ya podemos probarlo!

5. Zas! En toda la boca! :p

Si al probar el proyecto os aparece un error como este:

Compilation Error

Description: An error occurred during the compilation of a resource required to service this request. Please review the following specific error details and modify your source code appropriately.
Compiler Error Message: CS0103: The name ‘model’ does not exist in the current context
Source Error:

Line 1:  @model WebApp.DatosDataContext
Line 2:  
Line 3:  @{

Tranquilos… es normal.  Nos falta configurar ASP.NET para que use también el motor de Razor. Eso se hace… en el web.config. Pero no en el web.config general, sino en un web.config que esté dentro de /Views.

La forma más rápida de tenerlo es cojerlo de un proyecto nuevo de MVC3 que os creeis, pero si estáis perezoso, este os puede servir (recordad, va en View/web.config):

<?xml version="1.0"?>

<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>

<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>

</pages>
</system.web.webPages.razor>

<appSettings>
<add key="webpages:Enabled" value="false" />
</appSettings>

<system.web>
<httpHandlers>
<add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
<pages
validateRequest="false"
pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<controls>
<add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
</controls>
</pages>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<remove name="BlockViewHandler"/>
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler" />
</handlers>
</system.webServer>
</configuration>

Si ahora lo probáis el error es otro: un error de compilación de que deberíamos añadir una referencia a System.Data.Linq. Recordad que cuando ASP.NET compila una vista, usa las referencias que esten indicadas en el web.config. Así pues añadimos la siguiente línea dentro del tag <assemblies> del web.config principal:

<add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

Ok. Ya estamos listos!Ya Aparece nuestra vista Razor.

Para evitar que cualquiera pueda añadir usuarios, recordad de colocar [Authorize] en el controlador! Aunque el login se haya hecho con webforms, como son la misma aplicación, un usuario autenticado en webforms lo está en MVC y viceversa! 😀

Por supuesto hay muchos más temas que podríamos tratar, pero al menos espero que este post os sirva para ver que Webforms y MVC van de la mano sin ningún problema!

Un saludo!

PD: Os dejo el código del proyecto en: http://cid-6521c259e9b1bec6.office.live.com/self.aspx/BurbujasNet/ZipsPosts/webformsAndMvc.zip

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

[Reseña] Curso de CampusMVP para la certificación 70-515

Muy buenas! Los chicos de CampusMVP me han dado la ocasión de revisar uno de sus cursos, en concreto el que tienen para preparar la certificación 70-515 (desarrollo de aplicaciones web). Y el resultado es este post 😉

Antes que nada comentaros que yo ya tengo esta certificación, así que me ha sido muy fácil a posteriori ver si el curso cubría lo que entra en el exámen.

El sistema de aprendizaje

Estamos hablando de un curso on-line, por lo tanto tiene las ventajas y desventajas de todo curso on-line. La ventaja es que puedes dedicarte al cuando mejor te vaya: no hay horario fijo. Y esta resulta ser también su mayor desventaja. No malinterpretéis, me refiero a que se requiere cierta disciplina y fuerza de voluntad para sacar el curso adelante. A veces, todo aquello que no tiene un horario fijo tendemos a dejarlo para más adelante, y la verdad es que para aprovechar el curso se requiere invertir un montón de horas. En concreto la duración de este curso está estimada en unos 4 meses (16 semanas), dedicando de 8 a 10 horas cada semana. No se si os parece mucho o poco, pero si os parece mucho dejadme que os diga, que el esfuerzo vale la pena.

El sistema de aprendizaje es muy simple: primero hay una lección escrita y finalmente un soporte en vídeo. Me ha sorprendido muy gratamente el detalle de esos vídeos (y reconozco que con el tiempo que le he dedicado me ha sido imposible verlos todos!). Y además hay soporte del tutor del curso a través de foros y un sistema de mensajería instantanea. De hecho creo que éste es un punto diferencial con otros cursos.

Ah sí! Y también regalan acceso a tests de MeasureUp, para probar nuestros conocimientos con algo que se parezca al exámen real (mi experiencia con los tests de MeasureUp es que suelen ser más difíciles que el exámen real).

En las instrucciones del curso envían la metodología de uso, que no por simple deja de ser efectiva: leer, ver el vídeo, practicar y finalmente reflexionar e intentar ir más allá. Lo que comentaba al principio: es un curso que requiere esfuerzo, pero honestamente creo que vale la pena.

El temario

El curso está dividido en módulos, pero estos módulos no se corresponden a los módulos funcionales del 70-515, sinó que simplemente son módulos tecnológicos. Es decir se engloban las cuatro grandes tecnologías que conforman el ecosistema de desarrollo de aplicaciones web en .NET. Que son:

  1. Webforms ASP.NET 4
  2. ASP.NET Ajax
  3. ASP.NET MVC
  4. jQuery

Si os sorprende la presencia de jQuery, os digo que tiene mucho sentido, primero porque se ha convertido en un estándard de facto y segundo porque jQuery entra como temario en el 70-515.

Las versiones de los productos están adecuados a lo que se pide en el examen, así p.ej. no esperéis encontrar nada de MVC3 en el módulo de ASP.NET MVC, ya que el 70-515 está basado en MVC2. De este modo se evita confundir al alumno y evitar que responda incorrectamente preguntas (de una manera que podría ser correcta en MVC3 pero incorrecta en MVC2 p.ej.).

La relación con el 70-515

MMmmm… en el fondo creo que el 70-515 es una mera excusa que han aprovechado en CampusMVP para realizar un fabuloso curso sobre el desarrollo de aplicaciones web en tecnologías Microsoft. A ver, lo que se cuenta en el curso va mucho más allá de lo que se pide para aprobar el 70-515. Y esa decisión de CampusMVP me parece fenomenal, porque precisamente el 70-515 es un exámen que considero muy mal enfocado y muy sesgado en sus preguntas (opinión personal, por supuesto).

Todos los módulos (cada módulo es un mini-curso que empieza con lo básico y termina con técnicas avanzadas), tienen mucha, mucha información y abarcan mucho más de lo que el 70-515 demanda.

Yo no sé si este curso garantiza el aprobado del 70-515 como dicen en CampusMVP. Lo que sí sé, es que el temario del curso, y el cómo está explicado harán que aprendas de verdad el desarrollo de aplicaciones web en tecnologías Microsoft. Si has seguido bien el curso y has asmiliado bien los conceptos, ya te digo que lo que sabes es mucho más que lo que el 70-515 pide.

En resumen, si lo que quieres es aprender a desarrollar aplicaciones web, con tecnologías modernas (la úñtima versión de webforms y la penúltima de MVC) en .NET, este curso es un candidato que deberías tener en cuenta. Es un todo-en-uno en tecnologías web, sencillamente excelente.

Un saludo a todos!

PD: Edito (18/04/2011) para poner enlaces 😉

  1. Curso de Campus MVP para la preparación del 70-515 (el que he analizado)
  2. Otros cursos de Campus MVP sobre tecnologías MS
  3. Tienda on-line de Campus MVP

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

[jQuery tmpl] Pasar elemento de template como parámetro a una función del template

Bueno… Vaya título me ha salido, eh? 😛 A ver, realmente este post es para evitar que alguien pierda el mismo tiempo que he pedido yo, para una chorrada…

En fin, al tajo. No sé si conocéis jQuery templates. Para los que no, que sepáis que es un plugin de jQuery para convertir objetos json en html. No es la única manera de hacerlo, hace tiempo escribí sobre PURE (http://beebole.com/pure/) otra herramienta para hacer lo mismo, y que os animo a que al menos le echéis un vistazo. Poco después apareció la alfa de jquery-tmpl (y siguiendo con el autobombo escribí una pequeña comparativa, que, todo debe reconocerse, hoy ha quedado un poco desfasada). Poco después se anunció que jQuery-tmpl pasaba a ser considerado plugin oficial de jQuery y se pasó a llamar “jQuery templates”. Actualmente está en beta, pero ya es extremadamente estable. Luis Ruiz Pavón escribió un artículo introductorio a jQuery templates que os recomiendo que le echéis un vistazo (aunque la sintaxis actual sea un poco diferente a la de ese artículo, es lo que tiene escribir sobre versiones alfa y demás).

En jQuery templates se usan básicamente “tres» cosas:

  1. Una definición de template, que se suele incorporar dentro de un tag <script> con un type inválido para que el navegador lo ignore (no lo incorpore al DOM).
  2. Un contenedor donde se incrustará el DOM generado.
  3. Un objeto json inicio de los datos.

La sintaxis para definir el template es el punto fuerte de jQuery templates:

<script type="text/javascript">
$(document).ready(function () {
var data = { nombre: 'edu', twitter: 'eiximenis' };

$("#template").tmpl(data).appendTo("#placeholder");
});
</script>

<script id="template" type="text/x-jquery-tmpl">
<div style="background: green">Usuario ${nombre} - Twitter: <a href="http://twitter.com/${twitter}">${twitter}</a>
</script>

<div id="placeholder"></div>

Este código, en tiempo de ejcución genera el DOM siguiente:

<div style="background: green;">Usuario edu - Twitter: <a href="http://twitter.com/eiximenis">eiximenis</a></div>

Hay varios tags para controlar el template. Para este post vamos a ver uno, que es {{each}} que permite repetir parte del template por cada elemento del array:

<script type="text/javascript">
$(document).ready(function () {
var data = {
titulo: 'Twitters en geeks',
users: [{ nombre: 'edu', twitter: 'eiximenis' },
{ nombre: 'jorge', twitter: 'j0rgeSerran0' },
{ nombre: 'javi', twitter: 'jtorrecilla' },
]
};

$("#template").tmpl(data).appendTo("#placeholder");
});
</script>

<script id="template" type="text/x-jquery-tmpl">

<div style="background: #EEEEEE">
<h4>${titulo}</h4>
{{each users}}
Usuario ${$value.nombre} - Twitter: <a href="http://twitter.com/${twitter}">${$value.twitter}</a><br />
{{/each}}
</div>
</script>

<div id="placeholder"></div>

Con {{each}} iteramos sobre los elementos del array “users”. Dentro del template el valor $value me permite referenciar el valor del array para el que se está renderizando el template (p.ej. ${$value.nombre} me permite acceder al nombre del elemento que se está renderizando). Si el nombre de $value no nos gusta, lo podemos indicar como parámetro de each:

<script id="template" type="text/x-jquery-tmpl">
<div style="background: #EEEEEE">
<h4>${titulo}</h4>
{{each(idx, user) users}}
<strong>${idx}:</strong>Usuario ${user.nombre} - Twitter: <a href="http://twitter.com/${twitter}">${user.twitter}</a><br />
{{/each}}
</div>
</script>

En este código podemos usar ${idx} para acceder al índice del elemento y ${user} para acceder a cada elemento que se está renderizando.

Bien, vayamos ahora al tema del post…

Una cosilla interesante es que a la llamada a tmpl() se le puede pasar un segundo parámetro, con datos adicionales globables que pueden usarse en el template. Esos parámetros pueden ser, entre otras cosas, funciones. Para acceder a los elementos “globales” se usa la variable de template $item. Imaginad que tenemos esto ahora:

<script type="text/javascript">
$(document).ready(function () {
var data = {
titulo: 'Twitters en geeks',
users: [{ nombre: 'edu', twitter: 'eiximenis' },
{ nombre: 'jorge', twitter: 'j0rgeSerran0' },
{ nombre: 'javi', twitter: 'jtorrecilla' },
{ nombre: 'alguien' }
]
};

$("#template").tmpl(data,
{ getUrl: function (name) { return name != undefined ? "http://twitter.com/" + name : '#'; } }).appendTo("#placeholder");
});
</script>

Como segundo parámetro a tmpl() le pasamos un objeto con una función getUrl que dado una cadena me construirá la URL de twitter asociada. Si el la cadena es undefined, me generará una URL que no haga nada (#). Ahora toca llamar a esta función desde el template:

<script id="template" type="text/x-jquery-tmpl">
<div style="background: #EEEEEE">
<h4>${titulo}</h4>
{{each(idx, user) users}}
<strong>${idx}:</strong>Usuario ${user.nombre} -
Twitter: <a href="${$item.getUrl(${user.twitter})}">${user.twitter}</a><br />
{{/each}}
</div>
</script>

Fijaos que llamamos a getUrl usando $item.getUrl() (porque getUrl está definido en el ámbito global del template). Y como parámetro le pasamos el valor de la propiedad twitter del elemento que estamos renderizando. Esto, yo suponía que era ${user.twitter}. Pero eso no funciona. No aparece el template y en su lugar aparece un error de javascript en jquery-tmpl.js. Eso es muy común: errores en el template generan errores de javascript dentro de jquery-tmpl.js.

Después de varios intentos descubrí que pasaba: Cuando se pasan parámetros a una función definida dentro del ámbito global del template esos parámetros de pasan sin ${}. Es decir debemos usar:

<script id="template" type="text/x-jquery-tmpl">
<div style="background: #EEEEEE">
<h4>${titulo}</h4>
{{each(idx, user) users}}
<strong>${idx}:</strong>Usuario ${user.nombre} -
Twitter: <a href="${$item.getUrl(user.twitter)}">${user.twitter}</a><br />
{{/each}}
</div>
</script>

Fijaos en la llamada a getUrl, ahora simplemente la pasamos getUrl(user.twitter). Y eso funciona correctamente!

Así pues:

  • ${$item.getUrl(${user.name})} –> NO FUNCIONA
  • ${item.getUrl(user.name)} –> FUNCIONA CORRECTAMENTE

En fin… cosillas que descubre uno 😉

Saludos!

Opinión: De las palabras de un iluminado y de madurez de comunidad…

Buenas! Este post surge a raíz de una interesante conversación que mantenido con Jorge Serrano, Eugenio EstradaPablo Núñez y Pablo Iglesias.

A ver empecemos… todo viene a raíz de las palabras de un iluminado que se pueden encontrar en http://blog.expensify.com/2011/03/25/ceo-friday-why-we-dont-hire-net-programmers. Resumiendo este tio viene a decir que no contrata nunca a desarrolladores en .NET porque en definitiva no los considera buenos. Según él los desarrolladores en .NET sólo saben arrastrar cajas y poca cosa más. Bueno, es su opinión (no la comparto pero la respeto).

Eugenio publicó el siguiente tweet al respecto (http://twitter.com/eugenioestrada/status/52396715441000448):

image

A lo que yo respondí con el tweet que originó la conversación posterior (http://twitter.com/eiximenis/status/52397065011077121):

image

Luego ya empezamos a intervenir todos sobre el tema, que si sí, que si no, que si tal… La verdad es que en 140 carácteres cuesta expresar según que ideas, así que al final me he decidido escribir el post. En concreto yo decía que parte de culpa tenemos en la imagen que tiene de .NET, porque considero que como comunidad (de desarrolladores se entiende) nos falta cierta madurez.

Lo importante, como digo en mi tweet, no es lo que pueda opinar alguien, ya se sabe que las opiniones son como el culo: todo el mundo tiene la suya. Lo importante es si esa imagen de que los desarrolladores de .NET no somos “buenos” está extendida. Y si lo está, mirarnos un poco nosotros, como comunidad y ver si podríamos hacer algo mejor. Ahí iba. Y para empezar voy a dejarlo claro: No estoy de acuerdo con el tipo ese. Los desarrolladores de .NET somos tan buenos o tan malos como los de Java, PHP o cualquier otra comunidad (extendida, eh? que os conozco y me sacaréis algún ejemplo de algún lenguaje, como Brainfuck, donde haya que tener un Nobel para desarrollar). Además el propio autor considera a los de PHP y Java como buenos desarrolladores en contraposición a los de .NET.

Bueno, que me lío, como digo lo importante no es si eso lo piensa un iluminado o no. Lo importante es si lo piensan muchos. Y, mi percepción, es que hay bastante gente que opina como este tio. En general, creo, que la imagen de .NET en general y de los que desarrollamos bajo ella está por debajo de lo que se merece la plataforma y los que la usamos. Y es ahí donde honestamente creo que algo de culpa que tenemos. Recordad que este tio, sólo expresa su opinión, y no se le ve un talibán anti microsoft, se le vé un anti-desarrolladores-en-.NET que no es lo mismo.

Cuando digo que algo de culpa tenemos, lo resumo en que nos falta madurez como comunidad, en respecto a otras comunidades. Eso por supuesto, es una opinión personal, basada en mi experiencia. Es evidente que Microsoft pretende que desarrollar en .NET sea fácil. Eso se resume en muchas cosas: muchos recursos para aprender, mucha documentación y también muchos asistentes que realizan tareas. Eso no es malo, estoy de acuerdo con Jorge cuando dice literalmente que “Hay gente que por hablar de temas frikis se creen que saben más, pero a veces los problemas cotidianos requieren soluciones cotidianas”. Eso es lo que me gusta de .NET, que es fácil iniciarse en ella. Y entonces… ¿donde está el problema?

Bien, voy a generalizar a partir de ahora. Por lo tanto, por favor, que nadie se ofenda. El problema está en que al ser tan fácil desarrollar en .NET no profundizamos mucho. Nos quedamos en la superficie. Además MS nos facilita tanto las cosas que nos permite p.ej. desarrollar aplicaciones web sin conocer html o http. Un ejemplo concreto: he visto varias veces gente que desarrolla en asp.net poner un msgbox y dejar un iis “colgado”. Eso denota una falta total de conocimientos de como funciona la web en general (ojo, no estoy diciendo que tengamos que nacer enseñados). Y esa gente está haciendo aplicaciones porque .NET lo permite, por su facilidad. No he visto jamás a nadie desarrollando en jsp o php que se le ocurra hacer esto. Todas las decisiones tienen sus dos caras, y la que MS tomó de hacer .NET fácil acarrea estas consecuencias. Por supuesto no estoy diciendo que todos los que programan jsps o phps sean los putos masters del universo desarrollando aplicaciones web. Evidentemente que no. Habrá auténticos patanes desarrollando en estas plataformas. Seguro. Y harán aplicaciones. Seguro. Pero nosotros tenemos esta imagen y ellos no. Quizá, en proporción, seamos más. Quizá no y es todo un tema de imagen.

En general creo que somos una comunidad que nos cuesta innovar y buscar soluciones fuera del paragua de Microsoft. De nuevo voy a generalizar y seré un poco “duro”, que nadie se ofenda, por favor.

En general no sabíamos hacer unit test hasta que salió VS2005, y eso que nUnit ya llevaba su tiempo. El tema de builds automáticas e integración continúa era desconocida para nosotros, hasta que salió TFS. Y eso que nAnt y Cruise Control.NET ya existían. Por supuesto usar un modelo MVP para desarrollar aplicaciones de escritorio no sabíamos lo que era hasta que salió CAB (y honestamente después seguimos sin saberlo porque CAB era como era y tampoco llegó a pasar nunca de algo un poco freak dentro de .NET). Por supuesto hasta que Micrtosoft no sacó Entity Framework, no conocíamos que era un ORM, a pesar de que NHibernate ya tenía sus añitos. Y que decir de usar MVC para aplicaciones web? Nada, hasta que Microsoft sacó asp.net mvc. Y podría seguir con más ejemplos…

A lo mejor ahora parece que nos estemos despertando un poco, pero yo veo que seguimos danzando al ritmo que marca Microsoft. Si VS2011 integra una herramienta tipo SpecFlow empezaremos a hablar todos de las maravillas de BDD y como sin BDD no se puede desarrollar. Somos así.

Y eso creo que nos ocurre porque nos hemos acostumbrado a que Microsoft nos proporcione siempre el stack completo: base de datos, lenguajes, plataforma, APIs, buenas prácticas y nos diga que se puede hacer y que no. Lo que no esté en este stack de Microsoft, ni lo miramos. Pero cuando se incorpora algo nuevo, aunque sea peor que algo que ya existía pero no era de Microsoft, lo abrazamos sin dudarlo. Es por eso que creo que nos falta madurez como comunidad. En otras comunidades (sobre todo Java) nos llevan mucha ventaja en esto. No quiero entrar en el porqué, porque eso sería otro debate.

Y creo que todo esto contribuye a la mala imagen que podamos tener como desarrolladores de .NET. Y esto debería preocuparnos, porque si esa mala imágen se extiende entre los que al final nos deben contratar, entonces el problema lo vamos a tener nosotros!

Y por último respondiendo a una última pregunta de Jorge, sobre que se puede hacer para solucionar esto: Pues en general no lo sé. Intentando ser un buen desarrollador, en intentar aprender continuamente, en investigar que hay más allá y sobretodo no quedarse en .NET. Mirar en otros sitios que están haciendo y como lo hacen. Alguien me dijo un dia que todo desarrollador debería aprender (aprender como funciona y su filosofía, no convertirse en un crack) un lenguaje nuevo cada año. Me parece una visión muy acertada.

Un saludo a todos!

PD: Y para terminar, insisto en lo que he dicho antes. He generalizado. Ya lo he dicho pero lo digo de nuevo: No creo que los que desarrollamos en .NET seamos peores que los que lo hacemos en otras plataformas. Hay gente muy, muy, muy buena desarrollando en .NET, al igual que la hay en todas partes. Y al revés también, por supuesto.

[HTML/JS] Module pattern

Muy buenas! Cuando creas un sitio web, es normal que vayas añadiendo cada vez más código javascript en él. Al final, seguramente terminaréis desarollando una mini-api, propia que muchas veces reside en un archivo .js, lleno de funciones. Algo como:

function HazAlgo(id, params, callback)
{
}

function _HazAlgoHelper(domObj, params)
{
}

En este caso tenemos dos funciones, HazAlgo y _HazAlgoHelper. La función _HazAlgoHelper es realmente una función privada, es decir está pensada para ser llamada únicamente dentro de HazAlgo. Pero en Javascript no existe directamente el concepto de función privada así que le ponemos un idicador (que empieze por _) y asumimos que todas las funciones que empiecen por _, son privadas.

Como podemos ver, el código javascript no está muy “organizado”: tenemos todas las funciones juntas y además podemos ver aquellas que no deberíamos. El patrón de módulo existe para solventar esos problemas.

El patrón de módulo

La idea del patrón de módulo (module pattern) en Javascript es simular el concepto de una clase estática con métodos públicos y métodos privados. Lo de clase estática viene porque no quiero ir creando objetos de mi módulo, simplemente quiero tener ahí, las funciones públicas. Pero sin ver las privadas. Para los que no lo sepáis: en javascript no existe el concepto de variable estática.

La idea que hay detrás del patrón de módulo es muy simple, y como casi siempre pasa simple significa también brillante: Se trata de crear un objeto anónimo, cuyos campos públicos sean las funciones públicas. Finalmente se crea una instancia de ese objeto y se asigna al namespace global (eso es, al objeto window).

El código de base sería algo como:

(function(wnd) {
var miModulo = function() {
var _HazAlgoHelper = function(domObj, params) {alert("método privado");};
var _HazAlgo = function(id, params, callback) { _HazAlgoHelper(); alert("método público"}; };

return {
HazAlgo : _HazAlgo
};
}

wnd.m$ = miModulo();
})(window);

Bien… se que el código puede parecer lioso, la primera vez, pero vamos a analizarlo.

Podemos ver que estamos definiendo una función anónima que acepta un parámetro (llamado wnd).

¿Y que hace esa función anónima)? Pues para empezar define una variable miModulo que resulta ser… una función (var miModulo = funcion() { …}).

Y que hará esa función cuando se invoque? Pues tres cosas:

  1. Definir una variable llamada _HazAlgoHelper… que es otra función.
  2. Definir una variable llamada _HazAlgo… que es otra función.
  3. Devolver un objeto anónimo con un campo, llamado HazAlgo. El valor de HazAlgo es el mismo que _HazAlgo (es decir una función).

Con eso tenemos una función (miModulo) que cuando la invoquemos nos devolverá un objeto con un solo campo (llamado HazAlgo). Así, teoricamente:

miModulo.HazAlgo();    // Valido
miModulo._HazAlgo(); // No válido
miModulo._HazAlgoHelper(); // NO válido

Fijaos que la gracia está en que HazAlgo() realmente es lo mismo que _HazAlgo()… y desde _HazAlgo() podemos llamar sin ningún problema a _HazAlgoHelper() que es nuestra función privada. Así la clave es mapear las funciones públicas como campos del objeto anónimo devuelto.

¡Con eso hemos simulado el concepto de funciones privadas!

Pero, miModulo es una variable local, es local a la función anónima que estamos definiendo. Así que todavía nos queda un paso más: Guardar miModulo en alguna propiedad del parámetro wnd. Eso es lo que hacemos con la línea:

wnd.m$ = miModulo();

Invocamos miModulo (con parentesis, pues es una función) y guardamos el resultado (el objeto anónimo con el campo HazAlgo) en la propiedad m$ del parámetro wnd.

Ya casi estamos… Hasta este punto hemos definido simplemente una función anónima. Ahora toca invocarla. Fijaos en un detalle importante: la primera línea empieza con un paréntesis abierto. Con eso, de hecho, estamos invocando nuestra función anónima. Pero nuestra función anónima espera un parámetro (wnd) así que debemos pasárselo. Eso es lo que hacemos en la última línea: le pasamos window. Y porque window? Pues porque window es un namespace global: todo lo que esté en window es global.

Por lo tanto ahora, desde cualquier parte de mi página puedo hacer:

<script>m$.HazAlgo(1,2,3);</script>

Y eso es correcto, mientras que hacer.

<script>m$._HazAlgoHelper(1,2);</script>

Nos dará un error.

Para tener nuestro módulo listo para ser usado, basta con tenerlo en un .js propio e incluirlo en aquellas páginas donde lo necesitemos. ¡Y listos!

Espero que esto os parezca interesante y os ayude a organizar vuestro código javascript!

Un saludo!

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