WCF Web API: Strongly typed Resource Linker

I’ve been learning and experimenting a lot of WCF as part of my new job this week on Tellago.

Here is something that I did this week and I’ll like to share with my readers.

This post uses the WCF Web APIs Preview 3, releases on Jan 14 of 2011. The API might change in the near future.

One of the most important things about building Http-based REST services (wait! resources Guiño) is the location.

Let’s start with a pretty basic example (this is from ContactManager; the sample application at wcf.codeplex.com):

[ServiceContract]
[Export]
public class ContactsResource
{
    private readonly IContactRepository repository;

    [ImportingConstructor]
    public ContactsResource(IContactRepository repository)
    {
        this.repository = repository;
    }

    [WebGet(UriTemplate = "")]
    public List<Contact> Get()
    {
        return this.repository.GetAll();
    }

    [WebInvoke(UriTemplate = "", Method = "POST")]
    public Contact Post(Contact contact, HttpResponseMessage response)
    {
        this.repository.Post(contact);
        response.StatusCode = HttpStatusCode.Created;
        return contact;
    }
}

[ServiceContract]
[Export]
public class ContactResource
{
   private readonly IContactRepository repository;

   [ImportingConstructor]
   public ContactResource(IContactRepository repository)
   {
       this.repository = repository;
   }

   [WebGet(UriTemplate = "{id}")]
   public Contact Get(string id, HttpResponseMessage response)
   {
       var contact = this.repository.Get(int.Parse(id, CultureInfo.InvariantCulture));
       if (contact == null)
       {
           response.StatusCode = HttpStatusCode.NotFound;
           response.Content = new StringContent("Contact not found");
       }

       return contact;
   }

   [WebInvoke(UriTemplate = "{id}", Method = "PUT")]
   public Contact Put(string id, Contact contact, HttpResponseMessage response)
   {
       this.Get(id, response);
       this.repository.Update(contact);
       return contact;
   }

   [WebInvoke(UriTemplate = "{id}", Method = "DELETE")]
   public Contact Delete(string id)
   {
       var intId = int.Parse(id, CultureInfo.InvariantCulture);
       dynamic deleted = this.repository.Get(intId);
       this.repository.Delete(intId);
       return deleted;
   }
}

This article is focused in the method Post of the ContactsResource class.

The highlighted line set the status of the response to Created (Http Code = 202). In addition to that, I’d like to add to the header response the location indicating where this new “resource” is now.

We can do something like this:

response.StatusCode = HttpStatusCode.Created;
response.Header.Location = ..... + "contact/" +  contact.Id;

Where “contact” come from? Well, this is in the RouteTable, which is configured in the Global.asax.cs class as follows:

RouteTable.Routes.AddServiceRoute<ContactResource>("contact", configuration);
RouteTable.Routes.AddServiceRoute<ContactsResource>("contacts", configuration);

 

The problem

The problem with writing the route of the newly created resource in code as I did in the previous section, is not about “magic strings” or strongly-typeness; I can’t care less about magic strings because I am used to write tests. The main problem is:

  • Duplication; What is the uri template for this method? and How do I construct an Uri for this method?
  • I don’t know how is the URI template at this point without going to the method and looking at the attribute; then I’ve to go to the configuration of the routes table to see the prefix for the resource class.

The solution

I am going to use a little bit of reflection and the RouteTables to get the uri of the resource. I started with a some simple tests as follows:

[TestFixture]
public class ResourceLinkerTests
{
    public class SampleResource
    {
        [WebGet]
        public string GetSomething()
        {
            return string.Empty;
        }

        [WebGet(UriTemplate = "Zapato/{id}/{other}")]
        public string GetFoo(string id, 
                            string other, 
                            double somethingElseThanIsNotInTheRoute)
        {
            return string.Empty;
        }

        [WebInvoke]
        public void PostFoo() {}
        
        [WebInvoke(UriTemplate = "PostBar?lele={id}")]
        public void PostBar(int id) {}

        public void NotAWebMethod() {}
    }
    
    [SetUp]
    public void SetUp()
    {
        RouteTable.Routes.AddServiceRoute<SampleResource>("SuperResource", null);
    }
    
    [Test]
    public void CanGetRouteWithSimpleCase()
    {
        var uriResolver = new ResourceLinker("http://foo.bar");
        var result = uriResolver.GetUri<SampleResource>(sr => sr.GetSomething());
        result.ToString()
            .Should().Be.EqualTo("http://foo.bar/SuperResource/GetSomething");
    }


    [Test]
    public void CanGetValuatedUri()
    {
        var uriResolver = new ResourceLinker("http://foo.bar");
        var result = uriResolver.GetUri<SampleResource>(sr => sr.GetFoo("1", "2", 5467));
        result.ToString()
            .Should().Be.EqualTo("http://foo.bar/SuperResource/Zapato/1/2");
    }

    [Test]
    public void CanGetUriForWbeInvokeWithouAttribute()
    {
        var uriResolver = new ResourceLinker("http://foo.bar");
        var result = uriResolver.GetUri<SampleResource>(sr => sr.PostFoo());
        result.ToString()
            .Should().Be.EqualTo("http://foo.bar/SuperResource/PostFoo");
    }

    [Test]
    public void CanGetAnUriForWebInvokeWithUriTemplate()
    {
        var uriResolver = new ResourceLinker("http://foo.bar");
        var result = uriResolver.GetUri<SampleResource>(sr => sr.PostBar(123));
        result.ToString()
            .Should().Be.EqualTo("http://foo.bar/SuperResource/PostBar?lele=123");
    }

    [Test]
    public void WhenTheMethodIsNotAWebMethodThenThrow()
    {
        var uriResolver = new ResourceLinker("http://foo.bar");
        uriResolver.Executing(ur => ur.GetUri<SampleResource>(sr => sr.NotAWebMethod()))
                    .Throws();
    }

    [TearDown]
    public void TearDown()
    {
        RouteTable.Routes.Clear();
    }
}

As you might noticed, the SUT is the new class ResourceLinker. The SampleResource is a sample class that I use for testing the resolution of the URIs.

ResourceLinker has only one method named GetUri. I pass a lambda to the method ( Expression<Action<TResource>> ) with the parameters.

The GetFoo method has a parameter that is not included in the UriTemplate. This is really important because the Processor infrastructure of the WCF web api allows you to inject in these methods other things.

The first problem I faced was that calling AddServiceRoute within a unit test throws the following exception:

SetUp : System.InvalidOperationException : 'ServiceHostingEnvironment.EnsureServiceAvailable' cannot be invoked within the current hosting environment. This API requires that the calling application be hosted in IIS or WAS.

So, I made a wrapper around ServiceRoute as follows:

public interface IPrefixedRouteType
{
    Type ServiceType { get; }
    string RoutePrefix { get; }
}

public class LazyServiceRoute : RouteBase, IPrefixedRouteType
{
    private readonly string routePrefix;
    private readonly HttpHostConfiguration httpHostConfiguration;
    private readonly Type serviceType;
    private readonly Type webHttpServiceHostFactory;
    private readonly Lazy<ServiceRoute> lazyServiceRoute;

    public LazyServiceRoute(string routePrefix, 
                            HttpHostConfiguration httpHostConfiguration, 
                            Type serviceType)
        :this(routePrefix, httpHostConfiguration, serviceType, typeof(WebHttpServiceHostFactory))
    {}

    public LazyServiceRoute(string routePrefix, 
                            HttpHostConfiguration httpHostConfiguration, 
                            Type serviceType, 
                            Type webHttpServiceHostFactory)
    {
        this.routePrefix = routePrefix;
        this.httpHostConfiguration = httpHostConfiguration;
        this.serviceType = serviceType;
        this.webHttpServiceHostFactory = webHttpServiceHostFactory;
        lazyServiceRoute = new Lazy<ServiceRoute>(CreateServiceRoute);
    }

    private ServiceRoute CreateServiceRoute()
    {
        var hostFactory = (IConfigurableServiceHostFactory)Activator.CreateInstance(webHttpServiceHostFactory);
        hostFactory.Configuration = httpHostConfiguration;
        return new ServiceRoute(routePrefix, (ServiceHostFactoryBase)hostFactory, serviceType);
    }

    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        return lazyServiceRoute.Value.GetRouteData(httpContext);
    }

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        return lazyServiceRoute.Value.GetVirtualPath(requestContext, values);
    }

    public Type ServiceType
    {
        get { return serviceType; }
    }

    public string RoutePrefix
    {
        get { return routePrefix; }
    }
}

This is a really simple class, it has the same constructors than the original “ServiceRoute” but it delays the creation of the ServiceRoute until the first usage as a route (see the field lazyServiceRoute). On the other hand LazyServiceRoute exposes the ServiceType and the RoutePrefix.

To get some syntax sugar I created the following extension methods to the RouteCollection:

public static class RouteTableExtensions
{
    public static void AddLazyServiceRoute<T>(
                this RouteCollection routeCollection, 
                string routePrefix, 
                HttpHostConfiguration httpHostConfiguration)
    {
        routeCollection.Add(new LazyServiceRoute(routePrefix, httpHostConfiguration, typeof(T)));
    }

    public static string GetRoutePrefixForType<T>(this RouteCollection routeCollection)
    {
        var routeServiceType = routeCollection
                                    .OfType<IPrefixedRouteType>()
                                    .FirstOrDefault(r => r.ServiceType == typeof (T));
        if(routeServiceType != null) return routeServiceType.RoutePrefix;
        return null;
    }
}

The first method is a shortcut for inserting a LazyServiceRoute to the route collection. The second one returns the prefix for a given resource type.

Finally the implementation of the ResourceLinker is pretty straightforward (despite the amount of reflection)

public interface IResourceLinker
{
    Uri GetUri<T>(Expression<Action<T>> restMethod);
}

public class ResourceLinker : IResourceLinker
{
    private readonly Uri baseUri;
    
    public ResourceLinker()
        : this(ConfigurationManager.AppSettings["BaseUri"])
    {}

    public ResourceLinker(string baseUri)
    {
        this.baseUri = new Uri(baseUri, UriKind.Absolute);
    }

    public Uri GetUri<T>(Expression<Action<T>> restMethod)
    {
        var methodCallExpression = (MethodCallExpression) restMethod.Body;
        var uriTemplateForMethod = GetUriTemplateForMethod(methodCallExpression.Method);

        var args = methodCallExpression.Method
            .GetParameters()
            .Where(p => uriTemplateForMethod.Contains("{" + p.Name + "}"))
            .ToDictionary(p => p.Name, p => ValuateExpression(methodCallExpression, p));

        var prefix = RouteTable.Routes.GetRoutePrefixForType<T>();
        var newBaseUri = new Uri(baseUri, prefix);
        var uriMethod = new UriTemplate(uriTemplateForMethod, true);
        return uriMethod.BindByName(newBaseUri, args);
    }

    private static string ValuateExpression(MethodCallExpression methodCallExpression, ParameterInfo p)
    {
        var argument = methodCallExpression.Arguments[p.Position];
        var constantExpression = argument as ConstantExpression;
        if(constantExpression != null) return constantExpression.Value.ToString();

        //var memberExpression = (argument as MemberExpression);
        var lambdaExpression = Expression.Lambda(argument, Enumerable.Empty<ParameterExpression>());
        var result = lambdaExpression.Compile().DynamicInvoke().ToString();
        return result;
    }

    private static string GetUriTemplateForMethod(MethodInfo method)
    {
        var webGet = method.GetCustomAttributes(true).OfType<WebGetAttribute>().FirstOrDefault();
        if (webGet != null) return webGet.UriTemplate ?? method.Name;

        var webInvoke = method.GetCustomAttributes(true).OfType<WebInvokeAttribute>().FirstOrDefault();
        if (webInvoke != null) return webInvoke.UriTemplate ?? method.Name;

        throw new InvalidOperationException(string.Format("The method {0} is not a web method.", method.Name));
    }
}

And the code for the Post method is:

[WebInvoke(UriTemplate = "", Method = "POST")]
public Contact Post(Contact contact, HttpResponseMessage response)
{
    this.repository.Post(contact);
    response.StatusCode = HttpStatusCode.Created;
    response.Header.Location =
       resourceLinker.Get<ContactResource>(cr => cr.Get(contact.Id, null));
    return contact;
}

(assuming you inject IResourceLinker in your resource class)

Testing

When I was about to write my first test with the IResourceLinker I tried to mock it. But then I figure out that is better to use the real implementation along with the real routes.

The first step is to extract the RouteTable configuration to another class;

public static class RouteConfigurator
{
    public static void Configure(HttpHostConfiguration configuration)
    {
        RouteTable.Routes.AddServiceRoute<ContactResource>("contact", configuration);
        RouteTable.Routes.AddServiceRoute<ContactsResource>("contacts", configuration);
    }
}

Then, we can write a base test fixture like this:

public class ResourceBaseTestFixture
{
    protected const string SampleHostRoute 
        = "http://foo.bar";
    protected IResourceLinker resourceLinker 
        = new ResourceLinker(SampleHostRoute);

    [TestFixtureSetUp]
    public void SetUp()
    {
        RouteTableConfigurator.Configure(null);    
    }

    [TestFixtureSetUp]
    public void TearDown()
    {
        RouteTable.Routes.Clear();
    }
}

And then you can test as follows:

[Test]
public void WhenPOSTingAContactThenReturn202AndTheLocationOfTheContact()
{
    //initilization and mock of the repository.

    var httpResponseMessage = new HttpResponseMessage(); 
    
    //act
    resource.Post(new Contact { .... }, httpResponseMessage);

    string locationOfTheNewContact =
        resourceLinker.GetUri<ContactResource>(cr => cr.Get(123, null)).ToString();

    httpResponseMessage.Satisfy(r => r.StatusCode == HttpStatusCode.Created
                                  && r.Headers.Location.ToString() == locationOfTheNewContact);
}

And then I realized the first thing I should write when creating a rest service will be something like this:

[Test]
public void TheRouteForPostAContactShouldBeOk()
{
    resourceLinker.GetUri<Contacts>(cr => cr.Create(null, null)).ToString()
        .Should().Be.EqualTo(SampleHostRoute + "/Contacts");
}

[Test]
public void TheRouteForRetrievingASingleContactShouldBeOk()
{
    resourceLinker.GetUri<Contact>(cr => cr.Get(123, null)).ToString()
        .Should().Be.EqualTo(SampleHostRoute + "/Contact/123");
}

But that is a matter of taste, I guess Sonrisa

Happy RESTing and stay in sync, I’ll start writing some articles about WCF.


blog comments powered by Disqus
  • Categories

  • Archives