Friday, February 17, 2012

Mock Asynchronous Web Services (Silverlight)

 

Adding a Service Reference in a silverlight project will only generate asynchronous service methods. It is fairly well known how create a mock service for a normal service reference, in this post I will run through an easy way to do this for an asynchronous service. This should enable you to test your silverlight apps better (or develop them on the train without wi-fi in my case).

Example Service

Let’s use a really simple service definition which is created when you add a new WCF Service Application project:

    [ServiceContract]

    public interface IService

    {

        [OperationContract]

        string GetData(int value);

    }

    public class Service : IService

    {

        public string GetData(int value)

        {

            return string.Format("You entered: {0}", value);

        }

    }

and add a service reference to this in a silverlight project. If you now look at the Reference.cs generated we see the following IService definition:

 

 

The Code Behind

 

    public interface IService

    {

 

        System.IAsyncResult BeginGetData(int value, System.AsyncCallback callback, object asyncState);

 

        string EndGetData(System.IAsyncResult result);

    }

 

It has generated a BeginGetData and EndGetData pair of methods following the async pattern. The client proxy generated does implement this interface, however these methods are hidden and some new public methods and events are added (I’ve cut out most of the irrelevant stuff to make it clearer):

 

 

    public partial class ServiceClient : ClientBase<IService>, IService

    {

        public ServiceClient(Binding binding, EndpointAddress remoteAddress) :

            base(binding, remoteAddress)

        {

        }

 

        [EditorBrowsableAttribute(EditorBrowsableState.Advanced)]

        string IService.EndGetData(IAsyncResult result) { /*snip*/ }

 

        public void GetDataAsync(int value) { /*snip*/ }

 

        public event EventHandler<GetDataCompletedEventArgs> GetDataCompleted;

 

        [EditorBrowsableAttribute(EditorBrowsableState.Advanced)]

IAsyncResult IService.BeginGetData(int value, AsyncCallback callback, object asyncState) { /*snip*/ }

    }

 

We can see that the actual interface methods have been marked EditorBrowsableState.Advanced meaning you won’t see them in intellisense. The ‘visible’ members, GetDataAsync and GetDataCompleted exist only on the proxy class and it is these you normally use when calling the service in your client, something like this:

            ServiceClient client = new ServiceClient(new BasicHttpBinding(), new EndpointAddress("http://service.com/service.svc"));

            client.GetDataCompleted += new EventHandler<GetDataCompletedEventArgs>

                (

                    (sender,e) => Console.WriteLine(e.Result)

                );

 

            client.GetDataAsync(10);

 

 

It is this method and event that we need to implement in our mock.

 

 

Mocktastic

 

The easiest way I have found to do this is to first pull out the relevant members into an adapter interface:

 

    public interface IServiceAdapter

    {

        void GetDataAsync(int value);

        event System.EventHandler<GetDataCompletedEventArgs> GetDataCompleted;

    }

 

And implement a service adapter that takes an instance of the proxy and passes through the method calls and the event subscriptions by overriding the default add and remove accessors for the event:

 

    public class ServiceAdapter : IServiceAdapter

    {

        ServiceClient client;

 

        public ServiceAdapter(ServiceClient client)

        {

            this.client = client;

        }

 

        public void GetDataAsync(int value)

        {

            client.GetDataAsync(value);

        }

 

        public event EventHandler<GetDataCompletedEventArgs> GetDataCompleted

        {

            add

            {

                client.GetDataCompleted += value;

            }

            remove

            {

                client.GetDataCompleted -= value;

            }

        }

    }

 

 

 

Add another adapter which implements whatever mock functionality you want:

 

    public class MockServiceAdapter : IServiceAdapter

    {

        int mockData;

        public MockServiceAdapter(int mockData)

        {

            this.mockData = mockData;

        }

 

        public void GetDataAsync(int value)

        {

            //if you need to simulate a long running event spin up a thread call this event after a wait

            GetDataCompleted(null, new GetDataCompletedEventArgs(new object[] { mockData }, null, false, null));

        }

 

        public event EventHandler<ServiceReference.GetDataCompletedEventArgs> GetDataCompleted;

    }

 

 

And finally add a service factory interface and two implementations so we have a consistent way for the client to get an instance of the mock or real service:

 

    public interface IServiceAdapterFactory

    {

        IServiceAdapter Create(Binding binding, EndpointAddress endpoint);       

    }

 

    public class ServiceAdapterFactory : IServiceAdapterFactory

    {

        public IServiceAdapter Create(Binding binding, EndpointAddress endpoint)

        {

            return new ServiceAdapter(new ServiceClient(binding, endpoint));

        }

    }

 

    public class MockServiceAdapterFactory : IServiceAdapterFactory

    {

        public IServiceAdapter Create(Binding binding, EndpointAddress endpoint)

        {

            int mockData = 10;

            return new MockServiceAdapter(mockData);

        }

    }

 

The Result

Now we can pass the appropriate instance of IServiceAdapterFactory into our silverlight model and replace all uses of the ServiceClient with a call to the factory’s Create method:

 

    public partial class MainPage : UserControl

    {

        bool useMock = true;

 

        IServiceAdapterFactory serviceFactory;

 

        public MainPage()

        {

            InitializeComponent();

 

            if (useMock)

            {

                serviceFactory = new MockServiceAdapterFactory();

            }

            else

            {

                serviceFactory = new ServiceAdapterFactory();

            }

 

            IServiceAdapter client = serviceFactory.Create(new BasicHttpBinding(), new EndpointAddress("http://service.com/service.svc"));

            client.GetDataCompleted += new EventHandler<GetDataCompletedEventArgs>

                (

                    (sender,e) => Console.WriteLine(e.Result)

                );

 

            client.GetDataAsync(10);

        }

    }

1 comment: