Question

Is it possible to get a URL from an action without knowing ViewContext (e.g., in a controller)? Something like this:

LinkBuilder.BuildUrlFromExpression(ViewContext context, Expression<Action<T>> action)

...but using Controller.RouteData instead of ViewContext. I seem to have metal block on this.

Was it helpful?

Solution

Here's how I do it in a unit test:

    private string RouteValueDictionaryToUrl(RouteValueDictionary rvd)
    {
        var context = MvcMockHelpers.FakeHttpContext("~/");
        // _routes is a RouteCollection
        var vpd = _routes.GetVirtualPath(
            new RequestContext(context, _
                routes.GetRouteData(context)), rvd);
        return vpd.VirtualPath;
    }

Per comments, I'll adapt to a controller:

string path = RouteTable.Routes.GetVirtualPath(
    new RequestContext(HttpContext, 
        RouteTable.Routes.GetRouteData(HttpContext)),
    new RouteValueDictionary( 
        new { controller = "Foo",
              action = "Bar" })).VirtualPath;

Replace "Foo" and "Bar" with real names. This is off the top of my head, so I can't guarantee that it's the most efficient solution possible, but it should get you on the right track.

OTHER TIPS

Craig, Thanks for the correct answer. It works great, and it also go me thinking. So in my drive to eliminate those refactor-resistent "magic strings" I have developed a variation on your solution:

public static string GetUrlFor<T>(this HttpContextBase c, Expression<Func<T, object>> action)
    where T : Controller
{
    return RouteTable.Routes.GetVirtualPath(
        new RequestContext(c, RouteTable.Routes.GetRouteData(c)), 
        GetRouteValuesFor(action)).VirtualPath;
}

public static RouteValueDictionary GetRouteValuesFor<T>(Expression<Func<T, object>> action) 
    where T : Controller
{
    var methodCallExpresion = ((MethodCallExpression) action.Body);
    var controllerTypeName = methodCallExpresion.Object.Type.Name;
    var routeValues = new RouteValueDictionary(new
    {
        controller = controllerTypeName.Remove(controllerTypeName.LastIndexOf("Controller")), 
        action = methodCallExpresion.Method.Name
    });
    var methodParameters = methodCallExpresion.Method.GetParameters();
    for (var i = 0; i < methodParameters.Length; i++)
    {
        var value = Expression.Lambda(methodCallExpresion.Arguments[i]).Compile().DynamicInvoke();
        var name = methodParameters[i].Name;
        routeValues.Add(name, value);
    }
    return routeValues;
}

I know what some will say...dreaded reflection! In my particular application, I think the benefit of maintainability outweighs performance conerns. I welcome any feedback on this idea and the code.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top