Tuesday, November 30, 2010

ASP.NET MVC: Creating a URL From Route Values

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)
);
}
}

3 comments:

  1. Thanks for posting this - it's just the kind of thing I need for my current project. Very neat.

    ReplyDelete
  2. Thanks for posting this -- this came in handy today.

    ReplyDelete
  3. Anonymous3:28 pm

    I'm trying to wrap a custom Pager, within a view model. But I need to know which Route Values come from a previous view that implement it. Which class fits for that. Because I must generate dynamic url's for that view.
    Thanks in advance.

    ReplyDelete

Note: only a member of this blog may post a comment.