I’m back! I’ve had my work up to my eyeballs the last couple of weeks. Apparently moving to the other side of the world and starting a new job takes a lot of time. So after that excuse, it is time to get started with my new project.
My fiancée has this blog where she blogs about our new life in NZ. And this is interesting to you because…? Well, it isn’t, but it is the reason for this blog post as well as a few more coming soon. On her blog, she posts pictures. These pictures are taken by her or me by a camera with several megapixels. That makes them too big for her blog. So she has to resize them before uploading them. This takes time and is annoying. So I offered to solve it in a somewhat more modern way. By using flickr. So she will upload her images to flickr and then use a little Silverlight application to show them on her blog. So that’s where this blog post is about…the flickrVIEWR…
The final result will look like below. It looks like crap. My blog posts here is just about the technical parts. On my fiancée’s blog, it will be styled and look good and everything. I might add a final blog post covering that part in the future…but tight now it is technology…for the next couple of posts…
The first step in creating a flickr viewer is obviously to have a look at the flickr API. flickr has several APIs, but I have decided to go for the very simple REST API. I first thought I would just have the application show all public images in a specific photoset. But after a little chat with my fiancée, I was told that that was not an option. (Says the girl who doesn’t even know what flickr is…) So we decided to go for 2 different image selection alternatives. The viewer will support either showing all public pictures in a photoset or all public images with a specific tag. It will only show public images. Unfortunately I couldn’t find a good way to show private images, but I guess it doesn’t really matter.
To support the 2 different selection modes, I had to get familiar with 3 different methods in the flickr API, flickr.photosets.getPhotos, flickr.photos.search and flickr.photos.getInfo. Pulling information through a REST API is really simple. It is just a matter of making a HTTP request to using a specifically formatted URL. The response will be an XML string with the requested information. In the case of flickr, the URL looks like this
http://api.flickr.com/services/rest/?method=method_name&api_key=key
and then you append any method parameters to the querystring.
As you can see (if you didn’t just skip my url definition above), you need something called an API key. The API key is a key that you request from flickr. All the API information and API key requests can be made through this page.
So after having had a little look around flickr and taking about 30 seconds to think I came up with the following plan. I’m going to create a photo service interface that will be used by my view models. The reason for creating a service interface, is that it makes it possible for me to exchange the service implementation to support unit testing. It also makes it simple to solve a problem I happened to experience. I had no internet connection for a while, and couldn’t call flickr. By being able to create a new service implementation, I can run the service against a local service until the internet connection is back. Kind of sweet…
The service will be very simple. It’s going to support 2 methods and 6 events. It might seem odd to have 6 events and only 2 methods, but that is due to Silverlight’s asynchronous nature. Each method needs to be able to notify the user of failures, download progress and completion of its task. So the interface for the IPhotoService looks like this
public interface IPhotoService
{
void GetImage(IPhotoInfo photo, flickVIEWR.Interfaces.ImageSize size);
void GetPhotos(flickVIEWR.Interfaces.PhotoSearchType searchType, string searchParameter);
event EventHandler<ExceptionEventArgs> DownloadFailed;
event EventHandler<flickVIEWR.Interfaces.DownloadProgressEventArgs> DownloadProgressChanged;
event EventHandler<ImageEventArgs> ImageDownloadCompleted;
event EventHandler<ExceptionEventArgs> ImageDownloadFailed;
event EventHandler<flickVIEWR.Interfaces.DownloadProgressEventArgs> ImageDownloadProgressChanged;
event EventHandler<PhotoInfoEventArgs> PhotoDownloaded;
}
If you look at the interface above, you can see that I’m using another interface, IPhotoInfo, and a couple of EventArgs. The IPhotoInfo interface is visible below. The EventArg objects are not going to be shown here. They are simple. They all have 1 custom property, as indicated by the name. ExceptionEventArgs has an Exception property, DownloadProgressEventArgs has an integer indicating download progress. The only interesting one is the ImageEventArgs, which has a byte array property that contains the bytes representing the image.
public interface IPhotoInfo
{
string Title { get; }
string Description { get; }
}
IPhotoInfo is REALLY simple as you can see, but it needs to be there… The final two things in my service interface definition are two enums. One called ImageSize and one called PhotoSearchType. These are basically just a way to not have to pass around strings.
public enum PhotoSearchType : byte
{
Tag,
Group
}
public enum ImageSize : byte
{
Thumbnail,
Large,
Original
}
The ImageSize enum shows that we support 3 sizes. The flickr service I’m going to build will however only support thumbnail and large at the moment. flickr treats the original a little differently, so I have postponed that…not that it is a problem…but not really in scope for this…
So…let’s have a look at the actual service implementation. It will as I said be working with flickr and I will therefore call it flickrPhotoService. flickr, as I said before, needs an API key to work. I will also need and NSID if getting images by tag. NSID is a user id in flickr. It is basically just a string. So to support this I created a constructor conveying this need.
public class flickrPhotoService : IPhotoService
{
public flickrPhotoService(string apiKey,string nsid)
{
ApiKey = apiKey;
NSID = nsid;
}
private string ApiKey { get; set; }
private string NSID { get; set; }
}
My service will as I said over and over make calls to flickr. These calls are made using a preset format. Due to this I created a couple of helper properties and helper methods. These helper methods and properties are just there to make everything a little easier…
private string GetImageUrl(flickrPhotoInfo photo, string sizePostFix)
{
return string.Format(ApiImageUrlFormat, photo.Farm, photo.Server, photo.PhotoID.ID, photo.PhotoID.Secret, sizePostFix);
}
private string GetPhotoInfoUrl(string method, Dictionary<string, string> parameters)
{
StringBuilder url = new StringBuilder(string.Format(ApiMethodUrlFormat, method, ApiKey));
foreach (var key in parameters.Keys)
{
url.AppendFormat("&{0}={1}", key, HttpUtility.UrlEncode(parameters[key]));
}
return url.ToString();
}
private string ApiMethodUrlFormat { get { return "http://api.flickr.com/services/rest/?method={0}&api_key={1}"; } }
private string ApiImageUrlFormat { get { return "http://farm{0}.static.flickr.com/{1}/{2}_{3}{4}.jpg"; } }
As you can see the GetImageUrl just wraps a String.Format call. The GetPhotoInfoUrl is doing a bit more. I builds a request url by using the standard format and then appending each parameter passed into the method.
I also decided to wrap some other stuff in some nice helpers. Since the service will be making web requests I decided to wrap that functionality. I did this by creating 2 methods. One for downloading string, of in this case for calling flickr methods and getting Xml back, and one for downloading binary data (images). The methods took some useful delegates as parameters…
private void DownloadString(string url,
DownloadProgressChangedEventHandler progressChangedHandler,
DownloadStringCompletedEventHandler completedHandler)
{
WebClient webclient = new WebClient();
if (progressChangedHandler != null)
webclient.DownloadProgressChanged += progressChangedHandler;
if (completedHandler != null)
webclient.DownloadStringCompleted += completedHandler;
webclient.DownloadStringAsync(new Uri(url.ToString()));
}
private void OpenRead(string url,
DownloadProgressChangedEventHandler progressChangedHandler,
OpenReadCompletedEventHandler completedHandler)
{
WebClient webclient = new WebClient();
if (progressChangedHandler != null)
webclient.DownloadProgressChanged += progressChangedHandler;
if (completedHandler != null)
webclient.OpenReadCompleted += completedHandler;
webclient.OpenReadAsync(new Uri(url.ToString()));
}
So, why would I wrap that instead of just calling it manually…well…they are actually just being used once. So from a refactoring point of view, I don’t get that much…but it keeps my methods a little simpler as well as focused on their task…
Before I start showing even more helpers, I will show you some of the actual service interface members. As I said, the interface defines 2 methods and 6 events. The events are just added to the class. They have no functionality, which makes it useless to show them all. So in the code below, I only show one. However, they all look and work the same way…
protected void OnPhotoDownloaded(IPhotoInfo photoInfo)
{
if (PhotoDownloaded != null)
PhotoDownloaded(this, new PhotoInfoEventArgs(photoInfo));
}
public event EventHandler<PhotoInfoEventArgs> PhotoDownloaded;
The two method on the other hand need a bit more explanation… Actually, they might not, but just posting the code does not make it a good blog post…
The GetPhotos method starts off by defining what method to call on flickr. It also creates the necessary parameters. After this it gets the url to call by calling my helper…
public void GetPhotos(PhotoSearchType searchType, string searchParameter)
{
string method;
Dictionary<string,string> parameters = new Dictionary<string,string>();
if (searchType == PhotoSearchType.Tag)
{
method = "flickr.photos.search";
parameters.Add("user_id",NSID);
parameters.Add("tags", searchParameter);
parameters.Add("content_type","image");
}
else
{
method = "flickr.photosets.getPhotos";
parameters.Add("photoset_id", searchParameter);
}
string url = GetPhotoInfoUrl(method, parameters);
After that it is time to call flickr. Fortunately this is not that hard. All I have to do is call the DownloadString method passing in the url and a couple of handlers… I have actually decided to not listen to the download progress event. For my tiny little Xml string, it will basically just go 0 and then 100. So I skip that by passing in null for that handler.
DownloadString(url, null,
delegate(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null)
{
OnDownloadFailed(e.Error);
return;
}
XDocument doc = XDocument.Parse(e.Result);
XElement rspElement = doc.Descendants(XName.Get("rsp")).First();
flickrException ex = flickrException.Parse(rspElement);
if (ex != null)
{
OnDownloadFailed(ex);
return;
}
// More to come...
}
);
}
As you can see, it isn’t that complicated if you are used to anonymous methods. The first thing to do in the DownloadString event is to see if there has been an error. If there has, I notify the user. If not, I parse the result, that is the Xml string downloaded. The first thing to do after the parse it to see if the response from flickr contains an error code. If it does, I create a new flickrException and pass it to the user by raising the DownloadFailed event.
Error code I say…what is that? Well…flickr returns any payload inside a “rsp” element. If that elements “stat” attribute contains the value “fail”, then there is an exception from flickr inside. All this is documented on flickr’s web…have a look if you wonder what I am talking about…
Next it is time to parse the payload from flickr. I do this by using a little helper.
flickrPhotoId[] ids = ParsePhotosPayload(rspElement);
_imageCount = ids.Length;
UpdateDownloadProgress();
foreach (var id in ids)
{
GetPhotoInfo(id);
}
I will show you how it is implemented in a second. However, first I want to explain what else I am doing. Since I am not listening to download progress change for string downloads, I make some fake download progress data. I count every method to flickr as an equal part. So after every request to flickr I update the download progress. I do this in UpdateDownloadProgress. It is simple.
private void UpdateDownloadProgress()
{
_downloads++;
_downloadProgress = (100.0 / (_imageCount + 1)) * _downloads;
OnDownloadProgressChanged((int)Math.Ceiling(_downloadProgress));
}
After updating the download progress after that first call, I start calling flickr for each picture… This is done in the GetPhotoInfo method. It looks a lot like the GetPhotos method. The only change is actually just at the end. Where I handle the payload from flickr.
private void GetPhotoInfo(flickrPhotoId id)
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
string method = "flickr.photos.getInfo";
parameters.Add("photo_id", id.ID);
parameters.Add("secret", id.Secret);
string url = GetPhotoInfoUrl(method, parameters);
DownloadString(url, null,
delegate(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null)
{
OnDownloadFailed(e.Error);
GetPhotoInfo(id);
return;
}
XDocument doc = XDocument.Parse(e.Result);
XElement rspElement = doc.Descendants(XName.Get("rsp")).First();
flickrException ex = flickrException.Parse(rspElement);
if (ex != null)
{
OnDownloadFailed(ex);
return;
}
UpdateDownloadProgress();
flickrPhotoInfo photo = ParsePhotoInfoPayload(rspElement);
OnPhotoDownloaded(photo);
}
);
}
So when I get the payload, I parse it to get a new flickrPhotoInfo object. Then I notify the user that I have downloaded an image. I do this by raising the PhotoDownloaded event. So as you see, this event will possibly be raised several times per GetPhotos call. The parse method is just a few Xml things…
private flickrPhotoInfo ParsePhotoInfoPayload(XElement rspElement)
{
XElement el = rspElement.Element(XName.Get("photo"));
string id = el.Attribute(XName.Get("id")).Value;
string secret = el.Attribute(XName.Get("secret")).Value;
flickrPhotoId photoId = new flickrPhotoId() { ID = id, Secret = secret };
string title = el.Element(XName.Get("title")).Value;
string description = el.Element(XName.Get("description")).Value;
string server = el.Attribute(XName.Get("server")).Value;
string farm = el.Attribute(XName.Get("farm")).Value;
return new flickrPhotoInfo() { PhotoID = photoId, Title = title, Description = description, Server = server, Farm = farm };
}
This post is turning in to being ridiculously long…sorry about that. It just felt good to keep the entire service implementation in one post. So there is one post per section of the implementation. So please bear with me…there is just one more method to cover…the GetImage method.
It starts out by verifying that the passed in IPhotoInfo object is really a flickrPhotoInfo object. Just a little precaution…in this case it won’t ever be something else…but who knows about the future…
Next if checks the ImageSize to see what size to download. flickr defines the size by postfixing the filename in the url. In the case of a thumbnail it postfixes using “_s” and in the case of what flickr calls medium the don’t use a postfix. So the start of the method looks like this…
public void GetImage(IPhotoInfo photo, ImageSize size)
{
flickrPhotoInfo photoInfo = photo as flickrPhotoInfo;
if (photoInfo == null)
{
throw new ArgumentException("photo is of wrong type","photo");
}
string sizePostFix = "";
switch (size)
{
case ImageSize.Thumbnail:
sizePostFix = "_s";
break;
case ImageSize.Original:
throw new NotImplementedException();
}
Next it passes those values to the GetImageUrl method. After that it is just a matter of calling the OpenRead methos passing in the correct values…
string url = GetImageUrl(photoInfo, sizePostFix);
OpenRead(url,
delegate(object sender, DownloadProgressChangedEventArgs e)
{
OnImageDownloadProgressChanged(e.ProgressPercentage);
},
delegate(object sender, OpenReadCompletedEventArgs e)
{
if (e.Error != null)
{
OnImageDownloadFailed(e.Error);
return;
}
byte[] image = new byte[e.Result.Length];
e.Result.Read(image, 0, image.Length);
e.Result.Close();
OnImageDownloadCompleted(image);
}
);
The only interesting part here is that I convert the download stream to an array of bytes. The reason for this is that I don’t want to pass around an open stream… And there is actually another reason for it that you will see later. My view models will actually use a byte array anyway for different reasons…
So…that’s it. That’s the interface for my photo service as well as an implementation that works for flickr… The next post will cover my view models. There will be 2 models. One for the collection of photos and one for a single photo. But more about this later.
I’m really sorry for the post turning so freaking long. Due to this I also want to thank you a lot for sticking with my ramblings and reading it all. I do suggest reading all the posts, as they might be a little informative. But I also want to mention that all the code will be posted at the end of the last post. That is at the end of the third post from now… At least I think there will be three more. And then possibly a fourth showing how to style it so it looks good….or at least ok…
So…thanks for reading and don’t forget to come back for more. If you have any suggestions or comments, don’t hesitate to send me a mail or add a comment! Cheers for now!