zoukankan      html  css  js  c++  java
  • 一起谈.NET技术,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);
    }
    }
    }

      有了 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;
    }
    }

      总结

      大功告成,如果你感兴趣,不妨尝试下!写到这里吧,如果需要,请下载本文中的示例代码:MvcLowerCaseUrlRouteDemo.rar(209KB)如果你有其它办法,欢迎交流!

  • 相关阅读:
    django页面分类和继承
    django前端从数据库获取请求参数
    pycharm配置django工程
    django 应用各个py文件代码
    CF. 1428G2. Lucky Numbers(背包DP 二进制优化 贪心)
    HDU. 6566. The Hanged Man(树形背包DP DFS序 重链剖分)
    小米邀请赛 决赛. B. Rikka with Maximum Segment Sum(分治 决策单调性)
    区间树 学习笔记
    CF GYM. 102861M. Machine Gun(主席树)
    2016-2017 ACM-ICPC East Central North America Regional Contest (ECNA 2016) (B, D, G, H)
  • 原文地址:https://www.cnblogs.com/waw/p/2163179.html
Copyright © 2011-2022 走看看