次の方法で共有


Silverlight Async with Visual Studio 2012

A frequent complaint I hear from developers working on Silverlight applications for Microsoft Dynamics CRM is that they have to use asynchronous methods when communicating with the CRM web services. Asynchronous methods and callbacks are not particularly complex concepts or even challenging to program, but real complexity arises when you have a dependency chain of asynchronous methods, leading to high volumes of heavily indented code or scattered logic that is counter-intuitive to the intended logic of the application. With the upcoming release of Visual Studio 2012, this is no longer a problem!

UI Responsiveness

To ensure the UI remains responsive, Silverlight does not allow us to perform a call to the WCF endpoint whilst blocking the UI thread. This means when we perform a web service request, a thread is spawned to wait for the response. This is great for users, as it ensures their Silverlight applications do not lock up when web service queries are made. However, for developers, this can be a real headache. Multithreaded applications are far more complicated to write than single threaded ones. For one, developers need to worry about cross-thread object access; code run in a background thread needs to be dispatched back up to the UI thread before it can modify UI components. Secondly, developers are required to write callbacks to handle responses from the server. These quickly become unmanageable, especially when nested.

Asynchronous Methods & Callbacks

Typically when you retrieve data from CRM, you want to do something with the retrieved data. This could be as simple as counting the results of a query, or inspecting the value of a field. I can’t imagine a reason you would want to invoke the Retrieve or RetrieveMultiple operations and not want to inspect the returned value(s).

Let’s consider an example to demonstrate the differences between programming this synchronously and asynchronously. Let’s say you’re working with the WCF endpoint. Your requirement is to query CRM for all contacts in the system, and display their names to the user.

This is how it’s done in a synchronous way:

Code Snippet

  1. private void SynchronousExample(IOrganizationService service)
  2. {
  3.     //Build the query
  4.     var query = new QueryExpression
  5.     {
  6.         EntityName = "contact",
  7.         ColumnSet = new ColumnSet { Columns = new[] { "fullname" } }
  8.     };
  9.             
  10.     //Send it to the server and receive a response
  11.     var response = service.RetrieveMultiple(query);
  12.  
  13.     //Iterate across returned entities and output fullname
  14.     foreach (var contact in response.Entities)
  15.     {
  16.         Console.WriteLine(contact.Attributes[0]);
  17.     }
  18. }

 

However, we can’t do this synchronously in Silverlight, so we’d have to write a callback to specify what needs to happen when the response is received.

Code Snippet

  1. private void AsynchronousExample(IOrganizationService service)
  2. {
  3.     //Build the query
  4.     var query = new QueryExpression
  5.     {
  6.         EntityName = "contact",
  7.         ColumnSet = new ColumnSet { Columns = new ObservableCollection<string> { "fullname" } }
  8.     };
  9.  
  10.     //Send it to the server
  11.     service.BeginRetrieveMultiple(query, (result) => {
  12.         //Define a callback that indicates what to do when the response is recieved
  13.                 
  14.         //Capture the response
  15.         var response = ((IOrganizationService)result.AsyncState).EndRetrieveMultiple(result);
  16.  
  17.         //Dispatch back up to the UI thread to avoid thread permission exceptions
  18.         Dispatcher.BeginInvoke(new Action(
  19.             () => {
  20.                     //Iterate across results and output fullname
  21.                     foreach (var contact in response.Entities)
  22.                     {
  23.                         MessageBox.Show(contact.GetAttributeValue<string>("fullname"));
  24.                     }
  25.                 }
  26.             )
  27.         );
  28.  
  29.                 
  30.     }, service);
  31. }

 

So even with this simple example we can see there’s a lot of added complexity in the code. Not only is the code to send and receive the request more verbose, there’s also added complexity introduced as the response is executed on a different thread to the UI. Rather than using the IOrganizationService interface, we can use the events available to us on the OrganizationServiceClient, but the structure of the code is almost identical, with the same problems.

Taking this one step further, suppose we want to run a separate query against each of these contacts to retrieve some related information. In the asynchronous example we have to introduce another nested callback of a similar quantity. This quickly becomes unmanageable.

One approach to making this more manageable is to break the callback into a separate method. This approach goes some way to improving the maintainability of the code. However, it’s still quite verbose and we’ve still got the problem of having to dispatch back up to the UI thread in the response.

A better way

Writing code like this quickly hides the fundamental purpose of the code, making the logic very difficult to read. Wouldn’t it be great if we could write single threaded Silverlight applications without blocking the UI thread? Well now we can!

.NET 4.5 introduced new language keywords to overcome exactly this problem, and with Visual Studio 2012 we can use these keywords with Silverlight 5. These enhancements were first introduced as the Async CTP for .NET 4.0, and although the CTP is licenced for production use, it is not recommended. However, with the upcoming release of .NET 4.5, the keywords have been built into the language, and with the help of the Async Targetting Pack, we can use these features in Silverlight 5 applications.

I won’t go into huge detail on the inner workings of the new language features, rather a brief overview, but if you’re interested in some of the detail, I would highly recommend giving this video a watch.

The new async features are built on top of the TPL introduced in .NET 4.0, and introduce two new language constructs; async and await. Developers use async to indicate that a method runs asynchronously, and await to indicate that execution should pause until a task has been completed. Conveniently for Silverlight developers, this can all happen on the UI thread without having to block.

Setting It Up

The best way to demonstrate the benefits is to see it in action, but before we can do that we need to install the Async Targetting Pack. Begin by creating a new Silverlight application in Visual Studio
2012:

image

image

Then in the newly created project, select Tools -> Library Package Manager -> Package Manager Console

image

and in the Package Manager Console type the following and press enter:

Install-Package Microsoft.CompilerServices.AsyncTargetingPack

image

image

This only takes a few seconds to install, and once complete allows you to use the new language features within your application.

For this example, I have also configured the project to use the CRM SOAP endpoint as detailed in this SDK article.

Finally, I have also created a class of extension methods that enable the IOrganizationService methods to be used asynchronously. Create a new class in your project called AsyncExtensions.cs and insert the following code. Ensure the namespace matches the one that your service reference has.

Code Snippet

  1. using System;
  2. using System.Threading.Tasks;
  3.  
  4. namespace SilverlightApplication1.CrmSdk
  5. {
  6.     public static class AsyncExtensions
  7.     {
  8.         public static Task Associate(this IOrganizationService service, string entityName, System.Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities)
  9.         {
  10.             var tcs = new TaskCompletionSource<object>();
  11.  
  12.             service.BeginAssociate(entityName, entityId, relationship, relatedEntities, (asyncResult) =>
  13.             {
  14.                 try
  15.                 {
  16.                     ((IOrganizationService)asyncResult.AsyncState).EndAssociate(asyncResult);
  17.                     tcs.TrySetResult(null);
  18.                 }
  19.                 catch (Exception ex)
  20.                 {
  21.                     tcs.TrySetException(ex);
  22.                 }
  23.             }, service);
  24.  
  25.             return tcs.Task;
  26.         }
  27.  
  28.         public static Task<Guid> Create(this IOrganizationService service, Entity entity)
  29.         {
  30.             var tcs = new TaskCompletionSource<Guid>();
  31.  
  32.             service.BeginCreate(entity, (asyncResult) =>
  33.             {
  34.                 try
  35.                 {
  36.                     tcs.TrySetResult(((IOrganizationService)asyncResult.AsyncState).EndCreate(asyncResult));
  37.                 }
  38.                 catch (Exception ex)
  39.                 {
  40.                     tcs.TrySetException(ex);
  41.                 }
  42.             }, service);
  43.  
  44.             return tcs.Task;
  45.         }
  46.  
  47.         public static Task Delete(this IOrganizationService service, string entityName, System.Guid id)
  48.         {
  49.             var tcs = new TaskCompletionSource<object>();
  50.  
  51.             service.BeginDelete(entityName, id, (asyncResult) =>
  52.             {
  53.                 try
  54.                 {
  55.                     ((IOrganizationService)asyncResult.AsyncState).EndDelete(asyncResult);
  56.                     tcs.TrySetResult(null);
  57.                 }
  58.                 catch (Exception ex)
  59.                 {
  60.                     tcs.TrySetException(ex);
  61.                 }
  62.             }, service);
  63.  
  64.             return tcs.Task;
  65.         }
  66.  
  67.         public static Task Disassociate(this IOrganizationService service, string entityName, System.Guid entityId, Relationship relationship, EntityReferenceCollection relatedEntities)
  68.         {
  69.             var tcs = new TaskCompletionSource<object>();
  70.  
  71.             service.BeginDisassociate(entityName, entityId, relationship, relatedEntities, (asyncResult) =>
  72.             {
  73.                 try
  74.                 {
  75.                     ((IOrganizationService)asyncResult.AsyncState).EndDisassociate(asyncResult);
  76.                     tcs.TrySetResult(null);
  77.                 }
  78.                 catch (Exception ex)
  79.                 {
  80.                     tcs.TrySetException(ex);
  81.                 }
  82.             }, service);
  83.  
  84.             return tcs.Task;
  85.         }
  86.  
  87.         public static Task<OrganizationResponse> Execute(this IOrganizationService service, OrganizationRequest request)
  88.         {
  89.             var tcs = new TaskCompletionSource<OrganizationResponse>();
  90.  
  91.             service.BeginExecute(request, (asyncResult) =>
  92.             {
  93.                 try
  94.                 {
  95.                     tcs.TrySetResult(((IOrganizationService)asyncResult.AsyncState).EndExecute(asyncResult));
  96.                 }
  97.                 catch (Exception ex)
  98.                 {
  99.                     tcs.TrySetException(ex);
  100.                 }
  101.             }, service);
  102.  
  103.             return tcs.Task;
  104.         }
  105.  
  106.         public static Task<Entity> Retrieve(this IOrganizationService service, string entityName, System.Guid id, ColumnSet columnSet)
  107.         {
  108.             var tcs = new TaskCompletionSource<Entity>();
  109.  
  110.             service.BeginRetrieve(entityName, id, columnSet, (asyncResult) =>
  111.             {
  112.                 try
  113.                 {
  114.                     tcs.TrySetResult(((IOrganizationService)asyncResult.AsyncState).EndRetrieve(asyncResult));
  115.                 }
  116.                 catch (Exception ex)
  117.                 {
  118.                     tcs.TrySetException(ex);
  119.                 }
  120.             }, service);
  121.  
  122.             return tcs.Task;
  123.         }
  124.  
  125.         public static Task<EntityCollection> RetrieveMultiple(this IOrganizationService service, QueryBase query)
  126.         {
  127.             var tcs = new TaskCompletionSource<EntityCollection>();
  128.  
  129.             service.BeginRetrieveMultiple(query, (asyncResult) =>
  130.             {
  131.                 try
  132.                 {
  133.                     tcs.TrySetResult(((IOrganizationService)asyncResult.AsyncState).EndRetrieveMultiple(asyncResult));
  134.                 }
  135.                 catch (Exception ex)
  136.                 {
  137.                     tcs.TrySetException(ex);
  138.                 }
  139.             }, service);
  140.  
  141.             return tcs.Task;
  142.         }
  143.  
  144.         public static Task Update(this IOrganizationService service, Entity entity)
  145.         {
  146.             var tcs = new TaskCompletionSource<object>();
  147.  
  148.             service.BeginUpdate(entity, (asyncResult) =>
  149.             {
  150.                 try
  151.                 {
  152.                     ((IOrganizationService)asyncResult.AsyncState).EndUpdate(asyncResult);
  153.                     tcs.TrySetResult(null);
  154.                 }
  155.                 catch (Exception ex)
  156.                 {
  157.                     tcs.TrySetException(ex);
  158.                 }
  159.             }, service);
  160.  
  161.             return tcs.Task;
  162.         }
  163.     }
  164. }

 

Using the New Features

Now that my project is setup, I can start using the new features to interact with CRM. Here’s the Silverlight example from above rewritten to take advantage of the new keywords:

Code Snippet

  1. private async void AsynchronousExample(IOrganizationService service)
  2. {
  3.     //Build the query
  4.     var query = new QueryExpression
  5.     {
  6.         EntityName = "contact",
  7.         ColumnSet = new ColumnSet { Columns = new ObservableCollection<string> { "fullname" } }
  8.     };
  9.  
  10.     //Send it to the server and await the response
  11.     var response = await service.RetrieveMultiple(query);
  12.  
  13.     //Iterate across the results and output the fullname of each contact
  14.     foreach (var contact in response.Entities)
  15.     {
  16.         MessageBox.Show(contact.GetAttributeValue<string>("fullname"));
  17.     }
  18. }

 

There are several points to notice here. Firstly, notice the use of the async keyword in the method signature, this indicates that this method runs asynchronously and allows us to use the await keyword within the method body. The await keyword is used to await the results of the RetrieveMultiple extension method.

More interestingly though, there are several things to notice that are not here. Firstly, there no callbacks and all of the logic is defined within the same method body. This makes the code much more readable. Secondly, because execution is resumed on the UI thread when service.RetrieveMultiple returns a result, we do not have to dispatch back up to the UI to display the results to the user.

Also note there is nothing special required to invoke this method. Although the method is marked as asynchronous, the consuming code does not need to be aware of this:

Code Snippet

  1. public MainPage()
  2. {
  3.     InitializeComponent();
  4.  
  5.     var service = SilverlightUtility.GetSoapService();
  6.     AsynchronousExample(service);
  7. }

 

Under The Hood

So what’s going on in the extension method that we called? For a method to be awaitable, it needs to return one of three types; void, Task or Task<T>, so to await CRM web service calls we need to wrap the available methods up into a new method that returns an awaitable type. For example, this is the RetrieveMultiple extension method used in the previous example:

Code Snippet

  1. public static Task<EntityCollection> RetrieveMultiple(this IOrganizationService service, QueryBase query)
  2. {
  3.     var tcs = new TaskCompletionSource<EntityCollection>();
  4.  
  5.     service.BeginRetrieveMultiple(query, (asyncResult) =>
  6.     {
  7.         try
  8.         {
  9.             tcs.TrySetResult(((IOrganizationService)asyncResult.AsyncState).EndRetrieveMultiple(asyncResult));
  10.         }
  11.         catch (Exception ex)
  12.         {
  13.             tcs.TrySetException(ex);
  14.         }
  15.     }, service);
  16.  
  17.     return tcs.Task;
  18. }

 

The TaskCompletionSource<T> object is used to create a task of the appropriate type, which allows us to await the results of the standard BeginRetrieveMultiple method. Also note that error handling is also catered for, so if the web service throws an exception the awaiting code will be notified.

Some Considerations

This feature does require Visual Studio 2012, which is due for launch on September 12th 2012. Unfortunately, the excellent CRM 2011 Developer Toolkit does not currently work with Visual Studio 2012, which is worth bearing in mind if you’re contemplating a switch.

This feature also requires Silverlight 5. Silverlight 5 support within CRM is not straight forward; it is not supported when embedding directly on to forms, but wrapping your SL5 XAP resources inside an HTML web resource is supported.

Conclusion

So there we have it, the new async features in .NET 4.5 combined with the Async Targeting Pack provide Silverlight developers with the tools to transform their asynchronous code back to readable, maintainable methods, by removing the need to litter business logic with callbacks and UI dispatching code. We’ve seen how to install and use the basic features of the language enhancements, and have used a class of extension methods that enable us to work with the CRM WCF web service with the new async features.

Visual Studio 2012 RTM is now available to download on MSDN, and I would highly recommend taking a look!

Dave

Dave Burman Consultant Microsoft Consulting Services UK

View my bio

Comments

  • Anonymous
    February 28, 2013
    Dave, isn't "RetrieveMultiple" asynchronous by default when the service reference is made to the CRM 2011 WCF endpoint from a Silverlight project?

  • Anonymous
    March 01, 2013
    Hi King, yes all requests are asynchronous from Silverlight, and there's no way to change this behaviour.  The techniques in this post may suggest synchronous calls are used, but really it's just abstracting the underlying asynchronous mechanism away from the developer.

  • Anonymous
    March 18, 2013
    Hey mate, I've just used your Extension class on a new Silverlight Project I'm working on. Awesome stuff by the way! I have noticed one slight problem with the Update method. It never completes the Task! So when you await on it you have to wait and looooooong time. I'm not sure if the change I have made is the best way to complete the task. Ideally I would like to create a TaskCompletionSource that isn't templated. Just note  tcs.TrySetResult(null); which completes the task and returns control back to awaiting methods public static Task Update(this IOrganizationService service, Entity entity)        {            var tcs = new TaskCompletionSource<object>();            service.BeginUpdate(entity, (asyncResult) =>            {                try                {                    ((IOrganizationService)asyncResult.AsyncState).EndUpdate(asyncResult);                    tcs.TrySetResult(null);                }                catch (Exception ex)                {                    tcs.TrySetException(ex);                }            }, service);            return tcs.Task;        }

  • Anonymous
    March 19, 2013
    Hi David Great spot!  This actually applies (applied - now fixed) to all of the methods that wrap SDK methods with no return type - Update, Delete, Associate and Disassociate. The fix you've outlined looks like the correct one to me, and is the one I've implemented.  Discussion following this feedback item (social.msdn.microsoft.com/.../f6ee1462-d3ef-40ed-801a-76bdfaf01e1e) explains why there's no non-templated class available, and how you can implement one yourself if you need to. Thanks for the valuable feedback!