Silverlight 4, OData Paging, RX Extensions, Using View Model
Live example
This article picks up from where the Simple Silverlight 4 Example Using oData and RX Extensions article ends. This article implements paging. It also showcases some fancy RX Extensions code created by Dave Sexton (http://davesexton.com). However, it is a bit ironic that Dave Sexton was not that happy with the code (even though I felt it was so perfect it was suitable for framing).
The issue Dave Sexton had with the code was that he felt that there were alternative ways of writing the code that are more extensible. He provides examples of the alternate versions, as well as a full explanation, in the postings at this link. My goal for this article is to implement paging, using "View Model Style", using the smallest amount of code as possible. While the code in the previous article works, the code by Dave Sexton reduces the code significantly, as well as simplifies the call from the View Model to the Model, to one line of code.
If you are new to View Model Style it is suggested that you read Silverlight View Model Style: An (Overly) Simplified Explanation for an introduction.
Simple Silverlight 4 Example Using OData and RX Extensions
We are starting with the project created in Simple Silverlight 4 Example Using OData and RX Extensions. That tutorial explains how to create the OData service and to register the assemblies for the RX Extensions.
Commanding
Because we will enable paging, we will need to raise an event in the View Model from the View (the UI). We do this using Commanding. To support Commanding, we add the DelegateCommand.cs file to the project. The use of that file is covered in this article by John Papa: http://johnpapa.net/silverlight/5-simple-steps-to-commanding-in-silverlight/.
The Model
In the Simple Silverlight 4 Example Using oData and RX Extensions tutorial, we created a Model that connected to the OData service. We will open it up and replace all the code with the following code:
using System;
using System.Collections.ObjectModel;
using System.Data.Services.Client;
using System.Linq;
using SilverlightODataSample.wsSampleCustomerData;
namespace SilverlightODataSample
{
public class Model
{
public static ObservableCollection<customerrecord>
GetCustomers(int intPage, int intRecordsPerPage)
{
var collection = new ObservableCollection<customerrecord>();
var uri = new Uri(GetBaseAddress(), UriKind.RelativeOrAbsolute);
var sds = new SampleDataSource(uri);
int CurrentPage = ((intPage * intRecordsPerPage) - intRecordsPerPage);
var query = (from data in sds.SampleCustomerData
select data).Skip(CurrentPage).Take(intRecordsPerPage);
var results = new DataServiceCollection<customerrecord>();
var whenLoaded =
Observable.FromEvent<loadcompletedeventargs>(results, "LoadCompleted");
var disposable = whenLoaded.Subscribe(value =>
{
if (value.EventArgs.Error != null)
{
throw value.EventArgs.Error;
}
else
{
foreach (var item in results)
{
collection.Add(item);
}
}
});
results.LoadAsync(query);
return collection;
}
#region GetBaseAddress
private static string GetBaseAddress()
{
string strXapFile = @"/ClientBin/SilverlightODataSample.xap";
string strBaseWebAddress =
App.Current.Host.Source.AbsoluteUri.Replace(strXapFile, "");
return string.Format(@"{0}/{1}", strBaseWebAddress, @"Service.svc");
}
#endregion
}
}
The View Model
We will also alter the View Model. First, let's look at how simple the code required to call the Model from the View Model is:
#region GetCustomers
private void GetCustomers()
{
colCustomerRecord = Model.GetCustomers(CurrentPage, RecordsPerPage);
}
#endregion
The DataGrid
(or any UI element that can hold a collection that the Designer decides to use) will use colCustomerRecord
as its source.
#region CustomerRecord
private ObservableCollection<customerrecord> _colCustomerRecord
= new ObservableCollection<customerrecord>();
public ObservableCollection<customerrecord> colCustomerRecord
{
get { return _colCustomerRecord; }
private set
{
if (colCustomerRecord == value)
{
return;
}
_colCustomerRecord = value;
this.NotifyPropertyChanged("colCustomerRecord");
}
}
#endregion
This collection is an ObservableCollection
, so it automatically updates the UI element bound to it, whenever it is changed.
The GetCustomers
method requires the page requested, and the number of records per page. We create properties in the View Model to hold these values:
#region CurrentPage
private int _CurrentPage = 1;
public int CurrentPage
{
get
{
return this._CurrentPage;
}
set
{
this._CurrentPage = value;
this.NotifyPropertyChanged("CurrentPage");
}
}
#endregion
#region RecordsPerPage
private int _RecordsPerPage = 10;
public int RecordsPerPage
{
get
{
return this._RecordsPerPage;
}
set
{
this._RecordsPerPage = value;
this.NotifyPropertyChanged("RecordsPerPage");
}
}
#endregion
Next, we create ICommand
s to allow the View to page back and forth:
#region PreviousPageCommand
public ICommand PreviousPageCommand { get; set; }
public void PreviousPage(object param)
{
CurrentPage--;
GetCustomers();
}
private bool CanPreviousPage(object param)
{
return (CurrentPage > 1);
}
#endregion
#region NextPageCommand
public ICommand NextPageCommand { get; set; }
public void NextPage(object param)
{
CurrentPage++;
GetCustomers();
}
private bool CanNextPage(object param)
{
return (colCustomerRecord.Count > 0);
}
#endregion
We also create an ICommand
to allow the records per page to be set:
#region SetRecordsPerPageCommand
public ICommand SetRecordsPerPageCommand { get; set; }
public void SetRecordsPerPage(object param)
{
ContentControl Element = (ContentControl)param;
RecordsPerPage = Convert.ToInt32(Element.Content);
GetCustomers();
}
private bool CanSetRecordsPerPage(object param)
{
return (param != null);
}
#endregion
We implement INotifyPropertyChanged
to support automatic notifications when properties are changed. For example, so the UI can be automatically updated when a property such as current page is changed.
It is not really needed in this example, because the only control bound to the property is the one actually changing the value. However, if we had a text box that displayed the current page, we would need to implement this to enable automatic update notification:
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
Finally, this is the constructor for the View Model. It sets up the ICommand
s and calls GetCustomers
to load the first page:
public MainPageModel()
{
PreviousPageCommand =
new DelegateCommand(PreviousPage, CanPreviousPage);
NextPageCommand = new DelegateCommand(NextPage, CanNextPage);
SetRecordsPerPageCommand =
new DelegateCommand(SetRecordsPerPage, CanSetRecordsPerPage);
GetCustomers();
}
Creating the View
We complete the example by re-creating the View.
In the previous article, it simply pulled up the first page.
Now we add paging buttons, and a combo-box to allow the records per page to be selected. The diagram above shows what is wired to what.
Note: For the diagram, I enabled design time sample data. You can learn how to do that in this article: Silverlight MVVM: Enabling Design-Time Data in Expression Blend When Using Web Services.
The steps to create the View were covered in: Simple Silverlight 4 Example Using oData and RX Extensions. However, the buttons and the combo-box are new, so let's walk through the steps the Designer would use to implement them.
The Paging Buttons
Using Expression Blend 4+, we first drop two buttons on to the page.
Next, we drop an InvokeCommandAction
Behavior on each button.
In the Properties for each Behavior, we select Click
for the EventName
and we click the Advanced Options box next to Command.
We select Data Binding...
And we bind to the appropriate command.
The Combo Box
We also drop an InvokeCommandAction
Behavior on the combobox.
In the Properties for the Behavior, we set EventName
to SelectionChanged
.
We bind the Command
to SetRecordsPerPageCommand
.
However, in this case, we also need to pass the number of records selected, so we click the Advanced Options box next to CommandParameter.
We then bind CommandParameter
to SelectedValue
.
"View Model Style", Simple - Not a Lot of Code
Hopefully, you find this example simple and easy to understand. "View Model Style" can be used for 100% of your Silverlight projects. The amount of code required to use a View Model is about the same amount that would be required if you simply used code-behind. The reason for using View Model vs. code-behind is that you allow your UI to be completely designed by a Designer who does not know how to code. You may find that this is vital to producing professional looking applications. It is also testable, see: Unit Testing a Silverlight View Model Style Modal Popup.
If you require a more complex architecture, you can implement MVVM by adding additional abstraction. For example, instead of actually calling the OData service from the Model, you would create a "service class" that actually made the call to the service and filled the Model. The View, however, would not require any changes.