WPF Data Binding
While we’re talking about read-write-ability and client-side code, I’d be remiss not to mention WPF data binding with WCF Data Services. If you recall from the earlier Entity Framework chapters, the WPF designer in Visual Studio 2010 provided direct support for EF via the Data Sources window and it does the exact same thing for Services References, too. Figure 13 shows what you get just from drag ‘n’ drop.
Figure 13: Laying out a WPF application based on a WCF Data Services data source
In Figure 13, notice that we’ve laid out the TinyUsers on the left as the “master” and the TinyLinks on the right as the “details.” The TinyUsers data grid came from a drag ‘n’ drop of the TinyLinkContainer in the Data Sources window on the left (available via Data | Show Data Sources). The TinyLinks grid, on the other hand, came from the Links property underneath the TinyUsers collection so that WPF knows how the relationship between users and links is arranged. Notice also that we set up a Save button so that as items are added, changes or deleted in memory, we can bundle those changes and send them back to the service endpoint.
In the code behind, we have to retrieve the data we want to work on and implement the Save button:
public partial class MainWindow : Window { // Set up the service endpoint connection TinyLinkContainer service = new TinyLinkContainer(new Uri(@"http://localhost:1676/TinyLinkService.svc")); public MainWindow() { InitializeComponent(); } void Window_Loaded(object sender, RoutedEventArgs e) { // Set up the data binding var tinyUsersViewSource = (CollectionViewSource)this.FindResource("tinyUsersViewSource"); //tinyUsersViewSource.Source = service.TinyUsers.Expand("Links"); // read-only // Use DataServiceCollection if you want to enable writing as well as reads tinyUsersViewSource.Source = new DataServiceCollection<TinyUser>(service.TinyUsers.Expand("Links")); } void button1_Click(object sender, RoutedEventArgs e) { // Save the changes service.SaveChanges(SaveChangesOptions.Batch); } }
To start, we have a field that holds the service reference for use in our event handlers. When the Loaded event for the main window fires, we pull down the data from the service, being careful to expand the user data to include associated links (remember, unlike EF, Data Services doesn’t support lazy loading). Then, to enable full writeability in the WPF data grid, we wrap the resulting collection in an instance of the DataServiceCollection class. This is the adapter between WPF and Data Services specifically for read/write data binding support. If you want read-only support, you don’t have to use this class.
If you do use the DataServiceCollection class, however, you don’t have to do a lot of the manual things you were doing before – like AddLink and UpdateObject – the DataServiceCollection does for you, making it a handy shim[19]. Once we’ve retrieved the data and made it available for read/write data binding, we stuff it into the view source that our drag ‘n’ drop work in the WPF designer created for us. This is all we need to populate the two grids and enable full editing.
To save our changes, we simply call the SaveChanges method, which bundles all the new objects, the updates to existing objects and the deletes together and sends them to the service endpoint according to the standard OData protocol and then updates the WPF data grids with any server-generated data, i.e. new unique IDs for new objects. Figure 14 shows our program running.
Figure 14: Data Services and WPF in action
Notice in Figure 14 that, just like our master-detail EF sample, as we select a user in the left-hand column, the corresponding links are shown in the right-hand column. New rows can be added by typing into the blank rows in each grid, which will create new TinyUser and TinyLink objects as appropriate, the Delete key will delete object and simple editing will create updates. Notice also that the user “Joe” and the link “http://foo.edu” have Id columns of “0”. That’s because we haven’t pressed the Save button yet, so the IDs haven’t been generated yet. Once the SaveChanges code is executed, the Id values will automatically be updated (although you’ll probably want to fiddle with WPF settings to hide the Id values anyway).
Not only is the DataServiceCollection handy for data binding, it’s also useful for the client side of paging.
Footnote
[19] The handy shim does pull all of the data at once into client-side memory, however, so be careful how you use it.