0

I already have some code that's working for most cases, though I'm interested in making this more reusable and applicable to all possible routes that a site might have.

//--RouteBuilder.cs
public static RouteValueDictionary BuildRouteValueDictionary<TController>(Expression<Func<TController, System.Web.Mvc.ActionResult>> action)
    where TController : System.Web.Mvc.Controller => BuildRouteValueDictionary<TController>((LambdaExpression)action);
public static RouteValueDictionary BuildRouteValueDictionary<TController>(Expression<Func<TController, System.Web.Mvc.JsonResult>> action)
    where TController : System.Web.Mvc.Controller => BuildRouteValueDictionary<TController>((LambdaExpression)action);
private static RouteValueDictionary BuildRouteValueDictionary<TController>(LambdaExpression action)
    where TController : System.Web.Mvc.Controller
{
    //--Find the area and controller values for the given controller class
    var match = new Regex($@"^SomeSite\.(Areas\.(?<area>[a-zA-Z]+)\.)?Controllers\.(?<controller>[a-zA-Z]+)Controller$").Match(typeof(TController).FullName);
    if (match.Success)
    {
        var methodCall = (MethodCallExpression)action.Body;
        //--Compile/Invoke each action parameter that will be used as route values
        var routeValues = methodCall.Method.GetParameters()
            .Select((pi, i) => new
            {
                ParameterName = pi.Name,
                Value = Expression.Lambda(methodCall.Arguments[i]).Compile().DynamicInvoke()
            }).ToDictionary(x => x.ParameterName, x => x.Value);

        return new RouteValueDictionary(routeValues)
        {
            { "area", match.Groups["area"].Value },
            { "controller", match.Groups["controller"].Value },
            { "action", methodCall.Method.GetCustomAttributes(true).OfType<ActionNameAttribute>().FirstOrDefault()?.Name ?? methodCall.Method.Name }
        };
    }

    throw new InvalidOperationException("Cannot determine the route for the specified controller.");
}

//--This sits on a base controller
protected RedirectToRouteResult RedirectToRoute<TController>(Expression<Func<TController, ActionResult>> action)
    where TController : Controller =>
        RedirectToRoute(RouteBuilder.BuildRouteValueDictionary(action));

I've been looking into System.Web.Routing.RouteTable.Routes, and that seems really good for parsing a url into a controller and action, but not really the other way around (unless I'm missing something). The hope is to get the site-specific regex out of this and be able to handle any route for redirects or url generation while being able to enjoy the compile-time benefits of lambdas over anonymous objects and magic strings. Really, the only issue that remains is determining the correct area name.

CosworthTC
  • 358
  • 5
  • 15
  • Your hopes about lambdas aside, responding with an HTTP 302 response on *every request*, forcing the client to do a *second request* of the actual resource is the wrong way to go about this. Not only will this cause extra network traffic and performance issues, it is not SEO friendly. Instead, you should aim to [customize routes](https://stackoverflow.com/a/31958586) to respond to the *original request* with content instead of an HTTP 302 status. Redirects should only be used to *change the URL in the browser* if it is needed. Routing is a 2-way mechanism - use it. – NightOwl888 Mar 21 '18 at 18:53
  • This isn't used for every request. The places where it is used are edit POSTs which take the user back to a details view when successful, and a search engine isn't going to be navigating through private client data. Also, the `BuildRouteValueDictionary` is made use of in a generic version of `Url.Action(...)` to help build compile-time-safe links for menus and such. – CosworthTC Mar 21 '18 at 20:07

0 Answers0