Large Lists in WPF and WCF – Part Four

by Chad 1. February 2010 06:00

Wrapping Up
This is the final post of this series. In the previous post, I showed you how to add paging to your WCF service’s message contracts. I then showed a strategy for consuming paged results on the client asynchronously. This strategy will increase your end user’s perception of your application’s performance.

In this post, I will show you a client-side abstraction for consuming paged service operations. We’ll create a PagedRequestManager class that can be used to manage the requests of any paged service operation. The responsibility of this class is to keep track of the current page, request the next page, and publish notifications on the UI thread.

IPagedRequest and IPagedResponse
The first step in this process is to have Visual Studio create classes for your message contracts. To do this right-click your service reference and choose Configure Service Reference. In the Data Type section of the Service Reference Settings dialog, check the “Always generate message contracts” option. Visual Studio will now rebuild the proxy classes – this will likely break your client code so you’ll need to correct any build errors at this point.

Now we need to make all of our message contracts implement the same interfaces. For this we create two client-side interfaces: IPagedRequest and IPagedResponse. It’s important to realize that these are purely client-side interfaces who’s implementations simply map the interface properties to the appropriate message contract fields. This keeps the client-side paging requirements separate from the service’s.

public interface IPagedRequest

{

    PagingContext PageInfoState { get; set; }

}

public interface IPagedResponse

{

    PagingContext PageInfoState { get; }

    IEnumerable ResultItems { get; }

}

Below is a sample implementation for a paged response. The proxies generated for us by Visual Studio are partial classes, which enables us to extend them at design-time. To do this, create a new class with the same name (and namespace) as a message contract class and add the partial keyword to the class declaration. We’ll do this for the request and response message contracts for each service operation that supports paging. Notice that the ResultItems property is a loosely-typed IEnumerable; this keeps the interface simple so that it can be applied to responses that return any type of object – not just Foods.

public partial class GetAllFoodsResponse : IPagedResponse

{

    public PagingContext PageInfoState

    {

        get { return PageInfo; }

    }

 

    public System.Collections.IEnumerable ResultItems

    {

        get { return Results; }

    }

}

PagedRequestManager 
Next, we will create a class that knows how to process IPagedRequest and IPagedResponse messages.image I named this class the PagedRequestManager. At the heart of the PagedRequestManger is the AsyncOperationManger. The AsyncOperationManager class is a class that lives in System.ComponentModel (System.dll) and helps you manage concurrency in multithreaded applications. We’ll be using it to reliably dispatch updates from our worker thread back to the UI thread.

PagedRequestManager is a simple class that takes an IPagedRequest and IList. It then executes the request for all pages, adding the results from the paged response to the specified IList. After it’s loaded the last page, it fires an event to notify the UI that the operation has finished. Additionally, it supports cancelling the asynchronous operation at any time.

The sample below shows the paged request loop inside of the PagedRequestManager. This loop executes on a non-UI thread so that the UI isn’t waiting for the response to come back from the service. The asyncOp.Post(…) statement, uses the AsyncOperation class to load the results into the specified IList. The asyncOp object was created by the AsyncOperationManager and is smart enough to execute it’s delegate on the UI thread. This is important because the _asyncProcessResults delegate will add the ResultItems to a user-specified IList. In our example, this WPF list is an ObservableCollection. When the ObservableCollection fires it’s CollectionChanged events, we want to make sure those are fired on the UI thread.

do

{

    IPagedResponse response = client.ExecutePagedRequest(request);

 

    itemCount = response.ResultItems.OfType<object>().Count();

    asyncOp.Post(_asyncProcessResults, new object[]

        {

            response,

            resultsList,

            asyncOp

        });

 

    request.PageInfoState.Skip += itemCount;

    isCancelled = IsCancelled(asyncOp.UserSuppliedState);

} while (!isCancelled

    && request.PageInfoState.IsFullPage(itemCount));

Using the PagedRequestManager
Using the paged request manager is simple. In this example we create a Form-level PagedRequestManager object.

_pageRequestManager = new PagedRequestManager();

_pageRequestManager.ExecutePagedRequestAsyncCompleted += new EventHandler<ExecutePagedRequestAsyncCompletedEventArgs>(_pageRequestManager_ExecutePagedRequestAsyncCompleted);

When the Load Async button is clicked, we cancel any active requests, rebind our list, and execute a new request.

private void LoadWCFAsync_Click( object sender, RoutedEventArgs e)

{

    // Cancel any pending requests

    _pageRequestManager.CancelAsync(_searchTaskId);

 

    // Initialize and bind our ObservableCollection

    _fo odItems = new ObservableCollection<Food>();

    ((ObservableCollection<Food>)_foodItems).CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Window1_CollectionChanged);

    FoodList.ItemsSource = _foodItems;

 

    // Begin a new request

    _searchTaskId = new object();

    StartFirstResultTimer();

    _pageRequestManager.ExecutePagedRequestAsync(

        new GetAllFoodsRequest(new PagingContext() { Skip = 0, Take = 100 })

        , _foodItems, _searchTaskId);

}

Because the PagedRequestManager accepts any IPagedRequest object we can use this exact same pattern to execute a text search.

private void TextBox_TextChanged(object sender, TextChangedEventArgs e)

{

    // Cancel any pending requests

    _pageRequestManager.CancelAsync(_searchTaskId);

 

    // Initialize and bind our ObservableCollection

    _foodItems = new ObservableCollection<Food>();

    FoodList.ItemsSource = _foodItems;

 

    // Begin a new request

    string partialDesc = ((TextBox)sender).Text;

    if (!string.IsNullOrEmpty(partialDesc))

    {

        SearchForFoodRequest request = new SearchForFoodRequest()

        {

            PageInfo = new PagingContext() { Skip = 0, Take = 100 },

            PartialDescription = partialDesc

        };

 

        _searchTaskId = new object();

        _pageRequestManager.ExecutePagedRequestAsync(request, _foodItems, _searchTaskId);

    }

}

About time... 
This has been a lengthy example, but hopefully it illustrates that a little planning upfront can go a long way for improving the end user experience for your application. User’s no longer expect to click and wait – they want instant results or at least a responsive application while they wait for results.

The patterns shown in this series of posts make consuming large lists of data a manageable task. On my local machine, retrieving 7539 food objects synchronously took 0.7 seconds. When paged asynchronously, 100 objects per page, the first 100 results were displayed and useable in 0.03 seconds with the 7539th object displayed 1.6 seconds after the button was clicked. Although the time to display the last object took more that twice as long when paged, the first 100 objects were displayed 23 times faster. These numbers become even more revealing when consuming the service over a network or the internet.

I hope that this series of posts was useful to you and your projects. The source code can be downloaded here.

Tags:

WCF | WPF

Comments

2/14/2010 11:29:20 PM #

trackback

Windows Client Developer Roundup for 2/15/2010

This is Windows Client Developer roundup #11. The Windows Client Developer Roundup aggregates information

Community Blogs

Comments are closed

Powered by BlogEngine.NET 1.5.0.7
Theme by Mads Kristensen

About the author

Chad

Meeeee!!!

Hi, my name is Chad Boschert and I'm a software developer in Springfield, Missouri. I've been developing .NET applications in C# since 2002.

Recent Comments

Comment RSS

Calendar

<<  May 2012  >>
MoTuWeThFrSaSu
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910

View posts in large calendar