In this second post in the series on implementing a streaming data provider, we show how to use the WCF Data Services client library to access binary data exposed by an Open Data Protocol (OData) feed, as well as has how to upload binary data to the data service. For more information on the streaming provider, see the first blog post in this series: Implementing a Streaming Provider.
In the previous post, we showed how to implement IDataServiceStreamProvider to create a data service that uses OData to store and retrieve binary image files, the media resource (MR) in OData terms, along with metadata about the photos, the media link entry (MLE) in OData terms. The Visual Studio solution for this sample data service, which is published to this MSDN Code Gallery page, also contains a client application project. This client, which consumes feeds from the PhotoData service, accesses and displays image files stored by the data service. Creating a client application that can display photos from and send photos to our PhotoData sample data service requires the following basic steps:
As you may recall, the PhotoData sample data service exposes a single PhotoInfo entity (an MLE) which has a related image file (the MR). The HasStream attribute applied to the PhotoInfo entity tells the client that it is an MLE, as you can see in the PhotoInfo entity metadata returned by the data service:
Now, let’s get started on creating the client application.
Our client application is a Windows Presentation Foundation (WPF) application. This enables us to use data binding of PhotoInfo objects to UI elements.
First let’s create the WPF client application (I won’t go into too much detail here…you can review the XAML that defines these windows in the client project included with the sample PhotoData streaming data service):
Now let’s see how the MLE and MR data from the data service is propagated into these UI elements.
The following steps are required to query the PhotoData service for the data feed and get images for a specific PhotoInfo object.
private PhotoDataContainer context; private DataServiceCollection<PhotoInfo> trackedPhotos; private PhotoInfo currentPhoto; // Get the service URI from the settings file. private Uri svcUri = new Uri(Properties.Settings.Default.svcUri);
private PhotoDataContainer context; private DataServiceCollection<PhotoInfo> trackedPhotos; private PhotoInfo currentPhoto;
// Get the service URI from the settings file. private Uri svcUri = new Uri(Properties.Settings.Default.svcUri);
// Define a query that returns a feed with all PhotoInfo objects. var query = context.PhotoInfo; // Create a new collection for binding based on the executed query. trackedPhotos = new DataServiceCollection<PhotoInfo>(query); // Load all pages of the response into the binding collection. while (trackedPhotos.Continuation != null) { trackedPhotos.Load( context.Execute<PhotoInfo>(trackedPhotos.Continuation.NextLinkUri)); }
// Define a query that returns a feed with all PhotoInfo objects. var query = context.PhotoInfo;
// Create a new collection for binding based on the executed query. trackedPhotos = new DataServiceCollection<PhotoInfo>(query);
// Load all pages of the response into the binding collection. while (trackedPhotos.Continuation != null) { trackedPhotos.Load( context.Execute<PhotoInfo>(trackedPhotos.Continuation.NextLinkUri)); }
// Use the ReadStreamUri of the Media Resource for selected PhotoInfo object // as the URI source of a new bitmap image. photoImage.Source = new BitmapImage(context.GetReadStreamUri(currentPhoto));
The returned URI is used to create the image on the client that is displayed in the UI.
Note: The URI of the MR works very well for creating an image on the client. However, when you need the MR returned as a binary stream instead of just the URI, you must instead use the GetReadStream method. This method returns a DataServiceStreamResponse object that contains the binary stream of MR data, accessible from the Stream property.
The following steps are required to create a new PhotoInfo entity and binary image file in the data service.
// Create a new PhotoInfo object. PhotoInfo newPhotoEntity = new PhotoInfo(); // Ceate an new PhotoDetailsWindow instance with the current // context and the new photo entity. PhotoDetailsWindow addPhotoWindow = new PhotoDetailsWindow(newPhotoEntity, context); addPhotoWindow.Title = "Select a new photo to upload..."; // We need to have the new entity tracked to be able to // call DataServiceContext.SetSaveStream. trackedPhotos.Add(newPhotoEntity); // If we successfully created the new image, then display it. if (addPhotoWindow.ShowDialog() == true) { // Set the index to the new photo. photoComboBox.SelectedItem = newPhotoEntity; } else { // Remove the new entity since the add operation failed. trackedPhotos.Remove(newPhotoEntity); }
// Create a new PhotoInfo object. PhotoInfo newPhotoEntity = new PhotoInfo();
// Ceate an new PhotoDetailsWindow instance with the current // context and the new photo entity. PhotoDetailsWindow addPhotoWindow = new PhotoDetailsWindow(newPhotoEntity, context);
addPhotoWindow.Title = "Select a new photo to upload...";
// We need to have the new entity tracked to be able to // call DataServiceContext.SetSaveStream. trackedPhotos.Add(newPhotoEntity);
// If we successfully created the new image, then display it. if (addPhotoWindow.ShowDialog() == true) { // Set the index to the new photo. photoComboBox.SelectedItem = newPhotoEntity; } else { // Remove the new entity since the add operation failed. trackedPhotos.Remove(newPhotoEntity); }
// Create a dialog to select the image file to stream to the data service. Microsoft.Win32.OpenFileDialog openImage = new Microsoft.Win32.OpenFileDialog(); openImage.FileName = "image"; openImage.DefaultExt = ".*"; openImage.Filter = "Images File|*.jpg;*.png;*.gif;*.bmp"; openImage.Title = "Select the image file to upload..."; openImage.Multiselect = false; openImage.CheckFileExists = true; // Reset the image stream. imageStream = null; try { if (openImage.ShowDialog(this) == true) { if (photoEntity.PhotoId == 0) { // Set the image name from the selected file. photoEntity.FileName = openImage.SafeFileName; photoEntity.DateAdded = DateTime.Today; // Set the content type and the file name for the slug header. photoEntity.ContentType = GetContentTypeFromFileName(photoEntity.FileName); } photoEntity.DateModified = DateTime.Today; // Use a FileStream to open the existing image file. imageStream = new FileStream(openImage.FileName, FileMode.Open); photoEntity.FileSize = (int)imageStream.Length; // Create a new image using the memory stream. BitmapImage imageFromStream = new BitmapImage(); imageFromStream.BeginInit(); imageFromStream.StreamSource = imageStream; imageFromStream.CacheOption = BitmapCacheOption.OnLoad; imageFromStream.EndInit(); // Set the height and width of the image. photoEntity.Dimensions.Height = (short?)imageFromStream.PixelHeight; photoEntity.Dimensions.Width = (short?)imageFromStream.PixelWidth; // Reset to the beginning of the stream before we pass it to the service. imageStream.Position = 0; // Set the file stream as the source of binary stream // to send to the data service. The Slug header is the file name and // the content type is determined from the file extension. // A value of 'true' means that the stream is closed by the client when // the upload is complete. context.SetSaveStream(photoEntity, imageStream, true, photoEntity.ContentType, photoEntity.FileName); return true; } else { MessageBox.Show("The selected file could not be opened."); return false; } } catch (IOException ex) { MessageBox.Show( string.Format("The selected image file could not be opened. {0}", ex.Message), "Operation Failed"); return false; }
// Create a dialog to select the image file to stream to the data service. Microsoft.Win32.OpenFileDialog openImage = new Microsoft.Win32.OpenFileDialog(); openImage.FileName = "image"; openImage.DefaultExt = ".*"; openImage.Filter = "Images File|*.jpg;*.png;*.gif;*.bmp"; openImage.Title = "Select the image file to upload..."; openImage.Multiselect = false; openImage.CheckFileExists = true;
// Reset the image stream. imageStream = null;
try { if (openImage.ShowDialog(this) == true) { if (photoEntity.PhotoId == 0) { // Set the image name from the selected file. photoEntity.FileName = openImage.SafeFileName;
photoEntity.DateAdded = DateTime.Today;
// Set the content type and the file name for the slug header. photoEntity.ContentType = GetContentTypeFromFileName(photoEntity.FileName); }
photoEntity.DateModified = DateTime.Today; // Use a FileStream to open the existing image file. imageStream = new FileStream(openImage.FileName, FileMode.Open);
photoEntity.FileSize = (int)imageStream.Length;
// Create a new image using the memory stream. BitmapImage imageFromStream = new BitmapImage(); imageFromStream.BeginInit(); imageFromStream.StreamSource = imageStream; imageFromStream.CacheOption = BitmapCacheOption.OnLoad; imageFromStream.EndInit();
// Set the height and width of the image. photoEntity.Dimensions.Height = (short?)imageFromStream.PixelHeight; photoEntity.Dimensions.Width = (short?)imageFromStream.PixelWidth;
// Reset to the beginning of the stream before we pass it to the service. imageStream.Position = 0;
// Set the file stream as the source of binary stream // to send to the data service. The Slug header is the file name and // the content type is determined from the file extension. // A value of 'true' means that the stream is closed by the client when // the upload is complete. context.SetSaveStream(photoEntity, imageStream, true, photoEntity.ContentType, photoEntity.FileName);
return true; } else { MessageBox.Show("The selected file could not be opened."); return false; } } catch (IOException ex) { MessageBox.Show( string.Format("The selected image file could not be opened. {0}", ex.Message), "Operation Failed"); return false; }
// Send the update (POST or MERGE) request to the data service and // capture the added or updated entity in the response. ChangeOperationResponse response = context.SaveChanges().FirstOrDefault() as ChangeOperationResponse;
// When we issue a POST request, the photo ID and edit-media link are not updated // on the client (a bug), so we need to get the server values. if (photoEntity.PhotoId == 0) { if (response != null) { entity = response.Descriptor as EntityDescriptor; } // Verify that the entity was created correctly. if (entity != null && entity.EditLink != null) { // Cache the current merge option (we reset to the cached // value in the finally block). cachedMergeOption = context.MergeOption; // Set the merge option so that server changes win. context.MergeOption = MergeOption.OverwriteChanges; // Get the updated entity from the service. // Note: we need Count() just to execute the query. context.Execute<PhotoInfo>(entity.EditLink).Count(); } }
// When we issue a POST request, the photo ID and edit-media link are not updated // on the client (a bug), so we need to get the server values. if (photoEntity.PhotoId == 0) { if (response != null) { entity = response.Descriptor as EntityDescriptor; }
// Verify that the entity was created correctly. if (entity != null && entity.EditLink != null) { // Cache the current merge option (we reset to the cached // value in the finally block). cachedMergeOption = context.MergeOption;
// Set the merge option so that server changes win. context.MergeOption = MergeOption.OverwriteChanges;
// Get the updated entity from the service. // Note: we need Count() just to execute the query. context.Execute<PhotoInfo>(entity.EditLink).Count(); } }
Glenn Gailey Senior Programming Writer WCF Data Services