Wednesday, March 17, 2010

The Easy Way To Synchronously Call WCF Services In Silverlight

If you're reading this you probably know why Microsoft tries to force the use of asynchronous WCF service calls in Silverlight. If not and you cannot infer based on your knowledge of threading I suggest you read up on the matter.

While Microsoft may have a strong case for their decision, it can lead to bloated, messy code when several service calls are chained together. The following extension method, through heavy use of reflection, allows for synchronous service calls. Remember, do not block the UI thread.

ICommunicationObjectExtension.cs

using System;
using System.Net;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.ServiceModel;

namespace SyncWCFCalls
{
public static class ICommunicationObjectExtension
{
public static TEventArgs SynchronousCall<TEventArgs>(this ICommunicationObject Client,
string Method, params object[] Parameters)
where TEventArgs : EventArgs
{
Type clientType = Client.GetType();

string methodName = Method + "Async";
string completedName = Method + "Completed";

MethodInfo[] methods = null;

try
{
methods = clientType.GetMethods().Where(x =>
x.Name == methodName).ToArray();
}
catch { }

if (methods == null || methods.Length == 0)
{
throw new MissingMethodException(methodName + " not found");
}

MethodInfo method = null;

try
{
method = methods.Single(x =>
x.GetParameters().Length == Parameters.Length);
}
catch { }

if (method == null)
{
throw new MissingMethodException(methodName +
" parameter count mismatch");
}

EventInfo completedEvent = null;

try
{
completedEvent = clientType.GetEvent(completedName);
}
catch { }

if (completedEvent == null)
throw new MissingMemberException(completedName + " not found");

ManualResetEvent reset = new ManualResetEvent(false);

EventHandler<TEventArgs> completedHandler = null;

TEventArgs args = null;

completedHandler = (s, e) =>
{
completedEvent.RemoveEventHandler(Client, completedHandler);

args = e;

reset.Set();
};

completedEvent.AddEventHandler(Client, completedHandler);

method.Invoke(Client, Parameters);

reset.WaitOne();

return args;
}
}
}


Asynchronous WCF Service Call

void AsyncTest()
{
CalculatorServiceClient client = new CalculatorServiceClient();

Guid addState1 = Guid.NewGuid(), addState2 = Guid.NewGuid();

client.AddCompleted += (s, e) =>
{
if ((Guid)e.UserState == addState1)
{
// Do something with AddCompletedEventArgs
client.AddAsync(e.Result, 10, addState2);
}
else if ((Guid)e.UserState == addState2)
{
// Do something with AddCompletedEventArgs
client.SubtractAsync(e.Result, 5);
}
};

client.SubtractCompleted += (s, e) =>
{
// Do something with SubtractCompletedEventArgs
};

client.AddAsync(1, 1, addState1);
}


Synchronous WCF Service Call

void SyncTest()
{
CalculatorServiceClient client = new CalculatorServiceClient();

ThreadPool.QueueUserWorkItem(x =>
{
AddCompletedEventArgs addArgs1 =
client.SynchronousCall<AddCompletedEventArgs>("Add", 1, 1);
// Do something with AddCompletedEventArgs

AddCompletedEventArgs addArgs2 =
client.SynchronousCall<AddCompletedEventArgs>("Add",
addArgs1.Result, 10);
// Do something with AddCompletedEventArgs

SubtractCompletedEventArgs subtractArgs =
client.SynchronousCall<SubtractCompletedEventArgs>("Subtract",
addArgs2.Result, 5);
// Do something with SubtractCompletedEventArgs
});
}



Updated 3/21/2010

20 comments:

  1. ... and this helps us how? What's the difference between your approach to queue the work item, verse just a lambda expression?

    I'm sorry, it just doesn't make sense to me to do all of that plumbing and overhead just to make the call appear synchronous. To me, that seems more bloated than just learning to program to the asynchronous model.

    Can you elaborate more on the advantages of your approach?

    ReplyDelete
  2. You seem to be under the impression that it is either/or. Perhaps the issue here is my sample; it was a minimalistic attempt at showing usage. In the real world I would not use this unless the need to call several service methods in a specified order arose. When that need does arise (and oh how it does) the asynchronous model leads to an excessive number of methods whether they be anonymous or otherwise. To those of us who can be trusted not to block the UI thread being forced to take this approach is borderline insulting. Synchronous service calls are allowed on every other .NET platform; Silverlight should be no different.

    ReplyDelete
  3. This can be done if you call from UI itself, if you call from viewmodel would you able to achieve the same? I tried it and didnt work in the way it should.

    could you please give me the pointers?

    Thank you

    ReplyDelete
  4. This should work in your ViewModel. Could you post your code?

    ReplyDelete
  5. I've actually been looking for something like this, but let me ask, does it really work? The example usage that you demonstrated works correctly, but really doesn't solve the problem. If you queue the call into the background thread, the method will advance, and the call will not block until it is completed- the behavior of the service call is similar to the default behavior, just without needing to wire the event. If you make the call to SynchronousCall without queuing it into the background (from the UI thread), the reset.WaitOne() call will block indefinitely because the invoke is performed on the same thread. Am I missing something?

    ReplyDelete
  6. You are correct, this will not work on the UI thread. However, it will block any other thread. For a small number of service calls this is not very useful, but for several using blocking calls on a worker thread along with a single callback eliminates a substantial amount of event related boilerplate code.

    ReplyDelete
  7. I gotcha now ... and I understand! Thanks for your patience. I'm working on a blog post myself now that I understand the issue. My confusion was with terminology ... I'm thinking you're trying to make an asynchronous operation look synchronous, which is a little bit here but the reality is you are trying to make it sequential ... i.e. instead of chaining a dozen methods or having a thousand lambda expressions, how do we clean up the code?

    This is one way but I think a more generic coroutine solution is even better. I'll put something together and post it, and thanks again for the post here, been a learning process.

    ReplyDelete
  8. Take a look to my own system :

    http://www.codeproject.com/KB/silverlight/SyncCallInSilverlight.aspx

    synchronous and silverlight is not a myth :)

    ReplyDelete
  9. I am using silverlight enabled WCF service and operational contract method is accesed in webapplication with the following code.

    service.GetDataCompleted += new EventHandler(service_GetDataCompleted);
    service.GetDataAsync(param1, param2);
    //want to Do something with results

    But here i am unable to access results immediatly.

    ReplyDelete
  10. Thanks very much for this John. I love it. I searched high and low for encapsulation of this functionality, and yours was the best, particularly in how it leverages the generated service proxy. I simply add a Dispatcher.BeginInvoke() to the threaded task, and I'm doing synchronous service calls from the Silverlight UI thread!

    ReplyDelete
  11. This is great, also love the neat encapsulation. But what I don't undertsand how I use BeginInvoke to make a synchronous WCF call from the UI Thread? In the example, by disconnecting the synchronous call from the UI thread using the ThreadPool you are in effect are making it asynchronous again - by the time the WCF call returns, the calling process in the UI Thread completed. How do I block the UI Thread until after the data I need has been returned remotely from WCF?

    ReplyDelete
  12. This is the best Synchronously WCF call method!
    Can you change it to use WCF RIA Services?

    ReplyDelete
  13. Do you have a sample which calls a web service method and returns the result?

    ReplyDelete
  14. Hi I have Implement above code but i get following error can you please help me?

    An error occurred while trying to make a request to URI 'http://localhost/SilverlightSessionWeb/SessionService.svc'. This could be due to attempting to access a service in a cross-domain way without a proper cross-domain policy in place, or a policy that is unsuitable for SOAP services. You may need to contact the owner of the service to publish a cross-domain policy file and to ensure it allows SOAP-related HTTP headers to be sent. This error may also be caused by using internal types in the web service proxy without using the InternalsVisibleToAttribute attribute. Please see the inner exception for more details.

    ReplyDelete
  15. where do I place the ICommunicationObjectExtension class? on the web project or silverlight?
    I have a IService page, do I add it to there?

    ReplyDelete
  16. Great work!!!! Thanks a lot!!!

    ReplyDelete
  17. Hi,
    It's not doing synchronous call. Where to place ICommunicationObjectExtension class? do you have sample?
    What i am missing?

    ReplyDelete
  18. Can we use this approach in sequential httpwebrequest in Silverlight. I have four httpwebrequests and need to call them in sequential order without blocking UI thread. Do you have any idea about it.

    ReplyDelete