Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Async/Await in WPF to invoke WCF with Busy Indicator and Abort Feature

0.00/5 (No votes)
30 Nov 2015 1  
Implement WCF web service access from WPF with busy indicator and premature cancellation feature without using delegates, background worker, or separate event procedure

 

Introduction 

If you have programmed Silverlight, you have noticed that the web service access inherently in Silverlight is asynchronous.  On the other hand, in WPF the default model is synchronous.  In synchronous mode, while the round-trip to the server is made to fetch data, your UI will remain locked up and this might give your users an impression that the application has hung up.  Some users might jump around the window and click multiple times in an effort to make the application respond.  Some may "end-task" the process by hitting Ctrl+Alt+Delete.

Fortunately, .NET 4.5 introduced two keywords async and await, which magically convert a method into being asynchronous.  Within the body of that method, you can call a long-running process like a web service by prefixing the call with await keyword.  Every line of code that comes after this will be made to wait until the execution of the operation is finished.  During the time, the control is transferred back to the original caller of the async method.  Once the long-running process completes execution, the rest of the lines that come after the await method take over and complete their execution.

This article is for those who have programmed this kind of situation in the past on Winforms/WPF using background worker component or using another thread or even the combination of Async methods generated by the web service proxy plus operation-completed events.   The feature introduced in .NET 4.5 gives the developers a much better and cleaner alternative to handling event procedures, etc., and manage the operations at a single place - for example the click event of a button.

Background 

While designing a WPF + WCF application in our organization, it was our policy that the application should be user-friendly and should remain responsive as far as possible even while it is busy processing something under the hood.  We decided to make use of the async/await capabilities of .NET framework 4.5 so that we could avoid using background worker for implementing asynchrony, which used to be what we did during the ASMX web service days.  To get the similar functionality to busy indicator present in Silverlight toolkit, we made use of the Busy Indicator available as part of Extended WPF Toolkit from Codeplex.

Using the code 

The example shown here has two projects:

  1. A WCF service application project, which contains one method (GetBooks) to retrieve the information of Books and their authors.
  2. A WPF client application that consumes the service.  The WPF application should be of .NET 4.5 or else you will not be able to make use of async/await keywords.

Requirements:

  1. Visual Studio 2012  
  2. Extended WPF Kit downloaded and added to the Toolbox.

Though a complete working example has been attached as a zip file along with the article, I would brief on the main steps involved:

Step I:  Create WCF Service Application

Open Visual Studio 2012 and create a WCF service application and name it as MyService. In the solution explorer, right-click the IService1.cs file and rename it to IBookService.cs.  Visual Studio will ask you whether you want to rename all references.   Choose Yes.  Now, right click the Service1.svc file and rename it BookService.svc.  Open the BookService.svc file, right-click the word Service1, choose Refactor => Rename it to BookService.  This will make sure that Visual Studio correctly renames all references of service1 to BookService.

Delete/modify all the code that is inside inside both IBookService.cs, and make it look like following:  

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
 
namespace MyService
{
    [ServiceContract]
    public interface IBookService
    {
 
        [OperationContract]
        ObservableCollection<Book> GetBooks();
 
    }
 

    [DataContract]
    public class Book
    {
        [DataMember]
        public int BookId { get; set; }
 
        [DataMember]
        public string BookName { get; set; }
 
        [DataMember]
        public string Author { get; set; }
    }
}

Now, delete/modify all the code that is inside inside both BookService.cs, and make it look like following:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
 
namespace MyService
{
    public class BookService : IBookService
    {
 
        public ObservableCollection<Book> GetBooks()
        {
 
            //This lines simulates the lag in roundtrip to Server.
            System.Threading.Thread.Sleep(5000); //puts 5-second lag.

 
            //Create a list and convert it into an observable collection and return back to the client.
            return new ObservableCollection<Book>(
                new List<Book>
                {
                    new Book{ BookId = 1, BookName="Learning C#", Author = "Nejimon CR"},
                    new Book{ BookId = 2, BookName="Introuction to ADO.NET", Author = "Nejimon CR"},
                    new Book{ BookId = 3, BookName="Lambda Made Easy", Author = "Nejimon CR"},
                    new Book{ BookId = 4, BookName="Robinson Crusoe", Author = "Daniel Defoe"},
                    new Book{ BookId = 5, BookName="The White Tiger", Author = "Aravind Adiga"},
                    new Book{ BookId = 6, BookName="The God of Small Things", Author = "Arunthati Roy"},
                    new Book{ BookId = 7, BookName="Midnight's Children", Author = "Salman Rushdie"},
                    new Book{ BookId = 8, BookName="Hamlet", Author = "William Shakespeare"},
                    new Book{ BookId = 9, BookName="Paradise Lost", Author = "John Milton"},
                    new Book{ BookId = 10, BookName="Waiting for Godot", Author = "Samuel Beckett"},
                }
            );
        }
    }
}

What we essentially did was define an interface, define a class that will be used as a data contract to return the book data back to the client, and implement the interface in the in the service class. The GetBooks method returns the names of a few books along with their authors.  Of course, in a real application, you may retrieve the data from a database.

I have also put the following line at the top: 

System.Threading.Thread.Sleep(5000);     

This will simulate the lag experienced while accessing the web service over the internet so that I can demonstrate the use of busy indicator and abort feature.

Once your web service is ready, build it and see if it builds without errors.

Step II:  Create WPF Client Application:

As the next step, add one WPF project to the solution (File => New Project) and name it AsyncAwaitDemo.  Remember to keep the target framework version as .NET 4.5.  In the solution explorer, right click the WPF project and set it as Startup Project.

Right click the WPF project again and open the "Add Service Refeference" dialog.  Click "Advanced" at the bottom of the dialog and make sure "Allow generation of asynchronous operations" is checked.  Discover the service in your solution, keep the Namespace as "BookService," and click ok.  Now you have added a reference to your BookService.

Open the Mainwindow.xaml and add a Button, a Datagrid, and a Busy Indicator.  Please refer to the markup for the correct placement of controls:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" x:Class="AsyncAwaitDemo.MainWindow"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <xctk:BusyIndicator Name="busyIndicator">
            <xctk:BusyIndicator.BusyContent>
                <StackPanel>
                    <TextBlock HorizontalAlignment="Center">Please wait...</TextBlock>
                    <Button Content="Abort" Name="btnAbort" HorizontalAlignment="Center"/>
                </StackPanel>
            </xctk:BusyIndicator.BusyContent>
            
            <xctk:BusyIndicator.Content>
                <StackPanel>
                    <Button Content="Get Data" Name="btnGetData" HorizontalAlignment="Center"/>
                    <DataGrid Name="grdData" AutoGenerateColumns="True"/>
                </StackPanel>
            </xctk:BusyIndicator.Content>
 
        </xctk:BusyIndicator>
    </Grid>
</Window>

Open the MainWindow.xaml's code window (press F7) and alter the code to make it look as below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using AsyncAwaitDemo.BookService;
 
namespace AsyncAwaitDemo
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
 
        //Create event procs.
          btnGetData.Click += btnGetData_Click;
          btnAbort.Click += btnAbort_Click;
          grdData.AutoGeneratingColumn += grdData_AutoGeneratingColumn;
        }
 
        //Service Proxy
        BookServiceClient client;
 

        //this will make sure that the "ExtentionData" column added as part of Serialization is prevented from showing up on the grid.
        void grdData_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            if (e.Column.Header.ToString() == "ExtensionData")
            {
                e.Cancel = true;
            }
        }
 
          
        //If the request is still being executed, this gives a chance to abort it.
        void btnAbort_Click(object sender, RoutedEventArgs e)
        {
            if (client != null)
            {
                if (client.State == System.ServiceModel.CommunicationState.Opened)
                {
                    client.Abort();    
                }
                
            }
        }
 
        //Async method to get the data from web service.
       async void btnGetData_Click(object sender, RoutedEventArgs e)
       {
           try
           {
               busyIndicator.IsBusy = true;
 
               client = new BookServiceClient();
 
               var result = await client.GetBooksAsync();
               
               client.Close();
 
               grdData.ItemsSource = result;
 
               busyIndicator.IsBusy = false;
 

           }
           catch (Exception ex)
           {
               busyIndicator.IsBusy = false;
 
               if (!ex.Message.Contains("The request was aborted: The request was canceled."))
               {
                   MessageBox.Show("Unexpected error: " + ex.Message, 
                       "Async Await Demo", MessageBoxButton.OK, MessageBoxImage.Error);              
 
               }
           }
 

           this.UpdateLayout();
       }
    }
}

We are done with the coding.  Build the solution.  If everything has gone as expected, the application starts up and shows the UI.  Click Get Data button and you will see the following:   

 

Points of Interest   

What is interesting here is the following line:

async void btnGetData_Click(object sender, RoutedEventArgs e) 

The async keyword converts the event handler into an asychronously executing method, thus preventing the application UI from being unresponsive when a long-running process is on.

var result = await client.GetBooksAsync(); 

When the above line is met, after entering the execution of the GetBooksAsync, without waiting for completion, the control is handed back to the caller, thereby keeping the UI responsive.  On the other hand, any lines that come after GetBooksAsync are made to wait (hence the keyword await).  So the following lines are held from being executed until the web service call returns:

client.Close();
grdData.ItemsSource = result;
busyIndicator.IsBusy = false;

Read more about async/await here.

It might also be interesting that during serialization, WCF may add one more column to your data contract objects.  To prevent it from being displayed on the data grid (of course if you have turned on the AutoGenerateColumns), you may need to add the following code:

void grdData_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
  if (e.Column.Header.ToString() == "ExtensionData")
  {
     e.Cancel = true;
  }
} 

History

None.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here