ObservableCollection<T>, INotifyPropertyChanged y WinRT

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.

Aplicaciones "de una sola página” con HTML5 y ASP.NET MVC

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

Usar Recaptcha en ASP.NET MVC (desde cero)

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

C#5 Tomando una cerveza con Feynman (o await 2a parte)

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

C# 5: Async / Await

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).

Mi primera aplicación en MetroUI

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 😛 😛 😛

C# Básico: Objetos y referencias

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!

Binding de colecciones en ASP.NET MVC (iii)

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!

Binding de colecciones en ASP.NET MVC (ii)

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? 😀

Un saludo a todos!

Binding de colecciones en ASP.NET MVC

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