In my previous post, Windows Phone 7 and WCF Don’t Play Nicely, I talked about how you could overcome some of the shortcomings of working with the generated WCF proxy classes in Windows Phone 7. I wasn’t particularly happy with the workaround I posted so I gave it some more thought and decided to go back a step.
If we look at our WCF service that we’re connecting to it is made up of two parts: the interface definition and then the service implementation. In our case they’re very simple:
[ServiceContract] public interface IService1 {
[OperationContract]
int Step1();
[OperationContract]
int Step2();
}
public class Service1 : IService1{
public int Step1() {
return 1;
}
public int Step2() { return 2;
}
}
When we generate the service reference in our Windows Phone 7 application we end up with the following methods and events:
void Step1Async();
void Step2Async();
public event EventHandler<Step1CompletedEventArgs> Step1Completed;
public event EventHandler<Step2CompletedEventArgs> Step2Completed;
And just to remind you, we want to get to code that looks as simple as:
public void RunInBackground(Action callback) { var proxy = new Service1Proxy();
var r1 = proxy.Step1();
var r2 = proxy.Step2();
}
Here’s the trick – we’re going to start with another base class that is going to do the synchronous wrapping of our service method request. Essentially this follows the same pattern as what you would have seen with the ServiceHelper class from my previous post. However, in this case the parameter to the Invoke method is an Expression that references the asynchronous service call (eg “Invoke<Step1CompletedEventArgs>(()=>Proxy.Step1Async())” ) . From this expression we can pull out the name of the method to be invoked (eg “Step1Async”) and use that to determine the name of the corresponding event that will get raised when the service request completes (eg “Step1Completed”). The event name can be used to get a reference to the event itself, which we can then wire an event handler to in the same way that the ServiceHelper worked.
public class SyncClientBase<TClientBase, TServiceInterface> : IDisposable
where TClientBase : ClientBase<TServiceInterface>, TServiceInterface, new()
where TServiceInterface : class {
public TClientBase Proxy { get; private set; }
public SyncClientBase() {
Proxy = new TClientBase();
}
private static object downloadLock = new object();
private static AutoResetEvent downloadWait = new AutoResetEvent(false);
public TResult Invoke<TResult>(Expression<Action> asyncMethod)
where TResult : AsyncCompletedEventArgs { var memberExpression = asyncMethod.Body as MethodCallExpression;
var method = memberExpression.Method;
var eventName = method.Name.Replace("Async","Completed");
var evt = Proxy.GetType().GetEvent(eventName);
lock (downloadLock) { TResult data = default(TResult); EventHandler<TResult> handler = (s, e) => { data = e;
downloadWait.Set();
};
evt.AddEventHandler(Proxy,handler);
method.Invoke(Proxy, new object[] {});
downloadWait.WaitOne();
evt.RemoveEventHandler(Proxy, handler);
if (data.Error != null) {
throw data.Error;
}
return data;
}
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing){ if (disposing) {
if (Proxy != null) {
downloadWait.Set(); Proxy = null;
}
}
}
}
But how does this complex bit of code get us any closer to where we want to be. Well firstly this code is boilerplate code which doesn’t need to be modified for each new service reference we add. However, we do need to create another proxy class which inherits from SyncClientBase for each of our service references:
public class Service1Proxy: SyncClientBase<Services.Service1Client,
ServiceTester.Services.IService1>,
DemoService.IService1 {
public int Step1() { return base.Invoke<Step1CompletedEventArgs>(()=>Proxy.Step1Async()).Result; }
public int Step2() {
return base.Invoke<Step2CompletedEventArgs>(()=>Proxy.Step2Async()).Result;
}
}
There are some points to note about this proxy:
– Service1Proxy inherits from SyncClientBase where the type arguments are Service1Client, which is the proxy that is generated when you do Add Service Reference, and IService1, which is also generated when you add the service reference.
– Service1Proxy implements DemoService.IService1 which is the original service interface definition, not Services.IService1 which is generated when you Add Service Reference. You’ll need to add this file to your Windows Phone 7 application (if you add it as a link then when you change your service definition it will automatically be updated in your Windows Phone 7 project).
– For each method defined in the DemoService.IService1 the implementation follows the same pattern:
public int Step1(){ return base.Invoke<Step1CompletedEventArgs>(()=>Proxy.Step1Async()).Result; }
>> “Step1CompletedEventArgs” – This is the type parameter for the event that will be raised when the service request completes. In the case of Step1Async the corresponding event is Step1Completed which is of type EventHandler<Step1CompletedEventArgs>
>> “() => Proxy.Step1Async()” – This is an Action delegate that invokes the service request you want to call. In this case the Step1Async method is going to be called.
>> “.Result” – This just extracts the actual result data out of the event args and the type should match the return type of the method. In this case the Step1 method returns an integer so the Result property returns an integer value.
Wrapping this all up we get the following simple code to run on our background thread to invoke a sequence of service requests:
public void RunInBackground(Action callback) {
using (var proxy = new Service1Proxy()) {
var r1 = proxy.Step1();
var r2 = proxy.Step2();
}
}
All you have to do is make sure the Service1Proxy class stays up to date (ie add/modify/delete appropriate methods to make sure it matches IService1) and you have a nice wrapper for calling your services in a synchronous manner on a background thread without blocking the UI thread at all.