Ugly Stool Rotating Header Image

December, 2010:

WCF REST Client for SmugMug – 3 of n

The last post covered my failed attempts (to me) to use an XML schema to generate the XML parser.  Instead of continuing down the path of an auto-generated parser, I chose a slightly different approach.  I pushed all communication activities into WCF until the input was .NET methods, and the output was .NET types.  The following graphic better illustrates how WCF was used.

wcf-post-01-a

The magic of the green WCF strip is done with the IClientMessageFormatter interface.  Obra implements this interface, and specifically overrides the DeserializeReply  method to pre-process SmugMug’s XML responses.  DeserializeReply is used to strip the outer <rsp/> element, and the <method/> element from the XML response.

This XML data…

<rsp stat="ok">
  <method>smugmug.images.get</method>
  <Images>
    <Image FileName="image-1.jpg" Format="JPG" id="1001">
      <Album Key="FxWoe" id="1000"/>
    </Image>
  </Images>
</rsp>

…is converted to this XML data.

<Images>
  <Image FileName="image-1.jpg" Format="JPG" id="1001">
    <Album Key="FxWoe" id="1000"/>
  </Image>
</Images>

The pre-processed XML data contains only the relevant type metadata.  All of the extraneous information has been removed.  Writing a .NET class to represent these data are simple.  A class to represent the <Images/> element is show below.  This class is automatically picked up by the WCF XML deserailizer due to the XmlRoot attribute, and because it is located in the same .NET assembly as the WCF client.

[XmlRoot("Images")]
public class ImagesContract
{
    private readonly List<ImageContract> image =
        new List<ImageContract>();
    [XmlElement("Image")]
    public List<ImageContract> Images
    {
        get { return image; }
    }
}

Transforming the XML data to an instance of a class is done by the default implementation of IClientMessageFormatter.  Obra implements the IClientMessageFormatter interface, but it does so only to pre-process the response.  Once the response is pre-processed, Obra passes the response to the default implementation of DeserializeReply.

Obra’s implementation of DeserializeReply also checks for SmugMug error responses.  An example response is shown below.  If an error is encountered, a exception is throw with the information contained in the response.

<rsp stat="fail">
  <err code="17" msg="invalid method"/>
</rsp>

I have shown a better (at least alternative) solution for consuming a REST (POX) web service using WCF.  If you are parsing XML in your client you can do much better.  WCF exposes hooks that obviate the need for it, and WCF makes it trivial to communicate with a web service in a more type-safe manner.  Error conditions can be caught and dealt with earlier, which should make client code more straight-laced.  WCF makes it easy to push communication code away from your client.

In the next post I will cover how Obra uses WCF to send data to SmugMug.

WCF REST Client for SmugMug – 2 of n

In the last post I gave a brief overview of the SmugMug API, and what I wanted WCF to do.  To summarize, I wanted WCF to provide two things: strongly typed objects, and exceptions.

My first attempt to avoid XML parsing was to create an XML schema.  I had hoped to use an XML schema from SmugMug, but unfortunately I could not find one.  Furthermore, the SmugMug API wiki does not provide enough detail to create an XML schema.  SmugMug does a good, but not a great job of documenting the XML responses for all of the API methods.  (Granted, this is a tedious and laborious task, so it is understandable.)

The easiest way I found to generate an XML schema was to get example XML data directly from SmugMug by hand-crafting URI’s and using cURL.

The skeleton of my first attempt at an XML schema is shown below.

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:complexType name="Albums"/>
  <xs:complexType name="Images"/>
  <xs:complexType name="ImageExif"/>
  <xs:complexType name="Response">
    <xs:sequence>
      <xs:element name="method" type="xs:string"/>
      <xs:choice>
        <xs:element name="Albums" type="Albums"/>
        <xs:element name="Images" type="Images"/>
        <xs:element name="Image" type="ImageExif"/>
      </xs:choice>
    </xs:sequence>
  </xs:complexType>
  <xs:element name="rsp" type="Response"/>
</xs:schema>

SmugMug wraps all objects in a response (<rsp/>) element, but only one object is returned in a response, where an object is Albums, Images, or Image.  This is modeled in the schema with a choice element.  The xsd.exe tool does not generate desirable code based on the XML schema shown above.  The generated code is shown below.

[XmlElementAttribute("Albums", typeof(Albums)]
[XmlElementAttribute("Image", typeof(ImageExif)]
[XmlElementAttribute("Images", typeof(Images)]
public object Item {
    get {
        return this.itemField;
    }
    set {
        this.itemField = value;
    }
}

The type information for Albums, Images, and Image has been condensed to a C# object ((System.Object) because it is the only type common to Albums, Images, an Image.  (I potentially could have solved this issue using derivation in the schema if there was commonality across all of SmugMug types I cared about, but I had not bothered to investigate.)

Instead of using a choice element to model the fact that only one object is returned, I could have used a mixture of minOccurs and maxOccurs.  An example schema is shown below.

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:complexType name="Albums"/>
  <xs:complexType name="Images"/>
  <xs:complexType name="ImageExif"/>
  <xs:complexType name="Response">
    <xs:sequence>
      <xs:element name="method" type="xs:string"/>
      <xs:element minOccurs="0" maxOccurs="1" name="Albums" type="Albums"/>
      <xs:element minOccurs="0" maxOccurs="1" name="Images" type="Images"/>
      <xs:element minOccurs="0" maxOccurs="1" name="Image" type="ImageExif"/>
    </xs:sequence>
  </xs:complexType>
  <xs:element name="rsp" type="Response"/>
</xs:schema>

The schema duplicates the behavior of the choice element, but forces the xsd.exe tool to generate type information for the SmugMug objects.  The generated code is shown below.

[XmlElementAttribute]
public Albums Albums {
    get {
        return this.albumsField;
    }
    set {
        this.albumsField = value;
    }
}
[XmlElementAttribute]
public Images Images {
    get {
        return this.imagesField;
    }
    set {
        this.imagesField = value;
    }
}
[XmlElementAttribute]
public ImageExif Image {
    get {
        return this.imageField;
    }
    set {
        this.imageField = value;
    }
}

This code is closer to what I want.  The type is explicit – no casting required.  The objection is the fact that there are potentially many dead types to choose from.  If I am fetching Albums, why should the code ever have to consider anything but the list of Albums.  The calling code has to keep track of the method invoked, and then switch based on the SmugMug method to determine what property to interrogate.  Potentially, the client code devolves into the following.

Response response = this.InvokeSmugMugMethod();
switch (response.method)
{
    case "smugmug.albums.get":
        // do something with albums
        break;
    case "smugmug.images.get";
        // do something with images
}

In my next post I will describe the solution I used to solve these issues.

WCF REST Client for SmugMug – 1 of n

I created an open source project called Obra.  Obra is a PowerShell drive for SmugMug, which basically means you can treat SmugMug as if it was your C:.  You can execute directory listing, rename files, delete files, and look at file metadata.  In the case of SmugMug metadata means the album’s description, image dimensions, EXIF, etc.

I wrote Obra to learn about technologies I have not really used like WCF and PowerShell.  I had previously written a read-only ruby client for SmugMug, but it stalled when I started to implement write-support.  I did not want to lose people’s pictures, and I couldn’t figure out a good way to test my client without re-implementing the SmugMug REST service.  (This was me, not ruby.)  Fast-forward a couple of years, and I was now employed as a C# developer and learning about Nunit and NMock.  I wanted to incorporate these tools into the project as well.

When I started Obra I could not find much material about writing WCF REST clients.  Excluding REST, most of the material seemed biased towards WCF servers, not clients.  Hopefully, this series of posts rectifies that.

The SmugMug API supports REST, JSON, serialized PHP, and provides an XML-RPC service.  In SmugMug’s case REST really means POX, which is the interface Obra uses.  (I assume SmugMug did not re-invent the wheel when they designed their API, and rather based it on the Flickr API; they are very similar.) 

Client requests are HTTP GET’s (with the exception of uploading, which is a POST).  A request encodes the method and parameters in the URI.  For example, the following URI calls a method to get the albums for the session with ID 1ac.

http://api.smugmug.com/hack/rest/1.2.0/?method=smugmug.albums.get&SessionID=1ac

Responses all follow the same basic form.  The root element is <rsp/>, the first child node is <method/> and contains the name of the method called, and the second child node is the response element.  In the case of the method smugmug.albums.get the node is <Albums/>.

<rsp stat="ok">
   <method>smugmug.albums.get</method>
   <albums>
      <album id="0" title="2007-01-01" key="smgms">
         <category id="41" name="Airplanes" />
      </album>
   </albums>
</rsp>

Other methods return different elements for the second child node, such as <Categories/>, <Images/>, and <Login/>.

If the method call resulted in an error an error response is sent instead.  An example error response is shown below.

<rsp stat="fail">
  <err code="17" msg="invalid method" />
</rsp>

I wanted to wrap up this POX API in a tidy package.  I did not want any of the library code to parse XML.  I wanted the code to only operate on strongly typed objects, i.e. Album, Image, etc.  If a method call to SmugMug resulted in an error I wanted an exception to be thrown – avoid checking flags.  More importantly, I did not want to have to write any of this.  I rightly assumed that I code use WCF (and .NET) to handle all of the heavy lifting.  I’ll cover how in the following posts.

Page optimized by WP Minify WordPress Plugin