Luis Guerrero Hablando de .NET, Silverlight, WPF, Windows Phone 7 y depuración

29Jul/110

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.

2Jul/111

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.

30Jun/110

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.

19Jun/111

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.

2May/110

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.

29Apr/110

Ejecutar tareas elevadas durante el ciclo de vida del Rol de Azure

Cuando desarrollamos para Windows Azure podemos encontrarnos con distintos escenarios que van desde aplicaciones completamente .NET y aplicaciones que son migraciones de aplicaciones existentes. En ese sentido uno de los dolores de cabeza a la hora de trabajar con Azure son los registros de componentes COM durante el arranque del rol de Azure. Este tipo de problema se soluciona normalmente creando una tarea en el startup del rol que desea consumir ese tipo de componentes COM.

Si por ejemplo nosotros durante el ciclo de ejecución de nuestro rol queremos ejecutar un proceso con elevación, es decir con permisos completos de administrador no podemos hacerlo porque el proceso que hostea la web y el worker role no está elevado, y aunque nosotros lo indiquemos a la hora de ejecutar el proceso eso no va a funcionar.

Es por eso que podemos hacer un pequeño truco para que podamos ejecutar proceso elevados durante nuestro ciclo de ejecución del rol, es decir en cualquier momento, así podemos registrar componentes COM, o llamar a ejecutables del SO de manera mucho más cómoda. Para poder llegar a esa aproximación tenemos que buscar un entorno donde podamos ejecutar nuestras aplicaciones de manera elevada, y ese entorno es el entorno de startup del rol, así que de alguna manera lo que tenemos que tener es un proceso sentinel que se arranque en el startup del rol y que acepte peticiones para ejecutar procesos de manera elevada.

Pues justamente eso es lo que vamos a hacer, utilizando WCF para abrir un pipe de comunicación entre los procesos vamos a crear un servicio que escuche peticiones de otro proceso a través de un pipe para enviar un mensaje que representa una invocación de un proceso.

Vamos por pasos:

Definición del servicio

Como lo que queremos hacer es exponer un servicio de WCF a través de pipes de Windows, tenemos que definir la interfaz del contrato de operaciones:

[ServiceContract(Namespace = "http://azure.plainconcepts.com/schemas/04/2011/azure/executionhost")]
public interface IExecutionHost
{
    [OperationContract]
    void ExecuteTask(ProcessTask host);
}

Una vez que tenemos definido el contrato servicio tenemos que hacer dos cosas, primero hacer la implementación del servicio, es decir el proceso sentinel que escuchará las peticiones recibidas y hará el trabajo de ejecutar esos procesos.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class ExecutionHostService : IExecutionHost
{
    public void ExecuteTask(ProcessTask host)
    {
        Process process = new Process();
        process.StartInfo = host.StartInfo;
        process.Start();
    }
}

Otra cosa que tenemos que hacer en el proceso sentinel es hostear el servicio y ponerlo a escuchar peticiones a través del binding que nosotros seleccionemos, en este caso NetNamedPipeBinding:

public class ExecutionHostServiceManager
{
    public ExecutionHostServiceManager()
    {
        service = new ExecutionHostService();
        ServiceHost host = new ServiceHost(service);

        string address = "net.pipe://PlainConcepts/Azure/ExecutionHost";
        NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
        host.AddServiceEndpoint(typeof(IExecutionHost), binding, address);

        // Add a mex endpoint
        long maxBufferPoolSize = binding.MaxBufferPoolSize;

        int maxBufferSize = binding.MaxBufferSize;

        int maxConnections = binding.MaxConnections;

        long maxReceivedMessageSize =
            binding.MaxReceivedMessageSize;

        NetNamedPipeSecurity security = binding.Security;

        string scheme = binding.Scheme;

        XmlDictionaryReaderQuotas readerQuotas =
            binding.ReaderQuotas;

        BindingElementCollection bCollection = binding.CreateBindingElements();

        HostNameComparisonMode hostNameComparisonMode =
            binding.HostNameComparisonMode;

        bool TransactionFlow = binding.TransactionFlow;

        TransactionProtocol transactionProtocol =
            binding.TransactionProtocol;

        EnvelopeVersion envelopeVersion =
            binding.EnvelopeVersion;

        TransferMode transferMode =
            binding.TransferMode;
        host.Open();
    }


    private ExecutionHostService service;
}

Todo ello lo tenemos que poner en un pequeño programa de consola que será el proceso en sí que hosteará el pipe de Windows que aceptará peticiones a través de WCF:

class Program
{
    static void Main(string[] args)
    {
        new ExecutionHostServiceManager();
        Thread.Sleep(Timeout.Infinite);
    }
}

Fijaros que al final de la ejecución de la clase hay un Thread.Sleep(Timeout.Infinite) que nos permite esperar eternamente en el proceso para que así el proceso esté disponible durante todo el ciclo de vida del rol, permitiéndonos ejecutar un proceso elevado en cualquier momento.

Haciendo llamadas al servicio

Como bien es sabido para poder hacer llamadas a un servicio de WCF lo primero que tenemos que hacer es generar un proxy en el cliente para hacer esas llamadas. Como queremos hacerlo todo por código para simplificar, lo que vamos a hacer es una clase que herede de ClientBase<T> siendo T la interfaz del contrato de operaciones de nuestro servicio.

public class ExecutionHostClient : ClientBase<IExecutionHost>
{
    static ExecutionHostClient()
    {
        string address = "net.pipe://PlainConcepts/Azure/ExecutionHost";
        NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
        binding.CloseTimeout = TimeSpan.MaxValue;
        binding.ReceiveTimeout = TimeSpan.MaxValue;
        binding.SendTimeout = TimeSpan.MaxValue;
        EndpointAddress endpoint = new EndpointAddress(address);
        client = new ExecutionHostClient(binding, endpoint);
    }

    public ExecutionHostClient(Binding binding, EndpointAddress remoteAddress) :
        base(binding, remoteAddress)
    {
    }

    public void ExecuteTask(ProcessTask task)
    {
        Channel.ExecuteTask(task);
    }

    public static void ExecuteRemoteTask(ProcessTask task)
    {
        client.ExecuteTask(task);
    }

    private static ExecutionHostClient client;
}

Es importante que el proxy se inicialice con el mismo binding que el de servidor para que las invocaciones funcionen. En este ejemplo para simplificar tenemos una referencia estatica del proxy y solamente lo exponemos a través de un método estático.

Invocando servicios

Para el ejemplo actual podemos registrar los componentes COM de una carpeta que tengamos en nuestro worker role:

public class RegisterComHelper
    {
        public RegisterComHelper()
        {
 
        }
 
        public void Register()
        {
            // hay que buscar la localizacion en el servidor de azure de donde estan los ensamblados
            // como no sabemos donde estan los ficheros tenemos que buscar el modulo 
            // Habitania.RegisterCom.dll que es especifico para este ejemplo
            // así nos aseguramos que estamos buscando la dll correcta
            Process current = Process.GetCurrentProcess();
            var found = (from p in current.Modules.Cast<ProcessModule>().ToList()
                         where p.ModuleName == "PlainConcepts.Azure.WorkerRoleDemo.dll"
                         select p).FirstOrDefault();
 
            if (found != null)
            {
                // a partir de la locacion del modulo cargada por el proceso 
                // somos capaces de encontrar la informacion del directorio y buscar
                // la carpeta dlls que contiene la lista de dlls que queremos registar
                string directoryLocation = Path.GetDirectoryName(found.FileName);
 
                string dllPath = Path.Combine(directoryLocation, "V3COM30");
 
                string[] files = Directory.GetFiles(dllPath);
 
                foreach (var item in files)
                {
                    if (item.EndsWith(".dll"))
                        RegisterComObject(item);
                }
 
                dllPath = Path.Combine(directoryLocation, "V3COM");
 
                files = Directory.GetFiles(dllPath);
 
                foreach (var item in files)
                {
                    if (item.EndsWith(".dll"))
                        RegisterComObject(item);
                }
            }
        }
 
        private void RegisterComObject(string filePath)
        {
            ProcessStartInfo info = new ProcessStartInfo();
            info.FileName = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.System),
                "regsvr32.exe");
            info.Arguments = string.Format("/i {0}", filePath);
            info.UseShellExecute = false;
 
 
            ExecutionHostClient.ExecuteRemoteTask(new ProcessTask()
            {
                StartInfo = info
            });
        }
    }

Este método para trabajar con Windows Azure puede ser un poco complicado de montar, pero una vez hecho tenemos un mecanismo muy sencillo para hacer cosas más complicadas como por ejemplo ejecutar otro tipo de tareas de mantenimiento directamente desde ahí.

El codigo completo del ejemplo aquí.

Luis Guerrero.

Tagged as: , No Comments
7Apr/110

Por qué no deberías de escribir pruebas unitarias [Actualizado]

Reconozcámoslo escribir pruebas unitarias no sirve para nada. No sirve para nada porque tenemos que además de hacer nuestro trabajo de desarrollar software de calidad tenemos que escribir código que pruebe que testee nuestro código. Además para que las pruebas las podamos crear de manera cómoda y centrarnos en la palabra unitaria tenemos que hacer que nuestro código sea fácilmente aislable porque claro, como vas a hacer una prueba unitaria si para llamar a un método de una clase tienes que llamar a 300 factorías para crear una instancia de una clase, es un coñazo. Y conforme el proyecto avanza debemos mantener las pruebas cuando los métodos que estamos testeando hayan cambiado (otro coñazo), en definitiva tenemos que tirar el tiempo en algo que definitivamente no sirve para nada.

Y fijaros si no sirve para nada, que yo en mis desarrollos no utilizo pruebas unitarias, yo siempre que escribo código estoy seguro de que lo he escrito está bien y siempre va a funcionar de la misma manera. Siempre. Porque no os equivoquéis, si no escribo test unitarios, porque estoy seguro de lo bien hechas que están mis clases, es porque mis clases son un ejemplo de diseño SOLID, utilizo los patrones con moderación y me hecho aficionado a la inversión de control.

Para qué escribir pruebas unitarias si está claro que lo más importante de un proyecto es .NET y como yo en mi proyecto estoy usando la última beta de acceso de datos de Entity Framework mi proyecto está claro que saldrá bien porque con que ejecute un par de veces mi aplicación, haga un par de clicks sobre algunos botones y haga un par de consultas a la base de datos a través de WCF, quien va a necesitar testear contantemente el código de nuestra aplicación si para nada el desarrollo de software es una de las disciplinas más complejas y con mayor grado de abstracción de toda la historia del ser humano. Nadie. Es más ningún proyecto falla por la gestión del proyecto siempre por la tecnología, por eso hay siempre que usar últimas versiones.

Además los ordenadores siempre ejecutan el código de la misma manera, a quien le ha pasado alguna vez que desarrolla el software en su máquina lo ejecuta en un pc del mismo modelo con la misma versión de Windows y el software no funciona. A nadie.

Es por eso que yo hace tiempo que decidí no utilizar pruebas unitarias en mi desarrollo día a día, tampoco utilizo compilaciones automáticas, ni tampoco utilizo metodologías de desarrollo agiles, pues total siempre acabo los proyectos bien.

Además tampoco estoy pendiente de los mensajes que escribe la comunidad porque para nada llevo escuchando desde hace mucho tiempo que las pruebas unitarias están bien, porque claro cualquiera puede escribir lo que quiera, fíjense en mí que estoy diciendo que sin hacer pruebas unitarias todo el desarrollo de mis proyectos ha ido siempre bien. Yo estoy bien como estoy.

Luis Guerrero.

Modo ironia OFF

Atualización 9/4/2011


Parece que algunas personas no han entendido el significado del post viendo los comentarios y Twitts, el post está escrito con ironía para así recalcar lo importante que son las pruebas unitarias en el desarrollo de software. Y cuando me refiero a testing unitario no solo me refiero a ese tipo de testing, me refiero a testing de integración, tesing manual y demás formas de testing.

Yo personalmente programo mucho en la parte de Interfaz de usuario con Silverlight, y tengo que decir que escribir pruebas unitarias de código de UI de Silverlight (no confundir con testear el ViewModel) es complicado. Se están dando avances por parte de Microsoft para hacer que se pueda testear el código de la UI pero aun así sigue siendo complicado.

Mi amigo Vicente Cartas ha escrito un artículo que se llama “Por qué no deberías probar lo que desarrollas” que justamente viene a decir lo mismo pero poniendo otro tipo de ejemplos y muy centrado en el testing manual. Es un artículo de lectura obligada.

 

P.D. al final del articulo hay un texto que pone Modo Ironia OFF pero que tiene un color de fuente blanco así que si seleccionáis con vuestro navegador el texto veréis que esta hay desde el principio.


5Apr/110

El recolector de basura

Todo desarrollador que haya trabajado con .NET, alguna vez ha escuchado hablar del recolector de basura. En este artículo vamos a intentar poner un poco de luz sobre ese concepto, muchas veces misterioso para los programadores.

¿Por qué existe o necesitamos un recolector de basura?

El CLR es un maquina virtual en el que se ejecutan nuestras aplicaciones y .NET es un framework. Microsoft hizo este framework para tener una capa de abastración entre el sistema operativo y las aplicaciones. Una de las cosas más problemáticas en cuanto al desarrollo de aplicaciones es la gestión de memoria. La memoria no es finita y necesita de una gestión, reservar, liberar, compactar, reorganizar. Es por esto que .NET tiene el recolector de basura, para ayudarnos a recolectar los elementos no utilizados y reorganizar la memoria.

Esta caracteristica permite que podamos usar los objetos detro de nuestros lenguajes de programación sin tener en cuenta como se reclican, nostros hacemos el new y el recolector de basura, cuando el objeto ya no sea usado, lo recolectará.

¿Como se produce esta recolección de basura? y ¿como el GC sabe que objetos no se están usando?.

Como todos sabreis todos los objetos del framework son referencias a objetos, y cuando nosotros igualamos (a excepción, claro, de que esté sobrecargado el operador ==) lo que estamos haciendo es copiar la referecia en memoria donde está el objeto, es decir copiarmos su dirección y no su contenido. Esto tambien se aplica a los ValueType (structs) solo que el framework trabaja de otra manera, pero eso está fuera de este articulo.

El hecho de que todo sean referencias, hace que cuando nostros tenemos una clase y dentro de esa clase tenemos un campo de un tipo, lo que realmente tenemos es la dirección de memoria donde vive el objeto que referenciamos, lo podemos simplificar en que tenemos un grafo, una serie de elementos representados por vertices y por las aristas que son las referencias entre objetos.

Pues bien cuando nosotros creamos un objeto y lo asignamos lo que estamos haciendo es añadiendo un vertice a nuestro grafo de referencias. Así que una vez que establecemos un objeto a null a menos que tengamos otra referencia (arista) a nuestro elemento ese objeto está completamente aislado del grafo y nadie puede acceder a el.

Pues sabiendo esto, ¿como el GC es capar de saber que un objeto ya no es necesario y puede ser recolectado?, teniendo en cuenta que nadie lo referencia. El secreto está en el heap. El heap mantiene una colección de todos los elementos que el runtime ha creado, es decir, de todos los objetos que tenemos en nuestro grafo pero en formato de lista. Pues bien lo que el GC hace es coger un objecto y saber si hay alguna referncia a ese objeto (una arista) si no es capaz de encontrar una arista hasta ese objeto es porque el objeto nadie lo está referenciado y por eso puede ser recolectado con seguridad. Así se simple.

Ahora bien, el recolector de basura no enumera todos los objetos que estan en el heap y por cada uno de ellos recorre todo el grafo para encontrar una referencia a ese objeto, en vez de eso lo que utiliza son unos objetos raiz, llamados GCRoots, por los cuales empieza el grafo de nuestra aplicación.

Todo esto que he contado como podemos tener evidencias de que es cierto, teniendo un entorno de depuración montado con simbolos y WinDBG + SOS. (Podeis encontrar información de cómo configurar el entorno de depuración en mi blog)

· !dumpheap para mostrar todos los elementos del heap

· !gcroot 0123292 para mostrar cuales son las referencias a un objeto que puede ser:

o En la pila

o En un GCHandle

o En un objeto listo para la finalización

o O en un objeto encontrado el los lugares anteriores

Además de todo eso si sois aventureros y os gusta las experiencias fuertes, podemos visualizar gráficamente el grafo de refencias con sus GCRoots a través una herramienta de profilling que tiene Microsoft, CLRProfiler disponible en www.microsoft.com/downloads

clip_image002

Aquí tenemos una captura del grafo de nuestra aplicación de ejemplo, en la que podemos ver el <root> del que os hablaba.

¿Qué son las generaciones?

Las generaciones son agrupaciones de las edades de los objetos en memoria. Cuando un objeto se crea está en la generación 0, si se produce una recoleccion de basura lo supervivientes de la generación 0, se les promueve a la generacion 1, y la generación 0 se queda libre y compactada. Se empiezan a crear objetos de nuevo, se lanza otra recoleccion de basura de 0 a 1, los objetos que sobreviven de la generacion 1 pasan a la 2 y los que sobreviven de las generacion 0 pasan a la 1, así sucesivamente.

Solamente existen 3 generaciones, la 0, 1 y 2. Normalmente donde más objeto se generan y se destruyen es en la generación 0 porque es la más usada.

Tamaños:

· Generacion 0: 256 kb (cache segundo nivel del procesador)

· Generacion 1: 2Mb

· Generacion 2: 10Mb

Además de todo eso hay una generación especial para los objetos muy grandes en el framework, LOH (Large object heap), que son los objetos con más de 64kb

¿Cuándo se lanza una recoleccion de basura?

Esta es un pregunta complicada porque no tiene una respuesta directa, hay maneras por el cual se puede generar una recolección de basura o por cuales se puede retrasar. Lo más importante a saber es que las recolecciones de basura se hacen cuando la generacion 0 del GC está llena y se va a crear un nuevo objeto, es decir la recoleccion de basura no se produce cuando se llena la memoria, sino cuando se llena la memoria y se intenta crear un objeto. Además de eso hay que tener en cuenta varios factores. El recolector de basura tiene una herustica que le permite tunearse para ofrecer el máximo rendimiento en cada tipo de aplicación, además de que tiene un historial de las acciones que realiza. Por ejemplo, una de las cosas más importantes para el GC es la cantidad de memoria que libera, no la cantidad de objetos que recolecta, si por ejemplo se lanza una recoleccion de memoria y se liberan 150000 objetos que representa 23kb, seguramente el recolector de basura hará que crezca más la memoria de la generacion 0 antes de hacer otra recoleccion, porque lo que le interesa es recolectar mucha memoria no muchas referecias. En ese sentido si el recolector de basura se encuentra con que ha recolectado 150 objetos que ha sido un total de 400kb seguramente la siguiente vez que llene la generación 0 automaticamente generará una nueva recoleccion de basura.

Porque nunca deberia de llamar a GC.Collect

Como he explicado antes el recolector de basura tiene su propia heurística que le permite tunearse de manera automática y sin intervención del programador. En las primeras versiones de .NET se podía llamar a la función GC.Collect (que teóricamente fuerza una recolección de basura), pero que en realidad lo que hacía era sugerir una recolección de basura. En ese sentido muchos desarrolladores se quejaron porque en sus aplicaciones llamaban incesantemente al recolector de basura pero no se lanzaba ninguna recolección (lo sabían porque miraban los contadores de rendimiento de .NET), así que Microsoft tuvo que dar marcha atrás e incluir una sobrecarga en la llamada de GC.Collect que aceptaba por parámetro un entero con la generación máxima en la cual se iba a producir la recolección, y los más importante de todo, un enumerado de tipo GCCollectionMode que permitia decir si la recolección era forzada u optimizada.

Como podeis imaginar el modo optimizado es el predeterminado, es decir, el modo en el que le sugieres al recolector de basura que haga su trabajo, pero el modo forzado, como su nombre indica, forzaba a una recolección de basura. Así que si llamamos a esa función así, GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced) estamos obligando a una recolección de basura completa en nuestra aplicación.

Ahora bien porque nunca se debería de llamar a esta función así, porque básicamente si nos encontramos en una situación en donde la memoria de nuestro proceso sube en todos los escenario y el recolector de basura en su modo de funcionamiento normal no es capaz de bajar la memoria de uso del proceso, nos encontramos entonces ante un escenario de pérdida de memoria (leaking) y no en una situación donde el recolector de basura no es capaz de hacer su trabajo. Porque os puedo asegurar que en el 99.99% de las veces en que he visto una aplicación que hacia llamadas al recolector de basura para intentar liberar memoria era porque el proceso en sí tenía problemas de pérdida de memoria, no porque el recolector no fuera capaz por si solo de hacer su trabajo bien.

Graficas de memoria en forma de montañas

Llegado a este punto mucha gente se imaginará que la ejecución de las aplicaciones es matemáticamente siempre la misma, es decir, que si en un punto de mi aplicación genero 40mb de memoria y después termino de usar esos objetos automáticamente tengo que ver como mi proceso baja esos 40mb de manera discreta. Pues malas noticias para todos, los sistemas de gestión de memoria sin increíblemente complejos y más para una aplicación de .NET.

Puede que el CLR haya decidido por un casual no liberar esa memoria nativa porque ya que la tiene reservada y no tiene que devolverla al S.O. y más adelante la podrá usar sin tener que volver a pedir memoria a Windows. En ese sentido el CLR también utiliza las funciones de memoria virtual de Windows (VirtualAlloc, VirtualFree, VirtualQuery, VirtualProtect) así que en ese sentido también Windows hace un uso de la memoria de manera conservativa es decir, que no por generar 40mb y ejecutar el recolector de basura automáticamente se liberan 40mb.

Así si por ejemplo el sistema necesita liberar páginas de memoria porque otro proceso, a parte del nuestro en .NET, está haciendo un uso de memoria virtual (no una gran reserva de memoria virtual, acordarse de lo que es una falta de página y como Windows gestiona la memoria de manera perezosa) en ese momento puede decidir recuperar las páginas de memoria de nuestro proceso y entonces nuestra memoria bajará.

Así que simplificar un sistema de gestión de memoria virtual de Windows más el sistema de recolección de basura del CRL de .NET de esa manera es desde mi punto de vista barbaridad. Si queremos saber cuál es el estado de nuestro proceso podemos consultar los contadores de rendimiento para .NET o podemos usar las columnas de Private Bytes, Working Set y Virtual Size en Process Explorer o el comando !address –summary en WinDBG para saber exactamente esa memoria privada en que se gasta en heap, images, ect.

¿Qué son los objetos pineados en memoria?

Cuando hablamos de recolección de basura, también hablamos de compactación de la memoria y para que esa compactación de la memoria pueda ocurrir es necesario mover los datos de direcciones de memoria. En un mundo ideal donde las aplicaciones de .NET sean completamente administradas, es decir 100% .NET sin llamadas a Windows esto debería de bastar, pero el caso es que desde .NET podemos hacer llamadas a componentes no administrados que están fuera del paraguas del recolector de basura.

Imaginaros por un momento que estáis haciendo una llamada nativa a una función hecha en C++ que os pide una dirección de memoria donde vive un objeto que él internamente va a utilizar para realizar su trabajo, resulta que como parte de su trabajo ese componente de C++ tiene un temporizador que cada 30sg comprueba una serie de parámetros en ese objeto en contra de un componente de Windows, pero resulta que entre timer y timer, se lanza una recolección de basura y justo el objeto que este componente de C++ utilizaba (porque recordad que le hemos pasado la dirección de memoria, donde vive) es movida por el recolector de basura en una recolección. Justo cuando el siguiente timer se lance y nuestro componente en C++ vaya a leer la memoria se encontrará con que su memoria ahora mismo está ocupada por otro componente.

Este es solo un ejemplo de que a veces no interesa que determinados objetos de .NET estén siempre en la misma dirección de memoria, cualquiera podría haber sugerido que hagamos una copia del objeto y se la pasemos a C++, pero recordad que siempre pasamos referencias y no copias de objetos.

Es por eso en .NET podemos pinear objetos en la memoria, que como su nombre indica, permite que podamos indicarle al recolector de basura que en ningún caso mueva de dirección de memoria este objeto.

A través de una structura llamada GCHandle podemos pinear objetos así: System.Runtime.InteropServices.GCHandle.Alloc(new object,System.Runtime.InteropServices.GCHandleType.Pinned). Esta llamada nos devuelve un objeto de tipo GCHandle en el que podemos consultar el IntPtr del objeto, si esta inicializado y podemos liberarlo.

Que son las referencias débiles y que es la resurrección de objetos zombies

Como hemos dicho anteriormente cada vez que hacemos una asignación estamos copiando la referencia en memoria de un objeto, es decir, estamos haciendo una referencia fuerte de un objeto. Si existe una referencia fuerte es porque existe una referencia débil, que siguiendo la analogía tiene que ser una referencia que el recolector de basura no tenga en cuenta para evaluar si un objeto es referenciado por otro.

Para poder usar esas referencias débiles tenemos una clase en .NET llamada WeakReference que como su nombre indica nos permite generar esas referencias débiles. Esta clase tiene varias propiedades interesantes como: IsAlive que nos permite consultar si el objeto al que apuntamos sigue vivo; Target que es una referencia (débil) del objeto y TrackRessurection que nos permite hacer tracking de la resurrección de un objeto.

Pero, ¿Qué es exactamente la resurrección de un objeto?

Cada vez que un objeto es eliminado del grafo de referencias de una aplicación, este pasa a una cola llamada la cola de finalización (comando de WinDBG !finlizaqueue) en la que se le da una última oportunidad de ejecutar el código que tenga en el destructor o en el método Dispose (si implementa IDisposable). En ese instante en el que el objeto está en la cola de finalización un objeto está fuera del grafo de objetos, pero si durante la ejecución de ese código se referencia a si mismo de otro objeto que está en el grafo de objetos de la aplicación a través de un objeto estático, diremos que el objeto ha sufrido una resurrección puesto que el recolector de basura sacará a ese objeto de la cola de finalización y el objeto será de nuevo referenciable y volverá a la vida (metafóricamente hablando).

¿Por qué el .NET tiene una finalización no determinista?

Si durante el desarrollo de nuestras clases implementamos IDisposable o creamos un destructor para nuestra clase, el CLR no nos puede asegurar que el destructor de nuestra clase será siempre ejecutado. ¿Por qué?. En ese sentido tenemos que recordar que el CLR es un runtime ejecutado dentro de un proceso y puede que el proceso o el dominio de aplicación se descargue de manera inesperada y en ese sentido el CRL no puede esperar a que todo el código que tengamos en nuestros destructores se ejecute. Por eso se dice que .NET no es determinista en cuanto a la finalización de objetos, porque solamente en el caso de que el dominio de aplicación se descargue o la aplicación se cierre el CLR no nos asegurará que nuestros destructores se ejecuten.

Pero qué pasa si necesito por contrato que el destructor de mi clase se ejecute, o dicho de otra manera, que pasa con las clases que representan recursos del sistema que tienen que ser liberados si o sí.

En este sentido podemos heredad de la clase System.Runtime.ConstrainedExecution.CriticalFinalizerObject haciendo así que el CLR nos asegure que SIEMPRE se ejecutará el destructor de la clase. Como ejemplo diremos que la clase Thread, ReaderWriterLock, SafeHandle y demás heredan de esta clase.

Conclusiones

A lo largo de este articulo hemos repasado los básicos de la gestión de memoria por parte del CLR, y hemos visto que es un sistema extraordinariamente complejo para poder simplificarlo de la manera que lo hacen algunos así que en ese sentido paciencia con la memoria y si tenéis algún problema no dudéis con contactar con el equipo DOT (Debugging and Optimization Team) de Plain Concepts que estaremos encantados de buscar problemas de memoria y rendimiento en vuestras aplicaciones.

Luis Guerrero.

2Apr/110

Gestión de proxies de WCF en Silverlight 4

Una de las cosas que más esfuerzo me cuestan cuanto estoy desarrollando aplicaciones en Silverlight es la gestión del ciclo de vida de un proxy de WCF, es decir, crear la instancia del proxy con la configuración del binding correcta, subscribirme a los eventos complete de las operaciones que quiero invocar y en caso de falta en el proxy volver a hacer todo ese trabajo, de-subscribirme de los eventos complete y volver a repetir el proceso de nuevo.

Es por eso que en el día a día del desarrollo de aplicaciones de Silverlight siempre intento hacer que las cosas sean muy fáciles para mí y que pueda manejar el ciclo de vida de un proxy de una manera muy cómoda. Viendo un poco cuales son las limitaciones que tengo con los proxy de wcf, me encuentro con las que he comentado anteriormente:

  • Creación del proxy con la configuración del binding adecuada.
  • Invocación asíncrona de las operaciones
  • Ciclo de vida del proxy

La primera de las limitaciones se soluciona de manera fácil, con una factoría. Una clase helper o factoría que me permita instancias los proxy de manera cómoda con un sistema centralizado de configuración de bindings y demás opciones. Normalmente en un proyecto de Silverlight casi todos los proxy, por no decir todos, serán del mismo tipo, así que no tenemos que preocuparnos mucho por dar soporte a varios tipos de configuraciones.

public class ProxyFactory
{
    public static T CreateInstance<T, K>(Uri uri)
        where K : class
        where T : ClientBase<K>
    {
        T result = default(T);

        CustomBinding custom = new CustomBinding();
        custom.Elements.Add(new BinaryMessageEncodingBindingElement());
        custom.Elements.Add(new HttpTransportBindingElement()
        {
            MaxReceivedMessageSize = 2147483647,
            MaxBufferSize = 2147483647
        });
        SetDebugTimeouts(custom);

        EndpointAddress address = new EndpointAddress(uri.ToString());

        result = (T)(object)Activator.CreateInstance(typeof(T), custom, address);
        
        return result;
    }

    private static void SetDebugTimeouts(CustomBinding custom)
    {
        if (custom != null)
        {
            TimeSpan timeout = TimeSpan.FromSeconds(60 * 3);
            timeout = TimeSpan.MaxValue;
            custom.CloseTimeout = timeout;
            custom.OpenTimeout = timeout;
            custom.ReceiveTimeout = timeout;
            custom.SendTimeout = timeout;
        }
    }
}

Aquí tenemos el código de la factoría de proxy.

Si nos fijamos acepta dos parámetros genéricos, que son: K es la interfaz del contrato del canal de comunicaciones es decir el contrato de operaciones y T es la clase que hereda de ServiceBase<K>, es decir la clase que implementa el contrato de operaciones en cliente.

Así de esta manera en el cuerpo de la creación podemos crear a mano un custombinding y añadir los elementos que necesitamos, como por ejemplo: BinaryMessageEncondingBindingElement y HttpTransportBindingElement, además de configurar parámetros de esas clases. Creamos también una instancia de la clase EndpointAddress que contiene la dirección url del servicio wcf.

Una vez que tenemos todo esto podemos utilizar la clase Activator para crear una instancia dinámicamente del proxy (a través del tipo de T) pasándoles los parámetros previamente establecidos.

Para consumir el proxy sería de esta manera:

KeyPlayerServiceClient proxy = 
ProxyFactory.CreateInstance<KeyPlayerServiceClient, IKeyPlayerService>(ProxyUrl.KeyPlayerUrl);

Así podemos tener un sistema centralizado de creación de todos los proxies que queramos consumir desde Silverlight.

Manejar el resultado y el ciclo de vida

Pero esta manera de crear las instancias no soluciona el problema que tenemos de manejar el ciclo de vida del proxy, subscribirnos al evento complete de la operación y obtener el resultado de la operación. Es por eso que necesitamos una clase que nos permita invocar una operación asíncrona de un servicio web y que de alguna manera esa clase helper, cree la instancia de la clase, se subscriba al evento de complete, obtenga el resultado de la operación y llame a un delegado que nosotros le hemos especificado. Esto sería lo ideal.

Pues bien podemos hacer todo eso de manera cómoda con esta clase ProxyManager. Antes de centrarnos en cuál es la implementación concreta de la clase y los detalles vamos a ver cómo podemos consumir una operación típica de un servicio de wcf con esta clase ProxyManager.

Si tenemos un servicio con esta operación:

[ServiceContract]
public interface IServiceDemo
{
   [OperationContract]
   DateTime GetDateTime();
}

Tenemos una única operación que se llama GetDateTime y que obtenemos un objeto de tipo DateTime.

Una vez que hemos agregado la referencia al proxy de WCF desde nuestro proyecto de Silverlight, podemos hacer esto:

// manera directa de invocar un servicio web y solo centrarse en la respuesta segun su tipo
ProxyManager<ServiceDemoClient, IServiceDemo> manager = new ProxyManager<ServiceDemoClient, IServiceDemo>(ProxyUrl.DemoService);
// como sabemos que el servicio web devuelve un resultado de tipo DateTime, podemos
// agregar un Action<DateTime> porque DAteTiem es el tipo de devuelve
manager.InvokeOperation<DateTime, GetDateTimeCompletedEventArgs>(
    "GetDateTime",
    new Action<DateTime>(OnGetDateTimeCompleted),
    null);
private void OnGetDateTimeCompleted(DateTime dateTime)
{
}

En este ejemplo fijaros que utilizando la genericidad y la reflexión podemos hacer que la invocación del servicio sea mucho más cómoda, podemos resumirlo a una simple línea de código.

Requisitos tiene la clase ProxyManager

Para crear una instancia de la clase ProxyManager tenemos que pasar los mismos argumentos de tipos que el ProxyFactory, de hecho internamente utiliza el mismo mecanismo, además de la uri de la dirección del servicio web.

Una vez que tenemos el objeto creado tenemos que llamar al único método de la clase ProxyManager, InvokeOperation.

InvokeOperation acepta dos parámetros de tipo que son el tipo que devuelve la operación del servicio web, si nos acordamos era un DateTime, y el otro parámetro genérico es la clase que hereda de AsyncCompletedEventArgs que contiene el resultado de la invocación, en nuestro ejemplo, GetDateTimeCompletedEventArgs. De esta clase es donde el ProxyManager obtendrá el resultado de la operación.

Como parámetros de la función tenemos que suplir el nombre de la operación como un string, en nuestro caso GetDateTime (pero sin Async al final), después un delegado de tipo Action<T> con el resultado, y al final de todos los parámetros un array de tipo object con la palabra params que son los argumentos de la invocación, en nuestro caso null.

Lo bueno que tiene esta clase ProxyManager es que automáticamente por nosotros crea la instancia del proxy, se subscribe al evento complete de la operación que queremos invocar y automáticamente invoca nuestro delegado de tipo Action<T> con el resultado, además de eso cuando termina la invocación del objeto de tipo Action<T> se de-subscribe del evento y cierra el proxy. Toda una gozada.

Además de eso tenemos la posibilidad de utilizar una sobrecarga de la función InvokeOperation que nos permite tener también acceso al objeto de UserState de la invocación por si invocamos más de una vez la operación y necesitamos acceder a un objeto de usuario en la devolución de llamada:

// manera directa de invocar un servicio web y solo centrarse en la respuesta segun su tipo
ProxyManager<ServiceDemoClient, IServiceDemo> manager = new ProxyManager<ServiceDemoClient, IServiceDemo>(ProxyUrl.DemoService);
// como sabemos que el servicio web devuelve un resultado de tipo DateTime, podemos
// agregar un Action<DateTime> porque DAteTiem es el tipo de devuelve
manager.InvokeOperation<DateTime, GetDateTimeCompletedEventArgs>(
    "GetDateTime",
    new Action<DateTime>(OnGetDateTimeCompleted),
    null);

manager.InvokeOperation<DateTime, object, GetDateTimeCompletedEventArgs>(
    "GetDateTime",
    new Action<DateTime, object>(OnGetDateTimeWithArgumentsCompleted),
    null);
private void OnGetDateTimeWithArgumentsCompleted(DateTime result, object userState)
{

}

Con esto podemos hacer que la gestión de los proxies de WCF sea mucho más eficiente que cómoda para el programador que antes.

Codigo fuente completo de la clase ProxyManager:

using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.ServiceModel;

namespace ViewModelBinding.Services
{
    public class ProxyManager<TService, KServiceInterface>
        where KServiceInterface : class
        where TService : ClientBase<KServiceInterface>
    {
        public ProxyManager(Uri url)
        {
            proxy = ProxyFactory.CreateInstance<TService, KServiceInterface>(url);
        }

        public void InvokeOperation<TResult, KEventArgs>(string operationName, Action<TResult> action, params object[] args)
            where KEventArgs : AsyncCompletedEventArgs
        {
            if (!string.IsNullOrWhiteSpace(operationName))
            {
                InternalActionOf<KEventArgs, TResult> internalAction = new InternalActionOf<KEventArgs, TResult>(action);
                internalAction.EventInfo = proxy.GetType().GetEvent(string.Format("{0}Completed", operationName));
                internalAction.Delegate = new EventHandler<KEventArgs>(internalAction.OnActionExecuted);
                internalAction.EventInfo.AddEventHandler(proxy, internalAction.Delegate);
                internalAction.Proxy = proxy;

                if (args == null)
                {
                    args = new object[] { };
                }

                MethodInfo[] methods = proxy.GetType().GetMethods();
                string methodName = string.Format("{0}Async", operationName);
                var found = (from p in methods
                             where p.Name == methodName && p.GetParameters().Length == args.Length
                             select p).FirstOrDefault();



                if (found != null)
                {
                    found.Invoke(proxy, args);
                }
            }
        }

        public void InvokeOperation<TResult, TUSerState, KEventArgs>(string operationName, Action<TResult, TUSerState> action, params object[] args)
            where KEventArgs : AsyncCompletedEventArgs
        {
            if (!string.IsNullOrWhiteSpace(operationName))
            {
                InternalActionOf<KEventArgs, TResult, TUSerState> internalAction = new InternalActionOf<KEventArgs, TResult, TUSerState>(action);
                internalAction.EventInfo = proxy.GetType().GetEvent(string.Format("{0}Completed", operationName));
                internalAction.Delegate = new EventHandler<KEventArgs>(internalAction.OnActionExecuted);
                internalAction.EventInfo.AddEventHandler(proxy, internalAction.Delegate);
                internalAction.Proxy = proxy;

                if (args == null)
                {
                    args = new object[] { };
                }

                MethodInfo[] methods = proxy.GetType().GetMethods();
                string methodName = string.Format("{0}Async", operationName);
                var found = (from p in methods
                             where p.Name == methodName && p.GetParameters().Length == args.Length
                             select p).FirstOrDefault();



                if (found != null)
                {
                    found.Invoke(proxy, args);
                }
            }
        }

        private TService proxy;
    }
}
internal class InternalActionOf<EventArgs, TResult, TUserState>
       where EventArgs : AsyncCompletedEventArgs
{
   public InternalActionOf(Action<TResult, TUserState> action)
   {
       this.action = action;
   }

   internal void OnActionExecuted(object args, EventArgs e)
   {
       PropertyInfo property = e.GetType().GetProperty("Result", BindingFlags.Public | BindingFlags.Instance);
       if (property != null)
       {
           object result = property.GetValue(e, null);
           if (result != null)
           {
               if (typeof(TResult).IsAssignableFrom(property.PropertyType))
               {
                   action((TResult)result, (TUserState)e.UserState);
                   EventInfo.RemoveEventHandler(Proxy, Delegate);
               }
           }
       }
   }

   public EventHandler<EventArgs> Delegate { get; set; }
   public EventInfo EventInfo { get; set; }
   public object Proxy { get; set; }

   private Action<TResult, TUserState> action;
}
internal class InternalActionOf<EventArgs, TResult>
            where EventArgs : AsyncCompletedEventArgs
{
    public InternalActionOf(Action<TResult> action)
    {
        this.action = action;
    }

    internal void OnActionExecuted(object args, EventArgs e)
    {
        PropertyInfo property = e.GetType().GetProperty("Result", BindingFlags.Public | BindingFlags.Instance);
        if (property != null)
        {
            object result = property.GetValue(e, null);
            if (result != null)
            {
                if (typeof(TResult).IsAssignableFrom(property.PropertyType))
                {
                    action((TResult)result);
                    EventInfo.RemoveEventHandler(Proxy, Delegate);
                }
            }
        }
    }

    public EventHandler<EventArgs> Delegate { get; set; }
    public EventInfo EventInfo { get; set; }
    public object Proxy { get; set; }

    private Action<TResult> action;
}

Luis Guerrero.

8Sep/100

Entrevista a plain concepts, equipo de desarrolladores de bye bye brain

Entrevista realizada en http://wp7connect.blogspot.com/

¿Podeis hacer una breve presentación sobre el equipo?

 

El equipo lo formamos Ricardo Acosta, Rodrigo Diaz y Luis Guerrero. Ricardo Acosta es nuestro diseñador gráfico, modelador 3d y texturizador, y tanto Rodrigo Diaz como yo somos programadores de .NET.

Supongo que os habéis dado cuenta que la mayoría de blogs especializados en el sector ,de medio mundo, están hablando de vosotros después de que anunciarais en vuestros blogs personales, Bye Bye Brain, el juego para WP7 que estáis desarrollando ¿Os esperabais tanta repercusión en tan poco tiempo? ¿Cómo surgió la idea de desarrollar Bye Bye Brain?

No esperábamos tener tanta acogida como la que hemos tenido, empezamos con el video en vimeo y la cosa fue subiendo hasta que fuimos portada de gizmodo.com toda una hazaña.
La idea de desarrollar ByeByeBrain surgió a principio de año cuando nos enteramos que Microsoft iba a desarrollar una nueva plataforma móvil, nuestra empresa Plain Concepts es Partner Gold de Microsoft y pensábamos en hacer un juego.

¿Por qué consideráis que WP7 es una buena plataforma para lanzar vuestro proyecto, aún cuando todavía no se ha lanzado?


Esta pregunta es complicada de responder, pero supongo que nos lanzamos por ser afines a Microsoft, porque no esperábamos que hubiera muchos desarrolladores haciendo cosas, porque las dos tecnologías en las que se desarrolla XNA y Silverlight las utilizamos a diario así que pensamos que era la oportunidad perfecta para nosotros.

Muchos comentarios resaltan el hecho de que un equipo de tres personas ha realizado un desarrollo tan bueno ¿Cuánto tiempo os ha supuesto desarrollar Bye Bye Brain? ¿Cuál ha sido el aspecto o proceso más difícil?


El primer checking del proyecto se hizo el día 2 de julio a las 12 de la mañana y desde ese momento no hemos parado de trabajar, eso implica definir un pequeño prototipo del juego, pantallas funcionalidad y demás. La parte más difícil del proyecto para mí fue acomodarme a XNA, venia de Silverlight que es un framework de alto nivel con cosas muy cómodas y en XNA no teníamos nada, así en unos de las primeras semanas, definimos un pequeño framework de animaciones basada en Silverlight y un sistema para definir controles y relaciones entre los controles en XNA. Evidentemente no podemos definir los controles igual que en Silverlight pero queríamos definir una relación padre hijo con los DrawableGameComponents de XNA para que fuera más fácil trabajar con ellos. Definimos las DependencyProperties para XNA y alguna que otra cosa más. Con todo eso pudimos después hacer todas las animaciones de los elementos 2D de una manera cómoda ya que animar es tan fácil como en Silverlight.

¿Qué ventajas pensáis que tiene realizar juegos en XNA respecto a los desarrollos que se pueden realizar en otras plataformas móviles como Iphone o Android?

Las ventajas para nosotros en el .NET Framework y C# ya que son nuestros lenguajes principales de desarrollo diario. .NET Framework tiene todo lo que un desarrollador puede soñar y XNA también, así que nos sentimos muy cómodos trabajando con las herramientas. Además de la perfecta integración con Visual Studio 2010.

¿Qué breves consejos le daríais a desarrolladores independientes que están empezando a plantearse desarrollar juegos o aplicaciones para WP7?

Pues que es el momento de hacerlo ya que no hay muchos desarrolladores interesados y eso puede hacer que les sea fácil brillar ya que la plataforma es nueva. Además, desde mi punto de vista, las herramientas de Microsoft son las mejores.

¿Qué opináis de las herramientas de desarrollo para WP7? ¿Cuáles son sus puntos fuertes y sus puntos débiles?


Los puntos fuertes ya los he comentado, las API o frameworks, XNA y Silverlight, son súper productivos para desarrolla aplicaciones, ya que Silverlight con Visual Studio 2010 y con Expression Blend se puede hacer cosas muy chulas en muy poco tiempo. Sobre las cosas malas, son herramientas beta eso significa que contienen algunos bugs que no tienen workaround y que quizás el tiempo de despliegue de tu aplicación al emulador y al devices es muy lento.

¿Desde vuestro punto de vista y habiendo podido acceder a un terminal para desarrolladores qué crees que debe mejorar WP7 a corto-medio plazo?


Esto es una pregunta trampa, Microsoft lo está haciendo bien con WP7 pero realmente es su primera versión del SO, las otras plataformas van por la versión 2.2 o 4 así que evidentemente le falta muchas cosas, pero es un buen comienzo.

¿Cuánto tiempo calculáis que pasará desde que enviéis una aplicación a Microsoft hasta que esté evaluada e incorporada al Marketplace? ¿Cuáles son los puntos críticos de este proceso?

Por ahora no lo sabemos pero Microsoft ha prometido que el proceso será rápido.

Si podéis contarnos algo ¿qué proyectos futuros tenéis en mente respecto a WP7?


Pues tenemos pensado seguir con el desarrollo de ByeByeBrain con más mapas, ataques especiales, más mini juegos, etc. Además de empezar el desarrollo de otros juegos o aplicaciones.

¿Quereis añadir algo más que creas interesante para los usuarios interesados en WP7?


Pues decirles que si son desarrolladores este es su momento de que se bajen el SDK y se pongan en serio a hacer su proyecto con calma.

Podéis saber más sobre Plain Concepts (Luis Guerrero, Rodrigo Díaz y Ricardo Acosta) y sus desarrollos en la web de Plain Concepts y sus blogs personales.

Les damos las gracias a los tres por concedernos esta entrevista y les deseamos un gran éxito en este y otros proyectos sobre WP7. Estamos seguros que Bye Bye Brain será uno de los juegos más populares del Marketplace de WP7.