ASP.NET MVC 中的路由
普通的路由
在以前版本的ASP.NET MVC里,这些规则会被定义在RouteConfig.cs文件,也有Routing Assistant扩展可以实现基于特性的路由,不过是收费滴,另外还有一个扩展:http://attributerouting.net/ ,也很不错;理论上ASP.NET MVC 中要实现任意的规则URL 应该是没有问题的。
Attribute routing
ASP.NET MVC now supports attribute routing, thanks to a contribution by Tim McCall, the author of http://attributerouting.net. With attribute routing you can specify your routes by annotating your actions and controllers.
比如配置的酒店详情页路由
//HOTEL DETAIL routes.MapRoute(name: "HotelDetail", url: "hotel/{hotelCd}", defaults: new { controller = "Hotel", action = "HotelDetail" }, constraints: new { controller = "Hotel", hotelCd = @"[a-zA-Z0-9]*" } );
自定义路由约束
如果是要精确到数据是否准确,可以自定义的路由规则,实现 IRouteConstraint 接口,重写Match方法即可。
public class CityConstraint : IRouteConstraint { /// <summary> /// 匹配二级城市的URL /// </summary> /// <param name="httpContext"></param> /// <param name="route"></param> /// <param name="parameterName"></param> /// <param name="values"></param> /// <param name="routeDirection"></param> /// <returns></returns> public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { try { if (values["cityName"] == null || values["cityName"].ToString().ContainsAny(new string[] { "home", "list", "hotel", "member", "activity", "ajax","alipay", "gift"})) { return false; } var city = CacheData.getCitys().Where(data => data.Pinyin.Equals(values["cityName"].ToString(), StringComparison.OrdinalIgnoreCase)).ToList(); if (city == null || city.Count == 0) { return false; } return true; } catch (Exception ex) { return false; } } routes.MapRoute( name: "HotelCity", url: "{cityName}/p{pageNo}", defaults: new { controller = "HotelSEO", action = "Hotel", pageNo = "1" }, constraints: new { cityName = new CityConstraint(), pageNo = @"\d+" } //constraints: new { cityName = @"(|shanghai|beijing|hefei|chuzhou)"} );
考虑到hotel/xxx可以匹配到酒店编号不符合规则的酒店,也可能是用户手误打错了,可以捕获所有以hotel开头不符合正则的的URL到酒店的目录页
routes.MapRoute(name: "HotelMenu", url: "hotel/{*values}", defaults: new { controller = "Menu", action = "Index" } );
特性路由
要启用特性路由,只要项目中注册MapMvcAttributeRoutes方法
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); //启用特性路由 routes.MapMvcAttributeRoutes(); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
可选的URL参数和默认值
// eg: /hotel // eg: /hotel/123456 [Route("hotel/{hotelCd?}")] public ActionResult View(string hotelCd) { if (!String.IsNullOrEmpty(hotelCd)) { return View("HotelDetail", GetHotelDetail(hotelCd)); } return View("HotelMenu", GetHotelCitys()); }
路由前缀
比如现在的会员页面,都是以user 开头的如
// eg: /user [Route("user")] public ActionResult Index() { ... } // eg: /user/5 [Route("user/{userId}")] public ActionResult Show(int userId) { ... } // eg: /user/5/edit [Route("user/{userId}/edit")] public ActionResult Edit(int userId) { ... }
这个时候也在控制器加一个全局[RoutePrefix]特性
[RoutePrefix("user")] public class userController : Controller { // eg.: /user [Route] public ActionResult Index() { ... } // eg.: /user/5 [Route("{userId}")] public ActionResult Show(int userId) { ... } // eg.: /user/5/edit [Route("{userId}/edit")] public ActionResult Edit(int userId) { ... } }
重写路由规则
如果有的时候需要重写路由规则,就加个波浪线 ~
[RoutePrefix("review")] public class ReviewController : Controller { // eg.: /code-review [Route("~/code-review")] public ActionResult CodeView() { ... } }
也可以在controller中定义Route特性,将action作为一个参数。这个路由会被应用到controller上的所有action,除非某个action已经定义了一个[Route]特性,重写controller的默认行为。
[RoutePrefix("user")] [Route("{action=Index}")] public class ReviewsController : Controller { // eg.: /user public ActionResult Index() { ... } // eg.: /user/set public ActionResult Set() { ... } // eg.: /user/new public ActionResult New() { ... } // eg.: /user/edit/5 [Route("edit/{userId:int}")] public ActionResult Edit(int promoId) { ... } }
路由约束
路由约束让你可以限制参数在路由模板里如何匹配。通常的语法是{parameter:constraint},如:
// eg: /users/5 [Route("users/{id:int}"] public ActionResult GetUserById(int id) { ... } // eg: users/ken [Route("users/{name}"] public ActionResult GetUserByName(string name) { ... }
支持的约束类型:
Constraint | Description | Example |
---|---|---|
alpha | Matches uppercase or lowercase Latin alphabet characters (a-z, A-Z) | {x:alpha} |
bool | Matches a Boolean value. | {x:bool} |
datetime | Matches a DateTime value. | {x:datetime} |
decimal | Matches a decimal value. | {x:decimal} |
double | Matches a 64-bit floating-point value. | {x:double} |
float | Matches a 32-bit floating-point value. | {x:float} |
guid | Matches a GUID value. | {x:guid} |
int | Matches a 32-bit integer value. | {x:int} |
length | Matches a string with the specified length or within a specified range of lengths. | {x:length(6)}
{x:length(1,20)} |
long | Matches a 64-bit integer value. | {x:long} |
max | Matches an integer with a maximum value. | {x:max(10)} |
maxlength | Matches a string with a maximum length. | {x:maxlength(10)} |
min | Matches an integer with a minimum value. | {x:min(10)} |
minlength | Matches a string with a minimum length. | {x:minlength(10)} |
range | Matches an integer within a range of values. | {x:range(10,50)} |
regex | Matches a regular expression. | {x:regex(^\d{3}-\d{3}-\d{4}$)} |
可以用分号分隔,定义多个约束,如:
// eg: /users/5 // but not /users/10000000000 because it is larger than int.MaxValue, // and not /users/0 because of the min(1) constraint. [Route("users/{id:int:min(1)}")] public ActionResult GetUserById(int id) { ... }
比如MS的这篇文章,大致可以配置成如下的路由规则:
http://blogs.msdn.com/b/webdev/archive/2014/03/04/asp-net-mvc-5-lifecycle-document-published.aspx
[Route("/b/webdev/archive/{year:regex(\\d{4})}")]
[Route("/b/webdev/archive/{year:regex(\\d{4})}/{month:regex(\\d{1,2})}")]
[Route("/b/webdev/archive/{year:regex(\\d{4})}/{month:regex(\\d{1,2})}/{day:regex(\\d{1,2})}/{slug}")]
public ActionResult GetArchivePost(string year, string month,string day,string slug){ };
特性路由中自定义路由约束
public class ValuesConstraint : IRouteConstraint { private readonly string[] validOptions; public ValuesConstraint(string options) { validOptions = options.Split('|'); } public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { object value; if (values.TryGetValue(parameterName, out value) && value != null) { return validOptions.Contains(value.ToString(), StringComparer.OrdinalIgnoreCase); } return false; } }
注册路由
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); var constraintsResolver = new DefaultInlineConstraintResolver(); constraintsResolver.ConstraintMap.Add("values", typeof(ValuesConstraint)); routes.MapMvcAttributeRoutes(constraintsResolver); } }
public class TemperatureController : Controller { // eg: temp/celsius and /temp/fahrenheit but not /temp/kelvin [Route("temp/{scale:values(celsius|fahrenheit)}")] public ActionResult Show(string scale) { return Content("scale is " + scale); } }
路由名称
可以给一个路由指定一个名字,以便于URL生成,如:
[Route("menu", Name = "mainmenu")] public ActionResult MainMenu() { ... }
使用@Url.RouteUrl 生成一个超链接
<a href="@Url.RouteUrl("mainmenu")">Main menu</a>
特性路由VS普通的路由
其实这个问题与Spring是基于xml配置好,还是注解的方式好,具体还得看使用场景,每一种方式都有自己的优势与劣势,比如基于普通的路由可以自定义直接 的一些逻辑,基于特性的路由可以快速现实一个路由的实现。
// eg: /books/lang // eg: /books/lang/en // eg: /books/lang/he [Route("books/lang/{lang=en}")] public ActionResult Lang(string lang) { return View(); } [Route("~/attribute-routing-in-asp-net-mvc-5")] public ActionResult Hotel() { return Content("I'am mvc attribute route! "); }
Refer:
What's New in ASP.NET MVC 5
http://www.asp.net/mvc/mvc5
What’s New in ASP.NET MVC 5.2
http://www.asp.net/mvc/overview/releases/whats-new-in-aspnet-mvc-52
attribute-routing-in-asp-net-mvc-5
http://blogs.msdn.com/b/webdev/archive/2013/10/17/attribute-routing-in-asp-net-mvc-5.aspx