IResourceLinker for Wcf Web API

I wrote an approach to make -resource linking- easier with the new Web Api in this post. This is an alternative approach and I think it is much better.

This time I wrote the Uri template in only one place;

[ServiceContract, WithUriPrefix("products")]
public class ProductsResourceHandler
{
    [WebGet(UriTemplate = "")]
    public Product[]  Get()
    {
        //handle 
    }
    
    [WebGet(UriTemplate = "{productId}")]
    public Product GetProductById(int productId)
    {
        //handle 
    }
}

Now, if I with my uri template there; I can register all routes as follows:

public static class RouteTableConfigurator
{
    public static void Configure(IHttpHostConfigurationBuilder builder)
    {
        var routeInfo = typeof (RouteTableConfigurator)
            .Assembly.GetTypes()
            .Where(t => Attribute.IsDefined(t, typeof (ServiceContractAttribute))
                        && Attribute.IsDefined(t, typeof (WithUriPrefix)))
            .Select(t => new{ ServiceType = t, UriPrefix = GetPrefix(t)});

        var routes = routeInfo
            .Select(rinfo => new ServiceRoute(rinfo.UriPrefix, 
                                              new HttpConfigurableServiceHostFactory
                                                    {
                                                        Builder = builder
                                                    }, 
                                              rinfo.ServiceType));


        foreach (var route in routes)
        {
            RouteTable.Routes.Add(route);
        }
    }

    private static string GetPrefix(Type t)
    {
        return t.GetCustomAttributes(typeof(WithUriPrefix), true)
                .OfType<WithUriPrefix>()
                .First().Uri;
    }
}

This only works if you can register everything with the same configuration, otherwise you will must to define other set of conventions. But for this example it works

Then my IResourceLinker interface is:

public interface IResourceLinker
{
    string GetUri<T>(Expression<Action<T>> method, object uriArgs = null);
}

How the implementation should work?

[TestFixture]
public class LocationsSpecifications
{
    
    protected IResourceLinker resourceLinker 
        = new ResourceLinker("http://foo.bar");
    
    [Test]
    public void TheUriOfProductsIsOk()
    {
        resourceLinker.GetUri<ProductsResourceHandler>(pr => pr.Get())
            .Should().Be.EqualTo("http://foo.bar/products");
    }

    [Test]
    public void TheUriToGetAProductIsOk()
    {
        resourceLinker.GetUri<ProductsResourceHandler>(pr => pr.GetProductById(0), 
                                                       new{productId = 123})
            .Should().Be.EqualTo("http://foo.bar/products/123");
    }
}

And the implementation is:

public class ResourceLinker : IResourceLinker
{
    private readonly Uri baseUri;

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

    public string GetUri<T>(Expression<Action<T>> method, object uriArgs = null)
    {
        string prefix = GetServicePrefix<T>();

        var methodInfo = ((MethodCallExpression)method.Body).Method;
        var methodTemplate = GetUriTemplateForMethod(methodInfo);
    
        var newBaseUri = new Uri(baseUri, prefix);
        var uriTemplate = new UriTemplate(methodTemplate, true);

        return uriTemplate.BindByName(newBaseUri, ToDictionary(uriArgs ?? new{})).ToString();
    }

    public static IDictionary<string, string> ToDictionary(object anonymousInstance)
    {
        var dictionary = anonymousInstance as IDictionary<string, string>;
        if (dictionary != null) return dictionary;

        return TypeDescriptor.GetProperties(anonymousInstance)
            .OfType<PropertyDescriptor>()
            .ToDictionary(p => p.Name, p => p.GetValue(anonymousInstance).ToString());
    }

    private static string GetServicePrefix<T>()
    {
        var withUriPrefixAttribute = typeof (T)
                            .GetCustomAttributes(typeof (WithUriPrefix), true)
                            .OfType<WithUriPrefix>()
                            .FirstOrDefault();

        if(withUriPrefixAttribute == null )
        {
            var message = string.Format("Can't find the WithUriPrefix in {0}", typeof(T).Name);
            throw new InvalidOperationException(message);
        }
        return withUriPrefixAttribute.Uri;
    }

    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;

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

I am still not happy with this, but it is nicer and less invasive than the first aproach.


blog comments powered by Disqus
  • Categories

  • Archives