I’m currently building some JSON services for a client using ASP.NET MVC. One of the things I want to implement is HATEOS (read the link) so I need to be able to build the URLs (or URIs) of the endpoints that I can then embed into the JSON I return to the client.
I wanted to be able to take some route values, controller=”abc”, action=”xyz”, id=10, and return a virtual path. Unfortunately System.Web.Routing makes this a bit of trial, but you can make it work. I’ve wrapped my implementation up in a little UriBuilder class. Here’s a test showing it working:
[Test]
public void CreateUriFromRouteValues_should_create_the_correct_virtual_path()
{
var routes = new RouteCollection();
routes.MapRoute(
"ProgRock",
"{band}/{album}/{track}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
var uriBuilder = new UriBuilder(() => routes, () => new FakeHttpContext());
var uri = uriBuilder.CreateUriFromRouteValues(new
{
band = "Yes",
album = "Fragile",
track = "Roundabout",
info = "great keyboard solo"
});
uri.ShouldEqual("/Yes/Fragile/Roundabout?info=great%20keyboard%20solo");
}
And here’s the implementation:
public interface IUriBuilder
{
string CreateUriFromRouteValues(object values);
}
public class UriBuilder : IUriBuilder
{
private readonly Func<RouteCollection> getRouteCollection;
private readonly Func<HttpContextBase> getHttpContext;
public UriBuilder(Func<RouteCollection> getRouteCollection, Func<HttpContextBase> getHttpContext)
{
this.getRouteCollection = getRouteCollection;
this.getHttpContext = getHttpContext;
}
public string CreateUriFromRouteValues(object values)
{
var routeValues = new RouteValueDictionary(values);
var routeData = new RouteData();
var requestContext = new RequestContext(getHttpContext(), routeData);
var virtualPathData = getRouteCollection().GetVirtualPath(requestContext, routeValues);
if (virtualPathData == null)
{
throw new ApplicationException("virtualPathData is null");
}
return virtualPathData.VirtualPath;
}
}
In order to unit test it, you have to fake the HttpContext. Here’s my fake implementation:
public class FakeHttpContext : HttpContextBase
{
public override HttpRequestBase Request
{
get { return new FakeRequest(); }
}
public override HttpResponseBase Response
{
get { return new FakeResponse(); }
}
}
public class FakeRequest : HttpRequestBase
{
public override string ApplicationPath
{
get { return "/"; }
}
}
public class FakeResponse : HttpResponseBase
{
public override string ApplyAppPathModifier(string virtualPath)
{
return virtualPath;
}
}
You can also use the fake HttpContext if you want to be able to create URLs outside an ASP.NET application where HttpContext.Current is null.
In the application, I register my route and httpContext provider delegates like this:
public class MvcInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<Func<HttpContextBase>>()
.Instance(() => new HttpContextWrapper(HttpContext.Current)),
Component.For<Func<RouteCollection>>()
.Instance(() => RouteTable.Routes)
);
}
}