一起谈.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 parametersnew { 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 parametersnew { 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)如果你有其它办法,欢迎交流!

时间: 2024-09-20 09:29:34

一起谈.NET技术,ASP.NET MVC:自定义 Route 以生成小写的 Url的相关文章

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 方法: ~/Persons/Query ~/PERSON

asp.net mvc 自定义错误页面跳转

问题描述 asp.net mvc 自定义错误页面跳转 请教个问题,asp.net mvc 我用ajax从后台提取数据,服务器提取数据发生异常错误,然后我在global文件里继承了HandleErrorAttribute,在OnException方法中跳转自定义错误页面,但是,并没有跳转页面,而是在原来的界面上显示了错误页面,造成页面混乱,这个怎么回事?请高手指教下 解决方案 原因找到了,只是对于ajax请求异常错误,然后在后台跳转自定义错误页面的方式,没有搞到方法,请问大家有没有什么建议 解决方

ASP.NET MVC自定义错误页面真的简单吗?_实用技巧

如果你在设置asp.net mvc自定义错误页面时遇到问题,这并不止你一个人.惊讶之余你的做法是正确的,没有起到作用的原因是其一部分错误是由asp.net管道处理的,另一部分是由iis直接处理. 通常情况 (我期望是这种情况,在一些其他框架/服务器上) 我们只需要在一个地方配置自定义错误页就可以了,无论怎么哪儿引发的错误.就像这样︰ <customErrors mode="On"> <error code="404" path="404.

ASP.NET MVC中的视图生成简介

在 ASP.NET MVC 中,我们将前端的呈现划分为三个独立的部分来实现,Controller 用来控制用户的操作,View 用来控制呈现的内容,Model 用来表示处理的数据. 从控制器到视图 通常,在 Controller 中,我们定义多个 Action ,每个 Action 的返回类型一般是 ActionResult,在 Action 处理的最后,我们返回对于视图的调用. public ActionResult Index() {    return this.View(); } 默认情

ASP.NET MVC中的视图生成实例分析_实用技巧

本文实例分析了ASP.NET MVC中的视图生成过程.分享给大家供大家参考.具体如下: 在 ASP.NET MVC 中,我们将前端的呈现划分为三个独立的部分来实现,Controller 用来控制用户的操作,View 用来控制呈现的内容,Model 用来表示处理的数据. 从控制器到视图 通常,在 Controller 中,我们定义多个 Action ,每个 Action 的返回类型一般是 ActionResult,在 Action 处理的最后,我们返回对于视图的调用. 复制代码 代码如下: pub

一起谈.NET技术,使用User Control做HTML生成

User Control大家肯定不会陌生,在使用ASP.NET的过程中,除了aspx页面,最常见的就莫过于ascx了.ascx是一个有独立逻辑的组件,提供了强大的复用特性,合理使用,能够大大提高开发效率.通过User Control直接生成HTML内容其实已经是一个比较常用的技巧了(尤其在AJAX时代),不过网络上这方面的内容比较少,很多人还是在苦苦地拼接字符串,因此在这里我通过一个实例简单介绍一下这个技巧. 对一个对象(文章,图片,音乐,etc.)进行评论是应用中最常见的功能之一.首先,我们定

一起谈.NET技术,一个MVC分页Helper

本人写的一个分页Helper,支持普通分页(也就是,首页.上一页.下一页.末页等),综合分页(普通分页和数字分页的综合).下面是分页效果: 分页代码: PagerHelper.cs 代码   1 using System;  2  using System.Collections.Generic;  3 using System.Collections.Specialized;  4 using System.Linq;  5 using System.Web;  6 using System.

一起谈.NET技术,Visual Studio自定义调试窗体两个小技巧

本文翻译:Few Tips on Customizing Debugging Window View in Visual Studio . 使用DebuggerBrowsable特性可以自定义调试窗体. 使用DebuggerDisplay特性可以自定义调试信息的显示.  要使用这些特性,需要引用System.Diagnostics命名空间. 技巧1.使用DebuggerBrowsable特性 你可以通过在属性上使用DebuggerBrowsable特性来自定义调试窗体.这些特性可以用任何属性.

一个ASP.NET MVC 自定义URL的问题

问题描述 我想实现用户自定义自己主页的模板.我把每个模板写在了不同的控制器中.context.MapRoute("My_default","{id}/{action}",new{controller="default",action="Index",id=UrlParameter.Optional},new{id=@"[0-9]+"}); 其中controller为模板的控制器名,我不想在url中显示出模板