Bueno… empieza el 2012: el último año de nuestra existencia si los maias no andaban errados (unos tíos que hacían pirámides hace miles de años no pueden equivocarse demasiado). Pero bueno… hasta que no llegue el apocalípsis, ahí estaremos! ¡Al pie del cañón!

De todos modos, para empezar nada más que un post ligerito, sobre C# Básico, ya sabéis esta serie de posts donde vamos explorando cosillas, sin ningún orden en particular, básicas del lenguaje. Sí, ya sé que el ritmo de posts de la serie es abrumador (sobre un post por año) pero bueno… como dicen los ingleses: less give an stone.

Hoy vamos a hablar sobre los eventos, uno de los elementos de C# (realmente es de .NET en general pero aquí nos ceñiremos a C#) que más confusión causa, debido a que por un lado son una mezcla de convenciones, por otro VS nos autogenera código que nos puede hacer pensar que todo es más fácil, por otro porque usarlos es trivial pero entender lo que hay debajo de ellos, pues no lo es tanto… Ah sí, y por último a mucha gente le cuesta entender como funcionan porque están basados en delegates y el propio concepto de delegate no es trivial (¡aunque ya hemos hablado en esta serie de delegates!).

1. ¿Qué es un evento?

Bueno… antes que nada digamos que un evento es un mecanismo estándard de .NET. He dicho de .NET, no de winforms, o de WPF o de Silverlight. Repito: de .NET. Eso significa que cualquier clase puede lanzar eventos o suscribirse a ellas. No es necesario tener una aplicación gráfica para tener eventos, una triste aplicación de consola puede tener eventos. Esa es una diferencia de .NET con otros frameworks o plataformas: Los eventos no son parte de un framework en concreto, son parte integral de la plataforma.

En el mundo de .NET un evento es un mecanismo que tiene un objeto de una clase cualquiera de notificar algo a un conjunto de objetos cualesquiera. El cualesquiera del final es importante: si yo, como objeto, expongo un evento cualquier objeto de cualquier clase puede si desea suscribirse a este evento sin que yo tenga que hacer nada en especial. Eso los hace tremendamente útiles porque el desarrollador de la clase que lanza el evento no está obligado a conocer absolutamente nada sobre las posibles clases que se suscribirán a este evento.

2. ¿Como se usa un evento?

Antes de responder a esta cuestión respondamos a otra: ¿qué nos interesa decirle a quien se suscriba al evento? Por ejemplo si estamos creando una clase que informe de que se ha creado un fichero nuevo en disco, nos puede interesar pasar el nombre o la ruta del fichero. Si estamos creando una lista que informa cuando el usuario selecciona un elemento, nos puede interesar pasar el índice del elemento seleccionado, o el propio elemento.

Imaginemos el siguiente código (de una aplicación de línea de comandos).

static void Main(string[] args)

{

    var igualada = new Ciudad("Igualada");

    var castefa = new Ciudad("Castelldefels");

    var bandolero = new Persona("José Miguel", igualada);

    Console.ReadLine();

    bandolero.Emigrar(castefa);

}

Tenemos al bandolero, que estaba en la ciudad de Igualada y debido a una incomprensible decisión decide emigrar a Castelldefels. Ahora imaginad que tenemos una clase Censo que quiere recibir notificaciones cuando el censo de una Ciudad en concreto se ha modificado. Esto implica que la clase Ciudad debe informar cuando un habitante causa baja o alta en ella. Es en estos casos (cuando un objeto quiere informar a otro) que usamos un evento.

El uso de un evento se compone de dos partes:

  1. Quien lanza el evento (en nuestro caso la clase Ciudad)
  2. Quien recibe el evento (en nuestro caso la clase Censo)

2.1 Suscribirse a un evento

El que recibe el evento, debe suscribirse y para ello debe informar de que método quiere que sea llamado cuando se lance el evento. Es decir, debe proporcionar al evento un delegate con el método. El código puede ser algo parecido a:

public class Censo

{

    private readonly Ciudad _ciudad;

    public Censo(Ciudad c)

    {

        _ciudad = c;

        _ciudad.HabitanteDadoDeAlta += new CiudadEventHandler(Ciudad_HabitanteDadoDeAlta);

    }

 

    private void Ciudad_HabitanteDadoDeAlta(Persona persona)

    {

        Console.WriteLine("{0} vive ahora en {1}", persona.Nombre, _ciudad.Nombre);

    }

}

En la segunda línea del constructor la clase Censo se está suscribiendo al evento HabitandeDadoDeAlta que tiene la clase Ciudad. Y el parámetro es un delegate de tipo CiudadEventHandler que “apunta” al método Ciudad_HabitanteDadoDeAlta. Aquí dos preguntas:

  • ¿Como sabe la clase Censo que el método Ciudad_HabitanteDadoDeAlta debe recibir un parámetro Persona? Pues por el delegate CiudadEventHandler. Este delegate está definido de la siguiente manera:

delegate void CiudadEventHandler (Persona persona);

  • ¿Como sabemos que el evento HabitanteDadoDeAlta usa el delegate CiudadEventHandler? Pues eso se indica en la declaración del evento, que veremos ahora.

Bien, hemos visto como suscribirnos a un evento. Eso implica declarar la función gestora (que será llamada cuando se lance el evento) con los parámetros que defina el delegate usado por el evento. Y también usar el operador += para asignar una instancia del delegate (apuntando a nuestra función gestora) al evento.

2.2 Declarar un evento

Veamos ahora, como declarar un evento. Lo que es la declaración en sí, es muy, muy, muy, simple:

public event CiudadEventHandler HabitanteDadoDeAlta;

Esta línea (en la clase Ciudad) declara un evento, llamado HabitanteDadoDeAlta y que usa el delegate CiudadEventHandler. Lo declaramos público porque todo el mundo pueda acceder al evento, y así suscribirse a él (usando el operador +=).

2.3 Lanzar un evento

Una vez hemos declarado un evento, el siguiente paso es lanzarlo, cuando sea necesario. El código para lanzar un evento es extremadamente simple. En nuestro caso haríamos (en la clase Ciudad):

HabitanteDadoDeAlta(persona);

Fijaos que usamos el evento como si fuese un método. Dado que el delegate define que hay un parámetro de tipo Persona, eso es lo que le pasamos. ¡Y listos! Con esto todos  los que estén suscritos al evento, lo recibirán (y ejecutarán cada uno su función gestora, que recibirá los parámetros que nosotros hemos pasado). Las funciones gestoras se ejecutan una tras otra síncronamente.

Bueno… listos del todo no :) La verdad es que este código fallaría si nadie está suscrito al evento HabitanteDadoDeAlta, ya que entonces este vale null y recibiríamos una NullReferenceException. Lo que tenemos que hacer es comprobar antes que el evento no es null (para mayor comodidad he creado una función privada de la clase Ciudad que lanza el evento):

private void OnHabitanteDadoDeAlta(Persona persona)

{

    if (HabitanteDadoDeAlta != null)

    {

        HabitanteDadoDeAlta(persona);

    }

}

Este es el código típico que os encontraréis en la mayoría de sitios de ejemplos para lanzar un evento.

Nota: Debo decir que este código NO es 100% correcto. Contiene un sutil error que puede hacer que en según que casos (siempre relacionados con concurrencia) se reciba una NullReferenceException. En los foros de MSDN expliqué con bastante más detalle el tema y pongo el código correcto que debe usarse: http://social.msdn.microsoft.com/Forums/es/vcses/thread/d28cf2c7-b024-4c1a-93aa-8dc5005ff713. También el maestro entre maestros escribió hace tiempo al respecto en su blog.

¡Listos! Con esto ya lo tienes todo! Ya sabes como declarar eventos, lanzarlos y suscribirte a ellos. Veamos ahora algunos detalles más…

3. ¿Pero… las funciones gestoras no deben recibir siempre dos parámetros?

Es mala costumbre de algunos que enseñan eventos en winforms (o incluso en WPF y Silverlight) decir algo parecido a: “Las funciones gestoras de eventos siempre deben recibir dos parámetros. El primero es un object, que contiene el lanzador del evento, y el segundo contiene los datos del evento y es un objeto de una clase derivada de EventHandler”.

Bueno… eso no es más que una convención. Una convención no es nada más que una regla que todos acordamos seguir, para hacernos la vida más fácil. Pero para nada es una obligación del sistema o de la plataforma. Es cierto que en winforms, WPF y Silverlight los eventos siguen esta convención. Por esto estarás harto de ver código parecido a:

private void btnModificar_Click(object sender, EventArgs e)

{

    //...

}

Como digo, todos los eventos que están declarados en winforms, en Silverlight y en WPF siguen esa convención (en este ejemplo concreto el evento Click está definido usando el delegate EventHandler que ya viene con .NET) y no está de más decir que tus propios eventos deberían seguirla (aunque si no lo hacen funcionará todo igual).

Por lo tanto: no, las funciones no deben siempre incluir esos dos parámetros, pero si desarrollas en winforms, WPF o Silverlight, deberías hacerlo para seguir la convención marcada.

4. El código para suscribirme a los eventos es muy farragoso…

Sí, es cierto, es una queja común. Por eso en VS2005 creo que fue, lo simplificaron, así que si quieres puedes hacer:

_ciudad.HabitanteDadoDeAlta += Ciudad_HabitanteDadoDeAlta;

Fíjate que es mucho más sencillo: te olvidas de hacer el new del delegate, pasas simplemente el nombre de la función gestora y listos. En este caso el compilador de C# infiere y crea el delegate por tí.

Y sí, crees que tener que crear una función para gestionar un evento, es un rollo y usas VS2008 o superior, puedes usar lambdas:

_ciudad.HabitanteDadoDeAlta += (p) => { Console.WriteLine("{0} vive ahora en {1}", p.Nombre, _ciudad.Nombre); };

Este mecanismo es el más compacto de todos, ya que NO estás obligado a declarar la función gestora, en su lugar pasas la lambda expression que la implementa. Fíjate que el compilador es capaz, incluso de inferir que el parámetro p es de tipo Persona (porque así obliga el delegate del evento). Si no te sientes cómodo con la notación de lambdas, piensa que NO estás obligado a usarla!

Por supuesto el mecanismo de usar lambdas está recomendado cuando el código de las funciones gestoras es muy reducido, por visibilidad quieres tenerlo junto a la suscripción o bien te interesa usar closures.

Actualmente Visual Studio, incluso el 2010 compilando contra el Framework 4.0, sigue usando el primer código que hemos visto cuando debe generarte código para suscribirte a un evento… En MS tienen muy claro que lo que funciona no debe tocarse, o bien el tío que hizo esta parte de VS se ha ido y no saben como modificarla (si en MS trabajan como muchas empresas de España, probablemente será eso :P).

5. Desuscripción de eventos

Sí amigo… cuando dejes de estar interesado en recibir más notificaciones de eventos lanzados por un objeto debes desuscribirte del evento. Si no lo haces se seguirá ejecutando la función gestora que especificaste incluso cuando no te interesa para nada.

¿Cuando debes desuscribirte de un evento? Pues muy fácil, hay solo dos reglas:

  1. Cuando deja de interesarte recibir el evento (obvio)
  2. Cuando estés suscrito a un evento que lance un objeto que está fuera de tu ámbito de vida y tu vayas a “morir”. Por decir que está fuera de tu ámbito de vida, me refiero a que si es posible que el otro objeto siga existiendo después de que tu “mueras”.

El punto 2 mucha gente lo pasa por alto, y termina generando memory leaks. Sí, sí… también se pueden generar memory leaks en .NET.

Un ejemplo: en Winforms es normal que el formulario (Form) se suscriba a los eventos de los controles que él contiene. En este caso no es necesario que el formulario se desuscriba, ya que cuando se destruya el objeto Form, todos los controles que este contiene son destruídos también. Pero, si este mismo formulario está suscrito a un evento que lanza un objeto que puede vivir después de que el Form haya sido eliminado, entonces el formulario debe desuscribirse de este evento, cuando vaya a ser eliminado. Esto se suele hacer en el método Dispose().

El código para desuscribirse a un evento es trivial (se usa el operador –=):

_ciudad.HabitanteDadoDeAlta -= Ciudad_HabitanteDadoDeAlta;

No es posible desuscribirse a un evento al cual se haya suscrito a través de una expresión lambda, ¡o sea que mucho ojo con esto!

Y bueno… creo que ya está bien por hoy. Como siempre en los posts de esta serie de C# Básico, hemos empezado por lo elemental y hemos introducido algunas pinceladas más avanzadas. No sufras si no lo entiendes todo a la primera, hay una receta muy fácil: ¡practica y pregunta!

¡Un saludo y feliz 2012!

con 1 comment(s)
Archivado en: ,

Muy buenas!

Coged a alguien que no conozca mucho ASP.NET y preguntadle que relación tienen las siguientes clases entre ellas:

  1. HttpRequest
  2. HttpRequestBase
  3. HttpRequestWrapper

La respuesta más probable será que HttpRequestBase es la clase base, de la cual deriva HttpRequest y que HttpRequestWrapper es… bueno, por el nombre no queda muy claro: es un wrapper de algo pero de qué?

Pues no. Nada más lejos de la realidad. Aunque el nombre sugiera lo contrario HttpRequestBase no es la clase base de HttpRequest, de hecho ambas clases no tienen relación alguna entre ellas, pero en Redmond no tuvieron un buen día al escoger el nombre… Aunque al menos debe reconocerse que HttpRequestBase sí es clase base de alguien… ¡concretamente de HttpRequestWrapper!

Dado que no hay mucha gente que conozca esas dos últimas clases, hagamos pues una pequeña presentación.

HttpRequestBase es una clase cuyo interfaz público (es decir sus métodos y propiedades) son idénticos a los de HttpRequest. ¿Que HttpRequest tiene una propiedad llamada Cookies? HttpRequestBase la tiene también… y del mismo tipo. Y así con todas las propiedades y todos los métodos.

¿Y por qué han decidido crear semejante… cosa?

Pues para corregir una carencia que tenía el Framework: permitir abstraernos de HttpRequest de forma fácil: HttpRequestBase tiene la misma interfaz pública que HttpRequest pero no está vinculada a ASP.NET. No requiere un pipeline web ejecutándose, ni nada de nada: es un simple contenedor de datos.

Por supuesto ASP.NET está construido alrededor de HttpRequest, lo cual sigue dificultando mucho los tests unitarios… ¿así que entonces? Bueno ASP.NET arrastra toda una historia y no es fácil (ni sensato) romper con todo, así que lo máximo que podemos exigir es que lo más nuevo sí se haga bien. Y lo más nuevo es ASP.NET MVC. En efecto, ASP.NET MVC está construido alrededor de HttpRequestBase y no de HttpRequest.

P.ej. se puede acceder a la request desde un controlador:

var queryString = ControllerContext.HttpContext.Request.QueryString;

Pero si observáis con detalle veréis que la propiedad Request no es de tipo HttpRequest si no de tipo HttpRequestBase:

image

¿Y como podemos usar esto en nuestros tests unitarios?

Imaginad un controlador que tenga el siguiente código:

public ActionResult About()

{

    var queryString = ControllerContext.HttpContext.Request.QueryString;

    var modelo = new FooModel();

    modelo.Nombre = ControllerContext.HttpContext.Request.QueryString["p1"];

    return View(modelo);

}

El controlador recoge el valor del parámetro “p1” de la querystring y establece la propiedad Nombre del viewmodel con este valor (Nota: ¡Este código es solo para demostrar lo que se comenta en este post, en ASP.NET MVC hay maneras mejores de hacer esto!)

Ahora vamos a ver como sería un test unitario que probase este método. Para ello… y aquí es donde entra en juego HttpRequestBase: nos permite crear un mock o un fake de ella:

public class HttpRequestFake : HttpRequestBase

{

    public override NameValueCollection QueryString

    {

        get           

        {

            var values = new NameValueCollection();

            values.Add("p1", "v1");

            return values;

        }

    }

}

Ya tenemos nuestro fake de HttpRequestBase para que devuelva una querystring fijada por nosotros (donde el parámetro p1 valga v1).

Ahora nos toca hacer un par de fakes más: de HttpContextBase (para que devuelva nuestro objeto Request) y de ControllerContext para que nos devuelva el fake de HttpContextBase:

public class HttpContextFake : HttpContextBase

{

    public override HttpRequestBase Request

    {

        get

        {

            return new HttpRequestFake();

        }

    }

}

public class ControllerContextFake : ControllerContext

{

    public ControllerContextFake()

    {

        HttpContext = new HttpContextFake();

    }

 

    public override HttpContextBase HttpContext { get; set; }

}

Y finalmente ya podemos tener nuestro test unitario:

[TestMethod]

public void TestMethod1()

{

    var controller = new HomeController();

    controller.ControllerContext = new ControllerContextFake();

    var result = controller.About() as ViewResult;

 

    Assert.AreEqual("v1", (result.ViewData.Model as FooModel).Nombre);

}

Y listos! Con esto comprobamos que el controlador hace lo que se supone que debe hacer (asignar el valor del parámetro p1 de la querystring a la propiedad Nombre del viewmodel).

Gracias al hecho de que HttpRequestBase y HttpContextBase NO están ligadas a ningún pipeline de ASP.NET y de que ASP.NET MVC ha sido construído en torno a ellas y no a las reales HttpRequest y HttpContext nos es mucho más fácil la realización de tests unitarios.

Espero que os haya sido de interés! ;-)
Un saludo!

Ah sí… y HttpRequestWrapper? Pues HttpRequestWrapper no es nada más que una clase que deriva de HttpRequestBase y que sirve para… convertir un objeto HttpRequest en un HttpRequestBase… ya, probablemente un nombre mejor para ella hubiese sido HttpRequestAdapter. ;-)

con no comments
Archivado en:

Muy, muy, muy molesto…  ASP.NET MVC3 corriendo sobre un servidor web configurado en español (cultura es-ES).

Con la tabla de rutas estándar, cuatro acciones como las siguientes

  1. [HttpPost]
  2. public ActionResult Index(DoubleModel model)
  3. {
  4.     ViewBag.Valor = model.Valor;
  5.  
  6.     return View("Resultado");
  7. }
  8.  
  9. [HttpPost]
  10. public ActionResult IndexSoloDouble(double? valor)
  11. {
  12.     ViewBag.Valor = valor;
  13.  
  14.     return View("Resultado");
  15. }
  16.  
  17.  
  18. public ActionResult IndexRuta(double? id)
  19. {
  20.     ViewBag.Valor = id;
  21.     return View("Resultado");
  22. }
  23.  
  24. public ActionResult IndexGet(double? valor)
  25. {
  26.     ViewBag.Valor = valor;
  27.     return View("Resultado");
  28.     
  29. }

La clase DoubleMode simplemente tiene una propiedad double llamada Valor.

  1. La primera acción recibe un double via POST  dentro de un modelo
  2. La segunda recibe un double via POST sólo
  3. La tercera acción recibe un double como parámetro de ruta
  4. La cuarta acción recibe un double via querystring

Pues bien:

  1. La primera acción acepta 12,10 pero no 12.10 (usa la cultura del servidor web)
  2. La segunda acción acepta 12,10 pero no 12.10 (usa la cultura del servidor web)
  3. La tercera acción acepta 12.10 pero no 12,10 (usa la cultura ¿invariante?)
  4. La cuarta acción acepta 12.10 pero no 12, 10 (como la tercera, parece usar la invariante).

Aquí tenéis el código de la vista usada para enviar los datos:

  1. @using MvcApplication1.Models
  2. @model DoubleModel
  3.  
  4. @{
  5.     ViewBag.Title = "Index";
  6. }
  7.  
  8. <h2>Index</h2>
  9.  
  10. @{
  11.     Html.EnableClientValidation(false);
  12. }
  13.  
  14. @using (Html.BeginForm()) {
  15.     <div>
  16.     Introduce el valor:
  17.     <input type="hidden" name="Valor" value="12,10" />
  18.     </div>
  19.     <input type="submit" value="enviar 12,10 via POST" />
  20. }
  21.  
  22. @using (Html.BeginForm()) {
  23.     <div>
  24.     <input type="hidden" name="Valor" value="12.10" />
  25.     </div>
  26.     <input type="submit" value="enviar 12.10 via POST" />
  27. }
  28.  
  29.  
  30. @using (Html.BeginForm("IndexSoloDouble","Home"))
  31. {
  32.     <div>
  33.     <input type="hidden" name="Valor" value="12,10" />
  34.     </div>
  35.     <input type="submit" value="enviar 12,10 via POST (solo double)" />
  36. }
  37.  
  38.  
  39. @using (Html.BeginForm("IndexSoloDouble", "Home"))
  40. {
  41.     <div>
  42.     <input type="hidden" name="Valor" value="12.10" />
  43.     </div>
  44.     <input type="submit" value="enviar 12.10 via POST (solo double)" />
  45. }
  46.  
  47.  
  48.  
  49. @Html.ActionLink("Enviar 12,10 via GET", "IndexGet", new { valor = "12,10" }); | @Html.ActionLink("Enviar 12.10 via GET", "IndexGet", new { valor = "12.10" });
  50. <br/>
  51. @Html.ActionLink("Enviar 12,10 en ruta", "IndexRuta", new { id = "12,10" }); | @Html.ActionLink("Enviar 12.10 en ruta", "IndexRuta", new { id = "12.10" });

El problema parece estar en los ValueProviders de URL (RouteDataValueProvider y QueryStringVaueProvider), que parecen ignorar la cultura, mientras que el ValueProvider de POST (FormValueProvider) la respeta.

Lo dicho… muy molesto :(

Saludos!

Editado 16:43. PD: Iván Loire (@ivanloire) me comenta que este comportamiento es por diseño: los locales NO se aplican en los parámetros de URL para mantener las URLs canónicas. Si usáramos los locales del usuario podríamos tener URLs distintas que sirviesen el mismo contenido, y URLs válidas para un usuario podrían no serlo para otro. Es por ello que en MVC han optado por usar la cultura invariante en los ValueProviders de la URL. Dejo dos enlaces que me ha pasado Iván a dos posts donde se explica esto: http://bit.ly/tIZX64 y http://bit.ly/vCgbmf

con no comments
Archivado en:

Muy buenas! Para ser sinceros esta es una pregunta que me he hecho siempre y, creo yo, que se han hecho muchas personas que vienen de C++. ¿Debería tener C# referencias const? El hecho es que hasta ayer no había encontrado una explicación razonada y de alguien de peso (quien mejor que Eric Lippert, cuyo blog es lectura obligada) del porque C# no las incluye. Al final del post hay el enlace al post de stackoverflow en el que Eric explica sus razones por las que C# no tiene referencias const.

En este post voy a intentar explicar que son las referencias const (en C++) porque a Eric Lippert no le convencen (ojo que no queda claro con una sola lectura de lo que él dice, tuve que leérmelo un par o tres de veces junto con los comentarios, porque Eric es de los que cuando dice algo cualquier palabra cuenta), además de algunas opciones que hay actualmente en C# para simularlas.

El objetivo del post es captar vuestra opinión: es decir, creéis que estarían bien? O que sobran totalmente? O que tal y como están en C++ no, pero de otra forma podrían ser interesantes?

Dado que voy a exponer varias opiniones al respecto este post será largo… Pero espero que os resulte de interés ;-)

Referencias const en C++

Antes que nada aclaremos a que nos referimos por referencias const. Porque hay dos posibilidades:

  1. La referencia es constante, es decir, una vez inicializada su valor NO puede ser modificado (no puede apuntar a otro objeto). En C# eso sería una variable readonly.
  2. La referencia NO es constante pero a través de esa referencia NO puede modificarse el objeto apuntado.

En C++ ambas posibilidades existen, así que NO es lo mismo:

Foo& const foo

que

const Foo& foo

En el primer caso tenemos una referencia que es constante (básicamente lo que en C# conocemos como readonly, con la salvedad que pueden ser inicializadas en cualquier momento), mientras que en el segundo tenemos una referencia a través de la cual no podemos modificar el objeto. A esas referencias son a las que nos referimos con el nombre de “Referencias const”.

En C++ una clase debe declarar que métodos NO modifican los datos de la clase:

class Foo

{

    int value;

    public:

        int GetValue (void) const;

        void SetValue(int nv);

};

La clase Foo tiene dos métodos, GetValue y SetValue. Y el método GetValue está declarado como “const” para indicar que no modifica ningún miembro de la clase.

Así, el siguiente código NO compilará:

void FooConst1 (const Foo& foo)

{

    foo.SetValue(10);

}

El compilador nos avisará que estamos intentando modificar un objeto Foo a través de una referencia const (el error real es un mensaje raro de que no puede convertir this pero quiere decir eso :p).

Usar una referencia const convierte el objeto en inmutable? No. Usar una referencia const evita que pueda ser modificado dicho objeto a través de esa referencia. Única y exclusivamente. Mira ese código y piensa cual es el valor de i después de ejecutar la función Bar:

void Bar(const int& v1, int& v2)

{

    v2 = v1+1;

}

int _tmain(int argc, _TCHAR* argv[])

{

    int i=10;

    Bar(i, i);

}

El valor de i después de ejecutar Bar es de 11. Porque el valor de i se puede modificar a través de v2 que es una referencia tradicional (por supuesto intentar v1=v2+1 dentro de Bar sí que da error).

Ventajas de tener referencias const en el lenguaje

Como desarrollador el tener referencias const te permite:

  1. Tener la seguridad de que un método no va a modificar nada de tu clase. Si un método espera una referencia const sabes que el método no va a poder modificar el objeto que reciba.
  2. Devolver desde un método un objeto que no va a poder ser modificado por quien ha llamado el método. P.ej. en lugar de devolver una ReadOnlyCollection<T> (una clase que es una auténtica aberración desde el punto de vista de OOP) se podría devolver const List<T>.

La razón (de Eric Lippert) por la cual no existen en C#

En resumidas cuentas porque tal y como están implementadas en C++ no sirven. Él tiene dos objeciones al respecto, una muy pragmática y la otra mucho más profunda y filosófica.

  1. El casting, bien al estilo C, bien const_cast de C++, elimina la seguridad que podrían ofrecer las referencias const. Dicho de otro modo, la posibilidad de obtener una referencia tradicional a partir de una referencia const, convierten a éstas totalmente en inútiles (en lo que refiere a asegurar que a través de dicha referencia nadie podrá modificar el objeto). Imaginad que yo devuelvo una const List<T> con datos de mi objeto. Si quien obtiene la referencia puede a partir de esa referencia const, obtener una referencia tradicional (y por lo tanto añadir o eliminar elementos de la lista), yo pierdo la seguridad que tenía al devolver la referencia const. No puedo asumir que los contenidos del objeto no van a ser modificados. A eso se refiere Eric cuando dice que const is broken.
  2. La segunda razón es mucho más fuerte… Para Eric una referencia const sería realmente útil si convirtiese el objeto apuntado en inmutable. Lo deja muy claro con el siguiente comentario: If I have a reliably constant queue then I should be able to say "if (!q.Empty()) { M(); x = q.First(); }" and regardless of what M() does, the queue is still empty when it returns. The same way that if I say "const int y = 123; ... if (y > 0) { M(); " and after M() returns, y is still greater than zero because it is constant.

Es decir, del mismo modo que const int i=10; significa que la variable i nunca podrá ser modificada bajo ningún concepto, del mismo modo const Queue  q debería significar que el objeto q es inmutable. No puede modificarse. Eso plantea problemas muy serios en cuanto a como inicializamos estos objetos, o como convertimos un objeto mutable en inmutable, etc, etc. Pero quedaros con la clave: el objeto pasa a ser inmutable. No hay manera de echar esto atrás. No es una referencia const a un objeto, es una referencia a un objeto constante. Esa segunda razón es muy fuerte y va mucho más allá de lo que una referencia const de C++ pretende (que el objeto no sea modificable a través de esa referencia).

Olvidemos pues esa segunda razón, a pesar de que para Eric tiene un peso fundamental, y volvamos a la primera: la posibilidad de hacer casting y de obtener una referencia normal a partir de una referencia const. La solución parece rápida y trivial: se prohíben esos castings. Y punto.

Y como pasa casi siempre, nada es tan trivial:

  1. Que hacemos con reflection? Si alguien usa reflection teoricamente podría llamara a un método que modificase el objeto apuntado por la referencia (aquí sale a colación de nuevo el segundo argumento de Eric: dado que el objeto no es inmutable, sino que es la referencia la que está marcada como const). Es eso importante? Bueno, depende… actualmente con reflection podemos llamar a métodos privados de una clase, lo que (sin negar su utilidad) puede llegar a representar una violación todavía mayor.
  2. Que hacemos con object? O dicho de otro modo… una referencia const Foo puede ser pasada a un método que acepte Object? Seguramente sí, responderéis todos, ya que los Foo son Objects con independencia de que sean mutables o no. Pero ahora imaginad una jerarquía de clases:

    class Foo

    {

        public int PropFoo { get; set; }

    }

 

    class Bar : Foo

    {

        public string PropBar { get; set; }

    }

Si un método recibe un objeto Foo, le puedo pasar un const Bar? Los objetos Bar son todos ellos objetos Foo, pero los const Bar lo son? Tiene sentido que lo sean? Tiene sentido que no lo sean? O los objetos const Bar sólo pueden ser const Foo? Dicho de otro modo: tenemos UNA sola jerarquía de objetos que empieza por Object, o tenemos DOS (una que empieza por Object y la otra por const Object)? Si admitimos un sola jerarquía, entonces estaremos de acuerdo en que yo puedo tener un const Bar y ese ser modificado (al menos la propiedad PropFoo) a través de un método que espere un Foo.

Por supuesto hay versiones mixtas, como tener una sola jerarquía, pero sólo admitir llamadas a métodos que no muten el objeto. De esa manera una referencia const Bar podría ser pasada a un método Foo y eso sólo compilaría si el método no hace nada que pueda mutar el objeto (p.ej. estaría bien llamar al getter de la propiedad pero no al setter). Pero eso complica mucho (pero mucho, eh?) el tema y es que de hecho con esta visión estamos volviendo al segundo punto que comentaba Eric (no es la referencia lo que está marcada como const, es el objeto que está marcado como inmutable).  Y de todos modos para mi esa opción es inviable el hecho de que no hay nada que diga a priori a quien llama el método si esa llamada es correcta o no: que el código compilase dependería de lo que hiciera internamente un método X (que puede estar en otro assembly ya compilado) y no de sus parámetros, ni nada que yo pueda ver externamente. Lo único que podría hacer es intentar pasar mi referencia const Bar a un método que acepta un Foo y… ver si compila! Surrealista.

¿Son necesarias las referencias const tal y como están en C++?

Dicho de forma rápida y corta: No. Eso no significa que no sean útiles. Simplemente que hay otros métodos para hacer lo mismo. ¿Y en que consisten esos métodos? Pues básicamente en declararse una versión solo lectura de mi clase. La mejor manera de hacer esto es a través de una interfaz:

    interface IReadOnlyFoo

    {

        int PropFoo { get; }

    }

    class Foo : IReadOnlyFoo

    {

        public int PropFoo { get; set; }

    }

Si quiero devolver una referencia a un objeto Foo que no pueda modificarse devuelvo un objeto Foo a través de una referencia de tipo IReadOnlyFoo. Y listos! Si un método quiere aceptar como parámetro un objeto de tipo Foo pero NO quiere modificarlo puede aceptar un parámetro de tipo IReadOnlyFoo. Así el equivalente de const Foo es IReadOnlyFoo.

Por lo tanto la misma semántica que obtenemos a través de las referencias const, las obtenemos con este mecanismo. Cierto: es más tedioso y nos obliga a hacerlo por cada clase de la cual querramos tener una versión de sólo lectura.

Métodos puros

Una de las ventajas de la implementación de referencias const en C++ es que obliga al creador de la clase a declarar que métodos son susceptibles de ser llamados a través de una referencia const. A estos métodos se los conoce en C++ como métodos const. El compilador comprueba efectivamente que un método declarado como const lo sea:

class Foo

{

    int value;

    public:

        int GetValue (void) const;

        void SetValue(int nv);

};

 

int Foo::GetValue(void) const

{

    value--;

    return value;

}

Este código no compila, porque el método GetValue intenta modificar value a pesar de ser declarado como método const.

Debajo de los métodos const subyace un concepto realmente potente e interesante: los métodos puros. Dicho rápidamente, un método puro (no confundir con un método virtual puro de C++ que no tiene nada que ver) es aquel que siempre devuelve el mismo valor si se le pasan los mismos parámetros y además durante su ejecución no se observa ningún efecto colateral (en el caso de una clase significa que no modifica el estado de dicha clase). Ojo, que los métodos const y los métodos puros NO son exactamente lo mismo:

  1. Un método const puede ser impuro si devuelve datos aleatorios o dependientes de algo externo no controlable (p.ej. un fichero). Así p.ej. la propiedad Now de DateTime no es pura porque su valor no es predecible (cada día cambia!).
  2. Un método const NO puede modificar ningún miembro de la clase. Pero NO todos los miembros de la clase conforman su estado. Puede haber miembros privados que sean susceptibles de ser modificados pero que no formen parte del estado de la clase. Un método puro podría modificar esos miembros y un método const no (aunque este segundo punto puede solventarse con el uso de mutable en C++).

¿Y porque son interesantes los métodos puros? Bueno… tenerlos identificados permite ciertas optimizaciones (se puede memoizar (así sin erre) la llamada) y también habilita estos métodos para ser usados en comprobaciones de precondiciones, postcondiciones e invariantes. Por definición la comprobación de precondiciones, postcondiciones e invariantes deben ser métodos puros (dado que deben ser predecibles y no modificar el estado del objeto). Sin métodos puros el diseño por contratos no es posible (de forma segura y validada por el compilador)…

¿Hola? Si todavía estás aquí… muchas gracias por llegar hasta el final del post. Recuérdame que te pague una cervecita cuando nos veamos por ahí ;-)

He creado una encuesta para que podáis expresar vuestra opinión al respecto de todo lo comentado y por supuesto, tenéis los comentarios para explayaros a gusto! ;-)

Enlace a la encuesta: http://www.easypolls.net/poll.html?p=4eb26acb011eb0e44d6335ab

Enlace al post de stackoverflow: http://stackoverflow.com/questions/3263001/why-const-parameters-are-not-allowed-in-c-sharp

Un saludo! :)

con 2 comment(s)
Archivado en: ,

¡Hola! Un compañero me ha preguntado si era posible enlazar una propiedad (de tipo int) a un control slider de jQuery UI. La verdad es que sí que es posible y vamos a ver en este post una posible solución que de hecho es extrapolable a otras situaciones parecidas que podáis tener.

Templated helpers al rescate

En ASP.NET MVC2 introdujeron el concepto de templated helpers un mecanismo para construir la interfaz de usuario a partir del tipo de datos del modelo. Simplificando un poco, si colocamos en la carpeta DisplayTemplates y EditorTemplates una vista parcial ASP.NET MVC usará esta vista automáticamente cada vez que se use el método Html.DisplayFor o Html.EditorFor respectivamente.

Si tenemos un Modelo de tipo X que tiene una propiedad, llamémosle Foo, cuyo tipo sea BarType, si hacemos:

  1. @Html.EditorFor(x=>x.Foo)

ASP.NET MVC buscará la vista EditorTemplates/BarType (el nombre de la vista es el tipo de la propiedad usada en EditorFor).

Como esta regla del nombre de tipo puede ser demasiado genérica, también es posible usar el atributo [UIHint] indicando el nombre del template (la vista parcial) a usar para editar o mostrar los datos:

  1. public class FooModel
  2. {
  3.     [UIHint("FooEdit")]
  4.     public BarType Foo { get; set; }
  5. }

Ahora la llamada a Html.EditorFor, buscará la vista llamada FooEdit en EditorTamplates.

Vamos pues a crear el template para editar y visualizar una propiedad de tipo int usando el slider de jQuery UI.

Para ello creamos una vista parcial en Views/Shared/EditorTemplates y le damos el nombre que queramos, en mi caso slider.cshtml. Para crear un slider basta con tener un <div> y luego llamar al método slider() (doy por supuesto que jQuery UI se ha descargado y está referenciada). Así que empezaremos con este código:

  1. <script type="text/javascript">
  2.     $(document).ready(function () {
  3.         $("#slider").slider();
  4.     });
  5. </script>
  6. <div id="slider">
  7. </div>

Con esto creamos el slider pero dado que estamos en modo edición, necesitamos alguna manera para guardar el valor que el usuario seleccione. Una forma rápida de hacerlo es tener un hidden que mantenga en todo momento el valor que el usuario seleccione en el slider. Y aquí nos surge la primera duda: que valor ha de tener el atributo name para que luego ASP.NET MVC sea capaz de reconocerlo y enlazarlo a la propiedad del viewmodel? El problema es que el valor del atributo name depende del nombre de la propiedad que estamos enlazando así que hemos de recuperar este nombre… Por suerte podemos saber este nombre, usando la propiedad HtmlFieldPrefix de la propiedad TemplateInfo del ViewData. Es decir, podemos generar el campo hidden así:

  1. <input type="hidden" name="@ViewData.TemplateInfo.HtmlFieldPrefix"/>

Finalmente sólo nos queda suscrbirnos al evento change del slider y actualizar el campo hidden. Para ello deberemos añadir un id (he usado slider_hidden) al campo hidden y usar el siguiente código para crear el slider:

  1. <script type="text/javascript">
  2.     $(document).ready(function () {
  3.         var options = {
  4.             change: function (event, ui) {
  5.                 $("#slider_hidden").val(ui.value);
  6.             }     
  7.         };       
  8.         $("#slider").slider(options);
  9.     });
  10. </script>

Con eso ya podemos crear un ViewModel con una propiedad int, decorada con [UIHint(“silder”)] y observar como aparece el slider para editar la propiedad. P.ej. dado el siguiente ViewModel:

  1. public class Ratio
  2. {
  3.     public string Texto { get; set; } 
  4.     [UIHint("slider")]
  5.     public int Rating2 { get; set; }
  6. }

Una vista para editar un objeto Ratio sería tan sencilla como:

  1. @using MvcSliderBinding.Models
  2. @model Ratio
  3. @{
  4.     ViewBag.Title = "title";   
  5. }
  6. @using (Html.BeginForm())
  7. {
  8.     @Html.LabelFor(x => x.Texto)
  9.     @Html.EditorFor(x => x.Texto)
  10.     <br />
  11.     @Html.LabelFor(x => x.Rating)
  12.     @Html.EditorFor(x => x.Rating)
  13.     
  14.     <input type="submit" />
  15. }

Al usar @Html.EditorFor(x=>x.Rating), al ser Rating una propiedad decorada con UIHint(“slider”) se va a usar el editor template que hemos creado antes.

Ya tenemos enlazado una propiedad con el slider de jQuery! Ahora vamos a pulir detalles…

Preparándolo para que pueda haber más de un slider

El template de edición que hemos creado NO admite ser repetido en una misma vista, ya que usa ids estáticos para el <div> que será el slider y el hidden que contiene el valor. Si tuviéramos un viewmodel que tiene dos propiedades y quisiéramos usar dos sliders no nos funcionaría bien. Para solucionarlo nos basta con asegurar que los IDs son siempre distintos. Hay varias maneras, una es usar como id un prefijo y el valor que nos da HtmlFieldPrefix, ya que ese se supone único. Otro es usar un GUID, como se muestra a continuación:

  1. @model Nullable<int>
  2. @{
  3.     var suffix = Guid.NewGuid().ToString(); 
  4. }
  5. <script type="text/javascript">
  6.     $(document).ready(function () {
  7.         var options = {
  8.             value: @(Model.HasValue ? Model.Value : min),
  9.             change: function (event, ui) {
  10.                 $("#@suffix").val(ui.value);
  11.             }
  12.         };  
  13.         $("#slider_@(suffix)").slider(options);
  14.     });
  15. </script>
  16. <div id="slider_@(suffix)">
  17. </div>

Guardamos en suffix el GUID creado y lo añadimos al hidden y al div. Un detalle más que aprovecho para enseñaros es establecer el valor inicial del slider al valor que tenga la propiedad (que está en Model). Pero, si estamos creando el objeto el valor de Model será null. Es por ello que debo declarar que el modelo de la vista es de tipo Nullable<int>, en lugar de int, para poder aceptar esos valores nulos.

Accediendo a la información del viewmodel

Una cosa muy interesante al usar template helpers, es que nuestro template helper puede acceder a información del viewmodel que define la propiedad. Es decir, dentro del template helper, yo puedo saber cual es la propiedad que estoy editando (en mi caso es Rating) y tengo información sobre la clase que define dicha propiedad (en mi caso Ratio). A esos datos se puede acceder a través de ViewData.ModelMetadata:

image

Podeis usar las propiedades de ViewData.ModelMetadata para realizar ciertas tareas, como analizar el ViewModel en busca de atributos que os puedan ayudar a definir como renderizar el template… lo que se os ocurra.

Un ejemplo de esto, imaginad que tenemos una propiedad decorada con el atributo [Range], para indicar que acepta un rango de valores. Pues desde aquí podríais consultar dicho atributo Range (teneis acceso al Type del ViewModel y sabeis el nombre de la propiedad) y configurar el slider para que sólo acepte entradas en este rango.

Es lo que he hecho yo, salvo que en lugar de usar reflection para acceder al atributo Range, lo que he hecho es obtener los validadores que hay asociados a la propiedad y generar código acorde a ellos. Para ello uso el método GetVaidators() de ModelMetadata:

  1. @model Nullable<int>
  2. @{
  3.     var suffix = Guid.NewGuid().ToString();
  4.  
  5.     bool hasRange = false;
  6.     var rangeVal = ViewData.ModelMetadata.GetValidators(ViewContext.Controller.ControllerContext).OfType<RangeAttributeAdapter>().FirstOrDefault();
  7.     ModelClientValidationRangeRule rule = null;
  8.     var min = 0;
  9.     var max = -1;
  10.     if (rangeVal != null)
  11.     {
  12.         rule = rangeVal.GetClientValidationRules().OfType<ModelClientValidationRangeRule>().FirstOrDefault();
  13.         if (rule != null)
  14.         {
  15.             min = Convert.ToInt32(rule.ValidationParameters["min"]);
  16.             max = Convert.ToInt32(rule.ValidationParameters["max"]);
  17.         }
  18.     }
  19.  
  20. }

Llamo a GetValidatos y busco el validador de tipo RangeAttributeAdapter. Eso es lo mismo que buscar si existe un atributo [Range], salvo que es más genérico (aunque no entraremos en detalles, simplemente comentar que DataAnnotations es una manera de añadir validadores, pero pueden haber más). Si existe el validador de rango, obtengo su configuración, en concreto sus valores mínimo y máximo, y me los guardo. Con esto ahora tengo información para generar un slider que sólo acepte valores en este rango:

  1. <script type="text/javascript">
  2.     $(document).ready(function () {
  3.         var options = {
  4.          @if (rule!=null)
  5.          {
  6.             @:min: @min,
  7.             @:max: @max,
  8.          }
  9.             value: @(Model.HasValue ? Model.Value : min),
  10.             change: function (event, ui) {
  11.                 $("#@suffix").val(ui.value);
  12.             }
  13.         };       
  14.         $("#slider_@(suffix)").slider(options);
  15.     });

Listos! Ahora tenemos un editor que además respeta el validador de rango que tenga la propiedad.

Y así podríamos ir perfilando este template de edición con todo lo que necesitáramos para adaptarlo a nuestras necesidades… ¡Simple, sencillo y potentísimo!

Os dejo un proyecto VS2010 con la implementación del template de edición y uno de visualización para que podáis verlo en acción: https://skydrive.live.com/?cid=6521c259e9b1bec6&sc=documents&uc=1&id=6521C259E9B1BEC6%21167#

Espero que os haya sido útil! Un saludo!

con no comments
Archivado en:

NOTA: Este post está basado en la versión Developers Preview de Windows 8, que salió en Septiembre del 2011. Versiones posteriores pueden dejar (y con suerte dejarán) este artículo obsoleto.

Un post cortito: Si desarrollas aplicaciones Metro para Windows 8 usando C# y XAML no uses ObservableCollection<T>. Simple y llanamente no funciona.

En su lugar debe usarse IObservableVector<T> interfaz de la cual podéis encontrar una implementación aquí: http://code.msdn.microsoft.com/Data-Binding-7b1d67b5/sourcecode?fileId=44725&pathId=1428387049. Esa implementación proporciona además un método ToObservableVector para convertir una INotifyCollectionChanged (es decir una ObservableCollection<T>) en un IObservableVector<T>.

Relacionado con el tema: ojo con implementar INotifyPropertyChanged. En concreto, ojo con cual implementas pues resulta que ahora hay dos! Por un lado está el INotifyPropertyChanged de toda la vida (System.ComponentModel.INotifyPropertyChanged) y por otro uno nuevo que es el que usa WinRT: Windows.UI.Xaml.Data.INotifyPropertyChanged. Ese último es el que tenéis que utilizar en vuestros ViewModels.

Desconozco el porque de estos cambios (no usar INotifyCollectionChanged ni el INotifyPropertyChanged de toda la vida), aunque supongo que tienen que ver en no usar colecciones ni eventos propios de .NET y tenerlo todo controlado dentro del API de WinRT. También supongo que en siguientes versiones de Windows 8 eso se arreglará.

Así que si no queréis, como yo, perder un buen rato preguntándoos porque no se actualiza una ListBox… ya sabéis! ;-)

Un saludo!

PD: Algunos enlaces que he encontrado buscando acerca de esto:

  1. ObservableCollection no funciona: http://social.msdn.microsoft.com/Forums/en-AU/winappswithcsharp/thread/054913c2-6ad4-4b54-a349-c7ae846d4f8e
  2. Selecciona el INotifyPropertyChanged correcto en WinRT: http://blog.galasoft.ch/archive/2011/09/25/quick-tip-select-the-correct-inotifypropertychanged-in-windows-8.aspx –> Según menciona aquí esto parece que ya está corregido y que la nueva versión de Win8 ya no tendrá ese error.
con 4 comment(s)
Archivado en: ,,

Muy buenas!

Cada vez más nos encontramos con aplicaciones web que funcionan “en una sola página”, es decir que se carga la página inicial y luego todas las nuevas peticiones son via AJAX. Esas aplicaciones funcionan perfectamente hasta que el usuario le daba a atràs o a F5 para refrescar la página: en este momento se pierde el estado de la navegación.

Hasta ahora no había una manera estándard y sencilla para lidiar con esto, pero HTML5 ya está aquí y incluye una nueva API de historial que nos va ayudar con estos casos. Aunque hay más, voy a centrarme en este post en dos elementos de dicha API:

  • El evento popstate. Este evento de window se lanza cuando se navega a una dirección del historial. Bueno, sí, la definición es un poco ambigua pero básicamente traducido es que se lanza cuando se pulsa el botón de atrás en el navegador.
  • El método pushState del objeto history. Este método permite meter una entrada en el historial. Esa entrada consta de un objeto con datos arbitrarios y una url. Cuando llamemos a pushState la barra de direcciones se modificará para mostrar la nueva url, pero el navegador no navegará hacia allí. En su lugar habrá añadido una entrada ficticia en el historial con los datos que nosotros le hayamos indicado. Cuando se pulse atrás en el navegador, se lanzará el evento popstate y en él podremos recuperar esos datos y simular lo que sea que tenga que simularse para darle la sensación al usuario de que el botón de atrás funciona.

Sí, parece un poco lioso, pero en este post veremos como realizar una aplicación ASP.NET MVC que:

  1. Muestre una lista de productos y enlaces a los detalles
  2. Al pulsar en un detalle se muestre una página del producto
  3. Todo el refresco sea via ajax
  4. Al pulsar F5 el usuario se queda donde está. Es decir, si estaba viendo los detalles del producto 2 continuará viéndolos.
  5. El funcionamiento de back y forward será el esperado.
  6. En la barra de direcciones se mostrará la URL real que se está visitando, aunque esta se haya cargado via ajax.

En resumen, nuestra aplicación se va a comportar exactamente como se espera de una aplicación que no es ajax salvo que… usará ajax con todas las ventajas (menos refresco y mayor velocidad) que conlleva. Y lo mejor… no nos costará mucho hacerlo. ¡Viva HTML5!

1. La estrategia general

Tenemos que definir la estrategia general: por un lado mientras naveguemos por la aplicación todo serán peticiones ajax, pero si el usuario le da a F5, entonces ya no tendremos una petición ajax. En su lugar tendremos una petición estándard que tendremos que gestionar. Por lo tanto nuestros controladores deberán estar capacitados para servir la misma información les venga la petición por Ajax, o les venga de forma tradicional.

Para solventar esto, nos basta con meter todo el contenido en una vista parcial y devolver la vista parcial cuando la petición sea via Ajax. Cuando la petición sea tradicional entonces devolveremos una vista normal que lo único que hará será renderizar la vista parcial.

P.ej. Este es el código de la vista Home/Index.cshtml:

@{
ViewBag.Title = "Indice";
}

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

Simplemente renderizar Index_partial que es donde habrá el contenido. Luego nos basta un método en el controlador como el siguiente:

public ActionResult Index()
{
return Request.IsAjaxRequest() ?
(ActionResult)PartialView("Index_partial") :
(ActionResult)View();
}

Esa estrategia la repetiremos en todas las acciones de los controladores.

Bien, vayamos ahora a por las vistas. Este es el código de la vista Index_partial:

<div id="source">
<h2>Index</h2>
<a href="@Url.Action("List", "Products")" data-ajax="true" >Ver productos</a>
<br />
</div>

Tan sólo un enlace, con el atributo data-ajax=”true” y un div llamado source. El atributo data-ajax a true es importante porque es el que usaremos para interceptar este enlace y cargarlo via ajax. Por otro lado el div source también es importante porque es donde vamos a poner el contenido que nos venga de la petición ajax: es decir machacaremos todo nuestro contenido con el que nos venga de la petición ajax.

Con eso, ya tenemos la estrategia montada: Vamos a tener una vista “normal” y una parcial por cada acción y las vistas parciales serán las que realmente muestren el contenido.

2. Interceptar las llamadas a los links

Para esto vamos a valernos del atributo data-ajax que hemos creado antes. Este atributo es un atributo que me he inventado yo. En HTML5 te puedes inventar los atributos que te da la gana, siempre que empiecen por data-. Es una forma de estandarizar lo que antes hacíamos con clases CSS o bien inventándonos atributos. Pero a diferencia de usar una clase CSS (que usa algo pensado para aspecto para comportamiento) o inventarse un atributo cualquiera (que hace que el código deje de ser HTML válido) usar un atributo data-* no tiene ninguna contra-indicación. Así funcionan muchas de las características de HTML5: se han cogido muchas cosas que se hacían antes y se ha buscado una manera estándard de hacerlas!

Bueno, al tajo, vamos a crearnos un archivo myscripts.js al que vamos a meter algunas funciones, empezando por lo siguiente:

function bindLinks() {
$("a[data-ajax]").each(function () {
$(this).click(function (evt) {
evt.preventDefault();
ajaxload($(this).attr("href"), true);
});
});
}

La función bindLinks recorre todos los tags <a> que tengan el atributo data-ajax y les asigna una función gestora al evento click. Esa función gestora lo que hace es evitar que actúe el click estándard (por lo que NO navegaremos a través del link) y luego llama a ajaxload que es un método que veremos a continuación pasándole el valor del atributo href del tag <a> pulsado.

Ya tenemos todos los clicks de los enlaces interceptados. Para que esto se ejecute nos basta con asegurar que al cargar la página se llame a bindLinks. Para esto en la página de Layout añadimos:

<script type="text/javascript">
$(document).ready(function () {
bindLinks();
});
</script>

Finalmente la definición de la función ajaxload es casi trivial, ya que lo único que hace es llamar a load() de jQuery para cargar los datos y meterlos dentro del div source que hemos mencionado antes.

function ajaxload(url, add) {
$("#source").load(url, function () {
bindLinks();
});
}

El único detalle es que luego llama de nuevo a bindLinks() para volver a interceptar los clicks de los nuevos tags <a> que hayan aparecido!

3. Simular la modificación de la barra de direcciones

Vale, tenemos un enlace, que nos debería dirigir a una cierta URL, pongamos /Productos/Ver/10 pero en lugar de seguir el enlace, lo estamos cargando via ajax. Si queremos que el F5 funcione correctamente, debemos “modificar” la barra de direcciones, para que aparezca la URL “real”. Esto, que antes no se podía hacer, ahora es posible con la nueva History API de HTML5.

Cada vez que carguemos un enlace via Ajax añadiremos una entrada en el historial. Para ello usaremos el método pushState del objeto history. A este método se le pasan tres parámetros:

  1. Un objeto de estado. Es un objeto javascript cualquiera.
  2. Un título
  3. Una URL

El objeto de estado es una de las partes más importantes: cuando el usuario pulse el botón de atrás vamos a recibir el evento popstate y en este evento tendremos el objeto de estado. En este objeto pues nosotros podemos poner toda aquella información necesaria para que luego, en el evento popstate, podamos “deshacer” los cambios y darle la ilusión al usuario de que el botón de atrás funciona bien.

La URL que pasemos es la URL que se mostrará en la barra de direcciones. Pero sólo se mostrará, el navegador no navegará hacia allí. De nuevo es para engañar al usuario y hacerle creer que realmente ha ido a otra URL cuando en realidad simplemente hemos modificado el contenido de la página usando javascript (ajax).

Para modificar la barra de direcciones nos basta un pequeño añadido a la función ajaxload que teníamos antes:

function ajaxload(url, add) {
history.pushState({ uri: url }, '', url);
$("#source").load(url, function () {
bindLinks();
});
}

Cada vez que cargamos una vista via Ajax, añadimos una entrada en el historial. El primer parámetro es el objeto de estado. En este caso simplemente colocamos la URL “a la que navegamos” (simplemente porque no necesitaremos nada más). El tercer parámetro es la URL que mostrará el navegador en la barra de direcciones. Pero recordad: el navegador NO irá a esa URL (nosotros estamos cargando su contenido por ajax).

4. Soporte para back

Al modificar la barra de direcciones hemos dado soporte a F5. Porque al pulsar F5 el navegador refrescará la última entrada del historial que ahora contiene la URL que hemos puesto antes. Y recordad que nosotros antes hemos preparado los controladores para responder por Ajax y para responder a peticiones normales: por lo tanto en este punto tenemos una aplicación que se refresca totalmente por ajax… pero con soporte para F5. ¿No os parece genial?

Pues ahora vamos a añadir el soporte para el botón de atrás. Para ello nos vamos a basar en el evento popstate que se lanza cuando el navegador debe navegar a otra entrada del historial (básicamente cuando se pulsa el botón de back). En este evento recibimos el objeto de estado de la posición de historial que se está eliminando.

Vamos a modificar la página de Layout para añadir un manejador al evento popstate:

<script type="text/javascript">
$(document).ready(function () {
$(window).bind('popstate', function (evt) {
doPopstate(evt.originalEvent.state);
});
bindLinks();
});
</script>

Usamos el método bind() de jQuery para enlazar un manejador al evento popstate del objeto window y llamar a doPopstate, pasándole la propiedad state del evento (que es el objeto de estado).

La función doPopstate es nuestra y tiene el siguiente código:

function doPopstate(data) {
if (data != null) {
ajaxload(data.uri, false);
}
}

Si tenemos objeto de estado (teoricamente debemos tenerlo SIEMPRE, pero Chrome p.ej. al cargar una página por primera vez lanza un popstate sin objeto de estado, mientras que Firefox no lo lanza. Personalmente creo que es comportamiento de Firefox el correcto), nos limitamos a cargar via ajax la URL de este objeto de estado. Dado que están apilados será la URL anterior a la que estamos.

En este punto hemos tenido que añadir un parámetro a ajaxload (el booleano), para evitar que ajaxload nos añada una entrada de historial si estamos yendo hacia atrás. El nuevo código de ajaxload queda así:

function ajaxload(url, add) {
if (add) {
history.pushState({ uri: url }, '', url);
}
$("#source").load(url, function () {
bindLinks();
});
}

Por supuesto también modificamos la llamada a ajaxload dentro del manejador del evento de click de los enlaces en bindLinks(), para pasarle un true. Y con eso… hemos terminado.

Para entender exactamente que pasa he modificado ligeramente ajaxload y doPopstate para que hagan un log de lo que ocurre en un <div> de la página de Layout. Y eso es un poco más o menos lo que ocurre…

imageimage

imageimage

  1. Lanzamos la aplicación
  2. Pulsamos “Ver productos”
  3. Pulsamos “Detalle del producto 2”
  4. Pulsamos “Inicio”

Fijaos como en todo este tiempo el reloj de “Tiempo Actual” es siempre el mismo (estamos cargando via Ajax) y como la barra de direcciones va cambiando. También podéis ver el log en la parte inferior. Continuemos…

imageimage

imageimage

  1. Pulsamos “Ver productos”
  2. Pulsamos “Detalles del producto 1”
  3. Pulsamos BACK –> Fijaos en este punto en la aparición del evento popState y como recuperamos el evento del historial anterior.
  4. Pulsamos BACK de nuevo –> Otro evento popState y recuperamos la posición anterior.

De nuevo en todo este tiempo el reloj de “Tiempo actual” se ha movido: todo son refrescos Ajax. Sigamos…

imageimage

  1. Pulsamos “Ver Productos”
  2. Pulsamos F5 –> En este punto se lanza una petición “real”. Podeis ver como el reloj de “Tiempo actual” se ha modificado y el log ha desaparecido (normal se ha cargado toda la Layout de nuevo). Pero nos hemos quedado en la página donde estábamos (el listado de productos).

Bueno… os dejo el proyecto en VS2010 para que juguéis con él e investiguéis un poco como funciona el History API de HTML5.

Importante: El proyecto lo he probado con:

  1. Firefox 7.0 y funciona correctamente
  2. Chrome 15.0.874.83 y funciona correctamente
  3. Internet Explorer 9 y no funciona (no tiene soporte para History API)
  4. Opera 11.5 y funciona correctamente

Editado: Edito para informar que @wasat me ha comentado que Internet Explorer 10 soporta también History API. Para más info: http://msdn.microsoft.com/en-us/ie/hh272905.aspx#_HTML5History Gracias Jose por la información!!!!

Os dejo el enlace con el proyecto para que veais como está hecho y podais jugar con él: https://skydrive.live.com/?cid=6521c259e9b1bec6&sc=documents&uc=1&id=6521C259E9B1BEC6%21167#

Nota: También os dejo el enlace de History.js, que es un plugin de jQuery para soportar History API de forma consistente en todos los navegadores y que es mejor usar antes que meterse a hacerlo a mano: http://plugins.jquery.com/project/history-js No he mencionado el plugin antes porque el objetivo del post no era mostraros el plugin sino la nueva History API de HTML5.

Un saludo!!!!

con 8 comment(s)
Archivado en: ,,

Buenas! En este post vamos a ver como usar Recaptcha en ASP.NET MVC. Pero, antes que nada permitidme una aclaración: Si estás buscando integrar rápidamente Recaptcha en tu proyecto que sepas que puedes usar MvcRecaptcha o también el helper que viene en MVC3. Pero vamos a ver como hacerlo desde cero. ¿Por que? Pues simplemente porque me parece un buen ejemplo didáctico. Pero insisto: ya hay soluciones hechas, eso es sólo para ver como podríamos hacerlo desde cero

Añadir el captcha en una vista es sumamente sencillo: basta con incluir un tag <script> y dejar que él haga todo. También se puede crear usando javascript (lo que es útil si se quiere crear el captcha sólo si se cumplen ciertas condiciones en tiempo de ejecución), pero no vamos a verlo aquí (todos los detalles están en http://code.google.com/intl/ca/apis/recaptcha/docs/display.html en el apartado de “Ajax API”).

Para añadir recaptcha en nuestra página basta simplemente con añadir el siguiente código script:

<script type="text/javascript"
src="http://www.google.com/recaptcha/api/challenge?k=CLAVE_PUBLICA">
</script>

Este tag <script> renderizará el captcha en la posición donde se incluya.

Vamos a crearnos un helper que nos genere este tag script. El código es trivial:

public static class RecaptchaExtensions
{
public static IHtmlString Recaptcha(this HtmlHelper @this)
{
return Recaptcha(@this, "RecaptchaPublicKey");
}
public static IHtmlString Recaptcha(this HtmlHelper @this, string publicKeyId)
{
var publicKey = ConfigurationManager.AppSettings[publicKeyId];
return DoRecaptcha(@this, publicKey);
}

private static IHtmlString DoRecaptcha(this HtmlHelper @this, string publicKey)
{
var tagBuilder = new TagBuilder("script");
tagBuilder.Attributes.Add("type", "text/javascript");
tagBuilder.Attributes.Add("src", string.Concat("http://www.google.com/recaptcha/api/challenge?k=", publicKey));

return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal));
}
}

El método que realmente realiza el trabajo es el método privado DoRecaptcha, que usa un objeto TagBuilder para construir el tag <script>. Fijaos en que el valor de retorno de las funciones del helper es IHtmlString.

La función Recaptcha del helper recibe un parámetro que es el nombre del <appSetting> donde hay la clave pública de Recaptcha (hay una versión sin parámetreos que usa el <appSetting> cuya clave sea RecaptchaPublicKey.

Usar el helper es muy sencillo:

<div>
Necesitamos asegurarnos que eres humano. Actualmente sólo aceptamos
<i>Humanos estándar</i>:
@Html.Recaptcha()
</div>

Perfecto! Estamos listos para lo realmente interesante: Comprobar que el resultado que entra el usuario es válido.

Para ello, si consultamos la página donde se describe el proceso de verificación veremos que necesitamos 4 valores:

  1. La IP del cliente
  2. La clave privada de Recaptcha
  3. Dos valores adicionales, llamados challenge y response que nos envía recaptcha (son campos añadidos al formulario). Los nombres de los dos campos son recaptcha_challenge_field y recaptcha_response_field.

Bueno, para validar que el usuario ha dado de alta el captcha, lo podríamos hacer de muchas maneras, pero yo he escogido un filtro de acción. Eso me va a permitir decorar la acción del controlador de la siguiente manera:

[HttpPost]
[Recaptcha(Name="Captcha")]
public ActionResult Register(RegisterModel model)
{
//...
}

Si la validación con Recaptcha es errónea el flitro dejará un error en ModelState con la clave indicada en el parámetro Name (aquí el mensaje es fijo, pero por supuesto podría ser variable). El filtro lo configuraremos para que se ejecute antes de la acción, por lo que, dentro del método Register podremos usar ModelState.IsValid para preguntar si todo está correcto (incluyendo el captcha).

El uso de un filtro de acción es interesante porque elimina toda esa lógica de comprobación de la acción del controlador.

Bueno, si revisamos de nuevo la documentación de Recaptcha, vemos que debemos usar los 4 valores mencionados anteriormente y realizar un POST a la dirección http://www.google.com/recaptcha/api/verify. La respuesta de este POST nos indicará si la validación ha sido correcta (la primera línea valdrá true) o ha sido incorrecta (valdrá false). ¡Y ya está!

Para crear el filtro, derivamos de la clase ActionFilterAttribute y redefinimos el método OnActionExecuting, para que se ejecute justo ANTES de la acción del controlador:

public class RecaptchaAttribute : ActionFilterAttribute
{
public string Name { get; set; }

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.RequestContext.HttpContext.Request;
var challenge = request.Form["recaptcha_challenge_field"];
var response = request.Form["recaptcha_response_field"];
const string postUrl = "http://www.google.com/recaptcha/api/verify";
var result = PerformPost(request.UserHostAddress, challenge, response, postUrl);
if (!result)
{
filterContext.Controller.ViewData.ModelState.AddModelError
(Name ?? string.Empty, "Recaptcha incorrecto");
}
}

}

Este es el código básico: Recogemos los dos campos recaptcha_challenge_field y recaptcha_response_field, realizamos el POST y si el resultado NO es correcto añadimos un error usando el método AddModelError de ModelState.

El método PerformPost sería tal y como sigue:

private bool PerformPost(string remoteip, string challenge, string response, string postUrl)
{
var request = WebRequest.Create(postUrl);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
var stream = request.GetRequestStream();
var privateKey = ConfigurationManager.AppSettings["RecaptchaPrivateKey"];
using (var sw = new StreamWriter(stream))
{
const string data = "privatekey={0}&remoteip={1}&challenge={2}&response={3}";
sw.Write(data, privateKey, remoteip, challenge, response);
}
var recaptchaResponse = request.GetResponse();
string recaptchaData = null;
var recaptchaStream = recaptchaResponse.GetResponseStream();
if (recaptchaStream != null)
{
using (var sr = new StreamReader(recaptchaStream))
{
recaptchaData = sr.ReadToEnd();
}
return ParseResponse(recaptchaData);
}
else return false;
}

Usamos la clase WebRequest para realizar una petición POST con los campos indicados. Fijaos en la definición de la variable data que contiene las variables en el formato típico de post: nombre=valor&nombre=valor&… Luego simplemente volcamos esa variable en el stream de la request del objeto WebRequest.

Finalmente recogemos la respuesta, la guardamos toda en una cadena y la parseamos con el método ParseResponse que es tal y como sigue:

private static bool ParseResponse(string recaptchaData)
{
var reader = new StringReader(recaptchaData);
var first = reader.ReadLine();
var result = false;
if (first != null)
{
first = first.ToLowerInvariant();
bool.TryParse(first, out result);
}

return result;
}

Más simple imposible: leemos la primera línea y miramos si es true o false. Esa primera línea nos indica si ha ido bien o mal la validación del captcha.

Y listos! Por supuesto en la vista podemos usar Html.ValidationMessage para añadir el mensaje de error en caso de que la validación del captcha sea incorrecta:

@Html.ValidationMessage("Captcha")


El lugar donde coloquemos este llamada a Htm.ValidationMessage es donde aparecerá el mensaje de error en caso de que la validación del captcha sea incorrecta. Por supuesto el parámetro de ValidationMessage es la misma cadena que el valor del atributo Name del ActionFilter (en mi caso Captcha).

Nos falta ver el código de la acción del controlador, pero no tiene ningún secreto:

[HttpPost]
[Recaptcha(Name="Captcha")]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Creamos el usuario y lo autenticamos
}
// Si llegamos aquí hay algun error (puede ser el captcha
// puede ser cualquier otro).
return View(model);
}

Una prueba rápida nos permite ver que efectivamente si el usuario falla el captcha aparece el mensaje de error:

image

Y eso es todo!

En este post hemos visto como usar un ActionFilter para integrar la validación de Recaptcha en nuestro site de forma sencilla y fácil.

Insisto en lo que os he dicho al principio: hay soluciones ya hechas para integrar Recaptcha, pero a veces está bien ver las cosas desde cero, saber como funcionan e intentar ver como afrontarlas, no? Porque si siempre nos lo dan todo masticado… que gracia tiene?

Un saludo! :D

con 2 comment(s)
Archivado en:

En el post anterior vimos como gracias a C# 5 y las nuevas palabras clave async y await el uso de métodos asíncronos era tan sencillo como ir al bar y tomarnos una cerveza. Como resumen del post vimos que async nos permitía indicar que un método quería realizar llamadas asíncronas y await nos permitía esperarnos al retorno de una llamada asíncrona. Si no has leído el post, antes de leer este échale un vistazo.

En el post de ayer hice una simplificación, porque no quería liar las cosas más de la cuenta y total sólo iba a tomarme una cerveza. Hoy para mi desgracia, y para la vuestra, he quedado con Richard Feynman para tomarme esa misma cerveza… No interpretéis mal lo de “desgracia”, honestamente si pudiese elegir alguna personalidad de todos los tiempos con los que poder sentarme en una terraza y hablar de temas varios él estaría en uno de los primeros lugares de la lista.

Supongamos el mismo ejemplo de ayer. Es decir, yo me voy al bar,vistiendo mi jersey rojo y allí me encuentro al bueno de Richard, tomando una cervecilla. Me uno a él, pido una cerveza (o sea, le encargo al camarero la tarea de que vaya al almacén a buscar una cerveza y me la traiga)  y mientras esperamos hablamos de cosas de la vida. En esto, Richard que es un poco despistado y no se ha dado cuenta de que todavía no tengo mi cerveza, propone un brindis. Pero como todavía no tengo mi cerveza nos esperamos a que el camarero me la traiga. Cuando el camarero nos trae la cerveza, Richard sonríe y me dice que el nuevo jersey azul que me he comprado es muy bonito. A eso yo me miro y efectivamente veo que llevo un jersey azul nuevo. Pero… no estaba yo esperando a que el camarero me trajera la cerveza?? No recuerdo haber ido a comprar ningún jersei azul… o sí?

¿Que ha ocurrido? La verdad es que Richard me lo explicó, a su manera claro, usando su teoría de las múltiples historias, y resumiendo eso es más o menos lo que ocurrió…

La clave del meollo es que cuando nos esperamos a que el camarero nos trajera la cerveza no nos quedamos sin hacer nada. Al contrario, cada uno de nosotros continuamos haciendo las tareas que teníamos pendientes hacer. En mi caso, comprarme un jersei rojo (en el de Richard probablemente revolucionar la física moderna con algún nuevo descubrimiento). Luego, en el momento en que el camarero llega con mi cerveza, tanto Richard como yo continuamos haciendo lo que teníamos que hacer cuando yo tuviese mi cerveza: el brindis. Ya… eso es lo que ocurre cuando hablas con genios de la talla de Feynman: te dejan el cerebro hecho fosfatina.

Y ahora, por salud mental, permitidme que abandone el ejemplo de la cerveza y hable en términos de programadores, a ver si nos entendemos mejor. La clave es que await no realiza una espera bloqueante (si lo hiciera convertiríamos la tarea por la que esperamos en una llamada síncrona). Lo que realmente hace await es que el método async devuelve a su llamador justo en este momento. I el resto del método async (el código que sigue al await) se ejecutará cuando la tarea por la que está esperando await ha terminado.

Imaginad el siguiente código que representa un día en mi vida:

image

Simplemente IrAlBareto() y luego IrAComprarRopa().

El código de IrAlBareto(), que es un método async, es tal y como sigue:

image

image

Me voy al bar, hablo de cosas de la vida con Richard, pido mi cerveza al camarero, me espero por ella con await y luego hacemos un brindis. Ahora viene la clave: lo que ocurre al llamar a await es que el método IrAlBareto retorna a su llamador (el método UnDiaEnMiVida). Con lo que la ejecución del sistema continúa con la llamada a IrAComprarRopa():

image

Finalmente, cuando el método PedirCerveza, que es por el cual estábamos haciendo await termina, se continua ejecutando el código que sigue al await, es decir la llamada al método Brindis

Esa es la salida que genera el programa:

image

He dejado el código del proyecto en mi carpeta de Skydrive – AsyncAwait2. Es un proyecto console application de VS2011 Developers Preview.

Así pues recordad:

  1. Await realiza una espera no bloqueante sobre un objeto awaitable (una Task).
  2. Mientras dura la espera de await, el método async (que contiene la llamada a await) retorna a su llamador
  3. Cuando la espera de await ha terminado, se ejecuta el resto del código del método async que contenía el await (exacto… es como si el resto del código que sigue al await fuese el callback!).

Un saludo a todos… y felices cervezas! xD

con 2 comment(s)
Archivado en:

Muy buenas! Como dije en el post anterior estoy trasteando un poco con la Developers Preview de Windows 8 y la nueva API WinRT para crear aplicaciones Metro. El tema está en que esta nueva API está diseñada de forma muy asíncrona. Por suerte en C# 5 el uso de métodos asíncronos se ha simplificado mucho gracias a dos nuevas palabras clave: async y await. Y dado que, creedme, vais a tener que usarlas en cuanto os pongáis con WinRT me he decidido escribir este post para comentarlas un poco :)

async

Esta palabra clave se aplica a la declaración de un método pero, contra lo que se suele pensar por primera vez no declara que un método se ejecuta asíncronamente. La palabra clave async lo que indica es que este método se quiere sincronizar con métodos que se ejecutarán de forma asíncrona. Si no usáis async podréis seguir llamando métodos de forma asíncrona (de hecho hasta ahora lo veníamos haciendo), lo que no podréis hacer (de forma trivial) es sincronizaros con este método asíncrono. ¿Que significa sincronizarse con un método asíncrono? Fácil: esperar a que termine. Ni más, ni menos.

Es como si yo me voy a un bar y allí me encuentro a un colega. Me siento con él, y pido una cerveza, de la misma que está tomando él. Mientras me la traen hablamos de temas de la vida hasta que mi colega propone un brindis. Pero todavía no ha llegado el camarero con mi cerveza, así que yo y mi amigo nos quedamos esperando sin hacer nada a que el camarero llegue (sí, los dos somos un poco nerds). Cuando el camarero llega, hacemos el brindis y seguimos hablando.

Declarar un método como async es requisito indispensable para poder usar await.

await

Esa palabre clave es la que permite que un método que ha llamado a otro método asíncrono se espere a que dicho método asíncrono termine. Usando de nuevo el ejemplo del bar, cuando mi amigo dice de hacer el brindis debemos esperarnos a que llegue el camarero con mi cerveza.

La clave de todo es entender que desde el momento en que yo encargo mi cerveza al camarero (llamada al método asíncrono) hasta el momento en que decidimos que nos debemos esperar yo (con mi amigo) hemos estado haciendo otras cosas (hablando de la vida). Eso, de nuevo trasladado a código fuente, significa que entre la llamada al método asíncrono y el uso de await habrá más líneas de código fuente. Por lo tanto no usamos await cuando llamamos al método asíncrono, lo hacemos más tarde cuando queremos esperarnos a que dicho método termine (y recoger el resultado, es decir mi cerveza).

Así pues… sobre que aplicamos await? Pues todo método que quiera ser ejecutado asíncronamente debe devolver un objeto especial, que sea (ojo con la originalidad de los ingleses) awaitable. Sobre este objeto, es sobre el que llamaremos a await para esperarnos a que el método asíncrono finalice y a la vez obtener el resultado. ¿Y que es un objeto awaitable? Pues un conocido de la TPL que viene con .NET 4: Un objeto Task o su equivalente genérico Task<T>.

Métodos asíncronos

Para declarar un método que pueda ser llamado de forma asíncrona, lo único que debemos hacer es devolver un Task o Task<T> desde este método. Así se sencillo. Dejemos las cosas claras (al contrario que el chocolate): Devolver un Task NO convierte el método en asíncrono. Es la propia Task que es asíncrona. Podemos ver una Task como un delegate (o sea un método) que puede ser ejecutado de forma asíncrona. Trasladando eso de nuevo al ejemplo del bar, cuando yo pido la cerveza al camarero, le he encargado esta tarea y la he puesto en marcha. En términos de C# cuando llamo al método ServirCerveza de la clase camarero, este método me devuelve una Task<Cerveza>, que representa la tarea que he encargado al camarero. Luego yo debo poner en marcha esa tarea (con lo cual el camarero irá efectivamente a buscarla) y cuando toque esperarnos llamaremos a await sobre el objeto Task<Cerveza>. El resultado de llamar a await sobre una Task<Cerveza> es precisamente… un objeto de la clase Cerveza (mi cerveza para ser más exactos).

Código, código, código

Vamos a ver el ejemplo de la cerveza implementado en C# para que nos queden los conceptos más claros ;-)

Para ello, dado que en la versión de VS2011 que viene con la Developers Preview de Win8 no podemos crear aplicaciones de consola, vamos a crear una aplicación Metro. En la vista principal pondremos 3 texblocks que nos permitirán ver cuando pedimos la cerveza al camarero, cuando nos la trae y como”hablamos” entre medias. El código XAML es muy simple:

image

Lo siguiente que necesito es una clase que represente a mi Camarero y un método que me permita pedirle una cerveza de forma asíncrona. Recordad que entonces debo declarar el método que me devuelva, no un objeto de Cerveza sino un objeto de Task<Cerveza>:

image

El método ServirCerveza de la clase Camarero espera un parámetro (el tipo de cerveza se quiere) y lo que hace es devolver una Task<Cerveza>. Como comenté una Task es parecido a un delegate sólo que es asíncrona, y en este caso una Task<T> se inicializa a partir de una Func<T> que le indica precisamente que se tendrá que hacer cuando se inicie la tarea. En nuestro ejemplo el camarero debe ir al almacén (que está lejos y es una operación que tarda un poco) y devolver la cerveza que se ha pedido.

Vayamos ahora a lo que ocurre cuando se pulse el botón:

image

Ponemos en txtInicial la fecha y hora en que pedimos la cerveza. Y llamamos al método ServirCerveza del camarero. Este método retorna en el acto y nos devuelve una Task<Cerveza>. En este momento la ejecución de la tarea del camarero todavía no ha empezado. Cuando llamamos a task.Start() empieza la ejecución de dicha tarea de forma asíncrona. Y a la vez, yo y mi amigo seguimos hablando de cosas de la vida. El código de HablandoDeLaVida() se ejecuta concurrentemente con el de la tarea definida en ServirCerveza. Al final mi amigo propone el brindis y como no tengo cerveza nos esperamos, usando await sobre el objeto Task<Cerveza> que había recibido. Con esto nos esperamos a que finalice dicha tarea y obtenemos el resultado (que dado que era una Task<Cerveza> el resultado es una Cerveza). Y listos.

Observad como la función Button_Click ha sido declarada como async para indicar que quiere llamar a métodos asíncronos y usar await para sincronizarse con ellos (esperar a que terminen).

El uso de async y await, junto con la clase Task de la TPL hace que en C#5 el crear y consumir métodos asíncronos sea tan fácil como ir al bar y pedir una cerveza! :)

Un saludo!

PD: Un comentario final, que quiero poner por completitud del post. Si declaráis un método async (porque quiereis hacer await sobre algún método asíncrono) pero a la vez este método async puede ser llamado de forma asíncrona y por lo tanto devuelve una Task<T>, entonces en la implementación del método async no es necesario que creeis la Task<T>, sino que podeis devolver directamente un objeto de tipo T. Es decir, en nuestro ejemplo el siguiente código:

image

Compila correctamente. El método devuelve una Task<Cerveza> pero a diferencia de antes no tengo que crearla explicitamente. Y eso es debido al uso de async en la declaración. Eso supongo que es porque en muchos casos se van a ir encadenando métodos asíncronos y así nos ahorramos el tener que definir las Task<T> de forma explícita. Pero insisto, no os confundáis: es Task<T> lo que hace que el método pueda ser llamado de forma asíncrona, no async. De hecho si os fijáis en la imagen el nombre del método está subrayado en verde y eso es porque el compilador me está avisando que he declarado un método async… que no usa await en ningún momento, cosa que no tiene sentido (porque la única funcionalidad de async es permitir que el método use await).

con 5 comment(s)
Archivado en:

Muy buenas! Como muchos otros he descargado el Windows 8 Developers Preview, y he empezado a jugar con la nueva API de WinRT para la creación de aplicaciones basadas en MetroUI.

Vamos a ver como realizar una aplicación MetroUI usando C# que simplemente nos muestre las imágenes que tenemos en la carpeta de “Mis Imágenes”.

Para ello, abrimos el Visual Studio 2011 Express que viene con el Windows 8 Developers Preview y seleccionamos el tipo de proyecto de tipo “Windows Metro Style –> Application”:

image

Con eso VS2010 nos genera el esqueleto del proyecto inicial.

¡Hey eso es WPF!

Pues no. Aunque sin duda se le parece mucho. Veamos, por un lado tenemos la definición de la interfaz en XAML. Si tecleamos un poco, vemos que nuestros controles de WPF (o Silverlight) están aquí:

image

Tenemos TextBlock, Button, Grid, StackPanel… con las mismas propiedades y las mismas extensiones de XAML que nos podemos encontrar en WPF. Si no son las mismas son muy, muy parecidas. Y esa es la primera lección que extraemos: el conocimiento que hemos adquirido desarrollando con WPF o Silverlight no está perdido. Así pues, tranquilos por este lado: no empezamos de cero!

En mi caso he diseñado una página muy cutre que se compone básicamente de un botón, una etiqueta y una lista. Cuando se pulse el botón la lista debe mostrar los nombres y un thumbnail de las imágenes que tenemos en “Mis Imagenes”. Esa es la idea. La definición de la vista es muy simple:

image

Los que hayáis desarrollado en WPF o Silverlight no veréis nada nuevo. Insisto, todos los conceptos que conocemos están aquí. Estilos, recursos, templates, bindings… Todo funciona exactamente igual.

WinRT la nueva API que está detrás…

Aunque este XAML parezca de WPF o Silverlight, realmente no estamos usando WPF ni Silverlight. Eso significa que por detrás, es decir en el código C#, tenemos una nueva API que si que nos va a tocar aprender a utilizar… Pero es que si no… donde estaría la diversión?

Bueno, veamos lo primero que quiero es recorrer las imágenes de la carpeta de “Mis Imagenes” cuando se pulse el botón. Eso, hasta ahora, lo conseguiría usando el método GetFiles de la clase System.IO.Directory, p.ej. Ahora, no: olvidaos de todo esto. De hecho, si en el código C# tecleáis System.IO:

image

veréis que NO sale la clase Directory. No existe! Podéis pensar que a lo mejor falta alguna referencia, pero no… Simplemente no tenemos disponible esta clase. Así que debemos usar la nueva API WinRT que se encuentra colgando del namespace Windows.

Y esa nueva API tiene una característica muy especial: está diseñada de un modo muy asíncrono. Muchas de sus clases tienen métodos cuyo nombre termina en Async() y que son asíncronas. Por suerte en C#5 vamos a disponer de las palabras clave async y await que hacen que llamar a métodos asíncronamente sea lo más fácil del mundo. Ya veréis vais a usar await y async constantemente…

Bien, a lo que íbamos, cuando se pulse el botón quiero que:

  1. El TextBlock de estado diga “Obteniendo”.
  2. Se obtengan las imágenes que hay en mis imágenes.
  3. Y se enlacen con la ListBox para que se muestren.

Y el código es el siguiente:

image

Veis lo que os decía de async y await? Bueno… a ver, este código es muy simple, lo que hace es lo siguiente:

  1. Pone el texto “Obteniendo” en nuestro TextBlock de estado
  2. Llama al método DisplayImagesAsync(). Método que se ejecutará asíncronamente.
  3. Cuando el método termine, se ejecutará el código que está a continuación del await, es decir pondrá el TextBlock a blanco, para indicar que ya ha terminado.

En este caso, realmente no hay asincronidad. Me explico: el método DisplayImagesAsync() está pensado para que pueda ser usado asíncronamente, pero nosotros no hacemos nada entre que llamamos el método y nos esperamos (await) a que termine. Si entre la línea var displayImagesTask = DisplayImagesAsync() y la siguiente (await displayImagesTask) yo hubiese colocado código, este código se ejecutaría en paralelo al código del método DisplayImagesAsync(). Esta es la potencia brutal de async/await: facilitan hasta el absurdo la creación de métodos que pueden ser invocados asíncronamente y la espera para que terminen esos métodos.

Bueno… veamos el método DisplayImagesAsync(). Para empezar dicho método debe obtener acceso a la carpeta de “Mis Imagenes”. Para ello usamos la clase KnownFolders de Windows.Storage:

image

Con eso en picsFolder tenemos un objeto que nos permitirá recorrer la carpeta de “Mis Imágenes”. Para ello tiene un método GetItemsAsync() que nos devuelve una lista con todos los elementos. Así que, lo invocamos:

image

Esta forma de usar await es muy común y en el fondo es lo mismo que hemos hecho antes (cuando hemos llamado a DisplayImagesAsync) pero en una sóla linea: invocar el método asíncrono, esperarnos a que termine y en pics tenemos el resultado :)

Este resultado es un enumerable con todos los items de la carpeta. Eso incluye ficheros y directorios. Lo que vamos a hacer ahora es iterar por todos los ficheros y por cada fichero obtener un thumbnail junto con su nombre y su ruta:

image

Listos! Con esto rellenamos la lista images (una lista dinámica) con los datos (nombre, ruta y thumbnail) de las imágenes que tengamos. Nos queda una pieza para verlo todo y es el método GetBitmapData.

El método GetThumbnailAsync no devuelve directamente un bitmap con el thumbnail, sino que devuelve un objeto (de la clase StorageItemThumbnail) a partir del cual podemos obtener un stream a los datos que representan el bitmap del thumbnail. Pero nosotros queremos algo que sea un ImageSource (para enlazarlo a la propiedad Source del DataTemplate de la ListBox). Y eso que necesitamos es un objeto de la clase BitmapImage. Por suerte es muy fácil obtener un BitmapImage a partir de un StorageItemThumbnail:

image

Y listos! Con esto lo tenemos todo ya hecho… sólo nos queda enlazar la lista images con nuestra ListBox, y eso lo conseguimos con este código (al final de DisplayImagesAsync), que resultará muy familiar a quien haya desarrollado con WPF o Silverlight:

image

Y listos! Con esto conseguimos mostrar las imágenes que tenga el usuario en la carpeta “Mis Imágenes”:

image

Hemos realizado nuestra primera aplicación MetroUI! :)

Ah sí! Permisos, permisos, permisos

Que me olvido! Las aplicaciones MetroUI tienen asignado un sistema de seguridad, donde tienen que declarar exactamente que permisos necesitan. Esos permisos se mostraran al usuario cuando este quiera ejecutar la aplicación (o la compre desde el nuevo Windows Store). Para ello es necesario editar el archivo Package.appxmanifest y allí poner los permisos necesarios. Visual Studio 2011 viene con un editor para este archivo. En nuestro caso, debemos pedir específicamente permiso para acceder a la carpeta de imágenes del usuario:

image

Tenemos que ir a “Capabilities” y marcar la checkbox de Picture Library Access.

Ahora sí, que ya lo tenemos todo! ;-)

Un saludo!

PD: El código en este post son imágenes porque estoy posteando desde el propio Win8 y no tengo ningún plugin de código instalado en el Live Writer

PD2: He dejado el proyecto (de Visual Studio 2011 Developers Preview) en mi carpeta de skydrive. Lo podeís descargar desde aquí: MyFirstApp (código fuente)

PD3: No toméis esa aplicación como una guía de buenas prácticas ni nada parecido! Este post es simplemente para compartir mi experiencia de “desvirgamiento” en WinRT :P :P :P

con 1 comment(s)
Archivado en: ,,

La verdad es que ahora hacía bastantes meses que no publicaba nada de la serie “C# básico”. En esta serie pongo posts sobre temas básicos del lenguaje. No es un libro por fascículos, ni un tutorial al uso puesto que los posts no tienen orden en concreto y nacen a partir de inquietudes que observo (mayoritariamente en los foros, pero también por correos que recibo). Todos los posts de esta serie los podéis ver aquí.

En el post de hoy quiero hablar de la diferencia entre objetos y referencias ya que observo que no siempre está clara. Gente que entiende los conceptos básicos de herencia parece liarse en este punto. Muchas veces es un tema pasado rápidamente en muchos libros y tutoriales. Y es que, la verdad, es un tema muy sencillo… ;-)

MiClase miObjeto = new MiClase();

¿Qué hace este código? En muchos sitios leerás que lo que hace es crear un objeto de la clase MiClase. Eso es cierto, pero describe lo que hace lo que hay a la derecha del símbolo de asignación. Qué hace el código que está a la izquierda? Pues lo que hace es declarar una referencia de tipo MiClase. Otra palabra que se usa muchas veces en lugar de referencia es variable aunque no son técnicamente lo mismo (hay variables que no son referencias y las referencias pueden asignarse a otros elementos que no llamamos usualmente variables como p.ej. los parámetros a una función).

Las referencias contienen objetos. Yo prefiero decir que las referencias apuntan a objetos (aunque esta palabra parece como “maldita”, sin duda por culpa de los punteros) para que quede claro que un mismo objeto puede estar contenido en (apuntado por) más de una referencia.

El tipo de una referencia

Todas las refencias tienen un tipo. Este tipo es único e inmutable durante toda la vida de la referencia. El tipo de una referencia determina que objetos puede contener dicha referencia. En concreto:

  1. Objetos del mismo tipo. Es decir, una referencia de tipo MiClase puede contener objetos de la clase MiClase.
  2. Objetos de una clase derivada de la clase del tipo de la referencia. Si la referencia es de tipo MiClase puede contener objetos de cualquier clase derivada de MiClase.
  3. Objetos de cualquier clase que implemente el tipo de la referencia. Eso aplica sólo si el tipo de la referencia es una interfaz. En este caso la referencia puede contener un objeto de cualquier clase que implemente dicha interfaz.

Todas las clases en .NET derivan de Object. Por lo tanto, según el punto (2) una referencia de tipo Object, puede contener cualquier objeto de cualquier clase:

Object objeto = new CualquierClase();

¿Condiciona alguna cosa más el tipo de la referencia? Pues sí: el tipo de la referencia condiciona como vemos al objeto contenido en dicha referencia. Es decir, la referencia es como un disfraz para el objeto. Le permite “ocultar su tipo real” y mostrarse como el “tipo de la referencia”.

P.ej. dado el siguiente código:

class MiClase
{
public void Foo() {}
}

class MiClaseDerivada : MiClase
{
public void Bar() {}
}

MiClase c1 = new MiClaseDerivada();
MiClaseDerivada c2 = new MiClaseDerivada();

Podemos ver como MiClase define un método (Foo) y MiClaseDerivada que deriva de MiClase añade el método Bar. Luego c1 es una referencia de tipo MiClase que contiene un objeto de MiClaseDerivada (puede según el punto 2 anterior). Y c2 es una referencia de tipo MiClaseDerivada que contiene un objeto de MiClaseDerivada (posible según el punto 1 anterior). Entonces tenemos que:

c1.Foo();   // Ok.
c1.Bar(); // No compila.
c2.Foo(); // Ok.
c2.Bar(); // Ok.

La llamada c1.Bar() no compila. ¿Por que? Pues simplemente porque la referencia es de tipo MiClase. Y MiClase no tiene ningún método Bar. Da igual que el objeto contenido por dicha referencia sea de tipo MiClaseDerivada, que sí que tiene el método Bar. El compilador no se fija en los tipos de los objetos. Se fija en los tipos de las referencias.

Objetos compartidos

Como hemos dicho antes un mismo objeto puede estar contenido por más de una referencia:

MiClaseDerivada c1 = new MiClaseDerivada();
MiClase c2 = c1;

En este punto tenemos dos referencias. Pero un sólo objeto. Es decir, c1 y c2 contienen el mismo objeto, que es un objeto de tipo MiClaseDerivada. Si accedo al objeto a través de c1 lo veo como un objeto de tipo MiClaseDerivada (ya que este es el tipo de c1). Por otro lado si accedo al objeto a través de c2 lo veo como un objeto de tipo MiClase (al ser este el tipo de c2). Por lo tanto c1.Bar() es correcto y c2.Bar() no compila.

Pero insisto: son el mismo objeto. Observad el siguiente código:

class Program
{
public static void Main()
{
MiClaseDerivada c1 = new MiClaseDerivada();
MiClase c2 = c1;
c1.Incrementar();
c2.Incrementar();
Console.WriteLine("El valor de c1 es:" + c1.Valor);
Console.WriteLine("El valor de c2 es:" + c2.Valor);
}
}


class MiClase
{
private int valor;

public int Valor { get { return this.valor; } }
public void Incrementar()
{
valor++;
}
}

class MiClaseDerivada : MiClase
{
// Código
}

¿Cual es la salida por pantalla de dicho código? Pensadlo con detenimiento. Pues  la siguiente:

El valor de c1 es:2
El valor de c2 es:2

Eso es debido porque c1 y c2 contienen el mismo objeto. Por lo tanto inicialmente tenemos que el valor de dicho objeto es 0. Al llamar a c1.Incrementar() el valor pasa a ser 1. Y al llamar a c2.Incrementar(), el valor pasa a ser 2, ya que el objeto que contiene c2 es el mismo que el objeto que contiene c1.

Así pues recordadlo siempre: Asignar una referencia a otra NO crea un nuevo objeto. Simplemente hace que la referencia contenida a la izquierda de la asignación contenga EL MISMO objeto que la referencia situada a la derecha.

Comparando objetos y referencias.

De nuevo la forma más fácil es verlo con un código de ejemplo:

class Program
{
public static void Main()
{
Persona p1 = new Persona();
p1.Nombre = "Pepito";
p1.Edad = 20;
Persona p2 = p1;
Persona p3 = new Persona();
p3.Nombre = "Pepito";
p3.Edad = 20;
bool b = p2 == p1;
bool b2 = p3 == p2;
}
}

class Persona
{
public string Nombre { get; set; }
public int Edad { get; set; }
}

¿Cual es el valor de b y b2?

  • b vale true porque p1 y p2 contienen el mismo objeto
  • b2 vale false porque p3 y p2 contienen objetos distintos. Da igual que los dos objetos sean del mismo tipo y sean idénticos. En este caso son dos Personas idénticas: mismo nombre y edad. Pero el operador == compara referencias, no objetos.

Así pues recuerda: El operador == al comparar referencias devuelve true sólo si las dos referencias contienen el mismo objeto. En caso contrario devuelve false (aunque las dos referencias apunten a dos objetos idénticos).

Nota: Este comportamiento del operador == puede modificarse para que compare el valor de los objetos en lugar de indicar si las dos referencias contienen el mismo objeto. P.ej. la clase string tienen modificado dicho operador para comparar el valor de las cadenas. Esto queda fuera del alcance de este post.

La comparación de objetos (es decir, determinar si dos objetos son idénticos pese a ser dos objetos distintos) es algo que por norma general depende de la clase. P.ej. dos Personas serán iguales si tienen el mismo nombre y edad. Dos cadenas serán iguales si contienen los mismos carácteres. Depende de cada clase determinar que significa que dos objetos son iguales. Para estandarizar un poco la comparación de objetos, en .NET tenemos el método Equals. Dicho método está definido en la clase Object y por lo tanto, por herencia, existe en todas las clases. Si quiero indicarle al framework como comparar dos objetos de tipo Persona puedo añadir a la clase Persona el siguiente código:

public override bool Equals(object obj)
{
if (obj is Persona)
{
Persona otro = (Persona) obj;
return otro.Edad == Edad &&
otro.Nombre == Nombre;
}
return false;
}

Y para comparar los objetos, debo llamar a Equals en lugar del operador ==

bool b2 = p3.Equals(p2);

Conversiones (castings)

En el código del método Equals anterior hay el siguiente código:

Persona otro = (Persona)obj;

El código (Persona) es lo que se llama casting. El casting lo que hace es cambiar el tipo de una referencia. Es decir en el caso anterior obj era una referencia de tipo object (los parámetros también pueden ser referencias). Recordad que las referencias de tipo object pueden contener cualquier objeto. Pero yo quiero acceder a Nombre y Edad que son campos definidos en la clase Persona y por ello necesito una referencia de tipo Persona que me contenga el mismo objeto que la referencia obj.

Si directamente probáramos:

Persona otro = obj;

Dicho código no compila. ¿Porque? Pues porque otro es una referencia de tipo Persona y por lo tanto solo puede contener:

  1. Un objeto de tipo Persona
  2. Un objeto de cualquier clase que derive de Persona

Pero obj es una referencia de tipo object y puede contener un objeto de tipo object o bien un objeto de cualquier clase que derive de object… es decir, de cualquier clase. Imaginad, entonces:

object obj = new Perro();
Persona otro = obj;

Es evidente que el objeto contenido por obj es un perro y no una persona. Si el código de la segunda línea compilase estaríamos viendo un perro como una persona y bueno… se supone que no se puede, no? Por eso, como el compilador no puede garantizar que el objeto (recordad que el compilador no se fija en objetos) contenido por la referencia obj sea de un tipo válido para la referencia otro, se cura en salud y no nos deja compilar el código.

Pero… tu no eres el compilador y tu sí te fijas en los objetos. ¿Qué pasa en aquellos casos en que tu sabes que el objeto contenido por la referencia obj es de un tipo válido para la referencia Persona? Pues que debes decírselo al compilador. ¿Cómo? Usando el casting:

Persona otro = (Persona)obj;

Aquí le estás diciendo al compilador: Quiero que la referencia otro contenga el mismo objeto que la referencia obj y tranquilo, no te quejes porque yo te digo que el objeto es de tipo Persona. Con el casting el compilador te cree y te deja hacer la asignación.

Eh… que te crea el compilador no significa que te crea el CLR. El CLR no se fía ni de su madre, así que si tu haces:

object perro = new Perro();
Persona persona = (Persona)perro;

El compilador no se quejará, pero cuando ejecutes, vas a recibir una hermosa InvalidCastException. El CLR sí que se fija en los objetos, como tu :)

Ah! Y aunque el compilador no se fije en objetos… no lo insultes, eh? No intentes algo como:

Perro perro = new Perro();
Persona persona = (Persona)perro;

Eso no compila. La razón es porque no es necesario fijarse en los objetos para ver que una referencia de tipo Persona nunca podrá contener el mismo objeto que una referencia de tipo Perro: Persona y Perro no tienen nada en común. El compilador puede no fijarse en los objetos, pero no es tonto!

Un saludo!

con 5 comment(s)
Archivado en: ,

Bueno… vamos a seguir viendo el tema de binding de colecciones con ASP.NET MVC. En los dos posts anteriores hemos visto:

En este post vamos a ver como enlazar una colección de N elementos, de los cuales sólo nos llegan un determinado número, pero queremos fácilmente saber cuales son. Es decir, si nos llega sólo el primer elemento, el segundo y el octavo, recibir una lista con los ocho elementos, todos ellos a “null” (o un valor por defecto) excepto los informados (el primero, el segundo y el octavo en nuestro caso).

Si usamos el DefaultModelBinder esto no pasa: en los posts anteriores hemos visto como en el mejor de los casos (usando el parámetro index), recibimos sólo una colección con los tres elementos, y debemos usar ModelState.Keys para saber cuales son los índices reales informados. Es decir, si la vista sólo nos informa del primer, segundo y octavo elementos en el controlador recibimos una colección de tres elementos (los tres informados). Para saber que el tercer elemento (p.ej.) de dicha colección se corresponde al octavo índice real debemos usar ModelState.Keys. Vamos a ver ahora como podemos hacerlo para recibir, en este caso, una colección con los ocho elementos. De estos ocho, tan sólo el primer, el segundo y el octavo tendrán valor (el resto, un valor por defecto).

La solución es simple, y pasa por crearnos un Custom Model Binder :) Crear un model binder propio parece muy complejo, pero se trata de implementar una interfaz con un solo método (BindModel). Sí, si miras el código del DefaultModelBinder te parecerá enorme y complejo, pero piensa que el DefaultModelBinder está pensado para enlazar cualquier cosa, y nosotros vamos a hacer un model binder preparado para enlazar sólamente colecciones (IEnumerable<T> en nuestro caso).

Así pues, vamos a hacer este custom model binder, especializado en colecciones. Vamos a imitar en todo al Default Model Binder, excepto en que nosotros vamos a devolver una colección con el tamaño real (no solo con los elementos informados).

Os pongo primero el código del model binder y lo discutimos (por supuesto, si queréis preguntar algo concreto sobre el código, adelante!):

public class CollectionBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{

var model = CreateModel(bindingContext) as IList;
var prefix = bindingContext.ModelName;
var indexesKey = bindingContext.FallbackToEmptyPrefix ?
bindingContext.ValueProvider.GetValue("index") :
bindingContext.ValueProvider.GetValue(string.Format("{0}.index", prefix));
var indexes = indexesKey == null ? AllIndexes() : EnumerableFromIndexes(indexesKey.RawValue as string[]);
var genericType = GetGenericTypeOfModel(bindingContext);


foreach (var index in indexes)
{
var value = bindingContext.FallbackToEmptyPrefix ?
bindingContext.ValueProvider.GetValue(string.Format("[{0}]", index)) :
bindingContext.ValueProvider.GetValue(string.Format("{0}[{1}]", prefix, index));

if (value != null)
{
var valueConverted = Convert.ChangeType(value.AttemptedValue, genericType);
model.Add(valueConverted);
}
else
{
if (indexesKey == null) break;
else
{
model.Add(genericType.IsValueType
? Activator.CreateInstance(genericType)
: null);
}
}
}


return model;
}

private object CreateModel(ModelBindingContext bindingContext)
{
var genericType = GetGenericTypeOfModel(bindingContext);
var listOfTType = typeof(List<>).MakeGenericType(new Type[] { genericType });
return Activator.CreateInstance(listOfTType);
}

private Type GetGenericTypeOfModel(ModelBindingContext bindingContext)
{
var type = bindingContext.ModelType;
var genericTypes = type.GetGenericArguments();
return genericTypes.FirstOrDefault();
}

private IEnumerable<int> AllIndexes()
{
for (int i = 0; i < Int32.MaxValue; i++)
{
yield return i;
}
}


private IEnumerable<int> EnumerableFromIndexes(string[] indexesToUse)
{
if (indexesToUse != null)
{
foreach (var token in indexesToUse)
{
yield return Int32.Parse(token);
}
}
}
}

Como funciona el siguiente código? Pues nuestro collection binder hace lo siguiente:

  1. Crea un objeto para representar el modelo. Dicho objeto será siempre una List<T>, siendo T el parámetro genérico del IEnumerable del modelo.
  2. Mira si existe el parámetro index. Si dicho parámetro existe, lo usa para saber los indices reales de la colección.  Es decir, si indexes vale “0,1,2,3,4,5” (p.ej.) nuestro model binder va a devolver siempre una colección de 6 elementos (del 0 al 5) con independencia de los elementos reales informados en la vista. Esto es para imitar lo que hace el DefaultModelBinder y que vimos en el post anterior.
  3. Busca en los valueproviders los valores para todos los índices. Si el parámetro “index” no existía, todos los indices son literalmente “todos” (de 0 a Int32.MaxValue-1). Si el parámetro index no existe nos paramos cuando falta un elemento (porque si no, siempre devolveríamos una colección de Int32.MaxValue elementos!). Por su parte si el parametr index existe, iteramos sólo sobre sus valores, y si el valor no existe, lo añadimos al modelo con el valor por defecto del tipo genérico. Es decir, si index vale “0,1,2,3,4,5” y la vista no nos informa del valor del índice 3, pondremos el valor por defecto en el índice 3 y continuaremos hasta llegar a 5.

El uso de los value providers para obtener los valores nos independiza de si dichos valores vienen por GET, POST o lo que sea. De esta manera el Model Binder es independiente de la request de http.

Este CollectionBinder está preparado para trabajar con cualquier tipo de IEnumerable. Para usarlo, debemos registrarlo en global.asax:

ModelBinders.Binders[typeof(IEnumerable<string>)] = new CollectionBinder();

Con esto, lo hemos registrado para que los IEnumerable<string> se enlacen usando nuestro model binder!

¿Lo probamos? Para ello nos creamos un modelo:

public class FooModel
{
public string Name { get; set; }
public int Age { get; set; }
public IEnumerable<string> Data { get; set; }
}

Y luego un controlador con un método para recibir un FooModel:

public ActionResult Index()
{
var model = new FooModel();
model.Age = 10;
model.Name = "Nombre";
model.Data = new List<string> {"cero", "uno", "dos", "tres", "cuatro"};
return View(model);
}
[HttpPost]
public ActionResult Index(FooModel model )
{
int i = 0;
// Codigo...
}

Vamos ahora a hacer una vista para editar nuestro FooModel:

@using BindingColecciones3.Models
@model FooModel
<!DOCTYPE html>
<html>
<head>
<title>title</title>
</head>
<body>
<div>
@using (Html.BeginForm())
{
<div>
@Html.LabelFor(x => x.Name)
@Html.EditorFor(x => x.Name)
<br />
@Html.LabelFor(x => x.Age)
@Html.EditorFor(x => x.Age)
</div>
<ul>
@for (var idx = 0; idx < Model.Data.Count(); idx++)
{
<li>Checkbox #@idx:
<input type="checkbox" name="Data[@idx]" value="@Model.Data.Skip(idx).First()"/>
<input type="hidden" name="Data.index" value="@idx"/>
</li>
}
</ul>
<input type="submit" />
}
</div>
</body>
</html>

Fijaos en como creamos las checkboxes: El atributo name de cada checkbox es Data[0], Data[1], Data[2]… Eso es porque Data es el nombre de la propiedad IEnumerable<string> de nuestro modelo. El atributo value de cada checkbox será la cadena que se enlazará en el modelo. Si p.ej. sólo marcamos la tercera checkbox (cuyo value es “dos”, eso es lo que recibiremos en el controlador:

image

Fijaos que, a diferencia del CustomModelBinder, lo que recibimos ahora es una colección de 6 elementos (0-5) y sabemos exactamente cual era la única checkbox marcada. Esa misma vista, pero usando el DefaultModelBinder para enlazar los datos, devolvería lo siguiente al controlador (tal y como vimos en el post anterior):

image

Y deberíamos usar ModelState.Keys para saber que este “dos” es el valor de la tercera checkbox marcada.

Recordad que esto ocurre porque los navegadores no envían valores para una checkbox NO marcada. Es decir, en HTML las checkboxes no tienen el valor de true o false. Tienen sólo el valor que ponga en su value si están marcadas o no existen si no están marcadas.

Y finalmente una consideración sobre el código de este CollectionModelBinder: Tiene algunas limitaciones, alguna que otra cosa que se podría mejorar, alguna incongruencia (sobretodo en la gestión del parámetro index) y cosas que se le podrían añadir… Os dejo que vayáas pensando cuales… y alguna de ellas las veremos en un siguiente post, que por hoy, es suficiente, no? ;-)

Un saludo!

con 1 comment(s)
Archivado en:

Bueno… En el post anterior vimos como el DefaultModelBinder esperaba los nombres de los campos para poder realizar el enlace entre los datos de la request y un parámetro de tipo colección en el controlador.

Pero vimos que había un pequeño detalle. Supongamos el siguiente método del controlador:

[HttpPost]
public ActionResult Index(IEnumerable<int> results)
{
return View();
}

El método recibe una colección de enteros. Vamos a crearnos una vista de prueba:

@using (Html.BeginForm())
{
for (int i = 0; i < 10; i++)
{
<text>Pregunta @i:</text>
@Html.RadioButton("[" + i + "]", 1);
@Html.RadioButton("[" + i + "]", 2);
@Html.RadioButton("[" + i + "]", 3);
<p />
}

<input type="submit" value="enviar!" />
}

Mostramos simplemente 30 (10*3) radiobuttons. Esto nos mostrará 10 filas de radiobuttons. Las radiobuttons de cada fila se llaman igual “[i]”, siendo i el índice de la fila, que es lo que espera el DefaultModelBinder.

Ahora fíjemonos que pasa si el usuario selecciona tan solo ALGUNAS de las radiobuttons:

image

Lo que recibimos en el controlador es:

image

Tan sólo recibimos las radiobuttons marcadas hasta la primera que el usuario no ha marcado. A partir de este punto el DefaultModelBinder deja de enlazar! Por eso recibimos los valores de [0] y [1] ya que [2] es el primer valor que el usuario no informa.

Como enlaza colecciones el DefaultModelBinder

Bien… os animáis a explorar un poco el DefaultModelBinder? Dejadme que os muestre que pasa, a grandes rasgos, cuando se enlaza una colección… Si no te interesan tanto los detalles de como funciona el DefaultModelBinder puedes saltar al siguiente apartado ;-)

Así que, qué hace el DefaultModelBinder cuando debe enlazar el parámetro results? Simplificando, lo primero es mirar el tipo de este parámetro (IEnumerable<int>) y llamar al método CreateModel que debe devolver un objeto compatible con este tipo. La implementación por defecto devuelve List<T> si el tipo del modelo es IEnumerable<T>.

Una vez tiene el objeto (una List<int> vacía en nuestro caso) empieza a rellenarla. Esto se hace dentro de un método llamado BindComplexModel que entre otras cosas mira si el modelo es de tipo IDictionary<K,V>, un array o un IEnumerable<T>. Esos tipos tienen tratamientos “especiales”. Si no es ningún de estos tipos se asume que estamos enlazando un objeto.

Si estamos enlazando un IEnumerable<T> se llama a otro método de nombre UpdateCollection que es extremadamente simple. Hace dos cosas sólamente:

  1. Llama a un método GetIndexes para que devuelva que indices debe enlazar
  2. Por cada índice busca un valor en la request de nombre “[idx]” y lo intenta enlazar (llamando a BindModel de nuevo).

Centrémonos en este primer punto, el método GetIndexes. Lo “casi” único que hace es lo siguiente:

// just use a simple zero-based system
stopOnIndexNotFound = true;
indexes = GetZeroBasedIndexes();

Pone stopOnIdexNotFound a true y llama a GetZeroBasedIndexes(). Y que es GetZeroBasedIndexes()? Pues lo siguiente:

private static IEnumerable<string> GetZeroBasedIndexes() {
for (int i = 0; ; i++) {
yield return i.ToString(CultureInfo.InvariantCulture);
}
}

Un método que devuelve una colección infinita (entre comillas porque a Int32.MaxValue petaría).

Bien, ya tenemos los indices que vamos a mirar en la request: todos desde [0] hasta [Int32.MaxValue-1]

Ahora volvemos al código de UpdateCollection. Así es como recorre el bucle de índices:

foreach (string currentIndex in indexes) {
string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex);
if (!bindingContext.ValueProvider.ContainsPrefix(subIndexKey)) {
if (stopOnIndexNotFound) {
// we ran out of elements to pull
break;
}
else {
continue;
}
}
// codigo para enlazar el elemento y añadirlo (.Add) a la colección
}

Básicamente, en aquel punto donde en la request (recordad que el DefaultModelBinder accede a la request siempre a través de la propiedad ValueProvider) no se encuentre el parámetro correspondiente al índice (en nuestro caso [idx]) dejará de enlazar (el break sale del foreach) y devuelve todo lo enlazado hasta entonces.

Bueno… hemos visto como enlaza el DefaultModelBinder una colección y que realmente una vez no haya el parámetro de índice requerido en la request se para de enlazar. Pero… no os he enseñado todo el código, me he dejado una pequeña parte.

Recordáis que antes he dicho que el método GetIndexes() lo “casi” único que hacía era llamar a GetZeroBasedIndexes()? Pues bien antes de hacer esto hace otra cosa… Antes busca si existe un campo en la request llamado “index”.

Este valor si existe, debe contener un string[] con todos aquellos índices que el DefaultModelBinder debe buscar en la request. Pemitidme ahora que os enseñe el código completo del método GetIndexes():

string indexKey = CreateSubPropertyName(bindingContext.ModelName, "index");
ValueProviderResult vpResult = bindingContext.ValueProvider.GetValue(indexKey);
if (vpResult != null) {
string[] indexesArray = vpResult.ConvertTo(typeof(string[])) as string[];
if (indexesArray != null) {
stopOnIndexNotFound = false;
indexes = indexesArray;
return;
}
}
// just use a simple zero-based system
stopOnIndexNotFound = true;
indexes = GetZeroBasedIndexes();

No os perdáis en los detalles. Básicamente lo que hace es:

  1. Si existe un valor de request llamado “index” este valor debe contener un string[] que contendrá los índices a buscar. En este caso la variable stopOnIndexNotFound se pone a false, por lo que el método UpdateCollection no se parará cuando no encuentre un valor del array. Simplemente saltará al siguiente
  2. Si dicho valor no existe, hace lo que habíamos visto: pone la variable stopOnIndexNotFound a true y devuelve la colección de índices infinita empezando por 0.

El valor de request “index”

Así pues la solución consiste en añadir un campo en la request (en nuestro caso en el formulario) cuyo valor sea un string[] con los nombres de todos los campos índice :)

¿Y como se envia un string[] desde HTML? Pues muy sencillo, enviando N veces un campo con el MISMO name. Fijaos como nos queda la vista ahora:

@using (Html.BeginForm())
{
for (int i = 0; i < 10; i++)
{
<text>Pregunta @i:</text>
@Html.RadioButton("[" + i + "]", 1);
@Html.RadioButton("[" + i + "]", 2);
@Html.RadioButton("[" + i + "]", 3);
<input type="hidden" name="index" value="@i" />
<p />
}

<input type="submit" value="enviar!" />
}

Fijaos en el <input type=”hidden”> con name index que está dentro del for. En el HTML generado habrá 10 hiddens todos con el atributo “name” con el mismo valor “index” y cada uno con un valor distinto (de 0 a 9). Esto, a nivel del DefaultModelBinder, se recibe como un string[].

Bueno… y que ocurre ahora, si mando exactamente lo mismo que la vez anterior? Pues esto es lo que recibimos en el controlador:

image

Fijaos, que ahora recibimos 7 valores, que se corresponden a las 7 filas con alguna radiobutton marcada.

Vale, vale, vale… ya os oigo decir: “Sí, todo esto está muy bien, pero tampoco me sirve de nada. Aquí había 10 preguntas (0-9) y el usuario ha marcado sólo 7. Tengo las 7 respuestas ok, pero los índices son incorrectos!”. Efectivamente, vemos recibo una coleccción de 7 ints (las 7 respuestas) pero no se cuales han sido las que se han quedado en blanco! Yo había dejado sin marcar la #2, la #6 y la #8. Como puedo saber esto?

La respuesta es que tranquilos, que sólo hemos mirado en un lado, la respuesta completa la tenemos en otro. Efectivamente, el DefaultModelBinder nos ha creado una colección con los 7 valores entrados por el usuario. Pero puedo saber exactamente a que posición se corresponde cada valor? Pues sí, gracias a ModelState:

image

Fijaos en el valor de ModelState.Keys. Lo véis? Eso son las claves (los nombres) de los valores de la request. Exactamente! Con esto podemos hacer el mapeo:

  1. results[0] es el valor de la request "[0]”
  2. results[1] es el valor de la request “[1]”
  3. results[2] es el valor de la request “[3]” <—No hay ModelState.Keys con valor “[2]”, lo que significa que la fila #2 se había dejado sin ninguna radio marcada.

Lo veis? ;-)

Por supuesto, todo esto se complica si nuestro controlador recibe varios parámetros o bien recibe una colección que está como propiedad de un objeto, pero no se complica demasiado, no os creais. En un siguiente post lo veremos para dejarlo claro :)

Y finalmente es posible que digas: “Pues, perdón pero eso no me gusta nada!, No podría el ModelBinder devolverme un array con las posiciones rellenadas y con los índices correctos?”

Bueno… pues poder, se puede pero ya cuesta un poco más de trabajo. pero tranquilos que veremos como… pero de momento, basta por hoy, no? :D

Un saludo a todos!

con 5 comment(s)
Archivado en:

Buenas! Hoy voy a comentar un temilla que me comentó un colega el otro día y que puede dar algunos quebraderos de cabeza: el binding de colecciones.

Supongamos el siguiente viewmodel:

public class QuestionModel
{
public int IdQuestion {get; set;}
public string Text { get; set; }
public int IdAnswer { get; set; } // Id de la respuesta seleccionada
public IEnumerable<Answer> Answers { get; set; }
}

public class Answer
{
public int IdAnswer{ get; set; }
public string Text { get; set; }
}

Básicamente una pregunta contiene un Id, un texto y una lista de respuestas. Bien, supongamos que tenemos un método en el controlador que nos cree una lista de 10 preguntas, cada una de las cuales con 3 posibles respuestas, y la mande a una vista:

public ActionResult Index()
{
var questionModelList = new List<QuestionModel>();
for (var i = 1; i <= 10; i++)
{
questionModelList.Add(new QuestionModel()
{
IdQuestion = i,
Text = "Texto pregunta " + i,
IdAnswer = 0,
Answers = new List<Answer>() {
new Answer() {Text = "Respuesta A de " + i, IdAnswer = 1},
new Answer() {Text = "Respuesta B de " + i, IdAnswer = 2},
new Answer() {Text = "Respuesta C de " + i, IdAnswer = 3} }
});
}
return View(questionModelList);
}

Al igual que a la vista le mandamos una List<QuestionModel>, esperamos que eso sea lo que la vista nos devuelva en el controlador:

[HttpPost]
public ActionResult Index(List<QuestionModel> questionModelList)
{
// codigo...
}

Bueno, ahora veamos una posible implementación de la vista Index:

@model List<Ejemplo.ConRadioButtonNormal.Models.QuestionModel>
@{
ViewBag.Title = "Index";
}
@using (Html.BeginForm())
{
<div>
@foreach (var question in Model)
{
<div>
@Html.LabelFor(x=>question.Text, question.Text)
@foreach (var answer in question.Answers)
{
@Html.RadioButtonFor(m => answer.Text, answer.IdAnswer)
}
</div>
}
</div>
<p><input type="submit" value="Submit" /></p>
}

Buenooo… parece una implementación correcta no? Iteramos sobre todas las preguntas y por cada pregunta generamos una label y tantas radio buttons como respuestas tenga la pregunta… El problema es que… no es correcta! :)

Veamos el código HTML que nos genera:

<div>
<label for="question_Text">Texto pregunta 1</label>
<input id="answer_Text" name="answer.Text" type="radio" value="1" />
<input id="answer_Text" name="answer.Text" type="radio" value="2" />
<input id="answer_Text" name="answer.Text" type="radio" value="3" />
</div>
<div>
<label for="question_Text">Texto pregunta 2</label>
<input id="answer_Text" name="answer.Text" type="radio" value="1" />
<input id="answer_Text" name="answer.Text" type="radio" value="2" />
<input id="answer_Text" name="answer.Text" type="radio" value="3" />
</div>
<!-- Y así sucesivamente... :p -->

Fijaos que tanto el id como el name de todas las radiobutton son iguales! Y eso? Eso es porque name e id se sacan de la lambda expresion que se pasa como parametro al helper Html.RadioButtonFor<>. Y esa lambda es siempre la misma. Bueno… al menos el error es fácil de detectar porque una vez marqueis una radio button, al marcar cualquier otra de cualquier otra pregunta la primera se seleccionará. Recordad que HTML agrupa las radiobuttons según el atributo “name”, y si todas tienen el mismo, todas las radiobutton forman un solo grupo, y por lo tanto sólo una puede estar seleccionada.

Nota: Recordad la clave: la expresión lambda que se pasa a Html.RadioButtonFor<> es la que se usa para generar el valor del atributo name.

La siguiente pregunta que debemos hacernos es… cual debe ser el valor correcto de los atributos name de cada radiobutton? Para eso que ver como el DefaultModelBinder enlaza los datos cuando hay colecciones, y por suerte la regla es muy sencilla. Si estamos enlazando datos de una colección el DefaultModelBinder espera que el valor del atributo name tenga el formato: parametro[idx]. Es decir, analicemos la estructura de clases que el controlador espera.

El controlador espera una List<QuestionModel> llamada questionModelList. De ese modo:

  • questionModelList[0].Text se enlazaría con la propiedad Text del primer elemento de la lista.
  • questionModelList[1].Text se enlazaría con la propiedad Text del primer elemento de la lista.
  • questionModelList[0].Answers[0].Id se enlazaria con el valor de la propiedad Id de la primera respuesta del primer elemento de la lista.
  • questionModelList[0].Answers[1].Id se enlazaria con el valor de la propiedad Id de la segunda respuesta del primer elemento de la lista.

¿Lo veis? El DefaultModelBinder espera que el valor del atributo name sea “lo mismo que usaríamos en C#”.

Nota: Si te “hace daño a los ojos” lo de questionModelList al principio del atributo name (recuerdo que esto es el nombre del parámetro), que sepas que si el controlador sólo recibe una colección (como en nuestro caso) puedes eliminar el nombre del parámetro. En nuestro caso podriamos usar [0].Text como valor del atributo name p.ej.

Así, p.ej. podríamos modificar la vista para que quede parecida a:

@model List<Ejemplo.ConRadioButtonNormal.Models.QuestionModel>
@{
ViewBag.Title = "Index";
}
@using (Html.BeginForm())
{
<div>
@for (int idx = 0; idx < Model.Count; idx++)
{
<div>
<label>@Model[idx].Text</label>
@for (int idxAnswer = 0; idxAnswer < Model[idx].Answers.Count(); idxAnswer++)
{
var lstAnswers = Model[idx].Answers.ToList();
<input type="radio"
name="@string.Format("questionModelList[{0}].Answers[0].IdAnswer", idx)"
value="@lstAnswers[idxAnswer].IdAnswer" />
}
</div>
}
</div>
<p><input type="submit" value="Submit" /></p>
}

Eso genera un HTML como el siguiente:

<div>
<label>Texto pregunta 1</label>
<input type="radio"
name="questionModelList[0].Answers[0].IdAnswer" value="1" />
<input type="radio"
name="questionModelList[0].Answers[0].IdAnswer" value="2" />
<input type="radio"
name="questionModelList[0].Answers[0].IdAnswer" value="3" />
</div>
<div>
<label>Texto pregunta 2</label>
<input type="radio"
name="questionModelList[1].Answers[0].IdAnswer" value="1" />
<input type="radio"
name="questionModelList[1].Answers[0].IdAnswer" value="2" />
<input type="radio"
name="questionModelList[1].Answers[0].IdAnswer" value="3" />
</div>

Ahora los campos siguen la convención que espera el DefaultModelBinder. Con lo que los datos ahora si que serán recibidos por el controlador.

Bueno… ¿pero esto que sentido tiene?

Si os fijáis veréis que las radiobuttons de cada pregunta tienen todas el mismo name. Eso es correcto ya que sólo una respuesta puede seleccionarse por cada pregunta. Así todas las radiobuttons de la primera pregunta tienen el valor de questionModelList[0].Answers[0].IdAnswer y así sucesivamente.

Así pues, cual es la información que viaja del cliente al servidor? Aunque tengamos tres radiobuttons por pregunta, todas tienen el mismo name y sólo una viajará. Es decir, dadas las 10 radiobuttons viajarán 10 valores del cliente al servidor:

  • questionModelList[0].Answers[0].IdAnswer
  • questionModelList[1].Answers[0].IdAnswer
  • questionModelList[9].Answers[0].IdAnswer

Es decir, para el servidor tendremos una lista de 10 preguntas cada una de las cuales tendrá UNA sola respuesta (la seleccionada por el usuario). Y eso es exactamente lo que se bindea en el controlador:

image

Fijaos como p.ej. el campo Text de la pregunta es “null”. Normal, la vista no lo está enviando al controlador.

Parémonos un momento en este punto. Desde el controlador hemos mandado a la vista toda una List<QuestionModel> llena. Es decir la vista si que recibe el texto de las preguntas y las respuestas… Por que cuando estamos de vuelta en el controlador hemos perdido esta información?

Si te estás preguntando esto: bienvenido al mundo stateless de la web. Efectivamente el controlador envía toda una List<QuestionModel> a la vista y la vista usa esta información para generar un HTML, que es mandado al cliente. En este punto el trabajo del servidor ha terminado. El controlador muere y será creado de nuevo cuando se reciba otra petición del browser. Esta otra petición llega cuando pulsamos el botón de submit, pero el controlador sólo recibe lo que la vista envía. Y la vista sólo envia lo que está en el formulario: los IDs de las respuestas seleccionadas. La vista no envia los textos ni de la pregunta, ni de la respuesta. Por eso no los tenemos de vuelta. Recordad: la web es stateless. Si venís de Webforms, webforms os ocultaba esto y os hacía creer que la web es statefull. Pero MVC no oculta nada de nada: lo que enseña es, simplemente, lo que hay.

Bueno… dicho esto, ahora preguntaos, si tiene sentido que este método del controlador reciba una lista de QuestionModel. El tema es que si esta vista tiene que enviar las respuestas eso debería ser lo que debería esperar el controlador. Es decir, la vista recibe un List<QuestionModel> pero devuelve un array con los IDs de las preguntas seleccionadas… Total… la vista no va a devolver el texto de las preguntas y las respuestas al controlador, no? Si lo que la vista devuelve son los IDs de las respuestas seleccionadas esto es lo que debería recibir el controlador:

image

Fijaos simplemente que el controlador recibe una colección con los IDs de las respuestas seleccionados. Así idRespuestas[0] el ID de la respuesta seleccionada de la primera pregunta y así sucesivamente. La información del texto de las preguntas (en caso de ser necesaria) la obtendría el propio controlador (leyéndola de la BBDD, de la cache, de donde fuera).

Y como nos quedaría la vista? Pues como ahora no mandamos una estructura tan compleja al controlador, generar el nombre de los atributos name es mucho más fácil:

@model List<Ejemplo.ConRadioButtonNormal.Models.QuestionModel>
@{
ViewBag.Title = "Index";
}
@using (Html.BeginForm())
{
<div>
@for (int idx = 0; idx < Model.Count; idx++)
{
<div>
<label>@Model[idx].Text</label>
@for (int idxAnswer = 0; idxAnswer < Model[idx].Answers.Count(); idxAnswer++)
{
var lstAnswers = Model[idx].Answers.ToList();
<input type="radio"
name="@string.Format("[{0}]", idx)"
value="@lstAnswers[idxAnswer].IdAnswer" />
}
</div>
}
</div>
<p><input type="submit" value="Submit" /></p>
}

Vale, fijaos en dos cosillas:

  1. La vista sigue recibiendo una List<QuestionModel>. Normal, porque esto es lo que el controlador le sigue mandando. Pero eso NO implica que la vista deba mandar de vuelta esto mismo!
  2. El valor de los atributos “name” de las distintas radiobutton, es simplemente [0],[1],… y así sucesivamente (recordad como hemos comentado antes que si el controlador recibe una sola colección no es necesario poner el nombre del parámetro en el atributo name).

Una nota final…

Bueno, hemos visto como funciona el binding de colecciones en ASP.NET MVC, pero os quiero comentar sólo una cosilla más :)

Que ocurriria si le enviasemos al controlador los siguientes campos (sus valores son irrelevantes)?

  1. [0], [1], [3], [4]
  2. [1], [2], [3], [4]

En el primer caso vemos que falta [2]. Entonces el DefaultModelBinder se para en este punto. Es decir el controlador recibirá una colección con dos elementos (los valores de [0] y [1]. Los valores de [3] y [4] se han perdido).

En el segundo caso vemos que falta el primer elemento [0]. Entonces el DefaultModelBinder ni enlaza el parámetro. Es decir el controlador no recibirá una colección vacía, no… recibirá null en el parámetro.

En un próximo post veremos como podemos evitar esto ;-)

Un saludo!

ds

con 8 comment(s)
Archivado en:

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

con no comments
Archivado en:

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

con no comments
Archivado en: ,,

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! :D

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

con 2 comment(s)
Archivado en:

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

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
con 5 comment(s)
Archivado en:
Más artículos Página siguiente >