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();
}
}

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.