Click here to Skip to main content
Click here to Skip to main content

Tagged as

Paging Data from the Server with Silverlight

, 20 Oct 2011
Rate this:
Please Sign up or sign in to vote.
This blog post provides an implementation of IPagedCollectionView which allows paging of data from the server. An IPagedDataSource is introduced that allows any paged data source to be plugged in, with the standard controls such as DataPager making it easy to create paging applications.

This blog post provides an implementation of IPagedCollectionView which allows paging of data from the server. An IPagedDataSource is introduced that allows any paged data source to be plugged in, with the standard controls such as DataPager making it easy to create paging applications.

With web-based applications, bandwidth constraints often mean that when querying large datasets, the results must be paged, i.e., split into discrete pages each containing a small number of results, with an API available for moving forwards / backwards or to a specific page. There are numerous APIs available on the web that expose this type of interface, for example:

With paging being a common application requirement, Silverlight has the functionality to deal with this scenario built-in. The System.Windows.Data assembly contains the IPagedCollectionView interface, which defines the following methods, properties and events, allowing navigation of paged data:

The System.Windows.Controls assembly contains a DataPager control, that collaborates with this interface to provide a UI for paging the dataset. Simply set the Source property of this control to an instance of IPagedCollectionView and your user can page through the data:

NOTE: The Source property of DataPager is of type IEnumerable, which is a little odd, because it doesn’t really need to enumerate the collection of items being displayed. Internally, it probes the object you supply as the Source to see if it implements IPagedCollectionView, and if it does, the control becomes ‘active’.

This control gives you quite a bit of functionality for free, the buttons become enabled / disabled based on whether navigation forwards / backwards is possible. It respects the CanChangePage property, computes the total number of pages etc…

So, how do you make use of this interface and control?

The System.Windows.Data assembly also contains a concrete implementation of the paging interface, PagedCollectionView. This class gives much more than just paging, it also implements grouping, sorting and filtering of a source collection (IEnumerable). To make use of this class, simply construct an instance based on your collection of data and use that as your DataContext:

List<MyDataObject> sourceCollection = ParseData(serverResponse);
var collectionView = new PagedCollectionView(sourceCollection);
this.DataContext = collectionView;

The problem is PagedCollectionView allows you to sort, group and page a collection of data that is already held in memory. What I want to do, and what I think is a more common use-case, is page data that is supplied by the server. I don’t want to have to fetch all the data up-front then page it on the client, this would defeat the object of paging in the first place … to improve performance!

I discovered that there is an IPagedCollectionView implementation as part of RIA Services that provides this functionality, the DomainCollectionView. However, if I want to page data form a simple JSON formatted web service, adding a dependency to RIA Services seems like overkill. So I decided to create my own simple implementation of this interface.

In order to make a generic/ re-useable implementation of this paging interface, I first created an interface that would act as the source of the data:

/// <span class="code-SummaryComment"><summary>
</span>/// Defines a source of data that can be paged.
/// <span class="code-SummaryComment"></summary>
</span>public interface IPagedDataSource<TDataType>
{
  /// <span class="code-SummaryComment"><summary>
</span>  /// Asynchronously returns the data for the given page
  /// <span class="code-SummaryComment"></summary>
</span>  void FetchData(int pageNumber, Action<PagedDataResponse<TDataType>>

This interface is very simple, having a single method that fetches data for the given page. The result is returned asynchronously, providing both the items within the given page and the total item count.

My paging collection view extends ObservableCollection and takes an instance of IPagedDataSource in its constructor:

public class ServerSidePagedCollectionView<T> : ObservableCollection<T>,
  IPagedCollectionView
{
  IPagedDataSource<T> _pagedDataSource;
 
  public ServerSidePagedCollectionView(IPagedDataSource<T> pagedDataSource)
  {
    _pagedDataSource = pagedDataSource;
  }
 
  ...
}

As an aside, aren’t generics great?

The various properties defined in IPagedCollectionView (CanChangePage, TotalPages, etc …) are implemented as standard field-backed properties that notify changes via INotifyPropertyChanged which is inherited via ObservableCollection, I am not going to show them here.

The various methods that permit navigation are all implemented using the same pattern:

public bool MoveToFirstPage()
{
  RefreshData(0);
  return true;
}
 
public bool MoveToLastPage()
{
  RefreshData(TotalPages - 1);
  return true;
}    
 
public bool MoveToNextPage()
{
  RefreshData(PageIndex + 1);
  return true;
}
 
public bool MoveToPage(int pageIndex)
{
  RefreshData(pageIndex);
  return true;
}
 
public bool MoveToPreviousPage()
{
  RefreshData(PageIndex - 1);
  return true;
}

With the RefreshData method doing all the work, it is the only method that uses the supplied IPagedDataSource interface and is shown below:

/// <span class="code-SummaryComment"><summary>
</span>/// Fetches the data for the given page
/// <span class="code-SummaryComment"></summary>
</span>private void RefreshData(int newPageIndex)
{
  // set the pre-fetch state
  CanChangePage = false;
  OnPageChanging(newPageIndex);
 
  _pagedDataSource.FetchData(newPageIndex, response =>
    {
      // process the received data
      DataReceived(response);
 
      // set the post-fetch state
      PageIndex = newPageIndex;
      OnPageChanged();
      CanChangePage = true;
    });
}

With the simple implementation of IPagedCollectionView given above, I can now page data from a range of web services by simply implementing IPagedDataSource. For example, to page data from NetFlix, you can use the following implementation:

/// <span class="code-SummaryComment"><summary>
</span>/// A paged NetFlix movies search datasource.
/// <span class="code-SummaryComment"></summary>
</span>public class NetFlixDataSource : IPagedDataSource<SyndicationItem>
{
  private string _searchString;
 
  private int _pageSize = 10;
 
  public NetFlixDataSource(string searchString)
  {
    _searchString = searchString;
  }
 
  public void FetchData(int pageNumber, 
	Action<PagedDataResponse<SyndicationItem>>

As you can see, this simple implementation of our paging data source constructs the required URL based on the search string and request page number. It also uses the ‘syndication’ APIs which are able to parse the response from the NetFlix OData APIs.

To test out this implementation of the paged search interface, I have created a very simple movie search application. The view model for this application has a SearchString property, exposes the results as SearchResults property and has a single command which initiates a new search. The important parts of this class are shown below:

/// <span class="code-SummaryComment"><summary>
</span>/// Gets the paged search results
/// <span class="code-SummaryComment"></summary>
</span>public ServerSidePagedCollectionView<SyndicationItem> SearchResults
{
  get
  {
    return _searchResults;
  }
  private set
  {
    SetField(ref _searchResults, value, "SearchResults");
  }
}
 
/// <span class="code-SummaryComment"><summary>
</span>/// Gets a command which executes the search
/// <span class="code-SummaryComment"></summary>
</span>public ICommand ExecuteSearchCommand
{
  get
  {
    return new DelegateCommand(() =>
    {
      SearchResults = new ServerSidePagedCollectionView<SyndicationItem>(
        new NetFlixDataSource(SearchText));
    });
  }
}

When ExecuteSearchCommand is invoked, we create a new ServerSidePagedCollectionView, providing it with the NetFlix data source. From here on, the ServerSidePagedCollectionView is responsible for the paging of data.

The view is as follows:

<Grid x:Name="LayoutRoot" Background="LightGray"
      Width="400" Height="360">
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="Auto"/>
  </Grid.RowDefinitions>
 
  <StackPanel Orientation="Horizontal"
              Margin="4">
    <TextBox Text="{Binding SearchText, Mode=TwoWay}"
                Width="150"/>
    <Button Command="{Binding ExecuteSearchCommand}"
            Content=" Search Movies ..."
            Margin="5,0,0,0"/>
  </StackPanel>
 
  <ItemsControl ItemsSource="{Binding SearchResults}"
                Grid.Row="1"
                Height="300"
                ItemTemplate="{StaticResource MovieTemplate}">
    <ItemsControl.ItemsPanel>
      <ItemsPanelTemplate>
        <StackPanel Orientation="Vertical"/>
      </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
  </ItemsControl>
  <Rectangle Fill="White" Grid.Row="1"
            Opacity="0.5"
            Visibility="{Binding Path=SearchResults.CanChangePage, 
		Converter={StaticResource BoolToVisibilityConverter}}"/>
 
  <sdk:DataPager  PageSize="10" 
                  Source="{Binding SearchResults}"
                  Grid.Row="2"/>
 
</Grid>

The DataPager simply binds to the SearchResults as does the ItemsControl. I have also added a Rectangle element which grays-out the current search results when a new page is being fetched.

You can see the application in action below:

<object width="500" height="400" data="data:application/x-silverlight," type="application/x-silverlight-2" > Get Microsoft Silverlight</object>

So in summary, by implementing IPagedCollectionView with a pluggable datasource, IPagedDataSource, it is possible to very quickly and easily create an application that pages data from the server.

You can download the full source code from here.

Regards, Colin E.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Colin Eberhardt
Architect Scott Logic
United Kingdom United Kingdom
I am CTO at ShinobiControls, a team of iOS developers who are carefully crafting iOS charts, grids and controls for making your applications awesome.
 
I am a Technical Architect for Visiblox which have developed the world's fastest WPF / Silverlight and WP7 charts.
 
I am also a Technical Evangelist at Scott Logic, a provider of bespoke financial software and consultancy for the retail and investment banking, stockbroking, asset management and hedge fund communities.
 
Visit my blog - Colin Eberhardt's Adventures in .NET.
 
Follow me on Twitter - @ColinEberhardt
 
-
Follow on   Twitter   Google+

Comments and Discussions

 
GeneralExcellent Pinmemberktei4-Dec-12 18:11 
GeneralMy vote of 5 Pinmemberkienlamb18-Sep-12 11:10 
Well written and samle project build and run successfully.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140821.2 | Last Updated 20 Oct 2011
Article Copyright 2011 by Colin Eberhardt
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid