1. 首页
  2. 文章列表
  3. 浅谈MVC的Attribute路由,教你一步一步设计出漂亮的路由

导读:

最近做我这个博客项目,为了把路由设计得漂亮,每一个链接都到RouteConfig.cs,后来项目越来越大,控制器也越来越多,感觉这样做也太不灵活和增加了维护成本,所以查了一下MSDN,发现原来还有个Attribute路由的东西,为此专门深入的研究了一下MVC5的Attribute路由机制,因此有了这篇文章,本文就Attribute路由做出详细的入门和Demo。

几个路由示例:

文章分类:http://masuit.com/cat/6

文章详情页:http://masuit.com/77

专题页:http://masuit.com/c/2

一个杂项页:http://masuit.com/tools/ip

1、Attribute路由是什么鬼?如何启用Attribute路由?

ASP.NET MVC5中微软引入了一种新型路由:Attribute路由,顾名思义,Attribute路由是通过Attribute标签来定义路由。当然,MVC5也支持以前传统定义路由的方式,可以在一个项目中混合使用这两种方式来定义路由。

这其实也是在最近玩.NET Core的时候才发现的,原来MVC5也能用Attribute来定义路由,于是研究了一番。

  在传统的ASP.NET MVC版本中我们通常在 RouteConfig.cs 文件中通过以下方式来定义路由:

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    constraints: new { id = "\\d+" }
);

  在MVC5中,我们可以把路由定义和 Action 放在一起:

[Route("p/{page:int?}/{size:int?}/{orderBy:int?}")]
public async Task<ActionResult> Post(int page = 1,int size = 15,OrderBy orderBy = OrderBy.ModifyDate){...}

  当然,首先得启用Attribute路由,我们可以调用MapMvcAttributeRoutes方法来启用Attribute路由:

public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            routes.IgnoreRoute("{resource}.ashx");
            routes.MapMvcAttributeRoutes();
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
                constraints: new { productId = "\\d+" }
            );
            routes.MapRoute(
                name: "page2",
                url: "{controller}/{action}/{page}/{size}/{orderby}",
                defaults: new { controller = "Home", action = "Post", page = UrlParameter.Optional, size = UrlParameter.Optional, orderby = UrlParameter.Optional }
            );
        }
    }

2、URL可选参数和默认值

  我们可以使用问号“?”来标记一个可选参数,也可以对参数设定默认值:

/// 匹配:/p
/// 匹配:/p/1
/// 匹配:/p/1/10
/// 匹配:/p/1/10/0
[Route("p/{page:int?}/{size:int?}/{orderBy:int?}")]
public async Task<ActionResult> Post(int page = 1, int size = 15, OrderBy orderBy = OrderBy.ModifyDate)
//匹配: /12
[Route("{id:int}")]
public ActionResult Details(int id)

 3、路由前缀

  有时候在同一个 Controller 中,所有 Action 匹配的 URL 都拥有相同的前缀,如下:

        public class ReviewsController : Controller
    {
        // 匹配: /reviews
        [Route("reviews")]
        public ActionResult Index() { ... }
        // 匹配: /reviews/5
        [Route("reviews/{reviewId}")]
        public ActionResult Show(int reviewId) { ... }
        // 匹配: /reviews/5/edit
        [Route("reviews/{reviewId}/edit")]
        public ActionResult Edit(int reviewId) { ... }
    }

  我们看到 ReviewsController 下的所有 Action 前面都带有 "reviews",这时我们可以在 Controller 上使用 [RoutePrefix]设置路由前缀,为每个 Action 所匹配的 URL 加上共同的前缀 "reviews":

        [RoutePrefix("reviews")]
    public class ReviewsController : Controller
    {
        // 匹配: /reviews
        [Route]
        public ActionResult Index() { ... }
        // 匹配: /reviews/5
        [Route("{reviewId}")]
        public ActionResult Show(int reviewId) { ... }
        // 匹配: /reviews/5/edit
        [Route("{reviewId}/edit")]
        public ActionResult Edit(int reviewId) { ... }
    }

  但是,如果某一个 Action 不想要这个前缀怎么办?当然有办法,我们可以用波浪号“~”来去掉它:

    [RoutePrefix("reviews")]
    public class ReviewsController : Controller
    {
        // 匹配: /spotlight-review
        [Route("~/spotlight-review")]
        public ActionResult ShowSpotlight() { ... }
    }

4、默认路由

  我们除了可以在 Action 上使用[Route]外,也可以用在 Controller 上,当[Route]用在 Controller 上时,它就定义了一个默认路由规则,它会对这个 Controller 下的所有 Action 起作用,除非某个 Action 上也应用了 [Route] 特性覆盖了 Controller 上的[Route]。但要注意的是应用在 Controller 上的 [Route] 一定要加上 {action},否则会抛出“RouteData 必须包含名为'action'且值为非空字符串的项。”错误。应用在 Action 上的 [Route] 则不用加,因为 {action} 就是当前 Action。

    [RoutePrefix("promotions")]
    [Route("{action=index}")]
    //上面定义了默认路由,并且{action}的默认值为"index",
    //也就是说 URL 不包含 {action} 时,默认调用的 Action 是 Index。
    public class ReviewsController : Controller
    {
        // 匹配: /promotions
        public ActionResult Index() { ... }
        // 匹配: /promotions/archive
        public ActionResult Archive() { ... }
        // 匹配: /promotions/new
        public ActionResult New() { ... }
        // 匹配: /promotions/edit/5
        // 这里覆盖了默认路由规则
        // 按照默认路由,这里应该匹配:/promotions/editProduct?promoId=5
        [Route("edit/{promoId:int}")]
        public ActionResult EditProduct(int promoId) { ... }
    }

5、路由约束

  路由约束可以让你指定参数的类型以及范围等,格式为:{参数:约束},举例如下:

// 匹配: /users/5
[Route("users/{id:int}"]
// 这里约束了参数“id”必须为整数类型
public ActionResult GetUserById(int id) { ... }

  下面是支持的路由约束列表:

alpha,必须为大小写字母(a-z,A-Z),如:{x:alpha};

bool,必须为布尔值,如:{x:bool}

datetime,必须为DateTime(时间和日期)类型,如:{x:datetime}

decimal,必须为decimal类型,如:{x:decimal}

double,必须为64bit浮点数,如:{x:double}

float,必须为32bit浮点数,如:{x:float}

guid,必须为GUID,如:{x:guid}

int,必须为32bit整数,如:{x:int}

length,字符串长度必须为指定值或者在指定范围内,如:{x:length(6)} {x:length(1,20)}

long,必须为64bit整数,如:{x:long}

max,小于等于指定值的整数,如:{x:max(10)}

maxlength,字符串长度小于等于指定值,如:{x:maxlength(10)}

min,大于等于指定值的整数整数,如:{x:min(10)}

minlength,字符串长度大于等于指定值,如:{x:minlength(10)}

range,必须是给定范围内的整数,如:{x:range(10,50)}

regex,必须与正则表达式匹配,如:{x:(^\d{3}-\d{3}-\d{4}$)}

  你可以在一个参数后面应用多个约束,用冒号分隔它们,如下:

// 匹配: /users/5
// 但是不匹配 /users/10000000000 因为id的值已经超过了int.MaxValue,
// 也不匹配 /users/0 因为后面有个min(1)约束,id 的值必须大于等于 1.
[Route("users/{id:int:min(1)}")]
public ActionResult GetUserById(int id) { ... }

  值得注意的是加在可选参数上的约束,例如:

// 匹配: /greetings/bye
// 也匹配 /greetings 因为message是可选参数,
// 但是不匹配 /greetings/see-you-tomorrow 因为有maxlength(3)约束.
[Route("greetings/{message:maxlength(3)?}")]
public ActionResult Greet(string message) { ... }

6、自定义路由约束

  我们可以通过实现 IRouteConstraint 接口来自定义路由约束。下面的例子展示如何自定义路由约束:

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
{
    // 匹配 temp/celsius 以及 /temp/fahrenheit 但不匹配 /temp/kelvin
    [Route("temp/{scale:values(celsius|fahrenheit)}")]
    public ActionResult Show(string scale)
    {
        return Content("scale is " + scale);
    }
}

7、路由名称

  你可以为路由规则指定一个名称,以方便生成相应的URL,举例如下:

[Route("menu", Name = "mainmenu")]
public ActionResult MainMenu() { ... }

  你可以使用 Url.RouteUrl 来生成相应的 URL:

<a href="@Url.RouteUrl("mainmenu")">Main menu</a>

8、Area

  ASP.NET MVC 的 Area 概念对组织大型Web应用程序很有帮助,在Attribute路由中当然少不了对它的支持,只要使用 [RouteArea],就可以把 Controller 归属到某一个 Area 下,这时你可以放心的删除 Area 下的 AreaRegistration 类了:

    [RouteArea("Admin")]
    [RoutePrefix("menu")]
    [Route("{action}")]
    public class MenuController : Controller
    {
        // 匹配: /admin/menu/login
        public ActionResult Login() { ... }
        // 匹配: /admin/menu/show-options
        [Route("show-options")]
        public ActionResult Options() { ... }
        // 匹配: /stats
        [Route("~/stats")]
        public ActionResult Stats() { ... }
    }

 现在你可以和以往的版本一样使用 "Admin" Area,下面的代码会生成 URL "/Admin/menu/show-options":

Url.Action("Options", "Menu", new { Area = "Admin" })

 你还可以通过 AreaPrefix 来设置 Area 前缀,例如:

[RouteArea("BackOffice", AreaPrefix = "back-office")]

  如果你同时使用 Attribute、AreaRegistration 类这两种方式来注册 Area 的话,你应该在注册 Attribute 路由和传统路由映射之间使用 AreaRegistration 注册 Area,原因很简单,路由注册顺序必须是从最精确的匹配规则开始再到普通的匹配规则,最后才是模糊的匹配规则,这样就避免了在进行路由匹配时,过早的匹配了模糊规则,而相对精确的匹配起不到任何作用。下面的例子展示了这一点:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.MapMvcAttributeRoutes();
    AreaRegistration.RegisterAllAreas();
    routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
}

9、结语

现在,有了Attribute路由,设计漂亮的路由就更加容易了,什么样的路由都不在话下!微软大法好!

下载为Word文档

版权声明:

本文仅用于学习、研究和交流目的,欢迎非商业性质转载。本文链接:https://masuit.com/86

l  博主在此发文(包括但不限于汉字、拼音、拉丁字母)均为随意敲击键盘所出,用于检验本人电脑键盘录入、屏幕显示的机械、光电性能,并不代表本人局部或全部同意、支持或者反对观点。如需要详查请直接与键盘生产厂商法人代表联系。挖井挑水无水表,不会网购无快递。

l  文章内容部分来源于互联网,不代表本人的任何立场;涉及到的软件来源于互联网,仅供个人下载使用,请勿用于商业用途,版权归软件开发者所有,下载后请于24小时内删除,请支持正版!因下载本站任何资源造成的损失,全部责任由使用者本人承担!如果你是版权方,认为本文内容对您的权益有所侵犯,请联系博主,待博主进行严格地审查和背景调查后,情况属实的将在三天内将本文删除或修正。

l  博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。

l  博主是利用读书、参考、引用、抄袭、复制和粘贴等多种方式打造成自己的纯镀 24k 文章,请原谅博主成为一个无耻的文档搬运工!

l  博主只是一名普通的互联网从业者,不懂修电脑,不会卖电脑,不会帮你盗号,不会破解开机密码,找不回你丢失的手机等,如有这样的想法请绕道!

相关推荐:

C# vs Java:C# 五个不可替代的特性瞬间秒杀 Java 浅谈EF中LINQ查询原理
一小时学会 C#6.0 的新特性 C#经典面试题——深入理解IEnumerable和IQueryable两接口的区别
五分钟重温C#委托,匿名方法,Lambda,泛型委托,表达式树 经典面试题之——如何自由转换两个没有继承关系的字段及类型相同的实体模型
谈一谈.NET中的并行编程(TPL)——多线程、异步、任务和并行计算 深入浅出依赖注入容器——Autofac
谈一谈单例模式、静态类和线程内唯一对象有什么区别 AutoMapper6.x起步

评论区:

    还没有评论哦,赶紧来写评论吧

    分享按钮