2012-02-10 181 views
12

从mvc(2)用户控件中,我想遍历所有路由值。如何循环遍历所有路线?

所以,如果我有这样的控制器:

UserController 
AccountController 

我所需要的值的集合,它会出现在类似的网址:

/user/... 
/account/... 

即值用户帐户。

我怎样才能得到这个?

我试过RouteTables,但无法弄清楚。

+0

你的意思是根值..? – MethodMan 2012-02-10 16:59:40

+0

你有树/图形的种类? – Adrian 2012-02-10 17:19:07

+2

RouteTable确实是一个可以看的地方...也就是说,我们用相反的方式来做:我们使用属性来通告路由,并通过反射来建立路由表 - 我们当然可以在奇想中运行反射列出路线 – 2012-02-10 17:19:51

回答

15

哦,真的是一个很好的问题,让我的自我忙一小时。 为了实现所需的功能,我们需要挂接到MVC源代码和一点点反射。

  1. 缺省路由的名称不可用,所以我们需要写一个路由集合扩展名来保存路由名称中的RouteData令牌。

    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; 
    } 
    
  2. 修改在Global.asax图路线要进行的调用以前的扩展

    routes.MapRouteWithName(
          "Default", // Route name 
          "{controller}/{action}/{id}", // URL with parameters 
          new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults 
         ); 
    
  3. 修改了MVC PathHelper一点点。(包含此帮手项目)

    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; 
        } 
    } 
    
    } 
    
  4. 在控制器中添加少量帮手方法

    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; 
    } 
    
  5. 现在我们可以编写一些反射逻辑来读取控制器,动作和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)); 
         } 
        } 
    
    } 
    
  6. 要记住的要点:如果在路由已定义的任何默认参数,那么URL为那些控制器和操作将是空的。例如在上面的例子中 “/首页/索引” 将显示为 “/”

  7. 下载示例应用程序Link To Download

    List item

+0

Woa。巨大的答案,老兄! – Almo 2012-02-10 20:10:13

+0

是的,花了大约一个小时找到一种方式 – Manas 2012-02-10 20:12:00

+0

太棒了!这是我想要的,但不想写出来。我唯一知道的其他解决方案是使用FubuMVC。 :) http://codebetter.com/jeremymiller/2010/01/04/fubumvc-diagnostics-sneak-peek/ – Ryan 2012-02-10 20:39:33