Routing asynchronous service calls in Silverlight 2

The reasons for Silverlight 2's asynchronous service model existance are pretty well understood, such as not blocking the UI while performing some resource intensive operation (like retrieving data), but if you're used to a more synchronous request-response model it can seem a little alien at first.  Recently I was working with an application that was required to talk to a WCF service hosted in a traditional ASP.NET application.  The WCF service was providing a few methods to retrieve information on a catalog, which is easy enough to consume within a Silverlight application - but there were a couple of problems to overcome.

  1. Multiple methods in the Silverlight client could call into the WCF service client
  2. These methods behaved differently depending on where they were called from

Multiple entry points

Given that WCF service methods can only have a single completed handler assigned to them, I needed a way to build more intelligence into the completed handler to deal with the fact that the same service method could be called by a multitude of client methods. The first option to resolve this would be to include all the routing/contextual logic into the completed handler to make whatever decisions needed to be made, and control it using some state information stored in the result set to allow us to make that contextual decision.  This would definitely work, but it has a couple of downsides in my mind, namely if you have a complex application with a lot of contextual differences, your completed handler ends up being bloated. 

I'm ignoring the fact that we could factor it to call out to other methods to handle any of the gory details here by the way, that is definitely an option just one I didn't prefer to use.  Secondly, I didn't want to necessarily place the burden of the decision making on what contextual information to include on my WCF service (say I wanted to consume it directly from an ASP.NET application for example where that state doesn't have meaning). It also meant that whenever I added an additional context, I would have to affect the wire-up in the completed handler introducing risk of breaking changes down the road.

After doing some investigation it became obvious that I could subvert part of the infrastructure for calling a service method using the generated service client that fit my needs - and seemed to be a valid way of tackling the issue. With that in mind, let's run through a simple worked example (the solution for which is attached below). In our demo Silverlight application, we have a couple of textboxes to capture returned data, and a button to initiate one of the action types. We have created a service reference to a local WCF service that is hosted in an ASP.NET web application within the same solution - that has a single service method returning the current time from the server as a string. To start connecting everything together within the page usercontrol, we create a new delegate type:

public delegate void OnServiceCallComplete( object sender, EventArgs e );

We've already got a reference to our service client, so in the constructor of the page we wire up the events that we want to handle. For sake of simplicity I created a single private member variable to hold a reference to the service client.

private ServiceClient m_client = new ServiceClient();
public Page()
{
  InitializeComponent();
  //-- Initialise the complete handler
  m_client.GetTimeCompleted += new EventHandler( GetTimeCompleted );
}

This is a standard handler that will be called whenever the asynchronous GetTimeAsync method completes. The call itself will be initiated on a timer, and on the button click - so sake of brevity I'll only detail the button click event although one reviewing the code you will notice that the timer handler does intrinsically the same thing.

/// 
///     This is the handler for the button
/// 
private void Button_Click( object sender, RoutedEventArgs e )
{
 //-- Here we provide a handle to our contextual event handler as the userstate, that will be called when the method completes
 //-- It matches the delegate type defined above.
 m_client.GetTimeAsync( new OnServiceCallComplete( ClickHandler ) );
}

As you can see here we issue the call to the GetTimeAsync service method, using the previously defined service client reference - and we pass in an instance of the delegate we want calling when the method is complete, in this case "ClickHandler".

Here is the aync completed handler for the async service call:

//-- This is the service complete handler
void GetTimeCompleted( object sender, GetTimeCompletedEventArgs e )
{
  //-- Here we check the userstate, up-cast to the service delegate if necessary, then execute it
  if( e.UserState != null )
  {
   OnServiceCallComplete callback = e.UserState as OnServiceCallComplete;
   callback( sender, e );
  }            
}

As you can see when this method gets called, we inspect the UserState property of the "GetTimeCompletedEventArgs" object that is passed to this method, upcast it to the "OnServiceCallComplete" delegate that we defined previously, and then execute it. Last, but not least here is the eventual target that will be called by the async completed method.

/// 
///     This method is triggered by clicking the button
/// 
void ClickHandler( object sender, EventArgs e )
{
 GetTimeCompletedEventArgs args = e as GetTimeCompletedEventArgs;
 txtCurrentTime.Text = string.Format( "Click : {0}", args.Result );
}

Here we upcast the event args to "GetTimeCompletedEventArgs", which allows us to query the Result property and assign it to the textblock in question. A similar process occurs with a timer (which you can see in the demo application). 

So in conclusion, the beauty of this approach is that we can add whatever event chaining we need, without having to resort to potentially risky and complex logic routing in the completed handler itself which in the process could threaten the stability of the application as a whole - and we also don't need to build complex event bus or similar to manage the marshalling of events & data for us.  It's clean from a programming perspective as it allows us to encapsulate our functionality in the areas that matter and re-use them where appropriate. 

The only caveats I would state with this approach:

  • If order is important, you are going to have to create your own event queue or similar to make sure that things fire in the order that they are required to do so
  • You could potentially get in some race conditions so more complex messaging may be required
  • And I haven't tested this with very complex application so performance, reliability etc will have to be tested to ensure that it scales.
Download the the Visual Studio 2008 solution here.  If you have any feedback I'd be very interested to hear it.  Cheers!

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5