Silverlight, un mejor manejo de los servicios

Como todos es sabido Silverlight trae soporte para invocar servicios web, tanto servicios web tradicionales de .net como servicios de WCF. En esto últimos solo con soporte para httpBasicBinding.

Cada vez que generamos un proxy en Visual Studio para un proyecto de Silverlight, el proxy generado solo soporta invocaciones asíncronas. No podemos de ninguna manera hacer una invocación síncrona a un servicio web y esperar a la respuesta. Puede haber muchas maneras por las cuales Microsoft implemento este comportamiento predeterminado, pero creo que el más importante de todos es hacer que la UI de las aplicaciones de Silverlight *nunca* dejen de responder.

Así que puestos en antecedentes, podemos ahora estudiar nuestro problema e intentar mitigarlo. Cuando generamos un proxy en Visual Studio, lo que obtenemos es una función con el mismo nombre que la operación del servicio + “Async” y un evento que es el nombre de la operación + “Completed” en la que en los argumentos de respuesta tenemos el resultado de la invocación del servicio.

Algo como esto:

public partial class TraditionalServices : UserControl
{
    private ServiceClient service;
    public TraditionalServices()
    {
        InitializeComponent();

        service = new ServiceClient();
        service.GetDateCompleted += new EventHandler<GetDateCompletedEventArgs>(OnGetDateCompleted);
        service.GetEmployeeCompleted += new EventHandler<GetEmployeeCompletedEventArgs>(OnGetEmployeeCompleted);


        Loaded += new RoutedEventHandler(OnLoaded);
    }

    void OnLoaded(object sender, RoutedEventArgs e)
    {
        service.GetDateAsync();
    }

    void OnGetEmployeeCompleted(object sender, GetEmployeeCompletedEventArgs e)
    {
        
    }

    void OnGetDateCompleted(object sender, GetDateCompletedEventArgs e)
    {
        
    }
}

Conforme uno empieza a trabajar con los servicios de Silverlight encuentra que el código se puede volver un poco complicado puesto que no podemos invocar al servicio y después trabajar directamente con el resultado sino que lo tenemos que hacer en el método que se llamará cuando se termine la invocación del servicio.

Lo que os propongo en este artículo es que construyáis una clase Helper que os ayude a lidiar con estos problemas y hacer así el código un poco más claro.

Del código anterior para realizar la invocación a un servicio pasamos a esta:

service.GetDateTime(value =>
{
    string currentDateTime = value.ToString();
});

Como se puede apreciar el resultado es mucho mejor que la manera tradicional de trabajar con los proxies además de que el código queda mucho más claro.

¿Cómo está este helper implementado?

La idea es usar expresiones Lambda para hacer el código más legible, lo único que necesitamos es un delegado de tipo Action<T> en el que T será del mismo tipo del resultado de nuestra invocación al servicio web.

public void GetDateTime(Action<DateTime> value)
{
    service.GetDateAsync(value);
}

Con este simple patrón podemos simplificar las llamadas al servicio web y esconder su complejidad.

public partial class MainPage : UserControl
{
Helpers.ServiceHelper service;
public MainPage()
{
   InitializeComponent();

   service = Helpers.ServiceHelper.Instance;


   service.GetDateTime(value =>
   {
       string currentDateTime = value.ToString();
   });


   service.GetEmployee(value =>
   {
       ListBox listbox = new ListBox();
       listbox.ItemsSource = value;
       LayoutRoot.Children.Add(listbox);
   });

   service.DoStuff(() =>
   {
       // do stuff done
   });
}
}

Lo que hace la clase Helper es, cada vez que se realiza una invocación del servicio web se pasa por parámetro una referencia del delegado de tipo Action<T> que contiene la función que se llamará cuando termine la invocación. Una vez que termina la invocación del servicio lo que hacemos es obtener ese valor de vuelta y invocar al delegado con el resultado que es del mismo tipo que la signatura del delegado Action<T>.

public class ServiceHelper
{
private static ServiceHelper instance = new ServiceHelper();
public static ServiceHelper Instance { get { return instance; } }

private ServiceClient service;

private ServiceHelper()
{
   service = new ServiceClient();
   service.GetDateCompleted += new EventHandler<GetDateCompletedEventArgs>(OnGetDateCompleted);
   service.GetEmployeeCompleted += new EventHandler<GetEmployeeCompletedEventArgs>(OnGetEmployeeCompleted);
   service.AddCompleted += new EventHandler<AddCompletedEventArgs>(OnAddCompleted);
   service.DoStuffCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(OnDoStuffCompleted);
}

void OnDoStuffCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
   if (e.Error == null)
   {
       Action action = (Action)e.UserState;
       action();
   }
}

void OnAddCompleted(object sender, AddCompletedEventArgs e)
{
   if (e.Error == null)
   {
       Action<int> action = (Action<int>)e.UserState;
       action(e.Result);
   }
}

void OnGetEmployeeCompleted(object sender, GetEmployeeCompletedEventArgs e)
{
   if (e.Error == null)
   {
       Action<ObservableCollection<Employee>> action = (Action<ObservableCollection<Employee>>)e.UserState;
       action(e.Result);
   }
}

void OnGetDateCompleted(object sender, GetDateCompletedEventArgs e)
{
   if (e.Error == null)
   {
       Action<DateTime> action = (Action<DateTime>)e.UserState;
       action(e.Result);
   }
}

public void GetDateTime(Action<DateTime> value)
{
   service.GetDateAsync(value);
}

public void GetEmployee(Action<ObservableCollection<Employee>> value)
{
   service.GetEmployeeAsync(value);
}

public void Add(AddArguments args, Action<int> value)
{
   service.AddAsync(args.Operand1, args.Operand2, value);
}

public void DoStuff(Action value)
{
   service.DoStuffAsync(value);
}

public class AddArguments
{
   public int Operand1 { get; set; }
   public int Operand2 { get; set; }
}
}

Os podeis descargar de aquí el ejemplo completo. http://www.luisguerrero.net/downloads/servicehelper.zip

Saludos. Luis.

2 comentarios en “Silverlight, un mejor manejo de los servicios”

  1. Hola, segun veo es necesario escribir el helper para cada servicio, cosa que desanima inicialmente (a menos que hubiera una plantilla .tt o una custom tool que regenerara el helper).
    Solo quería comentar que se puede declarar el eventhandler en modo inline, y a continuación la llamada asíncrona, es decir:

    Cliente.GetTokenCompleted +=
    (object sender, GetTokenCompletedEventArgs e) =>
    {
    var resultado = e.Result;
    };
    Cliente.GetTokenAsync();

    Con lo que no sé si se justifica usar el helper por ahorrarse una línea, teniendo en cuenta que el helper agrega muchas más.
    Saludos

  2. Hola pvila, como tu bien comentas, eso mismo lo podemos obtener de esa misma manera, pero el código que generamos no es tan limpio y organizado. Una de las cosas que quiero con este Helper es esconder el tener que subscribirse a los eventos y luego obtener los datos, además de que puedo tener un lugar centralizado de tratamiento de excepciones de invocación al servicio web.

    Muchas gracias por el comentario. Luis.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *