zoukankan      html  css  js  c++  java
  • ASP.NET MVC:自定义 Route 生成小写 Url(转)

    先给出本文中测试用的 controller:

    public class PersonsController : Controller
    {
        public ActionResult Query(string name)
        {
            return View();
        }
    }

    ASP.NET 中 Url 大小写

    不严格来讲,ASP.NET MVC 对 Url 是不敏感的,以下 Url 都是相同的,都可以访问到 PersonController 的 Query 方法:

    1. ~/Persons/Query
    2. ~/PERSONS/QUERY
    3. ~/persons/query

    但对 MVC 的数据绑定来说,大小写似乎还是有区别的:

    1. ~/Persons/Query?Name=Bob
    2. ~/Persons/Query?Name=bob

    对以上两个 Url,Query 中 name 参数会接收到两个不同值:Bobbob。Action 中的参数只是原样接收,并没有作任何处理。至于name 字符串的大小写是否敏感要看具体的应用了。

    再回头看前面的三个 Url:

    1. ~/Persons/Query: 是 MVC 中默认生成的,因为在 .Net 中方法命名通常采用 PascalCase;
    2. ~/PERSONS/QUERY: 全部大写,这种写法很不友好,很难读,应该杜绝采用这种方式;
    3. ~/persons/query:这种方式比较好,易读,也是大多数人选择的方式。

    本文探讨如何在 MVC 中使用第三种方式,也就是小写(但不完全小写),目标如下:

    在不影响程序正常运行的前提下,将所有能小写的都小写,如:

    ~/persons/query?name=Bob&age=18

    ~/persons/query/name/Bob/age/18

    MVC 中 Url 的生成

    在 View 中生成超级链接有多种方式:

    <%: Html.ActionLink("人员查询", "Query", "Persons", new { name = "Bob" }, null) %>
    <%: Html.RouteLink("人员查询", new { controller = "Persons", action = "Query", name = "Bob" })%>
    <a href="<%:Url.Action("Query", "Persons", new { name="Bob" }) %>">人员查询</a>

    在 Action 中,可以使用 RedirectTo 来调转至新的页面:

    return RedirectToAction("Query", "Persons", new { name = "Bob" });
    return RedirectToRoute(new { controller = "Persons", action = "Query", name = "Bob" });

    ActionLink、RouteLink、RedirectToAction 和 RedirectToRouter 都会生成 Url,并最终显示在浏览器的地址栏中。

    这四个方法都有很多重载,想从这里下手控制 Url 小写实在是太麻烦了。当然也不可行,因为可能还有其它方式来生成 Url。

    MVC 是一个非常优秀的框架,但凡优秀的框架都会遵循 DRY(Don't repeat yourself) 原则,MVC 也不例外。MVC 中 RouteBase 负责 Url 的解析和生成:

    public abstract class RouteBase
    {
        public abstract RouteData GetRouteData(HttpContextBase httpContext);
        public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
    }

    GetRouteData 用来解析 Url,GetVirtualPath 用来生成 Url。ActionLink、RouteLink、RedirectToAction 和 RedirectToRouter 内部都会调用 GetVirtualPath 方法来生成 Url。

    因此我们的入手点就是 GetVirtualPath 方法。

    自定义 Route 以生成小写的 Url

    MVC 中 RouteBase 的具体实现类是 Route,我们经常在 Global.asax 中经常使用:

    public class MvcApplication : System.Web.HttpApplication
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            );
        }
        //...
    }

    MapRoute 返回 Route,MapRoute 有很多重载,用来简化我们构建 Route 的过程。

    Route 类没有给我们提供可直接扩展的地方,因此我们只能自定义一个新的 Route 来实现我们的小写 Url。但处理路由的细节也是相当麻烦的,因此我们最简单的方式就是写一个继承自 Route 的类,然后重写它的 GetVirtualPath 方法:

    public class LowerCaseUrlRoute : Route
    {
    
        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            //在此处进行小写处理
            return base.GetVirtualPath(requestContext, values);
        }
    }

    再来看下我们的目标:

    ~/persons/query?name=Bob&age=18

    ~/persons/query/name/Bob/age/18

    其实我们只需要进行两步操作:

    1. 将路由中的 area、controller、action 的值都变成小写;
    2. 将路由中其它键值对的键变成小写,如:Name=Bob 中的 Name。

    那我们先来完成这个功能吧:

    private static readonly string[] requiredKeys = new [] { "area", "controller", "action" };
    
    private void LowerRouteValues(RouteValueDictionary values)
    {
        foreach (var key in requiredKeys)
        {
            if (values.ContainsKey(key) == false) continue;
    
            var value = values[key];
            if (value == null) continue;
    
            var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
            if (valueString == null) continue;
    
            values[key] = valueString.ToLower();
        }
    
        var otherKyes = values.Keys
            .Except(requiredKeys, StringComparer.InvariantCultureIgnoreCase)
            .ToArray();
    
        foreach (var key in otherKyes)
        {
            var value = values[key];
            values.Remove(key);
            values.Add(key.ToLower(), value);
        }
    }

    GetVirtualPath 生成 Url 时,会将 requestContext.RouteData.Values、values(第二个参数) 以及 Defaults(当前 Router 的默认值)三个 RouteValueDictionary 进行合并,如在 View 写了如下的一个 ActionLinks:

    <%: Html.ActionLink("查看") %>

    生成的 Html 代码可能是:

    <a href="/Home/Details">查看</a>

    因为没有指定 Controller,MVC 会自动使用当前的,即从 requestContext.RouteData.Values 中获取 Controller,得到 ”Home“;”Details“来自 values;如果连 ActionLink 中 Action 也不指定,那将会从 Defaults 中取值。

    因此我们必须将这三个 RouteValueDictionary 都进行处理才能达到我们的目标:

    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
    {
        LowerRouteValues(requestContext.RouteData.Values);
        LowerRouteValues(values);
        LowerRouteValues(Defaults);
        return base.GetVirtualPath(requestContext, values);
    }

    再加上几个构造函数,完整的 LowerCaseUrlRoute 如下:

    public class LowerCaseUrlRoute : Route
    {
        private static readonly string[] requiredKeys = new [] { "area", "controller", "action" };
    
        public LowerCaseUrlRoute(string url, IRouteHandler routeHandler)
            : base(url, routeHandler) { }
        
        public LowerCaseUrlRoute(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
            : base(url, defaults, routeHandler){ }
    
        public LowerCaseUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler)
            : base(url, defaults, constraints, routeHandler) { }
        public LowerCaseUrlRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler)
            : base(url, defaults, constraints, dataTokens, routeHandler) { }    
    
        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            LowerRouteValues(requestContext.RouteData.Values);
            LowerRouteValues(values);
            LowerRouteValues(Defaults);
            return base.GetVirtualPath(requestContext, values);
        }
    
        private void LowerRouteValues(RouteValueDictionary values)
        {
            foreach (var key in requiredKeys)
            {
                if (values.ContainsKey(key) == false) continue;
    
                var value = values[key];
                if (value == null) continue;
    
                var valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
                if (valueString == null) continue;
    
                values[key] = valueString.ToLower();
            }
    
            var otherKyes = values.Keys
                .Except(requiredKeys, StringComparer.InvariantCultureIgnoreCase)
                .ToArray();
    
            foreach (var key in otherKyes)
            {
                var value = values[key];
                values.Remove(key);
                values.Add(key.ToLower(), value);
            }
        }
    }
    View Code

    有了 LowerCaseUrlRoute,我们就可以修改 Global.asax 文件中的路由了。

    创建 LowerCaseUrlRouteMapHelper

    这一步不是必须的,但有了这个 MapHelper 我们在修改 Global.asax 文件中的路由时可以非常方便:

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
        routes.MapLowerCaseUrlRoute( //routes.MapRoute(
            "Default", // Route name
            "{controller}/{action}/{id}", // URL with parameters
            new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
        );
    }

    尤其是已经配置了很多路由的情况下,其代码如下:

    public static class LowerCaseUrlRouteMapHelper
    {
        public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url){
            return routes.MapLowerCaseUrlRoute(name, url, null, null);
        }
        public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, object defaults){
            return routes.MapLowerCaseUrlRoute(name, url, defaults, null);
        }
        public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, string[] namespaces){
            return routes.MapLowerCaseUrlRoute(name, url, null, null, namespaces);
        }
        public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, object defaults, object constraints){
            return routes.MapLowerCaseUrlRoute(name, url, defaults, constraints, null);
        }
        public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces){
            return routes.MapLowerCaseUrlRoute(name, url, defaults, null, namespaces);
        }
        public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces){
            if (routes == null) throw new ArgumentNullException("routes");
            if (url == null) throw new ArgumentNullException("url");
            LowerCaseUrlRoute route2 = new LowerCaseUrlRoute(url, new MvcRouteHandler());
            route2.Defaults = new RouteValueDictionary(defaults);
            route2.Constraints = new RouteValueDictionary(constraints);
            route2.DataTokens = new RouteValueDictionary();
            LowerCaseUrlRoute item = route2;
            if ((namespaces != null) && (namespaces.Length > 0))
                item.DataTokens["Namespaces"] = namespaces;
            routes.Add(name, item);
            return item;
        }
    
        public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url){
            return context.MapLowerCaseUrlRoute(name, url, null);
        }
        public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, object defaults){
            return context.MapLowerCaseUrlRoute(name, url, defaults, null);
        }
        public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, string[] namespaces){
            return context.MapLowerCaseUrlRoute(name, url, null, namespaces);
        }
        public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, object defaults, object constraints)        {
            return context.MapLowerCaseUrlRoute(name, url, defaults, constraints, null);
        }
        public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, object defaults, string[] namespaces){
            return context.MapLowerCaseUrlRoute(name, url, defaults, null, namespaces);
        }
        public static LowerCaseUrlRoute MapLowerCaseUrlRoute(this AreaRegistrationContext context, string name, string url, object defaults, object constraints, string[] namespaces)
        {
            if ((namespaces == null) && (context.Namespaces != null))
                namespaces = context.Namespaces.ToArray<string>();
            LowerCaseUrlRoute route = context.Routes.MapLowerCaseUrlRoute(name, url, defaults, constraints, namespaces);
            route.DataTokens["area"] = context.AreaName;
            bool flag = (namespaces == null) || (namespaces.Length == 0);
            route.DataTokens["UseNamespaceFallback"] = flag;
            return route;
        }
    }
    View Code

    总结

    大功告成,如果你感兴趣,不妨尝试下!

    写到这里吧,如果需要,请下载本文中的示例代码:MvcLowerCaseUrlRouteDemo.rar(209KB)

    如果你有其它办法,欢迎交流!

    转载请注明本文地址:

    http://www.cnblogs.com/ldp615/archive/2010/11/10/asp-net-mvc-lower-case-url-route.html

  • 相关阅读:
    Leetcode 40. Combination Sum II
    Leetcode** 39. Combination Sum
    Leetcode** 210. Course Schedule II
    Leetcode** 207. Course Schedule
    Leetcode 257. Binary Tree Paths
    Leetcode** 131. Palindrome Partitioning
    Leetcode** 20. Valid Parentheses
    Leetcode 14. Longest Common Prefix
    dfs序 二进制优化 Codeforces Round #316 (Div. 2)D. Tree Requests
    Codeforces Round #318 D. Bear and Blocks
  • 原文地址:https://www.cnblogs.com/tianma3798/p/4372040.html
Copyright © 2011-2022 走看看