Thursday, February 23, 2012

Mock Asynchronous Web Services - Part 2

 

Following on from part 1, I will show how to mock out an synchronous service that has the BeginMethod and EndMethod access pattern (the IAsyncResult pattern) rather than the MethodAsync and OnMethodCompleted event variety (the Event based pattern). Also I will show how to introduce a delay to your asynchronous response, something we glossed over last post. This is fairly important as the whole point of using an async service is your app should be doing something else whilst waiting for the response. Eg. in silverlight you might have waiting spinners showing and various commands disabled until the service returns but the rest of the UI should remain responsive.

Before looking at that I just wanted to run through the some advantages of mocking out all your web service dependencies:

Unit testing – If you are unit testing bits of your app (normally the Model and maybe the View Model for silverlight) then it is far easier when you have control over the exact data returned. I’ve written and seen plenty of tests that rely on specific sets of data in a ‘real’ system and it is a pain to setup and rather brittle.

Manual testing of specific conditions – even if you are just manually testing your app there will be various conditions that are quite hard to replicate without a mock service. You can easily simulate various exceptions being thrown by the web service, odd sets of data being returned, timeouts etc… If you are having to setup data in a third party system to cover all these cases then this will take far longer than creating a few mock services will.

Off-line development – It is useful to be able to develop your app without needing various other systems to be up and running. For example the silverlight app I have been doing recently calls Dynamics CRM, Sharepoint and a custom web service, you don’t want all these running on your dev machine.

 

Anyway, on to creating a mock for a service that uses the standard Begin / End async pattern.

Firstly, exactly like in the last post, we pull out the relevant methods from the generated Reference.cs file into an adapter interface (you may recognise this as the signature of the Dynamics CRM service method that takes fetchXml):

    public interface ICrmServiceAdapter

    {

        IAsyncResult BeginRetrieveMultiple(QueryBase query, AsyncCallback callback, object asyncState);

        EntityCollection EndRetrieveMultiple(IAsyncResult result);

    }

 

 

and then implement a ‘pass through’ version for the real service:

 

    public class CrmServiceAdapter : ICrmServiceAdapter

    {

        IOrganizationService service;

        public CrmServiceAdapter(IOrganizationService service)

        {

            this.service = service;

        }

 

        public IAsyncResult BeginRetrieveMultiple(QueryBase query, AsyncCallback callback, object asyncState)

        {

            return service.BeginRetrieveMultiple(query, callback, asyncState);

        }

 

        public EntityCollection EndRetrieveMultiple(IAsyncResult result)

        {

            return service.EndRetrieveMultiple(result);

        }

    }

 

 

Then we need to create a mock implementation of this adapter interface. The key realisation here is that you need to create your own IAsyncResult object to return from the Begin method. This interface defines a few important properties:

 

AsyncState – this will hold the data that lets the EndRetrieveMultiple method know what mock data it should be returning (it could store the mock data itself)

AsyncWaitHandle – this is so consumers can await the completion of the operation

IsCompleted – this should indicate whether the operation has completed.

 

Here is the implementation of this interface we will use:

 

    private class AsyncResult : IAsyncResult

    {

        private WaitHandle waitHandle = new ManualResetEvent(false);

 

        public bool IsCompleted

        {

            get { return waitHandle.WaitOne(0); }

        }

 

        private Dictionary<string, object> state;

 

        public AsyncResult(Dictionary<string, object> state) { this.state = state; }

 

        public object AsyncState { get { return state; } }

 

        public WaitHandle AsyncWaitHandle { get { return waitHandle; } }

 

        public bool CompletedSynchronously { get { return false; } }

    }

 

We use a ManualResetEvent as the waithandle. This will allow us to signal that the operation has completed by calling the Set() method. The IsCompleted property just checks if this has been signalled yet by waiting for it with a timeout of 0.

 

Now our MockAdapter:

 

 

    public class CrmServiceMockAdapter : ICrmServiceAdapter

    {

        private class AsyncResult : IAsyncResult

        {

            /* snip - see above*/

        }

 

        const int maxResponseDelay = 5000;

 

        private void invokeResponse(Action action)

        {

            ThreadPool.QueueUserWorkItem(new WaitCallback( _ =>

                {

                    Thread.Sleep(new Random().Next(maxResponseDelay));

                    action();                   

                }));

        }

 

        public IAsyncResult BeginRetrieveMultiple(Xrm.Silverlight.Sdk.QueryBase query, AsyncCallback callback, object asyncState)

        {

            /* snip - set state according to the input so we know what to return from the End method */

            IAsyncResult asr = new AsyncResult(state);

            Action responseCompleteAction = () =>

            {

                ((ManualResetEvent)asr.AsyncWaitHandle).Set();

                callback(asr);

            };

 

            invokeResponse(responseCompleteAction);

            return asr;

        }

 

        public Xrm.Silverlight.Sdk.EntityCollection EndRetrieveMultiple(IAsyncResult result)

        {

            /* snip - return some mock data according to the contents of result.AsyncState */

        }

    }

  

Note how we queue up a work item on the ThreadPool to do the following (this is the bit you need to add to the code in the last post to simulate a long running service call):

 

1) Delay for a random amount of time

2) Set the ManualResetEvent to indicate completion

3) Call the callback function

 

Note Random is not thread safe so don’t be tempted to make this a member of the class.

 

 

And that pretty much covers it. Finish by creating a factory interface and two implementations exactly like in the last post and you should be up a running with a freshly minted mock CRM service:

var crmService = crmServiceFactory.Create();

crmService.BeginRetrieveMultiple(fetchXml, asr =>

{

var result = crmService.EndRetrieveMultiple(asr);

/*snip - do whatever with your results */

}, null);

 

Are you sure you want to use IAsyncResult?

It’s worth noting that using IAsyncResult async when developing from scratch is only really recommended in the following situations (taken from http://msdn.microsoft.com/en-us/library/ms228966.aspx):

 

  • Only expose the IAsyncResult pattern when you specifically require support for WaitHandle or IAsyncResult objects.

  • Only expose the IAsyncResult pattern when you have an existing API that uses the IAsyncResult pattern.

  • If you have an existing API based on the IAsyncResult pattern, consider also exposing the event-based pattern in your next release.

  • Only expose IAsyncResult pattern if you have high performance requirements which you have verified cannot be met by the event-based pattern but can be met by the IAsyncResult pattern.

 

If you do have an existing service that uses this pattern and you would rather use the Event based pattern then you could easily write an adapter interface to convert from one to the other. This is pretty much what is done for you when you generate an async service reference in .Net 3.5 and above, although for some reason it only adds the event based methods to the Client class rather than the generated interface.

 

No comments:

Post a Comment