Wednesday, December 9, 2009

Simplifying Asynchronous Calls in Silverlight using Action

This post explores a way to encapsulate web service calls from Silverlight in a way that is easy to use and understand.

As anyone who works with Silverlight knows, Silverlight forces web service calls to be asynchronous. There is a good reason for this, which I won't get into with this post. What I would like to do is demonstrate an approach that advocates a clean separation of the internals of the service from the application that is consuming it.

First, I am assuming you understand web services and WCF. I assume you've called services from Silverlight, and are aware of the nuances of configuring the endpoint, etc. I am also going to assume you are familiar with some frameworks such as Prism, MEF, Caliburn, etc that advocate clean separation of concerns and modular design.

Let's assume I have a service on my web page that is a calculator (oh, where did I get that idea?) It looks something like this:


[ServiceContract(Namespace = "http://www.wintellect.com/sample")]    
public interface ICalculatorService
{
    [OperationContract]
    long Multiply(int x, int y);
}

Simple, correct? The first thing I'm going to do is to create a similar interface on my Silverlight client. This interface will hide the details of how the service is connected and wired. I also want to make it easier to deal with the asynchronous nature of the calls. I've seen some pretty exotic implementations to try to make an asynchronous call look synchronous, and don't quite understand why we'd want to do it. Instead, let's just define a contract like this somewhere in the Silverlight application that is "seen" globally:


public interface ICalculator 
{
   void Multiply(int x, int y, Action<long> result); 
}

As you can see, it's a simple void method ... in fact, all methods we use to abstract the asynchronous calls will most likely be void. But the key is last parameter. This delegate allows me to specify how I want to deal with the result.

Now I create a project that is solely used to implement ICalculator and connect with the web service. First, if you haven't read my post about abstracting WCF service calls, be sure to read it: Abstracting WCF Service Calls in Silverlight. It explains how I use a base class to avoid having to change the ServiceReferences.ClientConfig file everytime I deploy an application, and also will give you the concept for the BaseService class you see below.


public class CalculatorService : BaseService<CalculatorServiceClient,ICalculatorService>, ICalculator
{
    public CalculatorService()
    {
        _GetClientChannel().MultiplyCompleted += new EventHandler<MultiplyCompletedEventArgs>(CalculatorService_MultiplyCompleted);
    }        

    #region Event handlers

    void CalculatorService_MultiplyCompleted(object sender, MultiplyCompletedEventArgs e)
    {
        Action<long> result = e.UserState as Action<long>;
        if (result != null)
        {
            result(e.Result); 
        }
    }

    #endregion 

    #region ICalculator Members

    public void Multiply(int x, int y, Action<long> result)
    {
        _GetClientChannel().MultiplyAsync(x, y, result);
    }

    #endregion
}

The base class preps the channel and credentials, etc for me. In the constructor of the implementation, I simply register for the completion event for the call. When a consumer of the service makes a call, the consumer provides a delegate to call back to when the service is done. This is passed in the user state, then cast back to an action and called once the service returns. If you wanted to have error handling, you'd just extend the action delegate to have an error-related parameter and check the e.Error when the call completed.

Now we can put it into use. In my bootstrapper, I'll bind the service to the contract like this:


Container.RegisterType<ICalculator,CalculatorService>(new ContainerControlledLifetimeManager());
            

Now I can throw a view model together that looks something like this:


public class CalculatorViewModel : INotifyPropertyChanged
{

    private bool _busy = false;

    public bool BusyState
    {
        get { return _busy; }
        set
        {
            if (!value.Equals(_busy))
            {
                _busy = value;
                OnPropertyChanged("BusyState");
                CalculateCommand.RaiseCanExecuteChanged();
            }
        }
    }

   public DelegateCommand<object> CalculateCommand { get; set; } 

    public CalculatorViewModel(ICalculator calculator)
    {
        Calculator = calculator;
        CalculateCommand = new DelegateCommand<object>(o => CommandCalculate(),
            o => !BusyState);
    }

    public ICalulator Calculator { get; set; }

    private int _x;

    public int X
    {
        get { return _x; }
        set
        {
            if (!value.Equals(_x))
            {
                _x = value;
                OnPropertyChanged("X"); 
            }
        }
    }

     private int _y;

    public int Y
    {
        get { return _y; }
        set
        {
            if (!value.Equals(_y))
            {
                _y = value;
                OnPropertyChanged("Y"); 
            }
        }
    }

    private long _answer;

    public long Answer
    {
        get { return _answer; }
        set
        {
            if (!value.Equals(_answer))
            {
                _answer = value;
                OnPropertyChanged("Answer"); 
            }
        }
    }

    public void CommandCalculate()
    {
        BusyState = true;
        Calculator.Multiply(_x, _y, answer => {
              BusyState = false;
              Answer = answer;
         }); 
    }

    private void OnPropertyChanged(string property)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(property));
        }
    }

    #endregion 

    #region INotifyPropertyChanged Members

   public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

Now I can simply data bind my XAML to the X, Y, and Answer properties, and bind a button to the Click command. The button will even disable itself while the client is waiting on the server to return the answer. As you can see, the implementation is very straightforward and I don't have to worry about how the call happens. In fact, using this type of abstraction, I could easily wire in a mock class to satisfy the service for testing like this... or maybe decide that a web service for multiplication is overkill and just satisy the contract locally:


public class MockCalculator : ICalculator 
{
   public void Multiply(int x, int y, Action<long> result) 
   {
      result(x*y);
   }
}

While the Action command is really a different way of expressing a delegate, it also makes the intent of the code clear and allows for a clean separation of mechanism from result when dealing with asynchronous calls.

Jeremy Likness

9 comments:

  1. Hi Jeremy,

    Further to your reply to my other post regarding using delegates, I have a couple of questions about this article.

    1 - Do you have the source code for this?

    2 - I am not sure where everything goes in the Silverlight Solution. Please can you let me know if my reasoning below is right?


    3 - This will be my WCF Contract Service on the server side:

    [ServiceContract(Namespace = "http://www.wintellect.com/sample")]
    public interface ICalculatorService
    {
    [OperationContract]
    long Multiply(int x, int y);
    }

    4 - I create another Interface on the client

    public interface ICalculator
    {

    void Multiply(int x, int y, Action result);
    }

    5 - You then say “create a project that is solely used to implement ICalculator and connect with the web service. Please could explain what type of project this is? Is it a class on the client (Silverlight)?

    6 - Please could you explain this code for me? Where do you create the CalculatorServiceClient class? Is it the proxy class you get in the client by referencing the WCF Service?

    public class CalculatorService : BaseService, ICalculator
    {

    7 - This is the base class from Abstracting WCF Service Calls in Silverlight 3 tutorial. So the base class accepts two parameters. Where do you implement the TChannel?

    What does this whole statement mean? Where is the ClientBase class implemented?

    public abstract class BaseService : ClientBase where TClient: ClientBase, new() where TChannel: class


    8 - Since, I have not used Prism, how can get by without the bootstrapper?

    Container.RegisterType(new ContainerControlledLifetimeManager());

    ReplyDelete
  2. Jeremy,
    What would u say if we separate silverlight project from interface project and create different projects for real WCF calls (implementing ICalculator) and mockup.
    So, solution will look like this:

    SilverlignProjectsSolution
    - Interfaces
    ICalculator
    -MockupService
    FakeData: ICalculator
    - RealDataService
    RealData: ICalculator
    - ServiceLocator
    DataServiceLocator (returns IsDesignTime?FakeData:RealData)
    - SilverlightApp
    MVVMModel (using ServiceLocator to get ICalculator implementation)

    If solution build like this ServiceReference will be in RealData project only.

    Is it good design?

    ReplyDelete
  3. Yes, for many larger applications I typically have a separation like this:

    Core - interfaces/abstract classes that support it globally
    Framework - implementations/concrete types used globally
    Interface.Data - interfaces/abstract classes for the data access layer
    Interface.EF - this would be an implementation of the above that uses Entity Framework, etc.

    ReplyDelete
  4. Thanks for confirmation of my idea.


    I have two more projects in solution:
    Interface.MockData - classes which implement Interface.Data and provide design time data

    ServiceLocator - class returning Interface.EF or Interface.MockData.


    Interface.EF project should be silverlight project (not Silverlight Lib), otherwise ServiceReferences.ClientConfig file is not available.


    Couple things bothers me..
    WCF service returns data type ServerDTO object.
    Silverlight project should use similar ServerDTO class but it may add more stuff, like property validation, INotifyDataError implementation, etc... It means I have to have Model classes used by ViewModel and have some sort of convertion from Model to ServerDTO classes...
    What I do is I define Model classes in different project. These model classes have all validation attributes and I link this classes in my Server Project. On server side I use these model classes as DTO classes and when I generate client on Silverlight side proxy reuses the same model classes.
    Some people say it is impossible, because DTO classes must have DataContract/DataMember attributes and these attributes are unknown for Silverlight side.. But, it my model classes don't have it as WCF sp1 can serialize all public members without DataMember attributes...
    You see what I mean?
    So, what is your opinion abt this design?
    Thanks

    ReplyDelete
  5. I was more referring to generic projects, the EF stuff would likely be on the server side, not the client side. The way I typically handle DTOs is to have shared models between server/client. The WCF end point can generate whatever it wants, I simply marshall it into the Silverlight version and deal with a clean entity that doesn't have to reference the service stack elsewhere in the project.

    Any models that need heavy CRUD get moved into a view model specifically designed to suport that.

    ReplyDelete
  6. so, you have DTO classes and silverlight models, right?
    And I believe marshaling conversion between them is abstracted as well??
    Would you mind create some test solution to show this point?
    Or if you want i can give you mine for review and publish if it is in line with your thoughts..

    ReplyDelete
  7. Nice post Jeremy.

    Quick question about data errors. How would you handle error if MultiplyCompletedEventArgs returns e.Error here.
    I see that you can add multiple eventhandlers to MultiplyCompleted event but fail to imaging how will you handle it, or will async call look like.

    ReplyDelete
  8. In that case I'd simply change the callback signature to have a tuple or a class that has both the result and the exception, and call it with both, and then the callback can handle the error as needed. Could even change the signature of the contract to be where T: Exception for example.

    ReplyDelete
  9. Hi Jeremy,

    Do I need to add servicereference in Silverlight project.
    Suppose my service method returns an "Employee" object.

    In Service completed method...
    Action result = e.UserState as Action;

    To get part working, I need to add service reference in SL project.

    Please clarify this.

    ReplyDelete