Monday, March 29, 2010

Playing about with REST on .Net

I’ve been planning to do more work with Azure and iPhone and figured I should embrace the new wave of developments with REST.  This seems to be the way in which the technology is going even for Microsoft.  I’m a bit of a fan of WCF, so figured I’ve give it a go implementing this using everything I’ve learned from that and with a little help from “RESTful .Net” by Jon Flanders it worked out well.

The Server

First step is to create standard VS2010 WCF Service Application project called WCFServer.

image

This will give you all the basic items needed to run an IIS Hosted WCF Service.  I won’t go into the details of these to much as I’ll assume you understand the requirement for deleting or renaming the various classes to fit those I’ve used.

The project will consist of 4 files:

  1. IRESTService.cs; which is an Interface definition for the server contracts.
  2. RESTService.svc; the web service definition file
  3. RESTService.svc.cs; the code behind for the service
  4. web.config; the application configuration and definition file.

IRESTService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WCFServer
{
    [ServiceContract]
    public interface IRESTService
    {
        [OperationContract()]
        [WebGet(UriTemplate="/GetRestData/{value}")]
        string GetRestData(string value);

        [WebGet(UriTemplate = "/")]
        [OperationContract]
        string GetRestDataRoot();
    }
}

My Service has 2 very simple HTTP GET methods, GetRestDataRoot which will return a simple text string and GetRestData which takes a string and then responds with a string.  These are hardly high tech and are only to illustrate hte process.

RESTService.svc

<%@ ServiceHost Language="C#" Debug="true" Service="WCFServer.RESTService" CodeBehind="RESTService.svc.cs" %>

This defines the service file and the link to the code behind.

RESTService.svc

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.ServiceModel.Activation;

namespace WCFServer
{
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class RESTService : IRESTService
    {
        public string GetRestData(string value)
        {
            return string.Format("You entered: {0}", value);
        }

        public string GetRestDataRoot()
        {
            return string.Format("Root");
        }
    }
}

As you can see from the code behind I’m not really doing anything complicated at this stage, just returning strings.

web.config

<?xml version="1.0"?>
<configuration>

…………… < sniped > ………

<httpModules>
   <add name="NoMoreSVC" type="WCFServer.RestModule, WCFServer"/>
   <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</httpModules>

…………… < sniped > ………

  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    <services>
      <service name="WCFServer.RESTService" behaviorConfiguration="WCFServer.RESTServiceBehavior">
        <!-- Service Endpoints -->
        <endpoint address="" binding="webHttpBinding" contract="WCFServer.IRESTService" behaviorConfiguration="web">
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="WCFServer.RESTServiceBehavior">
          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="web">
          <webHttp/>
        </behavior>
      </endpointBehaviors>

    </behaviors>
  </system.serviceModel>
</configuration>

In the web.config I’ve highlighted some of the more interesting elements for implementing REST.  The endpoint definition has defined the binding as “webHttpBinding" which is needed and we set the behaviorConfiguration to “web".  The endpointBehaviors is then defined to use webHttp.

You’ll also notice that I’ve added another called called RestModule and registered this as a httpModule, this was only done to remove the need to access the URL with a .svc on the end.  It’s probably over the top for what I need but it just looked better.

restmodule.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace WCFServer
{
    public class RestModule : IHttpModule
    {
        public void Dispose()
        {
        }

        public void Init(HttpApplication app)
        {
            app.BeginRequest += delegate
            {
                HttpContext ctx = HttpContext.Current;
                string path = ctx.Request.AppRelativeCurrentExecutionFilePath;
                int i = path.IndexOf('/', 2);
                if (i > 0)
                {
                    string svc = path.Substring(0, i) + ".svc";
                    string rest = path.Substring(i, path.Length - i);
                    string qs = ctx.Request.QueryString.ToString();
                    ctx.RewritePath(svc, rest, qs, false);
                }
            };
        }
    }
}

Once you have all this up and running we can test it by running the manipulating the URL in my case (http://localhost:1286/Service1.svc).  If you try to access it via the REST interface you‘ll get the following http://localhost:1286/Service1/

image

and http://localhost:1286/RESTService/GetRestData/Testing123 should return the following:

image

The Client

OK, now we need a client to access the REST server, for this I’ll use a simple UnitTest project.

ServiceTest.cs

using System;
using System.Text;
using NUnit.Framework;
using NMock2;
using System.Data;

namespace UnitTests
{
    [TestFixture]
    public class CompanyObjectTests
    {
        private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType);
        private Mockery _mockSQLDatabse;

        [SetUp]
        public void TestSetup()
        {
            log4net.Config.XmlConfigurator.Configure();
            _log.Info("Starting up for testing");
        }

        [TearDown]
        public void TestShutDown()
        {
            _log.Info("Shutting down test");
        }

        [Test(Description = "Test the service")]
        public void ServiceTest()
        {
            IClientContract client = new ClientContract();
            string result = client.GetRestDataRoot();
            Assert.IsTrue(result == "Root", "The result should be 'Root'");
            _log.DebugFormat("result={0}", result);

            result = client.GetRestData("Testing");
            Assert.IsTrue(result == "You entered: Testing", "The result should be 'You entered: Testing'");
            _log.DebugFormat("result={0}", result);
        }
    }
}

Here I’ve created an interface (IClientContract) and a concrete class (ClientContract).

IClientContract

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace UnitTests
{

    [ServiceContract]
    public interface IClientContract
    {
        [OperationContract]
        [WebGet(
            BodyStyle = WebMessageBodyStyle.Bare,
            ResponseFormat = WebMessageFormat.Xml,
            UriTemplate = ""
            )]
        string GetRestDataRoot();

        [OperationContract]
        [WebGet(
            BodyStyle = WebMessageBodyStyle.Bare,
            ResponseFormat = WebMessageFormat.Xml,
            UriTemplate = "/GetRestData/{value}"
            )]
        string GetRestData(string value);
    }
}

ClientContract

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace UnitTests
{

    public class ClientContract : ClientBase<IClientContract>, IClientContract
    {
        public string GetRestDataRoot()
        {
            return this.Channel.GetRestDataRoot();
        }
        public string GetRestData(string symbol)
        {
            return this.Channel.GetRestData(symbol);
        }
    }
}

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
  </configSections>
  <appSettings>
    <add key="ApplicationName" value="" />
  </appSettings>

  <log4net>
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
      <layout type="log4net.Layout.PatternLayout">
        <param name="Header" value="[Header]\r\n"/>
        <param name="Footer" value="[Footer]\r\n"/>
        <param name="ConversionPattern" value="%d [%t] %-5p %c %m%n"/>
      </layout>
    </appender>
    <root>
      <level value="ALL"/>
      <appender-ref ref="ConsoleAppender"/>
    </root>
  </log4net>

  <system.serviceModel>
    <bindings />
    <client>
      <endpoint address="http://localhost:1286/RESTService.svc"
                behaviorConfiguration="rest"
                binding="webHttpBinding"
                contract="UnitTests.IClientContract"/>
    </client>
    <behaviors>
      <endpointBehaviors>
        <behavior name="rest">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
    </behaviors> 
  </system.serviceModel>
</configuration>

Running this in NUnitGUI will give you green lights all the way.

image

Source Code

All the source code for this project can be found here.