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.

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

12 Horas de Visual Studio – Calidad de Software y patrones de diseño en Windows Phone 7.5

Hoy es el evento de 12 Horas de Visual Studio de Microsoft y Globbtv, podeis ver el evento en directo aquí http://www.globbtv.com/vstudio12horas/

Este es el material que voy a utilizar sobre mi charla sobre “Calidad de Software y patrones de diseño de Windows Phone 7.5”

clip_image002

El código de ejemplo os lo podéis descargar de aquí, http://bit.ly/12HorasVSWindowsPhone

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.

Como tener más de un emulador de Windows Phone 7 en la misma máquina

Si en alguna ocasión habéis tenido la necesidad de tener más de un emulador ejecutándose en la misma máquina, para por ejemplo, depurar vuestro código de Socket para Mango, ahora podéis hacerlo.

Esto son los pasos.

Ve a la carpeta C:\ProgramData\Microsoft\Phone Tools\CoreCon\10.0\addons

En esa carpeta hay un fichero llamado ImageConfig.en-us.xsl copiadlo por otro fichero llamado por ejemplo ImageConfig2.en-us.xsl.

clip_image002

Una vez hecho esto abridlo con un bloc de notas elevador (como administrador)

clip_image004

En el nodo PLATFORM hay una propiedad llamada ID, teneis que generar una nueva GUID. Podéis usar la herramienta que viene en Visual Studio para generar GUID’s. Una vez generada la nueva GUID, cambiar el atributo del nodo xml.

Podéis cambiar el nombre del emulador cambiando el atributo name del mismo nodo.

clip_image006

Buscad una propiedad llamada VMID en el valor tenéis que poner la misma GUID.

Ahora cerrad el Visual Studio y abridlo de nuevo, ahora teneis dos valores en el combo de Device de Windows Phone 7.

clip_image007

Aquí tenéis una captura de dos emuladores ejecutando ByeByeBrain.

clip_image009

Espero que sea de utilidad.

Saludos. Luis.

Como generamos las releases en Plain Concepts, el caso de las aplicaciones de Windows Phone de Prisa.

Como generamos las releases en Plain Concepts, el caso de las aplicaciones de Windows Phone de Prisa.

Como dije anteriormente en mi artículo, ya están disponibles las aplicaciones de El País, As.com y CincoDías.com en el Marketplace de Windows Phone 7. Estas aplicaciones han sido desarrolladas y diseñadas íntegramente por Plain Concepts, en este artículo explicaré cuales son los procesos de desarrollo que seguidos dentro de PlainConcepts para asegurar la calidad de la aplicación y las técnicas empleadas.

Las aplicaciones están desarrolladas en C# usando Visual Studio 2010 y la metodología usada es Scrum, adaptándola para el desarrollo con un equipo tan pequeño y a las particularidades del proyecto en cuestion. Durante su desarrollo han participado un equipo de dos programadores, un diseñador y un scrum master. Se han empleado 7 iteraciones a aproximadamente 2 semanas cada una para completar el desarrollo, más una iteración final para estabilizar código. Durante la penúltima y última iteración una persona fuera del equipo se encargó del testing en la aplicación en dispositivos físicos.

Aquí os muestro la gráfica de Burndown and Burn Rate del proyecto:

clip_image002

Como se puede ver, conforme se iban generando trabajo se iba completando y aunque hay muchos picos, que indican que se ha ido bajando las horas de las tareas adecuadamente, en general la progresión de la aplicación ha sido buena. Conforme se iba cerrando trabajo se iba generando nuevo. La banda de color azul de fondo indica tareas que se quedaron fuera de la funcionalidad final de la aplicación, pero que no se eliminaron del TFS.

La gráfica del estado de todas las iteraciones, muestra cómo se fueron cerrando las tareas de cada iteración de manera correcta. Además podemos observar como al principio el equipo era demasiado optimista en cuanto a las estimaciones que se hacían, pero conforme las iteraciones fueron avanzando la confianza, la experiencia y el mayor conocimiento del proyecto hacen que las estimaciones y el reparto de tareas sean cada vez más cercanas a la realidad. Aunque como he dicho se iban generando tareas fuera de la iteración con trabajo de fondo que no se fueron cerrando en el TFS.

clip_image004

Como está el proyecto organizado

Al desarrollar una aplicación en Silverlight para Windows Phone 7, el patrón principal a la hora de desarrollar todas la aplicación ha sido MVVM, este patrón permite tener una vista hecha en xaml sin apenas nada de código y llevarse toda la lógica de la aplicación al ViewModel donde se realizaba todo el trabajo, en este desarrollo no se ha usado ningún framework de terceros para MVVM ya nosotros mismo hemos generado las utilidades que necesitábamos para el proyecto en un proyecto común a las 3 aplicaciones. Así el diagrama de arquitectura general se queda así:

clip_image006

Donde PlainConcepts.Common está la funcionalidad común a los tres proyectos; convertes, controles, tipos de datos comunes, parseadores de xml, navegación, serialización, comandos y viewmodels.

En cada uno de los proyectos están sus respectivas vistas, controles de usuario, páginas del teléfono y demás artefactos específicos, imágenes, iconos, etc.

Configuración del TFS, políticas de check-in, ramas y builds.

Para manejar el ciclo de vida del desarrollo y la integración continua durante el proyecto se han usado diferentes artefactos para asegurar que el desarrollo era incremental.

Políticas de check-in

El proyecto tenia habilitado el multiple chech-out y como políticas de check-in teníamos, requerir asocial un work ítem en cada check-in y requerir agregar un comentario en cada check-in. Eso nos permite tener trazabilidad de como el proyecto se ha desarrollado y que changeset del servidor está asociado a cada work ítem. Por no decir que también de esta manera sabemos que work ítems están asociados a cada compilación.

Políticas de ramas (branching)

Durante el desarrollo inicial de la aplicación no había ninguna configuración especial en el servidor sobre ramas, es decir únicamente teníamos una rama dev (Developer) en la que se trabajaba. Pero conforme el cliente fue pidiendo versiones estables para ver el progreso del desarrollo, se optó por esta configuración:

clip_image007

La carpeta dev se pasó a llamar Main. A partir de esta rama Main se generaron, dev y todas las releases de las diferentes aplicaciones.´

Esto nos permitía tener en la rama Main versiones estables de las tres aplicaciones entregables al cliente, libre de bugs, estabilizadas y fuera del ciclo de desarrollo. El tener una rama Main te permite que el equipo de desarrollo siga trabajando en dev, pero conforme la funcionalidad vaya generándose que se vayan mezclándose (merge) la funcionalidad de dev a Main y así hacer un entregable al cliente.

Las ramas de reléase de cada aplicación tienes las versiones reléase publicadas en el Marketplace y las sucesivas actualizaciones que se vayan generando. Así si encontramos un bug en la versión del servidor, tenemos el changeset etiquetado podemos generar un fix en la rama reléase de esa aplicación, sin tener que impactar en las demás aplicaciones y cuando el bug este corregido y testeado, propagar ese cambio de la rama reléase de la aplicación, a la rama Main y de ahí a dev y el resto de releases (en caso de que el bug sea de algo común).

Compilaciones (Builds)

Otra requisito importante para seguir integración continua durante el desarrollo son las compilaciones, en el proyecto hay 5 compilaciones creadas y todas ellas activas.

  • As Release 1.0: gated checking en la rama de release (modo release).
  • CincoDias Release 1.0: gated checkin en la rama de release (modo release).
  • ElPais Release 1.0: gated checkin en la rama de reléase (modo release).
  • Prisa.WP7: continuous integration en la rama de dev (modo debug y release).
  • Prisa.WP7 Main: gated checkin en la rama de main (modo debug y release).

Con esta lista de compilaciones en el equipo nos podíamos asegurar de que cada checkin era incremental, porque podíamos verificar que la compilación era correcta por lo menos en Main y en las ramas de release. Esto nos permitía saber que el código que teníamos en Main y Release compilaba sin problemas para que en cualquier momento se pudiera hacer un cambio, además de eliminar el molesto “Works on my machine” ayudando así a los tester a tener versiones incrementales de la funcionalidad compilada por el servidor y poder ir generando bugs sobre las versiones generadas.

Aquí podemos ver el estado de las compilaciones correctas a través del tiempo:

clip_image009

Otra de las cosas interesantes de tener un servidor de compilaciones es que conforme la funcionalidad va generándose se van generando binarios de la aplicación que se pueden consumir, así que el equipo de Plain Concepts decidió dar acceso al cliente, a través de http a la carpeta de salida de todos los binarios de las aplicaciones. De esta manera el cliente puede tener acceso ubicuo a su producto y desplegarse la última versión siempre que lo desee.

Testing

Aunque no es posible integrar el testing de Visual Studio en proyecto de Silverlight para Windows Phone 7, el testing de la aplicación se definió como tesing manual que se hacía por personas independientes del proyecto en terminales físicos y nunca sobre el emulador, con conexiones 2G (GPRS), 3G, 3.5G y WiFi, sobre varios terminales diferentes.

Como se publica una versión en el Marketplace

El proceso para publicar una versión en el Marketplace implica tener los cambios solicitados por el cliente en la rama reléase de la aplicación, que la compilación en modo reléase, desplegar esa fichero generado por el servidor de compilación en el teléfono y testear que todos los cambios que el cliente había solicitado están integrados y que de la lista de test previos que todo este correcto, para que se no se no se hayan introducido nuevos bugs.

Una vez hecho esto tenemos que subir el número de versión de la aplicación y hacer checkin, este último check-in con el cambio de versión, es el xap que se sube al Marketplace. Se etiqueta la build con una etiqueta de calidad “Published in WP7 Marketplace” y se retiene la build.

clip_image011

Además de eso se aplica una etiqueta en la rama de la aplicación con la versión de la aplicación para en caso de que haya un bug saber el changeset asociado.

Conclusiones

El desarrollo de software es una disciplina muy complicada y que no se debe infravalorar, en Plain Concepts nos gusta hacer las cosas bien y sabemos lo complicado que es montar un proyecto, mantenerlo y sobre todo desarrollar funcionalidad. Así que con este post queremos mostrar un poco cual es el proceso que seguimos internamente para desarrollar. Eso no significa que todos los proyectos se montan de la misma manera, sino que cada proyecto es único a la hora de crearse y desarrollarse, y no existen reglas fijas de metodologías y artefactos que utilizar. Estas deben ser adecuadas al proyecto en cuestión haciendo que sean útiles para el equipo y para el proceso en sí. Lo difícil de todo esto es saber que metodologías usar, que tecnologías usar y cómo gestionar todo ese flujo. Como dice Rodrigo Corral, “los proyectos no fracasan por la solución tecnológica, sino por la gestión en sí”, es decir, que confiar en que el proyecto va a ser un éxito simplemente por usar todas las últimas tecnologías y herramientas, es simplemente un error, porque no te elimina la necesidad de gestionar personas y recursos.

Si tenéis alguna pregunta, o queréis comentar algo sobre el proceso podéis hacerlo en los comentarios.

Saludos.

Luis Guerrero.

Nuevas aplicaciones de El País, As.com y Cinco Días para Windows Phone 7

Ya están disponibles, en el Marketplace, las aplicaciones de El País, As.com y Cinco Días para los teléfonos Windows Phone 7 de Microsoft.

173   173  173x173

Las aplicaciones te permiten ver las noticias de El País, galerías de fotos, videos, opiniones, las viñetas del día y mucho. La aplicación de As.com te permite ver las noticias sobre la liga, la última hora y nba.

La aplicación de Cinco Días es diferente a las dos, porque te permite ver la lista de mercados, sus valores, graficas de los valores y mercados a pantalla completa girando la pantalla, guardar favoritos para verlos después.

 

Espero que os guste.

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.