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

Large Lists in WPF and WCF – Part Three

by Chad 25. January 2010 09:37

Paging Your Results
In part one of this series I gave an overview of data binding lists in WPF. In part two, I showed how to asynchronously consume a WCF service so that your WPF user interface remained responsive. In this post, I’m going show an example for paging your results to control the message size and to get results to the client faster.

Let’s Get Started
In the last post, I mentioned that eventually your result set will grow large enough that the default WCF configuration will prevent your message from being received. With the default settings, once the size of the message exceeds ~65K, you will receive this exception:

“The maximum message size quota for incoming messages (65536) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element.”

The simplest fix is to increase the quota for MaxRecievedMessageSize in the client’s service configuration. This is only a temporary fix as eventually your message size may exceed the new limit. To help control the message size, a service should allow the client to page large sets of data.

Update the Service
To enable the client to page data, we need to allow them to specify a record offset and a page size. I prefer the LINQ to SQL terms “Skip” and “Take”. Skip is the zero-based record offset and Take is the maximum number of records per page. An alternative would be to specify a page size and page index, but I prefer the Skip-Take approach because it allows me to dynamically change the page size to control perceived performance.

So the first change to our NutrientDB service is to add a data contract to describe paging context.

[DataContract(Namespace=ServiceConstants.NAMESPACE)]

public class PagingContext

{

    [DataMember]

    public long Skip { get; set; }

    [DataMember]

    public int Take { get; set; }

}

Next we add the PagingContext to our GetAllFoodsRequest.

[MessageContract(WrapperNamespace=ServiceConstants.NAMESPACE)]

public class GetAllFoodsRequest

{

    [MessageBodyMember]

    public PagingContext PageInfo { get; set; }

}

Then we add the PagingContext to GetAllFoodsResponse. This allows a consumer of the service to know which page of results are being returned.

[MessageContract(WrapperNamespace = ServiceConstants.NAMESPACE)]

public class GetAllFoodsResponse

{

    [MessageBodyMember]

    public IEnumerable<Food> Results { get; set; }

    [MessageBodyMember]

    public PagingContext PageInfo { get; set; }

}

And finally, update our service operation to implement the paging.

public GetAllFoodsResponse GetAllFoods(GetAllFoodsRequest request)

{

    if (request == null) { throw new ArgumentNullException("request"); }

 

    PagingContext ctx = request.PageInfo

        ?? new PagingContext() {Skip = 0, Take = int.MaxValue};

 

    return new GetAllFoodsResponse()

    {

       Results = NutrientDataAccess.GetAllFoods(ctx.Skip, ctx.Take),

       PageInfo = ctx

    };

}

Update the Client
In the WPF client project, I right-click my service reference and choose “Update Service Reference”. This will break my existing code since the message contracts for my service changed. I now have to specify a PagingContext to the GetAllFoods methods. Also, I removed the 100 record limit from the database – it now returns the full ~7500 records.

Returning ~7500 Food objects in one request would throw the MaxReceivedMessageSize quota exception mentioned above. To prevent this (without having to update the client service configuration,) I update the client to retrieve all Food objects from the service 100 records at a time. I want to do this asynchronously so that the UI remains responsive.

private void LoadWCFAsync_Click(object sender, RoutedEventArgs e)

{

    NutrientDBClient client = new NutrientDBClient();

    client.GetAllFoodsCompleted +=

        new EventHandler<GetAllFoodsCompletedEventArgs>(

            client_GetAllFoodsCompleted);

 

    // Assign a new ObservableCollection to the ListView;

    // When items are later added to the ObservableCollection,

    // the ListView will automatically display them.

    _foodItems = new ObservableCollection<Food>();

    FoodList.ItemsSource = _foodItems;

 

    // Request the first 100 Food objects

    var pageInfo = new PagingContext() {Skip = 0, Take = 100};

    client.GetAllFoodsAsync(pageInfo);

}

 

void client_GetAllFoodsCompleted(object sender, GetAllFoodsCompletedEventArgs e)

{

    NutrientDBClient client = (NutrientDBClient)sender;

    if (e.Error == null)

    {

        // Use the PagingContext from the response to determine

        // if this is the last page of results

        PagingContext pageInfo = e.Result;

        if (e.Results.Length < pageInfo.Take)

        {

            client.Close();

        }

        else

        {

            // Request the next 100 Food objects asynchronously

            pageInfo.Skip += e.Results.Length;

            client.GetAllFoodsAsync(pageInfo);

        }

 

        // Update the ObservableCollection<Food> that has been

        // data bound to the ItemsSource property of the ListView.

        // Because _foodItems is an ObservableCollection, WPF will

        // automatically update the ListView with the new items.

        foreach (var food in e.Results)

        {

            _foodItems.Add(food);

        }

    }

    else

    {

        MessageBox.Show(e.Error.Message);

        client.Close();

    }

}

image image image

 

 

 

 

Now, instead of waiting for all 7539 records to load before beginning their work, a user can begin to browse the Food items after the first 100 results are returned. I load all pages at once, but this code could be easily modified to only request the next page when the user scrolls near the bottom of the list. Another modification that you might want to make is to request a smaller first page and larger subsequent pages. This would improve the perceived performance since the user will nearly instantly see their first page of results.

Summary
In this post I showed how you can add paging to your services to give clients better control over message size. At the end I touched on how paging can enable you to solve our third problem with large lists - “A noticeable delay to load data will begin to slow your users down.”

In the final post of this series, I will take this example to the next level to show how to abstract the paging logic out of your UI code, making it easily usable from anywhere in your client application.

Source code for this post can be downloaded here.

Tags:

WCF | WPF

Large Lists in WPF and WCF – Part Two

by Chad 18. January 2010 16:18

Welcome Back
In part one of this four-part series, I showed how data binding to list controls works in WPF. This post will show you how to build a simple WCF service to serve up a list of Food objects. I will then show some patterns for dealing with large lists of items.

Building the Food Service
For detailed instructions on building a WCF service, you can download my WCF presentation materials and follow the source code and slides or lookup any of the many tutorials elsewhere. This series of posts will focus mainly on the message contracts for lists.

The NutrientDB service will expose a GetAllFoods operation that will return all of the Food objects in the nutrient database. I will start with a small set of Food objects, and then increase the size of the database to illustrate some of the issues that you need to consider for large sets of data.

Starting Simple
The simplest approach is to have your message contract return an IEnumerable<T> of your objects.

// Server-side code

[MessageContract(WrapperNamespace = ServiceConstants.NAMESPACE)]

public class GetAllFoodsResponse

{

    [MessageBodyMember]

    public IEnumerable<Food> Results { get; set; }

}

Clients of the service simply ask for all of the Food objects in the database and the service returns them all at once.

// Client-side code

private void LoadWCF_Click(object sender, RoutedEventArgs e)

{

    NutrientDBClient client = new NutrientDBClient();

 

    _foodItems = client.GetAllFoods();

    FoodList.ItemsSource = _foodItems;

 

    client.Close();

}

image

The problems with this approach usually aren’t visible with small sets of data and low network latency. However, as the data set increases and/or network speeds decrease (i.e. deploy your service to the internet) some problems will begin to present themselves. The three main problems are:

  1. The UI will “lock up” while data is downloading.
  2. Message size will increase; Eventually the default settings for WCF services will prevent the message from being received.
  3. A noticeable delay to load data will begin to slow your users down.

Let’s resolve the first problem now.

The UI will “lock up”
In WPF the user interface executes on it’s own thread. This thread is managed by an object called the Dispatcher. The Dispatcher is used to synchronize calls on the UI thread. When an event, such as a button click, is fired the event handler code is running on the UI thread. When our long-running WCF call is made on the UI thread, other UI operations are blocked from executing. This means that redrawing of the window, scrolling, and clicking all have to wait for your WCF request to complete. Additionally, WPF requires that any updates to the UI happen on the Dispatcher thread.

To solve this problem we need a way to offload the WCF request to a non-UI thread, then return back to the UI thread to update the ListView. Built into the Visual Studio 2008 service proxy generator is a setting to create asynchronous methods for each service operation. To get to this setting: right-click your service reference > choose “Configure Service Reference” > check “Generate asynchronous operations” > click OK.

When you do this Visual Studio will rebuild your proxies with four new members: BeginGetAllFoods(), EndGetAllFoods(), and GetAllFoodsAsync() methods, as well as a GetAllFoodsCompleted event. This gives you two options for calling WCF services asynchronously: BeginGetAllFoods() and EndGetAllFoods() are used together and GetAllFoodsAsync() and GetAllFoodsCompleted are used together. I’ll demonstrate the latter.

private void LoadWCFAsync_Click(object sender, RoutedEventArgs e)

{

    NutrientDBClient client = new NutrientDBClient();

    client.GetAllFoodsCompleted +=

        new EventHandler<GetAllFoodsCompletedEventArgs>(

            client_GetAllFoodsCompleted);

 

    client.GetAllFoodsAsync();

}

 

void client_GetAllFoodsCompleted(object sender, GetAllFoodsCompletedEventArgs e)

{

    if (e.Error == null)

    {

        _foodItems = e.Result;

        FoodList.ItemsSource = _foodItems;

    }

    else { MessageBox.Show(e.Error.Message); }

 

    NutrientDBClient client = (NutrientDBClient)sender;

    client.Close();

}

Now when I increase the number of foods in the database to 100 and run the program, there is short delay while I wait for data to come back, but the UI is responsive the whole time. When the GetAllFoodsCompleted event handler, client_GetAllFoodsCompleted(), is called we are already back on the UI thread, so I do not have to worry about managing thread synchronization.

Summary
In this post, I started with a very simple implementation of a WCF service that returned a set of Food objects. I pointed out tree problems with the simple solution: Unresponsive UI, excessive message size, and a noticeable delay to display results. I provided a solution to the unresponsive UI by generating asynchronous proxy methods and invoking the WCF calls on a non-UI thread. I will address the last two problems in parts three and four.

Source code for this post can be downloaded here.

Tags:

WCF | WPF

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 2013  >>
MoTuWeThFrSaSu
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar