Writing a RESTful WCF Web Service for Active Resource

Thursday, December 2, 2010 by Rick Thomas

I was recently tasked with writing a Ruby on Rails application for iGoDigital’s personalized product recommendations platform that consumes business logic contained in a .NET strongly-typed dataset.  Because this is the first of many times we’ll be doing this, I decided to make the API as easy to consume as possible. Enter ActiveResource –The Rails Way of consuming web services.  ActiveResource is to web services what ActiveRecord is to databases – CRUD operations are automatically wired up for the developer.

In order to take advantage of the simplicity of ActiveResource, the web service it’s consuming must be RESTful. REST-based web services lend themselves perfectly to the Rails MVC architecture. Simply provide the web service an object and an HTTP verb (GET Users, for example, or DELETE Widget), and database-like operations are easily performed over the tubes.

In order to get REST working in the .NET world, I decided to use Windows Communication Foundation to wire up my web service. To get myself started, I found this great screencast by Michael Kennedy that walks viewers through the process of building a basic RESTful web service in WCF. Luckily I already had a .NET 3.5 website consuming the strongly typed dataset, so I added my API to that application. This web application was written in Visual Basic, so that architectural decision was predetermined.

The great advantage of WCF is that it provides a no-brainer approach to determining what an http request is trying to accomplish. Developers familiar with the Rails routes.rb file will feel comfortable with the syntax. When you create a WCF service in Visual Studio, it creates 3 files: A markup page (ServiceName.svc), a code file (ServiceName.svc.vb), and an interface file (IServiceName.svc.vb).  It also adds a bunch of junk to web.config, which Michael Kennedy explains how to delete.

The only change you need to make to the markup page is to add a reference to the WebServiceHostFactory – this is the utility that allows you route incoming service request based on URL patterns. Next we move on to the interface file to define our routes and indicate what methods will handle them. 

Here’s a sample:

<OperationContract()> _

    <WebGet(ResponseFormat:=WebMessageFormat.Xml, UriTemplate:=" /Departments /{DepartmentID}/Employees")> _

    Function GetEmployeesByDepartmentID(ByVal DepartmentID As Integer) As List(Of Employee)

This operation contract will respond to a GET request to http://www.myserver.com/myApp/ServiceName.svc/Departments/123/Employees and make a call to the method GetEmployeesByDepartmentID with DepartmentID 123. Pretty simple! The GetEmployeesByDepartmentID simply return a list of Employee objects, which is a simple serializable business object that contains the data we want (Employee.Name, Employee.Address, etc).  A call to the above URL will result in a response like this:

<ArrayOfEmployee>
<Employee>
<ID>12</ID>
<Name>Bob Smith</Name>
<DepartmentID>123</DepartmentID>
. . . .

The next step is to move over to our rails development environment. We’ll create two models (Department and Employee), and open up the Employee model. In theory, all you’ll need to do is change it to inherit ActiveResource instead of ActiveRecord, and define the URL of the service, and your model is complete.

class Employee < ActiveResource::Base
                self.site = 'http://www.myserver.com/myApp/ServiceName.svc/Department/:department_id/'
end

 

If you were to fire up the Rails console, and run this search:

employees = Employee.find(:all, :params=>{:department_id=>123})
you’ll run into our first problem – a 404 error.  That’s because ActiveResource is appending a format extension to every request, like this:
http://www.myserver.com/myApp/ServiceName.svc/Department/123/employees.xml. Unfortunately, WCF doesn’t handle this extension gracefully (even if you add it to our ServiceContract), so we’re going to have to make ActiveResource behave. To do this, we’ll override the element_path and collection_path methods defined by ActiveResource and remove the .#{format.extension} variable. 

Now, our class will look like this:

class Employee < ActiveResource::Base

                class << self

                                def element_path(id, prefix_options = {}, query_options = nil)

                                  prefix_options, query_options = split_options(prefix_options) if query_options.nil? 

                                  "#{prefix(prefix_options)}#{collection_name}/#{id}#{query_string(query_options)}"

                                end

                                def collection_path(prefix_options = {}, query_options = nil)

                                      prefix_options, query_options = split_options(prefix_options) if query_options.nil?

                                      "#{prefix(prefix_options)}#{collection_name}#{query_string(query_options)}"

                                end

                end

               

                self.site = 'http://www.myserver.com/myApp/ServiceName.svc/Department/:department_id/'
end

 

If we go back to our console and run our search again, we’ll see we’ve moved past our 404 errors, and run into another headache – a hash.collect! error.  After some digging, I discovered that ActiveResource is very particular about the format of the XML it consumes. In particular, if it’s retrieving a collection, it’s looking for root XML tag like <Employees type=”array”> instead of <ArrayOfEmployee>. Alas, this is not easily achieved in our WCF service (at least in our version of VB), so we’ll have to change our approach a little bit.

 

Instead of using WCF’s built-in object serialization, we’re going to have to write our own XML.  Luckily, WCF will happily return an XMLElement, so we’ll change our interface to look like this:

<OperationContract()> _

    <WebGet(ResponseFormat:=WebMessageFormat.Xml, UriTemplate:=" /Departments /{DepartmentID}/Employees")> _

    Function GetEmployeesByDepartmentID(ByVal DepartmentID As Integer) As XmlElement

We’ll go back to our GetEmployeesByDepartmentID method in ServiceName.svc.vb, and change it to return an XmlElement.  In our method, we’ll create an XmlWriter that conforms to the format demanded by ActiveResource, like this:

 

        Dim xdoc As New XmlDocument()

        Dim w As XmlWriter = xdoc.CreateNavigator.AppendChild()

        w.WriteStartDocument()

        w.WriteStartElement("Employees")

        w.WriteAttributeString("type", "array")

        For Each row As Employee In MyDataSet.Employees

            w.WriteStartElement("Employee")

            w.WriteElementString("ID", row.ID)

            w.WriteElementString("Name", row.Name)

            . . . .

            w.WriteEndElement()

        Next

        w.WriteEndElement()

        w.Close()

        Return xdoc.DocumentElement()

 

If we deploy this and return to our Rails console, we’ll find that our employee search will now run successfully. Victory!


Tagged in: TechnologyDev

Comments for Writing a RESTful WCF Web Service for Active Resource

Leave a comment





Captcha