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

Large Lists in WPF and WCF – Part One

by Chad 9. January 2010 15:56

Introduction to series
Welcome to part one of a four-part series on managing large lists in WPF and WCF. In this series, I will walk you through some of the design considerations for building snappy WPF/WCF solutions. Although this series specifically addresses WPF and WCF, it’s concepts are applicable to Silverlight and other client and service frameworks.

Part one of the series illustrates the basics of WPF data binding; specifically binding to lists of objects.

List-based WPF Controls
ListView, ListBox, ItemsControl, ComboBox, DataGrid, and others. List-based controls are bound to an object that implements IEnumerable via the list control’s ItemSource property.

<ListView x:Name="FoodList"

     Grid.Row="1" >

</ListView>

 

// Binding the ListView to List<Food>

_foodItems = GetLocalFoodItems();

FoodList.ItemsSource = _foodItems;

ItemTemplate
By default, list controls will display the result of Object.ToString(). To customize the display of your objects you use the ItemTemplate.

image

<ListView x:Name="FoodList"

     Grid.Row="1" >

    <ListView.ItemTemplate>

        <DataTemplate>

            <WrapPanel>

                <Label Content="{Binding Id}" />

                <Label Content="{Binding Description}" />

            </WrapPanel>

        </DataTemplate>

    </ListView.ItemTemplate>

</ListView>

image

Adding items to the list with ObservableCollection<T>
Currently our ListView is data bound to a List<Food> object. When new Food objects are added directly to the List<Food>, we would like for them to automatically show up in the ListView.  Unfortunately the ListView’s binding has no way to detect when items are added to the List<Food>, so the bound list is now out of sync with the list of items displayed in the UI.

We could manually synchronize this ourselves, but that seems like a lot of work.  Fortunately, the DataBinding architecture in WPF has a built-in mechanism to do this for us. WPF watches for two interfaces that enable it to keep its bindings synchronized: INotifyPropertyChanged and INotifyCollectionChanged. Both of these interfaces expose events that WPF listens for to update UI components with state changes.

In the System.Collections.ObjectModel (WindowsBase.dll) namespace ObservableCollection<T> is a generic collection that implements both of these interfaces. If we replace our List<Food> with an ObservableCollection<Food>, adding items to the ObservableCollection will now automatically update the UI.

// Add a new Food object to the ObservableCollection<Food>.

// This will automatically update the ListView.

_foodItems.Add(

    new Food() { Id = ++_id, Description = "Bacon" }

    );

WhatsThat<T> 

If you’re unfamiliar or only somewhat familiar with Generic types in the .NET framework, you may be wondering about the <T> syntax. Version 2.0 of the .NET framework introduced Generic Types. These are types that accept a type parameter so that they can provide strongly typed operations where a System.Object would have been required previously.

When reading the statement List<T>, you say “List of T”, where T is a type parameter for some other type. So List<Food> is read “List of Food”.

image

Summary
In this post, you saw an example of data binding a collection of objects to the WPF ListView control. I showed how to customize the UI of the control by overriding the ItemTemplate. Then I illustrated the different behavior between binding the ListView controls ItemsSource property to List<T> versus an ObservableCollection<T>. These techniques work for all of the WPF list controls.

In part one of the series, the data source was a hard-coded list of Food objects. In part two, I will build a simple WCF service that will serve up Food objects and show some patterns that can be used to maintain a responsive UI when loading large lists of objects.

The source for part one 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