Reputation: 9611
From inside a mvc (2) user control, I want to loop through all the route values.
So if I have controllers like:
UserController
AccountController
I need a collection of the values that will appear in the url like:
/user/...
/account/...
i.e. the values user, account.
How can I get this?
I tried RouteTables but couldn't figure it out.
Upvotes: 13
Views: 3791
Reputation: 2542
Oh, really a good question to keep my self busy for an hour. To achieve the required functionality , we need to hook into MVC source and little bit of reflections.
By default Route names are not available , so we need to write a Route collection extension to save Route Name in RouteData tokens.
public static Route MapRouteWithName(this RouteCollection routes,string name, string url, object defaults=null, object constraints=null)
{
Route route = routes.MapRoute(name, url, defaults, constraints);
route.DataTokens = new RouteValueDictionary();
route.DataTokens.Add("RouteName", name);
return route;
}
Modify the global.asax maproute call to invoke previous extension
routes.MapRouteWithName(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Modified the MVC PathHelper a little bit.(Include this helper in the project)
using System;
using System.Collections.Specialized;
using System.Web;
public static class PathHelpers
{
// this method can accept an app-relative path or an absolute path for contentPath
public static string GenerateClientUrl(HttpContextBase httpContext, string contentPath)
{
if (String.IsNullOrEmpty(contentPath))
{
return contentPath;
}
// many of the methods we call internally can't handle query strings properly, so just strip it out for
// the time being
string query;
contentPath = StripQuery(contentPath, out query);
return GenerateClientUrlInternal(httpContext, contentPath) + query;
}
private static string GenerateClientUrlInternal(HttpContextBase httpContext, string contentPath)
{
if (String.IsNullOrEmpty(contentPath))
{
return contentPath;
}
// can't call VirtualPathUtility.IsAppRelative since it throws on some inputs
bool isAppRelative = contentPath[0] == '~';
if (isAppRelative)
{
string absoluteContentPath = VirtualPathUtility.ToAbsolute(contentPath, httpContext.Request.ApplicationPath);
string modifiedAbsoluteContentPath = httpContext.Response.ApplyAppPathModifier(absoluteContentPath);
return GenerateClientUrlInternal(httpContext, modifiedAbsoluteContentPath);
}
string relativeUrlToDestination = MakeRelative(httpContext.Request.Path, contentPath);
string absoluteUrlToDestination = MakeAbsolute(httpContext.Request.RawUrl, relativeUrlToDestination);
return absoluteUrlToDestination;
}
public static string MakeAbsolute(string basePath, string relativePath)
{
// The Combine() method can't handle query strings on the base path, so we trim it off.
string query;
basePath = StripQuery(basePath, out query);
return VirtualPathUtility.Combine(basePath, relativePath);
}
public static string MakeRelative(string fromPath, string toPath)
{
string relativeUrl = VirtualPathUtility.MakeRelative(fromPath, toPath);
if (String.IsNullOrEmpty(relativeUrl) || relativeUrl[0] == '?')
{
// Sometimes VirtualPathUtility.MakeRelative() will return an empty string when it meant to return '.',
// but links to {empty string} are browser dependent. We replace it with an explicit path to force
// consistency across browsers.
relativeUrl = "./" + relativeUrl;
}
return relativeUrl;
}
private static string StripQuery(string path, out string query)
{
int queryIndex = path.IndexOf('?');
if (queryIndex >= 0)
{
query = path.Substring(queryIndex);
return path.Substring(0, queryIndex);
}
else
{
query = null;
return path;
}
}
}
Add few Helper methods in controller
public static string GenerateUrl(string routeName, string actionName, string controllerName, RouteCollection routeCollection, RequestContext requestContext)
{
RouteValueDictionary mergedRouteValues = MergeRouteValues(actionName, controllerName);
VirtualPathData vpd = routeCollection.GetVirtualPathForArea(requestContext, routeName, mergedRouteValues);
if (vpd == null)
{
return null;
}
string modifiedUrl = PathHelpers.GenerateClientUrl(requestContext.HttpContext, vpd.VirtualPath);
return modifiedUrl;
}
public static RouteValueDictionary MergeRouteValues(string actionName, string controllerName)
{
// Create a new dictionary containing implicit and auto-generated values
RouteValueDictionary mergedRouteValues = new RouteValueDictionary();
// Merge explicit parameters when not null
if (actionName != null)
{
mergedRouteValues["action"] = actionName;
}
if (controllerName != null)
{
mergedRouteValues["controller"] = controllerName;
}
return mergedRouteValues;
}
Now we can write some reflection logics to read controllers, actions and routenames.
Dictionary<string, List<string>> controllersAndActions = new Dictionary<string, List<string>>();
// Get all the controllers
var controllers = Assembly.GetExecutingAssembly().GetTypes().Where(t => typeof(Controller).IsAssignableFrom(t));
foreach (var controller in controllers)
{
List<string> actions = new List<string>();
//Get all methods without HttpPost and with return type action result
var methods = controller.GetMethods().Where(m => typeof(ActionResult).IsAssignableFrom(m.ReturnType)).Where(a=>!a.GetCustomAttributes(typeof(HttpPostAttribute),true).Any());
methods.ToList().ForEach(a => {
actions.Add(a.Name);
});
var controllerName = controller.Name;
if (controllerName.EndsWith("Controller"))
{
var nameLength = controllerName.Length - "Controller".Length;
controllerName = controllerName.Substring(0, nameLength);
}
controllersAndActions.Add(controllerName, actions);
}
List<string> allowedRoutes = new List<string>();
var routeNames = RouteTable.Routes.Where(o=>o.GetRouteData(this.HttpContext)!=null).Select(r=>r.GetRouteData(this.HttpContext).DataTokens["RouteName"].ToString());
foreach (var cName in controllersAndActions)
{
foreach (var aName in cName.Value)
{
foreach (var item in routeNames)
{
allowedRoutes.Add(GenerateUrl(item, aName, cName.Key, RouteTable.Routes, this.Request.RequestContext));
}
}
}
Points to remember :If in the route you have defined any default parameters, then url for those controller and action will be empty. e.g. in above example "/Home/Index" will be shown as "/"
Download the sample application Link To Download
Upvotes: 16