Conoce Windows Azure con los #CloudIO de desarrolloweb.com

Desde Microsoft estamos decididos a que conozcas Windows Azure, así hasta finales de Abril vamos a tener una serie de #AzureIO en desarrolloweb.com.

Se pueden seguir las actualizaciones sobre los #CloudIO en @deswebcloud

Para la gente que no conozca el formato, www.desarrolloweb.com es un portal de ayuda a programadores de Web y otras soluciones que organizan los AzureIO. Se puede encontrar más información en esta dirección: http://www.desarrolloweb.com/en-directo/

Esta es la lista completa de todos los conceptos que vamos a cubrir en estos #AzureIO, así que estad atentos al blog y a las cuentas oficiales de Twitter de Microsoft España donde iremos publicando las direcciones de los eventos.

Windows Azure Virtual Machines

Máquinas virtuales de Windows Server y Linux en la nube

Enlace de YouTube.

Windows Azure Mobile Services

Crear un backend para tu app movil en segundos. Con soporte para Windows Store, Windows Phone, iOS, Android y HTML / Javascript

Windows Azure Notification Hubs

Envía millones de notificaciones push a dispositivos Windows, iOS o Android con una única llamada de API.

Windows Azure Storage

Es el servicio de almacenamiento con tres abstracciones, Tablas, Colas y Blobs

Windows Azure Web Sites

Te permite crear tu sitio web en segundos. Elige entre mucha de las plantillas de aplicación, o conecta tu repositorio de código para publicar directamente.

Windows Azure Cloud Services

Los servicios en la nube, permite a las aplicaciones empresariales escalar hasta el infinito, elije entre roles web o de trabajo.

Active Directory y MFA

Aloja tu controlador de dominio en Windows Azure y habilita a tus usuarios la autenticación en dos pasos. (Multi-Factor Authentication)

Windows Azure Media Services

Es el servicio que te permite la ingesta masiva de videos, la codificación en diferentes formatos y calidades, la protección de tu IP con PlayReady y la distribuccion de los contenidos a través de la CND con soporte para re empaquetar el contenido al vuelo.

HDInsight

Crea tu cluster de Apache Hadoop en segundos y utiliza el algoritmo Map-Reduce.

Windows Azure Cache & Traffic Manager

El servicio de Windows Azure Cache permite guardar objetos en una cache distribuida de alto rendimiento. Traffic Manager es la manera de gestionar como los usuarios acceder a tu servicio mundialmente.

Backup Service

Este servicio te permite hacer backup de tus datos de onpremises en la nube.

Luis Guerrero.

Technical Evangelist Windows Azure

@guerrerotook

Material del evento de comunidad de Sevilla

El pasado día 5 de noviembre, coincidiendo con el evento de TechDay Tour 2013 de Microsoft, se celebró un evento de comunidad en Sevilla en el participe junto con Javier Suarez y Pablo Escribano. Mi sesión fue sobre patrones de software en aplicaciones Windows Phone 8 y Windows 8.

En la sesión se explicarlos los patrones de desarrollo más comunes y cuando había que usarlos y cuando no. Os dejo la presentación de la charla y el código de ejemplo.

image

Descarga del codigo fuente.

Luis Guerrero.

3 formas diferentes de publicar una Web en Windows Azure.

Introducción

Windows Azure es una plataforma muy flexible en la que se pueden publicar y consumir todo tipo de aplicaciones. En este post se repasarán las 3 formas que hay de publicar un Web, viéndose las ventajas e inconvenientes de las tres formas.

Las tres maneras son: Máquinas virtuales (Infraestructura como servicio IaaS), Servicios de nube (Plataforma como servicio, PaaS) y Windows Azure Web Sites.

Maquinas Virtual, IaaS

Las máquinas virtuales, como su nombre indica es la capacidad que tiene Windows Azure de hostear una máquina virtual basada en ficheros vhdx. Desde el portal de Windows Azure se puede seleccionar nueva máquina virtual y seleccionar creación rápida. De esa manera se tiene una maquina encendida y funcionado.

Una vez generada la VM se puede instalar desde ahí, Internet Information Services y copiar nuestra aplicación Web en la máquina.

Ventajas

  • Se tiene toda la flexibilidad para instalar y administrar la máquina virtual desde escritorio remoto.
  • Se puede alojar la aplicación web en Linux.
  • La máquina virtual es persistente.

Inconvenientes

  • No hay mecanismo definido para publicar la web. Copiar los ficheros por escritorio remoto a mano o cualquier otro mecanismo.
  • Hay que configurar e instalar IIS y ASP.NET.
  • Se tiene que configurar y administrar el balanceo de carga a mano para el puerto de HTTP/HTTPS. Cuando se quieran agregar más maquinas se tiene que hacer a mano.
  • Las tareas de administración se tienen que repetir para todas las máquinas.
  • El usuario es responsable del mantenimiento del S.O.

Servicios en la nube (Plataforma como servicio)

Este fue el primer servicio con el que se lanzó Windows Azure. Permite empaquetar una aplicación Web, independientemente del tipo de lenguaje o runtime, y publicar esa aplicación en el número de máquinas que se hayan designado en el fichero de configuración.

En este método de publicar aplicaciones web, se genera una máquina virtual, se instala y configura IIS con los valores presentes en el fichero de configuración del servicio y se copia el código de la aplicación web al directorio de publicación de IIS.

El proceso es completamente automático y no requiere atención por el usuario en ningún momento. No tiene límite en cuanto al número de instancias de máquinas. (Cómo curiosidad decir que el máximo de máquinas que el autor ha configurado han sido 750, toda una pasada)

Ventajas

  • Flexibilidad para empaquetar y publicar la aplicación.
  • No requiere configuración por parte del usuario, está completamente automatizado.
  • Permite incrementar y reducir el número de instancias a petición del usuario, también de manera automática.
  • Tiene soporte para el autoscale de Windows Azure.
  • Permite configurar aplicaciones web en subdominios y subcarpetas como aplicaciones (en IIS).
  • Permite configurar todos los certificados SSL que se deseen.

Inconvenientes

  • Las máquinas donde se ejecutan no son persistentes. Hay que guardar todos los datos fuera de la máquina donde se ejecuta. Por ejemplo en un Windows Azure Storage.
  • Las peticiones Http al servicio no tienen afinidad, lo que significa que cualquier petición puede ir a cualquiera de las maquinas del servicio en la nube. Hay que configurar la sesión de ASP.NET para que se guarde en Sql Server, Windows Azure Cache Service o Windows Azure Storage.
  • Hay que ser capaz de empaquetar la aplicación web y todas sus dependencias de software.

Windows Azure Web Sites

Windows Azure Web Sites es otro servicio de Windows Azure enfocado a la publicación de aplicaciones Web pero, ¿Cuál es la diferencia con un servicio en la nube? Los servicios en la nube pueden contener, además de un rol de tipo Web, un rol de tipo de trabajo. Es decir un servicio en la nube es una colección de roles de trabajo y de web. Un rol de trabajo es como un servicio de Windows, pero que se ejecuta en Windows Azure y ejecuta tareas en segundo plano. Esas tareas pueden ser generar informes, consolidad información en la base de datos, envíos masivos de emails, cambio de resolución de imágenes y todo aquello que se tiene que hacer en nuestra aplicación web, pero que no se quiere que se impacte en el rendimiento del sitio web.

Volviendo al tema de Windows Azure Web Sites, este servicio ofrece tres modos de funcionamiento:

  • Gratis: los usuarios tienen hasta un máximo de 10 sitios web de manera gratuita, 1 Gb de espacio en disco y 165 megas de salida al día.
  • Compartida: hasta un máximo de 100 sitios web, que se pueden escalar hasta un máximo de 6 instancias, con 1 Gb de espacio en disco y el tráfico de salida se factura al precio estándar.
  • Estándar: hasta un máximo de 500 sitios web con una CPU dedicada, 10Gb de espacio en disco y el tráfico de salida se factura al precio estándar.

Con estas tres configuraciones se ofrecen diferentes maneras y precios de alojar tu aplicación web según los requerimientos del usuario.

Otra de las diferencias con respecto a los servicios en la nube, es la forma de publicar tu sitio web. WAWS ofrece varias maneras de publicar:

  • Microsoft Web Publishing: Permite publicar un proyecto de web directamente desde Visual Studio.
  • Team Foundation Service: se pude publicar directamente desde TFS Service, la solución en la nube de TFS. No funciona con TFS on premises.
  • Repositorio local de Git: puedes hacer push desde un repositorio local a uno online que representa tu sitio web.
  • Github es un servicio de hosting en Git de terceros.
  • Dropbox: te permite configurar una carpeta de la cuenta para publicar directamente en tu sitio web.
  • Bitbucket: otro servicio de hosting de Git como Github.
  • Codeplex: el servicio de hosting de proyectos de Microsoft.
  • External repository: un repositorio externo en una URL.

Otro de los aspectos que se pueden configurar en el servicio es la versión de .NET Framework en la que se ejecuta la Web, V3.5 o V4.5. Otro de los lenguajes que vienen instalados es PHP que se puede deshabilitar o cambiar la versión de 5.3 a 5.4.

Ventajas

  • Flexibilidad en cuanto al método de publicación de la web.
  • Perfecta para trabajo en equipo.
  • Rapidez, dar de alta un sitio web en Windows Azure son segundos y tener tu web online también.

Inconvenientes

  • No se puede acceder a la configuración de IIS, solamente la configuración que se ofrece desde el portal de administración de Windows Azure.
  • El número de instancias de los modos de compartido y estándar son máximo 6 y 10 respectivamente. En los servicios de la nube no hay límite.

Espero que el artículo haya sido de ayuda para decidirse con qué servicio de Windows Azure se ajusta mejor a las necesidades de cada proyecto.

Migrando una aplicación de Windows 8 a Windows 8.1. Caso práctico Reddit 8.1

Windows 8.1

El lanzamiento de Windows 8.1 es inminente y al ser una actualización gratuita del Sistema Operativo, será mucha gente, por lo decir la mayoría, la que se instale la actualización. Eso significa que si eres desarrollador de Aplicaciones de la Tienda de Windows, estarás muy ocupado ahora mismo migrando tu aplicación de Windows 8 a Windows 8.1.

En esta serie de artículos se van a detallar los diferentes pasos de migración de las aplicaciones. Y para tener un ejemplo concreto se hablará de una aplicación concreta Reddit8. Un cliente de Reddit para Windows 8 que ya está en la Store y que he procedido a migrar a Windows 8.1. Con este pretexto pretendo enseñar los diferentes desafíos que he tenido a nivel personal durante la migración y como la aplicación ha evolucionado.

TfsService

La primera recomendación antes de empezar con la migración es la de tener un control de código fuente. Esto es así porque cuando se empiece la migración, el proceso es irreversible y no se podrá volver a la versión anterior de Windows. Teniendo esto en cuenta se extrae que las aplicaciones de Windows 8.1 no son compatibles con Windows 8. Pero eso no significa que no se quiera seguir dando soporte a los clientes que tengan Windows 8.

Pon un control de código fuente en tú vida.

Es por eso que el primer paso antes de nada, es asegurarse de que se está trabajando con un control de código fuente. Yo recomiendo usar Team Foundation Service (http://tfs.visualstudio.com/) porque se integra perfectamente con Visual Studio, incluye gestión de elementos de trabajo, está en la nube y accesible desde cualquier sitio, permite trabajar con equipos de manera ágil y planificar los procesos de desarrollo.

Y lo mejor de todo esto, es que se puede usar de manera gratuita hasta 5 usuarios simplemente teniendo una cuenta de Microsoft.

image

Branch

Lo primero que hay que hacer, después de tener el código fuente en TfsService es crear una rama dentro del proyecto en el que se esté trabajando. En el ejemplo que se está siguiendo en el artículo, se pasó de llamar la carpeta Reddit8 a llamarse Reddit8.1 y ser una rama de la carpeta antes mencionada.

Migrando a Windows 8.1

La primera vez que se abre un proyecto de Windows 8 en Visual Studio 2013 Preview, VS avisa de que la solución es un proyecto de Windows 8 y que si se desea migrar a Windows 8.1. Si se elige la opción de sí, se migran todos los proyectos que tengan la solución a Windows 8.1 siendo este proceso irreversible.

En el caso de querer hacer el proceso de migración en otro momento lo único que hay que hacer es ir a las propiedades del proyecto, y en la pestaña de aplicación aparece un desplegable con la opción de cambiar la plataforma.

image

Errores de compilación

Una vez que se ha realizado el proceso de cambiar la plataforma de destino de todos los proyectos, 4 en Reddit8, se compila el proyecto y aparecen los primeros errores.

image

Que se pueden resumir en errores del framework de publicidad de Microsoft, que no es la versión correcta, y el resto de errores, son a la hora de actualizar las Tiles secundarios de la aplicación. Esto es comprensible, porque una de las características nuevas de Windows 8.1 es justamente que se han cambiado el tamaño de los Tiles.

Estos errores son fáciles de corregir. El primero simplemente es bajarse la versión para Windows 8.1 del SDK de Advertising de Microsoft de aquí. (http://msdn.microsoft.com/en-us/library/dn283993(v=msads.10).aspx)

Y el resto de errores no son tan inmediatos de corregir, porque la forma en la que se generar los Tiles secundarios ha cambiado, así que no es simplemente corregir los errores sino que hay que añadir nueva funcionalidad.

API Obsoletas

Después de corregir esos errores de los Tiles secundarios aparecen una lista de errores más grande con API que se han hecho obsoletas en Windows 8.1 y que hay otra manera de consumirlas. Se resumen brevemente en:

  • ScrollViewer. Ya no tiene métodos específicos para cambiar el desplazamiento vertical y horizontal, sino que se han integrado todos en un método llamado ChangeView que te permite cambiar desplazamiento vertical, horizontal y valor de zoom.
  • ItemsControls. Ya no se puede usar la propiedad ItemContainerGenerator que permite obtener el contenedor de una colección de elementos a partir de índice o del valor. Simplemente se ha de eliminar el texto de ItemContainerGenerator, ya que, por ejemplo, ContainerFromIndex forma parte de la clase ItemsControls y no de la clase ItemContainerGenenator.
  • ApplicationView.Value. Si se estaba usando la plantilla de código que venía con Visual Studio que incluía una clase llamada LayoutAwarePage, se ha simplificado el desarrollo de este componente.
  • DataPackage. El método SetUri, se ha cambiado por SetWebLink o SetApplicationLink.

Cambio en experiencia de usuario

Las novedades de Windows8.1 no solo están en el código sino también en los controles nuevos que permiten acercase mucho más a la experiencia de usuario de Windows8 de manera más sencilla.

A continuación se verán algunos de los cambios de funcionalidad que se han podido mejorar con controles y funcionalidad específica de Windows 8.1.

HomePage

La página principal de la aplicación ha pasado de tener un GridView con dos categorías, frontpage y subreddits.

image

A esto otro.

image

El cambio ha sido a mucho mejor. Se ha utilizado el nuevo control Hub, que soporta zoom semántico y organizar la información en secciones para mostrarlas todas seguidas.

En el caso concreto de Reddit8 se muestra una imagen que pertenece a la frontpage, que en la medida de lo posible se intenta que no se repita y el usuario siempre pueda ver contenido directamente.

El Hub control es una colección de HubSections, incluyendo una cabecera de todo el control. Cada una de las secciones incluye una cabecera y un contenido que es establecido a través de una plantilla. Así de esta manera podemos personalizar tanto el contenido como el aspecto de cada una de las secciones.

clip_image011Otro cambio incluido en la app, es la nueva barra de búsqueda que tiene la misma funcionalidad que el charm de búsqueda de Windows 8. Además se pueden seguir haciendo sugerencias de contenido conforme el usuario escribe. De esta manera ya no es necesario activar la capacidad de busqueda, ya que los dos son incopatibles entre sí.

Vista de Subreddit

Esta es la vista antes del cambio. Un GridView exactamente igual que en la página principal, pero que además mostraba información sobre el subreddit.

image

Esta es la manera en la que se muestra el contenido ahora:

image

Se ha vuelto a utilizar el mismo tipo de layout que en la página principal, pero esta vez el título de Hub es el nombre del subreddit (/r/windows8). Se ha eliminado la información del subreddit por considerarse superflua y se ha puesto en la barra de comandos.

image

En la barra de comandos de abajo aparecen, utilizando la nueva CommandBar, los comandos que son principales y secundarios. Los principales son refrescar, subscribirse al subreddit (si has iniciado sesión) agregarlos a favoritos para la navegación de la barra de arriba. En el caso de los comandos secundarios está anclar al inicio e información. Este último utiliza el controls Flyout para mostrar contenido de manera mucho más cómoda y directa que antes.

El desarrollo de la barra de comandos, se ha simplificado y se ha hecho más acorde con el estilo de aplicaciones de Windows 8.

Live tiles

Otra de las novedades de Windows 8.1 es el cambio de tamaño de los Tiles en el menú de inicio. Ahora cuando un usuario quiere anclar un elemento puede elegir el tamaño en el que quiere anclarlo.

clip_image018

Eso significa que se puede tener tiles en todos los tamaños, incluso cuando el usuario los crea.

clip_image019

Conclusión

Windows 8.1 es una mejora muy sustancial de las API que Microsoft ofrece a los desarrolladores. Ahora es más fácil que nunca adecuar nuestras aplicaciones de Windows 8 con la estética de Modern UI. Y en el caso concreto de Reddit8 la mejora, desde mi punto de vista, es más que notable. Ahora el contenido es el centro del diseño y de la funcionalidad.

Si quieres probar la nueva funcionalidad de este cliente de Reddit, puedes ir aquí para descargarte las betas que hay públicas.

http://www.reddit.com/r/reddit8/

Material de la charla de Computación paralela en Windows de la CodeMotion

Como viene siendo habitual aquí tenéis el material de la charla sobre computación paralela del pasado sábado día 24 de marzo.

clip_image002

El código de ejemplo lo podéis descargar de aquí: http://bit.ly/TPLCodeMotion

Y ya sabéis nada de dejar los try/catch vacíos.
¡Espero que disfrutéis de todos los cores del mundo!

Saludos. Luis.

San Valentín se Baila

Este domingo en la plaza de
Callao de Madrid habrá un evento de El Corte Inglés, Microsoft y Xbox 360 para
celebrar el día de los enamorados. En este evento Plain Concepts presentará dos
aplicaciones para Windows Phone 7 y Surface 2 para que las parejas de
enamorados puedan hacerse fotografías con el teléfono móvil (un Nokia Lumia) y
después componer una tarjeta de felicitación en un Surface 2.

Os invitamos a todos a que os paséis este domingo por
la mañana por Callao con vuestras parejas, para celebrar San Valentín y disfrutar
de estas aplicaciones.

Windows Phone 7

Las imágenes se suben a Azure y luego desde la aplicación de Surface 2 se pueden componer.

Surface 2

Hack-a-thon, repaso a las novedades de networking de Windows Phone 7

Este fin de semana en Málaga se ha celebrado el Hack-a-thon un evento de Microsoft para incentivar el desarrollo de aplicaciones de Windows Phone 7 en entornos universitarios. Es todo un fin de semana de programación de apps y entre medias los desarrolladores pueden elegir las charlas que quieres escuchar. Así que es un agenda dinámica que se decide en base a las valoraciones.

Yo es la segunda vez que participo en un evento de este tipo y en mi caso la charla que me ha tocado es la de Networking. Así que estando ahora mismo en el evento aprovecho para hacer un repaso de las novedades de Windows Phone 7.1 (Mango) en el apartado de comunicaciones.

clip_image002

Peticiones HTTP

Dentro de apartado de peticiones HTTP de toda la vida, WP7 tiene dos clases para realizar este trabajo: WebClient y HttpWebRequest (+Response). Las dos API se distinguen una de la otra por la simplicidad y opciones que ofrecen.

Hay que recordad que todas las comunicaciones en WP7 son asíncronas.

WebClient

WebClient es la API más sencilla para hacer peticiones HTTP, simplemente hay que crear una instancia de esta clase, suscribirse al evento deseado, por ejemplo, DownloadStringCompleted y llamar al método DownloadStringAsync(Uri) para descargarse el contenido de una URI como un string.

public partial class MainPage : PhoneApplicationPage
{
WebClient client;

// Constructor
public MainPage()
{
InitializeComponent();

client = new WebClient();
client.DownloadStringCompleted +=
new DownloadStringCompletedEventHandler(
client_DownloadStringCompleted);
}

void client_DownloadStringCompleted(object sender,
DownloadStringCompletedEventArgs e)
{
if (e.Error == null)
{
XElement twitterElements = XElement.Parse(e.Result);


var postList =
from tweet in twitterElements.Descendants("status")
select new TwitterPost
{
UserImage = tweet.Element("user").Element("profile_image_url").Value,
PostText = tweet.Element("text").Value,
DatePosted = tweet.Element("created_at").Value
};

tweetsListBox.ItemsSource = postList;
}
}

private void loadButton_Click(object sender, RoutedEventArgs e)
{
string url = "http://twitter.com/statuses/user_timeline/" +
nameTextBox.Text + ".xml";
client.DownloadStringAsync(new Uri(url));
}
}

En este ejemplo podemos ver como en los argumentos DownloadStringCompletedEventArgs podemos obtener una propiedad llamada Result que contiene el string con el contenido de la petición.

En este tipo de peticiones no podemos añadir cookies ni configurar ningún otro tipo de propiedad para la petición.

Las opciones de personalización son:

  • Añadir cabeceras en la petición y leer las cabeceras de la respuesta.
  • Configurar credenciales para autenticación de usuarios.
  • Permitir la lectura buffereada del contenido de la lectura y de la escritura.
  • Codificación usada para lectura y escritura.

HttpWebRequest

HttpWebRequest es la clase de bajo nivel que permite hacer peticiones HTTP configurando todas las opciones que queramos, es mucho más flexible, pero más complejo de consumir. Estas API utiliza el APM (Asychonous Programming Model) de .NET lo que significa que utiliza para las notificaciones asíncronas IAsyncResult.

Estas son las características:

  • Acceso a todas las cabeceras.
  • Podemos agregar cookies en las peticiones y leer las cookies de respuesta.
  • Podemos especificar el método de la petición (GET o POST)
  • Podemos escribir en el cuerpo de la petición.

Así tenemos un ejemplo completo de peticiones usando HttpWebRequest:

public class ComplexRestRequest : BaseRequestProcessor
{
public override void ProcessRequest(Uri uri, string body)
{
content = body;

request = HttpWebRequest.Create(uri);
request.Method = "POST";

request.Headers["Authorization"] = AuthorizationService.AuthorizationToken.Token;
request.Headers["IsComplex"] = "true";
request.BeginGetRequestStream(new AsyncCallback(OnBeginGetRequestStream), null);

}

private void OnBeginGetRequestStream(IAsyncResult result)
{
Stream stream = request.EndGetRequestStream(result);
byte[] buff = System.Text.Encoding.UTF8.GetBytes(content);
stream.Write(buff, 0, buff.Length);
buff = null;

request.BeginGetResponse(OnBeginGetResponse, null);
}

private void OnBeginGetResponse(IAsyncResult result)
{
try
{
response = request.EndGetResponse(result);
string authorizationHeader = response.Headers["Authorization"];
if (!string.IsNullOrEmpty(authorizationHeader))
{
AuthorizationService.UpdateAuthorizationToken(authorizationHeader);
}
string content = null;
if (response.ContentLength > 0L)
{
using (Stream stream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(stream, Encoding.UTF8, true))
{
content = reader.ReadToEnd();
}
}
}

FireEndRequestCompleted(new HttpResult(content, false, null));
}
catch (Exception ex)
{
Trace.WriteLine(ex.ToString());
FireEndRequestCompleted(new HttpResult(null, true, ex));
}
}

private WebRequest request;
private WebResponse response;
private string content;
}

Socket

La siguiente gran funcionalidad de comunicaciones, muy esperada, en Windows Phone 7 son los sockets. Los sockets permiten una comunicación más directa en un canal de comunicación orientado a conexión (TCP) o no orientado a conexión (UDP y multicasting).

clip_image004

Windows Phone 7 soporta direcciones IPv4 pero no soporta IPv6. Toda la API es asíncrona.

Background file transfers

Si tenemos que descargar o subir ficheros al isolated storage de nuestra aplicación, pero queremos que esa descarga se haga cuando el usuario no esté usando la conexión a internet de su dispositivo móvil, podemos usar Background file transfers.

Esta API permite programar la descarga de un fichero al almacenamiento aislado de Windows Phone 7 incluso si nuestra aplicación no está ejecutándose. Soporta HTTP y HTTPS pero no FTP. Alguna de las cabeceras HTTP están reservadas, principalmente las de control de cache.

Estos son los valores de cuota:

  • Tamaño máximo de subida: 5MB
  • Tamaño máximo de descarga sobre 2G/3G: 20 MB
  • Tamaño máximo de descarga sobre WiFi: 100MB

API de información de comunicaciones

Todas las aplicaciones que utilicen recursos online deberán de ser tolerantes a faltas de conectividad por parte del usuario en su dispositivo. Si el usuario está modo avión, no tiene cobertura ni Wifi, la aplicación no debería de fallar y cerrarse, sino que debería de ofrecer la posibilidad de reconectarse de nuevo.

Para eso necesitamos saber cuál es el estado de las comunicaciones del dispositivo.

  • Consulta del operador móvil
    • DeviceNetworkInformation.CellularMobileOperator
  • Consulta si hay red disponible
    • DeviceNetworkInformation.IsNetworkAvailable
  • Consulta si hay red celular 2G/3G
    • DeviceNetworkInformation.IsCellularDataEnabled
  • Consulta si el romaing está habilitado
    • DeviceNetworkInformation.IsCellularDataRoamingEnabled
  • Consulta si el WiFi está habilitado
    • DeviceNetworkInformation.IsWiFiEnabled

Eligiendo la mejor serialización para aplicaciones móviles

Cuando desarrollamos aplicaciones móviles tenemos que tener en cuenta el tamaño de los datos que enviamos al cliente. Por eso tenemos que elegir la serialización que permite utilizar el menor tamaño para enviar los datos. Aquí tenemos una comparativa de los diferentes formatos para los mismos datos envíados.

Wire Serialization Format

Size in Bytes

ASMX SOAP – DataSet (XML)

39670

ODATA XML

73786

ODATA JSON

34030

REST + JSON

15540

REST + JSON GZip

8680

Luis Guerrero.

Autorización de usuarios para una API web en WCF o como hacer una gestión light de sesión en WCF

En uno de los proyectos en los que estoy trabajando ahora mismo tenemos que hacer una API para que se consuma desde Javascript puro, es un proyecto en HTML5, así que tenemos que maximizar la productividad para este tipo de cliente.

Nuestra API tiene un login de usuarios, un registro y partir de ahí los servicios debería de ser securizados, es decir, solamente para el usuario actual. Así que me surge la necesidad de autenticar estas peticiones para asegurarme de que es un usuario válido para acceder al servicio.

Viendo un poco como los demás servicios, Twitter, Facebook y compañía lo hacen, decidí que cuando el usuario hacer login se le devuelva un token de autorización temporal (que tienen este aspecto VvTnZEpvrYBDZfF1hCIR8kZR0yW7jKrA) obligar a que cada petición se añada una cabera más de Autrorization para que yo desde el servidor puede leerla y comprobar que es un usuario válido.

Ahora bien yo estoy desarrollando mi solución con WCF utilizando JSON como formato de cliente, para que así sea más cómodo consumirlo con el cliente, así que tenía dos maneras de solucionar esta manera de autorización, habilitar la compatibilidad de ASP.NET en WCF y hacerlo a través del objeto de HttpContext.Request o directamente utilizar la infraestructura de WCF.

Decidí usar únicamente WCF.

Autorizar al usuario

Lo primero de todo es que tengo que comprobar las credenciales del usuario en el login, podéis elegir el mejor mecanismo para eso. Una vez que sabemos que el usuario es un usuario válido tenemos que devolver el token de autorización para que pueda usarlo en sucesivas peticiones al servicio. ¿Cómo generamos esa autorización?

Yo he preferido hacerlo de la manera más sencilla y mantenerlo lo más sencillo posible. Yo genero un string formado por la id del usuario logeado y la fecha del login en ticks, así que me queda algo como esto: 1345-634475405148831292.

Evidentemente enviar ese string directamente al cliente es un grave problema de seguridad así que lo que tenemos que hacer es encriptar y añadir un hash a esa cadena.

private string CreateAuthorizationString(User user)
{
    string result = null;

    if (user != null)
    {
        string key = "{0}-{1}";
        key = string.Format(key, user.UserId, DateTime.Now.Ticks);

        ICryptoTransform transform = new TripleDESCryptoServiceProvider().CreateEncryptor(this.key, this.iv);
        byte[] input = Encoding.Default.GetBytes(key);
        byte[] buff = new byte[input.Length];
        buff = transform.TransformFinalBlock(input, 0, input.Length);
        
        result = Convert.ToBase64String(buff);
    }

    return result;
}

Yo para ese caso utilizo TripleDES como algoritmo simétrico y luego el string generado lo convierto a Base64 para tenerlo en un cómo string.

Comprobar la autorización en WCF

Una vez que tenemos generado el token de autorización tenemos que implementar un mecanismo para poder comprobar esa autorización en el servicio, teniendo un caso especial, uno cuando el usuario se quiere autorizar (hay que permitir la petición) y cualquier otra petición.

Yo en la definición de mi servicio tengo un webHttpBinding y tengo aplicado un endPointConfiguration y un serviceBehaviorConfiguration.

<behaviors>
  <endpointBehaviors>
    <behavior name="JsonEndpointBehavior">
      <webHttp defaultBodyStyle="Bare" defaultOutgoingResponseFormat="Json"
        automaticFormatSelectionEnabled="true" faultExceptionEnabled="true" />
    </behavior>
  </endpointBehaviors>
  <serviceBehaviors>
    <behavior name="DefaultServiceBehavior">
      <serviceMetadata httpGetEnabled="true" />
      <serviceDebug includeExceptionDetailInFaults="true" />
      <serviceAuthorization principalPermissionMode="Custom" serviceAuthorizationManagerType="Microsoft.Magazine.Foundation.MagazineServiceAuthorizationManager, Microsoft.Magazine.Foundation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </behavior>
  </serviceBehaviors>
</behaviors>

En el serviceBehavior tengo aplicado un ServiceAuthorization en el que especifico que el manager de objetos principal (identidades) será personalizado y especifico el tipo que se encargará de gestionar la autorización de las identidades.

Así que lo que tenemos que hacer es implementar los dos casos, cuando el usuario está intentado hacer login, tenemos que permitir la autorización y cuando el usuario hacer cualquier otra petición tenemos que asegurarnos de que es un usuario válido.

protected override bool CheckAccessCore(OperationContext operationContext)
{
    bool result = false;

    Message message = operationContext.RequestContext.RequestMessage;
    object value;
    if (message.Properties.TryGetValue("HttpOperationName", out value))
    {
        if ((string)value == "LoginUser")
        {
            result = true;
        }
    }

    if (!result)
    {
        HttpRequestMessageProperty httpRequestMessage;
        object httpRequestMessageObject;
        if (message.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
        {
            httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
            if (!string.IsNullOrEmpty(httpRequestMessage.Headers["Authorization"]))
            {
                string authorization = httpRequestMessage.Headers["Authorization"];
                result = new Login().IsValidAuthorization(authorization);
            }
        }
    }

    if (result)
    {
        operationContext.ServiceSecurityContext.AuthorizationContext.Properties["Principal"] = Thread.CurrentPrincipal;
    }

    return result;
}

La manera que tengo de comprobar que el usuario quiere hacer login es comprobando la operación de http que está invocando, que es, justamente la operación del servicio que invoca. Así que si está invocando LoginUser significa que está haciendo login así que result lo establezco en true.

En caso de que result no sea true, tengo que asegurarme de que la petición tiene la cabecera authorization, para ello tenemos que extraer de las propiedades del mensaje el objeto del tipo HttpRequestMessageProperty que contiene las propiedades de la petición http asociada a este mensaje. Acordaros que nosotros usábamos webHttpBinding con WebGet.

Dentro de ese objeto tenemos acceso a las cabeceras de http normales, buscamos Authorization y entonces intentamos validar ese token.

Validar el token de autorización

Una vez que ya tenemos el string que representa el token de autorización tenemos que desencriptar el contenido y parsear el formato para verificar la id del usuario y la fecha del login.

public bool IsValidAuthorization(string value)
{
    bool result = false;

    value.EnsureIsNotNullOrEmpty();

    ICryptoTransform transform = new TripleDESCryptoServiceProvider().CreateDecryptor(this.key, this.iv);
    byte[] buff = Convert.FromBase64String(value);

    buff = transform.TransformFinalBlock(buff, 0, buff.Length);
    string ticket = Encoding.Default.GetString(buff);

    string[] values = ticket.Split('-');
    if (values != null && values.Length == 2)
    {
        int userId;
        long ticks;
        if (int.TryParse(values[0], out userId) && long.TryParse(values[1], out ticks))
        {
            if (IsValidUser(userId) && Math.Abs((new DateTime(ticks) - DateTime.Now).Hours) < 1)
            {
                result = true;
            }
        }
    }

    return result;
}

Así que dentro de mi infraestructura validar el usuario es comprobar que es un usuario válido (está en la base de datos) y que el tiempo de la última vez que el usuario hizo login fue una hora.

Conclusiones

Con estos pasos tengo un sistema centralizado de autorización, utilizo en todo momento la infraestructura de WCF, sin habilitar la compatibilidad con ASP.NET, que penaliza el rendimiento, y no necesito en cada petición obtener la referencia al usuario actual sino que seré capaz de obtenerlo a través del objeto principal del thread que procesa la petición.

Es importante resaltar la importancia de encriptar el token de autorización para evitar problemas de robo de sesiones y generar sesiones automáticamente, ya que la clave y el vector de inicialización del algoritmo TricpleDES está seguro en la parte de servidor.

Luis Guerrero.

Dejar tus excepciones fluir

“Dejar tus excepciones fluir” es una frase que Rodrigo Corral nos repite durante el desarrollo de software constantemente, pero, ¿qué quiere decir con esta frase?

Normalmente se debería de pensar lo contrario de las excepciones, es decir, capturarlas siempre para que no se produzcan errores en el software y que todo funcione correctamente. Pero vamos a ver a través de un ejemplo, como a veces es mucho mejor dejar a las excepciones fluir por la pila y no capturarlas.

Recientemente, en un equipo de Plain Concepts que está desarrollando unas aplicaciones para Windows Phone 7, hemos tenido la necesidad de crear una clase que nos guarde en el almacenamiento aislado datos, para que después podamos leer cuando la aplicación se arranque. En principio es una clase muy sencilla, utiliza DataContractSerializer para guardar todos los datos en el isolated storage. Aquí os muestro los métodos y propiedades de esta clase.

  • Save
  • Load
  • Delete
  • Exist

La clase es genérica, eso significa que para poder usarla tienes que pasar como parámetro el tipo que quieres guardar, y todos los métodos de Save y Load devuelven T. Además tiene dos constructores públicos, uno sin parámetros en el que se utiliza el nombre del tipo como nombre del fichero para guardarlo en Isolated Storage y otro constructor con un parámetro. La lista de know types de DataContractSerializer, por si los necesita para la serialización.

En principio el funcionamiento de la clase es bastante sencillo. Especificamos el tipo y ya podemos salvar, cargar y preguntar si el fichero existe para cargar.

Si tenemos una clase como Item declarado así:

public class Item
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Podemos usar la clase SaveManager de esta manera:

SaveManager<Item> save = new SaveManager<Item>();
if (!save.Exist)
{
    save.Save(new Item()
    {
        Name = "Jonh",
        Age = 30
    });
}

Item savedItem = save.Load();

Ahora viene la parte más importante de todas, que es decidir cómo vamos a tratar las excepciones dentro de la implementación de la clase SaveManager.

Nos queda claro que la clase es un envoltorio de la clase DataContractSerializer para así hacer el guardado y la carga de clases serializadas mucho más sencilla.

public class SaveManager<T> where T : class
{
    public SaveManager() { }

    public SaveManager(List<Type> knownTypes)
    {
        serializer = new DataContractSerializer(typeof(T), knownTypes);
        saveFileName = typeof(T).Name;
    }

    public void Save(T value)
    {
        if (value == null)
        {
            throw new ArgumentNullException("value", "value can't be null");
        }

        using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
        {
            using (Stream saveStream = file.CreateFile(saveFileName))
            {
                serializer.WriteObject(saveStream, value);
            }
        }

    }

    public T LoadWithTryCatch()
    {
        T result = default(T);
        try
        {
            using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
            {
                using (Stream saveStream = file.OpenFile(saveFileName, FileMode.Open, FileAccess.Read))
                {
                    result = (T)(object)serializer.ReadObject(saveStream);
                }
            }
        }
        catch { }
        return result;
    }

    public T Load()
    {
        T result = default(T);
        using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
        {
            using (Stream saveStream = file.OpenFile(saveFileName, FileMode.Open, FileAccess.Read))
            {
                result = (T)(object)serializer.ReadObject(saveStream);
            }
        }
        return result;
    }

    public T LoadWithAllTryCatc()
    {
        T result = default(T);
        try
        {
            using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
            {
                try
                {
                    using (Stream saveStream = file.OpenFile(saveFileName, FileMode.Open, FileAccess.Read))
                    {
                        result = (T)(object)serializer.ReadObject(saveStream);
                    }
                }
                catch (IsolatedStorageException isolatedException)
                {
                    throw isolatedException;
                    // error del isolated Storage
                }
                catch (ArgumentNullException argumentNullException)
                {
                    throw argumentNullException;
                    // error en un argumento (referencia nula)
                }
                catch (ArgumentException argumentException)
                {
                    throw argumentException;
                    // error en un argumento
                }
                catch (DirectoryNotFoundException directoryNotFoudnException)
                {
                    throw directoryNotFoudnException;
                    // directorio no encontrado
                }
                catch (FileNotFoundException fileNotFoundException)
                {
                    throw fileNotFoundException;
                    // fichero no encontrado
                }
                catch (ObjectDisposedException objectDisposedException)
                {
                    throw objectDisposedException;
                    // objecto disposeado durante su utilización
                }
            }
        }
        catch (IsolatedStorageException isolatedException)
        {
            throw isolatedException;
            // se ha producido un error al acceder al isolated storage
        }
        return result;
    }

    public void Delete()
    {
        using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
        {
            if (file.FileExists(saveFileName))
            {
                file.DeleteFile(saveFileName);
            }
        }
    }

    public bool Exist
    {
        get
        {
            bool result = false;
            using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
            {
                result = file.FileExists(saveFileName);
            }
            return result;
        }
    }

    private string saveFileName;
    private DataContractSerializer serializer;
}

¿Qué puede ir mal?

Pensando de nuevo en las excepciones, tenemos que tener claro que situaciones excepcionales se puede dar en el código y tratarlas de manera adecuada. Por situaciones excepciones nos referimos a cosas que no están planeadas en el flujo de ejecución normal de nuestra aplicación, y las excepciones son los objetos que representan los errores ocurridos durante la ejecución de la aplicación.

Veamos por ejemplo el método Load, pensemos en la lista de errores que se pueden producir:

  • Que la cuota de Isolated Storage sea 0
  • Que el Isolated Storage este deshabilitado.
  • Que la ruta del fichero esté mal formada
  • Que la ruta del fichero sea nula
  • Que el directorio del que se quiere leer no exista
  • Que no se encuentre el fichero
  • Que no se encuentre el fichero y el modo de apertura esté en Open
  • Que el contenido del fichero no sea una serialización válida del tipo que estamos intentando leer.

La lista de excepciones es bastante larga, ¿de dónde viene esta lista de excepciones? Si miramos a la implementación del método,

public T Load()
{
    T result = default(T);
    using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
    {
        using (Stream saveStream = file.OpenFile(saveFileName, FileMode.Open, FileAccess.Read))
        {
            result = (T)(object)serializer.ReadObject(saveStream);
        }
    }
    return result;
}

Nos damos cuenta que el método Load utiliza dos clases para hacer el trabajo, IsolatedStorageFile y DataContractSerializer. Así que viendo el uso de estas dos clases, uno ya se imagina de donde pueden venir las excepciones de la lista anterior.

Ahora bien, pongamos como ejemplo file.OpenFile(), que nos permite abrir un fichero que está en el isolated storage. Según la lista anterior tenemos 5 tipos diferentes de excepciones que se pueden producir al llamar al método OpenFile. Pero ahora bien, ¿Cuál es la mejor estrategia para tratar esas excepciones?

Deja las excepciones fluir

Hasta ahora nos hemos centrado en definir un escenario para poder discutir cual es la mejor opción para tratar esas excepciones. Según lo dicho hasta ahora, tenemos un método Load que no acepta ningún parámetro y que devuelve una instancia recién creada del tipo T leído desde el Isolaged Storage con el nombre del tipo T.

Ahora bien, tenemos dos aproximaciones a la hora de usar las excepciones, podemos por un lado, envolver el código en un gran Try/Catch cacheando una excepción de tipo System.Exception.

public T LoadWithTryCatch()
{
    T result = default(T);
    try
    {
        using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
        {
            using (Stream saveStream = file.OpenFile(saveFileName, FileMode.Open, FileAccess.Read))
            {
                result = (T)(object)serializer.ReadObject(saveStream);
            }
        }
    }
    catch { }
    return result;
}

El envolver el código así nos permite controlas las excepciones que se producen, pero una vez que se produce una excepción, según este código, estamos haciendo un catch sin código y como la variable result se ha inicializado a default(T) nos damos cuenta que el método Load, en caso de que se produzca una excepción, devolverá null.

¿Es esto una buena aproximación?, depende del implementador de la clase, en este caso nosotros. Tendríamos que documentar que en caso de que se produzca una excepción el método devuelve null. Desde mi punto de vista esto me parece erróneo, porque lo que estamos haciendo es ocultar el problema detrás de un catch y el llamador de la función no sabrá jamás cual es el motivo por el que se produce la excepción, podría hacer sido un feichero que no existe, que el DataContractSerializer lance una excepción porque se te ha olvidado decorar con un DataContract la clase base del tipo que estás serializando, podrían ser miles de cosas, pero nosotros decidimos devolver un null. Como ya he dicho esto reduce la visibilidad del problema, hace que la depuración de código que utiliza este componente sea mucho más complicada, ya que no devuelve ninguna información sobre el error ocurrido.

La segunda aproximación que tenemos es justamente tratar todos los tipos de excepciones, escribir código para todos los tipos y relanzar las excepciones de nuevo.

public T LoadWithAllTryCatc()
{
   T result = default(T);
   try
   {
       using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
       {
           try
           {
               using (Stream saveStream = file.OpenFile(saveFileName, FileMode.Open, FileAccess.Read))
               {
                   result = (T)(object)serializer.ReadObject(saveStream);
               }
           }
           catch (IsolatedStorageException isolatedException)
           {
               throw isolatedException;
               // error del isolated Storage
           }
           catch (ArgumentNullException argumentNullException)
           {
               throw argumentNullException;
               // error en un argumento (referencia nula)
           }
           catch (ArgumentException argumentException)
           {
               throw argumentException;
               // error en un argumento
           }
           catch (DirectoryNotFoundException directoryNotFoudnException)
           {
               throw directoryNotFoudnException;
               // directorio no encontrado
           }
           catch (FileNotFoundException fileNotFoundException)
           {
               throw fileNotFoundException;
               // fichero no encontrado
           }
           catch (ObjectDisposedException objectDisposedException)
           {
               throw objectDisposedException;
               // objecto disposeado durante su utilización
           }
       }
   }
   catch (IsolatedStorageException isolatedException)
   {
       throw isolatedException;
       // se ha producido un error al acceder al isolated storage
   }
   return result;
}

Escribiendo un manejador por cada uno de los tipos de excepciones, como se puede apreciar, disminuye la legibilidad del código y hace que la complejidad del método aumente, haciendo que mantenibilidad disminuya.

¿Esta aproximación nos aporta algo? Pensando de nuevo en nosotros mismo, los implementadores de la clase, que me aporta saber que el fichero no existe a la hora de leer el fichero para de serializar. Aunque sea capaz de interceptar una excepción de fichero no encontrado no podría hacer nada, porque la responsabilidad de mi clase se centra en leer el fichero y de serializarlo con DataContractSerializar, no es mi responsabilidad asegurarme que el fichero este ahí, sino el llamador. Imaginaros ahora que también se lanza una excepción durante la de serialización del objeto, ¿qué debería de hacer? ¿Notificar al llamador de que se ha producido la excepción?, ¿Intentar otra aproximación?, no tengo muchas opciones puesto que si el desarrollador se ha olvidado de poner el DataMember o el DataContract en alguna de las clases no voy a poner solucionar ninguno de los problemas del serializador, así que realmente, de nuevo, la responsabilidad de este error no es el implementador sino del desarrollador que la usa.

Así que de nuevo llegamos al método que teníamos implementado al principio del artículo:

public T Load()
{
    T result = default(T);
    using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
    {
        using (Stream saveStream = file.OpenFile(saveFileName, FileMode.Open, FileAccess.Read))
        {
            result = (T)(object)serializer.ReadObject(saveStream);
        }
    }
    return result;
}

Una implementación en la que no existe ningún bloque de Try/Catch. ¿Por qué?, porque no teniendo ningún bloque de código solucionamos los problemas de saber qué hacer con la excepción y la de notificar al usuario que algo ha ocurrido mal.

Así si durante el desarrollo de la aplicación que se esté usando esta clase, nos damos cuenta que la hacer un llamada al método Load, nos lanza una excepción directamente a nosotros, diciendo que el fichero no existe, ¿Qué representa este error?, no que tengamos un error en sí en el código del SaveManager, sino que si estamos asumiendo que debería de haber un fichero en el Isolated storage, previamente guardado con las opciones del usurario y ahora no está, que tenemos un bug en nuestro software. Es decir, que el hecho de que la excepción se lance y fluya a través de la pila hasta el gestor de preferencias de nuestra aplicación, es un síntoma de que tenemos un error en nuestro software, de que no estamos guardando correctamente las preferencias del usuario o que no estamos comprobando antes de leer el fichero que el fichero existe.

Viendo las excepciones de esta manera, uno utiliza las excepciones como mecanismo para parar la ejecución del código y notificar al usuario de que algo no está bien. Si, por ejemplo, en nuestra primera implementación hubiéramos devuelto nulo en el método Load, nunca nos habríamos dado cuenta de que no estábamos guardando los datos, o que al guardar los datos tenemos algún error.

Así que la conclusión a la que podemos llegar es que es bueno dejar que las excepciones fluyan por el código y que salvo en contadas ocasiones hagamos un catch. Esto por supuesto no es una afirmación que se pueda extender a todas las clases, porque depende de la implementación que estemos haciendo. En este caso concreto lo que estamos haciendo es utilizando bloques externos, para generar una funcionalidad concreta para nuestra aplicación, bloques, que en sí mismo manejan de manera correcta la excepciones.

La clave para decidir si tenemos que colocar un bloque de Try/Catch supongo que viene dado por la responsabilidad de las operaciones, es decir, si yo que estoy implementado la clase tengo que ser responsable de asegurarme que el fichero exista y si no existe crearlo, entonces debería de hacer las comprobaciones o poner los bloques de Try/Catch adecuados para asegurarme de que el fichero existe. Otra manera de pensar en cómo tratar las excepciones es pensar que si la ejecución del código tiene sentido cuando no existe un fichero. Es decir, ¿puedo implementar un método Load teniendo en cuenta que el fichero no existe?, en mi opinión creo que no, porque justamente ese es uno de los requisitos del método, leer el fichero y de serializarlo. Si el fichero no existe no podemos continuar.

Llevando este concepto al extremo nos encontramos con los BSOD de Windows, los pantallazos azules de Windows. ¿Por qué existen los BSOD? La primera respuesta a esta pregunta es porque existe una función para el modo kernel que se llama, KeBugCheckEx, que según la MSDN “permite apagar el sistema de manera controlada cuando el llamador descubre una inconsistencia irrecuperable que podría hacer que el sistema se corrompiese si el llamador continúa ejecutándose”. La definición lo deja bastante claro, si resulta que se ha corrompido la memoria de alguna manera y sabemos a ciencia cierta que después de esa comprobación el sistema va a dejar de funcionar correctamente, lo que tenemos que hacer es lanzar una excepción, es decir, un suicidio controlado de Windows que permita verificar el problema. Podemos ver entonces el BSOD como un síntoma de que hay un problema, no como un problema en sí. Pues esta manera que tiene Windows de avisarnos que hay un problema es el mismo ejemplo de nuestro método Load(), a diferentes niveles está claro. Pero si lo pensamos así qué sentido tiene intentar de serializar una clase de un fichero, si el fichero no existe. Pues ninguna.

Por eso no vale de nada que pongamos un catch al final del método porque es un requisito que tengamos el fichero disponible, por eso las excepciones se llaman así porque son situaciones excepcionales, que no se preveían en el flujo de ejecución. Yo como implementador del método no me espero que no exista el fichero, pero puede que el que realiza la llamada sí, así que es su responsabilidad hacer algo con esta excepción no yo.

Utilizar las excepciones como método de validación

Este es otro tópico de las excepciones que también se suele tratar de manera incorrecta, sale mucho más barato desde el punto del rendimiento comprobar que los parámetros son distintos de nulo y del tipo adecuado que envolverlo todo en un Try/Catch enorme y devolver nulo. Pero este es un tema que veremos en otro post.

El código de ejemplo aquí.

Luis Guerrero.

Como implementar TemplateSelector en el ListBox de Windows Phone 7

Si solo has trabajando con Silverlight nunca has conocido el TemplateSelector de WPF, que como su nombre indica permite hacer un selector por discriminador para las plantillas de datos. En el caso que nos atañe ListBox, tiene una propiedad llamada ItemTemplate en la que se establece la plantilla de datos para cada uno de los ítems.

¿Para qué se puede querer cambiar la plantilla?

Imaginaros el escenario de estar haciendo una aplicación para mostrar una lista de noticias provenientes de un rss, podemos tener una plantilla para las noticias con imágenes, otra plantilla para las noticias sin imágenes y además podemos querer una plantilla especial para una noticia destacada. Este tipo de escenario que es el más común es bastante difícil de conseguir con Silverlight puesto que no tiene TemplateSelector.

Para conseguir esta funcionalidad tenemos que implementarlo a mano.

Hay varias maneras de llegar hasta esta aproximación la que desde mi punto de vista es la más adecuada es crear un ListBox personalizado, porque nos permite tener toda la funcionalidad y aspecto existente del ListBox pero con el selector de plantillas.

El proceso de creación de un ListBox personalizado se hace en dos partes, la primera se tiene que hacer una clase que herede de ListBox. En esta clase, que llamaremos DataTemplateListBox, tenemos que sobrescribir dos métodos virtuales:

  • GetContainerForItemOverride: este método devuelve un objeto que será el ítem container que el ListBox usará para alojar los ítems que se establezcan en el ItemSource. En el caso del ListBox, la implementación predeterminada de este método devuelve un ListBoxItem que es el contenedor predeterminado del ListBox. En nuestro ejemplo tenemos que generar un ListBoxItem personalizado que tendrá la funcionalidad de establecer el DataTemplate.
  • PrepareContainerForItemOverride: Este método es llamado cuando el ListBox está a punto de empezar a hacer la pasada de Layout, y es el momento justo para establecer el código de nuestros discriminador. Este método acepta dos parámetros, uno llamado element de tipo DependencyObject que es el contenedor del ListBox, y el otro ítem de tipo object que es el objeto que nosotros estamos estableciendo al ListBox.
public class DataTemplateListBox : ListBox
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new DataTemplateListBoxItem();
    }
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        base.PrepareContainerForItemOverride(element, item);
        DataTemplateListBoxItem lbi = (DataTemplateListBoxItem)element;
        if (item is News)
        {
            News newItem = (News)item;
            if (!newItem.IsHighLighted)
            {
                lbi.CustomTemplate = (DataTemplate)App.Current.Resources["NewsDataTemplate"];
            }
            else
            {
                lbi.CustomTemplate = (DataTemplate)App.Current.Resources["HighLightedNewsDataTemplate"];
            }
        }
    }
}

Como hemos dicho anteriormente en el proceso de creación del ListBox tenemos que crear también un ListBoxItem que será el contenedor neutro que tendrá una propiedad de tipo DataTemplate, que será el DataTemplate usado para dibujar ese elemento con una plantilla especifica.

[TemplatePart(Name = "DisplayContent", Type = typeof(ContentControl))]
public class DataTemplateListBoxItem : ListBoxItem
{
    #region Properties
    
    public DataTemplate CustomTemplate
    {
        get { return (DataTemplate)GetValue(CustomTemplateProperty); }
        set { SetValue(CustomTemplateProperty, value); }
    }

    public static readonly DependencyProperty CustomTemplateProperty =
        DependencyProperty.Register(
            "CustomTemplate", 
            typeof(DataTemplate), 
            typeof(DataTemplateListBoxItem), 
            new PropertyMetadata(null));

    #endregion

    #region Constructors
    public CategoryItemsListBoxItem()
    {
        DefaultStyleKey = typeof(DataTemplateListBoxItem);
    }
    #endregion
}

Este es el código de ejemplo de un DataTemplateListBoxItem, pero también necesitamos generar un estilo predeterminado que contenga un elemento de tipo ContentControl que será el elemento que tendrá la interfaz del usuario del elemento.

<Style TargetType="controls:DataTemplateListBoxItem" >
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="controls:DataTemplateListBoxItem">
                <ContentControl x:Name="DisplayContent" Content="{TemplateBinding DataContext}" 
                                ContentTemplate="{TemplateBinding CustomTemplate}"  />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Como se puede apreciar en el código xaml lo que se hace es agregar a la plantilla un ControlTemplate y en ese ControlTemplate se establecen dos propiedades Content con un binding de plantilla (TemplateBinding) a la propiedad DataContext, con eso conseguirnos establecer el objeto como contenido y que se dibuje. La otra propiedad que falta por establecer es justamente la propiedad ContentTemplate que en este caso se hace lo mismo, hacer un binding de plantilla con la propiedad CustomTemplate que es de la clase DataTemplateListBoxItem.

Ahora lo que tenemos que hacer es insertar nuestro DataTemplateListBox en el control MainPage y enlazarle un ViewModel con datos para mostrar un par de noticias generadas por código:

<Grid x:Name="ContentPanel" Grid.Row="1">
   <DataTemplateDemo_Controls:DataTemplateListBox ItemsSource="{Binding Items}"/>
</Grid>

Con eso conseguimos tener un ListBox en el nosotros decidimos en cada uno de los elementos como queremos aplicarle una plantilla de datos. Este es el resultado:

image

La demo no es muy impresionante en sí, pero permite tener la flexibilidad de poder elegir elemento por elemento cual es la plantilla que vamos a usar de manera programática.

La demo completa la podéis descargar de aquí.

Luis Guerrero.