I Did Say I was an OData Newbie

Shortly after publishing my last OData post, I got an email from Phani who took me to task over my statement that there was no access to the ImageUri property. In fact, the image is a media resource (and thus not embedded in the feed itself) and there’s a simple way to access the URI as Phani explained.

(When I say “took me to task” I mean he very politely pointed out there was a better way of doing it. BTW Phani has written an OData Browser for WP7 and there’s load of useful information about OData and WP7 on his blog)

In essence, you make a call to DataServiceContext.GetReadStreamUri() passing the entity whose stream URI you want. Our original OData request results in a collection of Image entities – call GetReadStreamUri() passing each of those and we can get ourselves the URI for each image.

The big benefit here – aside from it being the right way to do it :) - is that this is guaranteed to be the correct URI for the image; the service is returning it as such. Using the string formatting technique I’d employed makes assumptions about the URI format. It may work today but equally it may fail in the future.

So, what changes do we need to make to use the new found knowledge?

Firstly, we can ditch our value converter. You can delete it from the project and feel good about it. You should also delete the lines we added to the PhoneApplicationPage xaml to create an instance of the value converter:

 xmlns:local="clr-namespace:WindowsPhoneApplication1"

and

 <local:ThumbnailConverter x:Key="ThumbnailConverter" />

We’ll also change the binding property in our ItemTemplate in a moment or two.

We need to handle the LoadCompleted event on the DataServiceCollection. When data is received from the service, we transform the results to give us a collection of objects with a Uri property suitable for binding to the ListBox.

First up we’ll need a new class with the appropriate property we can bind to. Something like

 public class ImageBinder
{
  public Uri ImageUri { get; set; }
}

is about the simplest thing that’ll work. Add a handler for LoadCompleted:

 void images_LoadCompleted(object sender, LoadCompletedEventArgs e)
{
  var images =
    sender as DataServiceCollection<TwitpicOData.Model.Entities.Image>;

  var query = from x in images
              select new ImageBinder
              {
                ImageUri = ctx.GetReadStreamUri(x)
              };

  ListBox1.DataContext = query;
}

I’m using a Linq query to project the results to a different type – the ImageBinder type we created – and then setting the result as the DataContext for the ListBox.

With that we should be good to go. Here’s the full code for the MainPage class (I made a couple of minor changes to the Click event handler and added a using alias for TwitpicOData.Model.Entities as it was a bit of a mouthful):

 using System;
using System.Data.Services.Client;
using System.Linq;
using Microsoft.Phone.Controls;
using TwitpicOData.Model.Entities;
using System.Windows;
using TwitpicEntities = TwitpicOData.Model.Entities;

namespace WindowsPhoneApplication1
{
  public partial class MainPage : PhoneApplicationPage
  {
    TwitpicData ctx = 
      new TwitpicData(new Uri("https://odata.twitpic.com"));

    public MainPage()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
      var images =
        new DataServiceCollection<TwitpicEntities.Image>(ctx);

      string uriString = 
        string.Format("/Users('{0}')/Images?$top=20", textBox1.Text);
      Uri queryUri = 
        new Uri(uriString, UriKind.Relative);
      images.LoadCompleted += 
        new EventHandler<LoadCompletedEventArgs>(images_LoadCompleted);
      images.LoadAsync(queryUri);
    }

    void images_LoadCompleted(object sender, LoadCompletedEventArgs e)
    {
      var images =
        sender as DataServiceCollection<TwitpicEntities.Image>;

      var query = from x in images
                  select new ImageBinder
                  {
                    ImageUri = ctx.GetReadStreamUri(x)
                  };

      ListBox1.DataContext = query;
    }
  }
}

The only other thing to do is change the ItemTemplate as I mentioned so the source on the Image control is bound to the ImageUri property on the ImageBinder object.

 <ListBox.ItemTemplate>
  <DataTemplate>
    <Image Source="{Binding ImageUri}"            
       Margin="0,0,5,5"           
       Width="100"           
        Height="100"/>
  </DataTemplate>
</ListBox.ItemTemplate>

The app doesn’t look any different and it doesn’t perform any better but at least I can take comfort from the fact it’s using the correct mechanism to access those images.