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.