ASP.NET Core 入门教程 3、ASP.NET Core MVC路由入门

一、前言

1、本文主要内容

  • ASP.NET Core MVC路由工作原理概述
  • ASP.NET Core MVC带路径参数的路由示例
  • ASP.NET Core MVC固定前/后缀的路由示例
  • ASP.NET Core MVC正则表达式匹配路由示例
  • ASP.NET Core MVC路由约束与自定义路由约束
  • ASP.NET Core MVC RouteAttribute绑定式路由使用介绍

2、本教程环境信息

软件/环境 说明
操作系统 Windows 10
SDK 2.1.401
ASP.NET Core 2.1.3
IDE Visual Studio Code 1.27
浏览器 Chrome 69

本篇代码基于上一篇进行调整:https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-02

3、前置知识

你可能需要的前置知识

  • MVC框架/模式介绍

https://baike.baidu.com/item/mvc

  • 正则表达式

http://www.runoob.com/regexp/regexp-tutorial.html

二、ASP.NET Core MVC 路由简介

1、ASP.NET Core MVC路由工作原理概述

ASP.NET Core MVC路由的作用就是将应用接收到请求转发到对应的控制器去处理。

应用启动的时候会将路由中间件(RouterMiddleware)加入到请求处理管道中,并将我们配置好的路由加载到路由集合(RouteCollection)中。当应用接收到请求时,会在路由管道(路由中间件)中执行路由匹配,并将请求交给对应的控制器去处理。

另外,需要特别注意的是,路由的匹配顺序是按照我们定义的顺序从上之下匹配的,遵循是的先配置先生效的原则。

2、路由配置参数说明

参数名 说明
name 路由名称,不可重复
template 路由模板,可在模板中以{name}格式定义路由参数
defaults 配置路由参数默认值
constraints 路由约束

在路由配置中,MVC框架内置了两个参数,controller,action。
路由匹配通过后,需要根据这两个参数将当前请求交由对应的Controller+Action去处理。所以,这两个参数缺少任何一个,都会导致路由无法正常工作。
通常我们有两个选择:

  • 在template中指定{controller},{action}参数
  • 在默认值中为controller、action指定默认值

三、ASP.NET Core MVC 路由示例

1、准备工作

为了方便我们进行测试,我们先准备好承接路由的Controller&Action

  • 创建TutorialController

在Controllers文件夹下新增控制器TutorialController.cs并继承于Controller

using System;
using Microsoft.AspNetCore.Mvc;

namespace Ken.Tutorial.Web.Controllers
{
    public class TutorialController : Controller
    {
      
    }
}

  • 增加Action:Index
public IActionResult Index()
{
    return Content("ASP.NET Core Tutorial by ken from ken.io");
}
  • 增加Action:Welcome
public IActionResult Welcome(string name, int age)
{
    return Content($"Welcome {name}(age:{age}) !");
}

2、带路径参数的路由

路由配置:

routes.MapRoute(
        name: "TutorialPathValueRoute",
        template: "{controller}/{action}/{name}/{age}"
    );

此路由适配URL:

  • /tutorial/welcome/ken/20

不适配URL:

  • /tutorial/welcome/ken

如果我们希望不在路径中设置age,也可以被路由到,那么可以将age指定为可选参数,将模板中的{age}修改为{age?}即可

routes.MapRoute(
        name: "TutorialPathValueRoute",
        template: "{controller}/{action}/{name}/{age?}"
    );

此路由适配URL:

  • /tutorial/welcome/ken/20
  • /tutorial/welcome/ken
  • /tutorial/welcome/ken?age=20

3、固定前后缀的路由

固定前缀路由配置:

routes.MapRoute(
    name: "TutorialPrefixRoute",
    template: "jiaocheng/{action}",
    defaults: new { controller = "Tutorial" }
);

此路由适配URL:

  • /jiaocheng/index
  • /jiaocheng/welcome

由于路径参数中不包含controller参数,所以需要在默认值中指定。

固定后缀路由配置

routes.MapRoute(
    name: "TutorialSuffixRoute",
    template: "{controller}/{action}.html"
);

此路由适配URL:

  • /tutorial/index.html
  • /tutorial/welcome.html
  • /home/index.html
  • /home/time.html

固定后缀的路由适用于伪静态等诉求
固定前后缀可以根据自己的需求结合起来使用。
当然,你也可以在路由模板中间设定固定值。

四、ASP.NET Core MVC 路由约束

1、路由约束介绍

路由约束主要是用于约束路由参数,在URL格式满足路有模板要求之后,进行参数检查。如果参数不满足路由约束,那么依然会返回未匹配该路由。最常用的可能就是参数类型校验、参数长度校验、以及通过正则满足的复杂校验。

在开始之前需要在Startup.cs中引用相关命名空间

using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Constraints;

2、参数长度约束

路由配置:约束name长度不能>5

routes.MapRoute(
    name: "TutorialLengthRoute",
    template: "hello/{name}/{age?}",
    defaults: new { controller = "Tutorial", action = "Welcome", name = "ken" },
    constraints: new { name = new MaxLengthRouteConstraint(5) }
);

此路由适配

  • /hello
  • /hello/ken
  • /hello/ken/1000

次路由不适配

  • /hello/kenaaaa

我们也可以直接在模板中配置路由约束:

routes.MapRoute(
    name: "TutorialLengthRoute2",
    template: "hello2/{name:maxlength(5)}/{age?}",
    defaults: new { controller = "Tutorial", action = "Welcome", name = "ken" }
);

3、参数范围约束

路由配置:约束 1<=age<=150

routes.MapRoute(
    name: "TutorialLengthRoute",
    template: "hello/{name}/{age?}",
    defaults: new { controller = "Tutorial", action = "Welcome", name = "ken" },
    constraints: new {  age = new CompositeRouteConstraint(new IRouteConstraint[] { 
                            new IntRouteConstraint(), 
                            new MinRouteConstraint(1), 
                            new MaxRouteConstraint(150) }) }
);

此路由适配:

  • /hello/ken/1
  • /hello/ken/150

此路由不适配

  • /hello/ken/1000

我们也可以直接在模板中配置路由约束:

routes.MapRoute(
    name: "TutorialLengthRoute2",
    template: "hello2/{name}/{age:range(1,150)?}",
    defaults: new { controller = "Tutorial", action = "Welcome", name = "ken" }
);

4、带有正则表达式约束的路由

路由配置:

routes.MapRoute(
    name: "TutorialRegexRoute",
    template: "welcome/{name}",
    defaults: new { controller = "Tutorial", Action = "Welcome" },
    constraints: new { name = @"k[a-z]*" }
);

此路由适配:

  • /welcome/k
  • /welcome/ken
  • /welcome/kevin

此路由不适配

  • /welcome/k1
  • /welcome/keN
  • /welcome/tom

这里我们用正则表达式约束了参数name,必须通过正则k[a-z]*匹配通过,即:以小写字母k开头,且后续可跟0到多个小写字母

我们也可以直接在模板中配置路由约束:

routes.MapRoute(
    name: "TutorialRegexRoute2",
    template: "welcome2/{name:regex(k[a-z]*)}",
    defaults: new { controller = "Tutorial", Action = "Welcome" }
);

5、自定义路由约束

1、创建自定义约束

在项目根目录创建目录Common,并在目录创建类:NameRouteConstraint.cs,然后实现接口:IRouteConstraint

using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;

namespace Ken.Tutorial.Web.Common
{
    public class NameRouteConstraint : IRouteConstraint
    {
        public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
        {
            string name = values["name"]?.ToString();
            if (name == null) return true;
            if (name.Length > 5 && name.Contains(",")) return false;
            return true;
        }
    }
}

这里我们约束当name长度>5时,name中不能包含,

2、路由配置

引入命名空间

using Ken.Tutorial.Web.Common;

在ConfigureServices引入路由约束

public void ConfigureServices(IServiceCollection services)
    {
        //引入MVC模块
        services.AddMvc();

        //引入自定义路由约束
        services.Configure<RouteOptions>(options =>
        {
            options.ConstraintMap.Add("name", typeof(NameRouteConstraint));
        });
    }

配置路由

routes.MapRoute(
    name: "TutorialDiyConstraintRoute",
    template: "diy/{name}",
    defaults: new { controller = "Tutorial", action = "Welcome" },
    constraints: new { name = new NameRouteConstraint() }
);

此路由适配:

  • /diy/ken
  • /diy/ken,
  • /diy/kenny

此路由不适配

  • /diy/kenny,

当然,按照惯例,依然可以在模板中配置路由约束

routes.MapRoute(
    name: "TutorialDiyConstraintRoute2",
    template: "diy2/{name:name}",
    defaults: new { controller = "Tutorial", action = "Welcome" }
);

五、ASP.NET Core MVC 绑定式路由配置

1、路由配置风格

  • 集中式配置

前面章节提到的路由配置都是在Startup类中进行的集中式路由配置,集中配置的路由,除了template中没有配置{controller}参数,默认都是对所有控制器(Controller)生效的。这种集中配置的方式一般我们只要配置一个默认路由,其他情况我们只需要不满足默认模板的情况下进行配置即可。尤其是对URL没有友好度要求的应用,例如:后台管理系统

  • 分散式配置/绑定式配置

对于集中式路由配置的方式,如果某个Controller/Action配置了特殊路由,对于代码阅读就会不太友好。不过没关系,ASP.NET Core MVC也提供了RouteAttribute可以让我们在Controller或者Action上直接指定路由模板。

不过要强调的是,一个控制器只能选择其中一种路由配置,如果控制器标记了RouteAttribute进行路由配置,那么集中式配置的路由将不对其生效。

2、绑定式路由配置

在项目Controllers目中新建TestController.cs继承与Controller
并配置Action与路由

using System;
using Microsoft.AspNetCore.Mvc;

namespace Ken.Tutorial.Web.Controllers
{
    [Route("/test")]
    public class TestController : Controller
    {
        [Route("")]
        [Route("/test/home")]
        public IActionResult Index()
        {
            return Content("ASP.NET Core RouteAttribute test by ken from ken.io");
        }

        [Route("servertime")]
        [Route("/t/t")]
        public IActionResult Time(){
            return Content($"ServerTime:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} - ken.io");
        }
    }
}

配置项 说明
[Route("/test")] 表示该Controller访问路由前缀为/test,必须以/开头
[Route("")] 表示以Controller的路由配置为前缀访问该Action;可以通过/test路由到该Action
[Route("/test/home")] 表示忽略Controller的路由配置;可以通过/test/home路由到该Action
[Route("servertime")] 表示以Controller的路由配置为前缀访问该Action;可以通过/test/servertime路由到该Action
[Route("/t/t")] 表示忽略Controller的路由配置;可以通过/t/t路由到该Action

RouteAttribute中配置的参数,就相当于我们集中式配置中的路由模板(template),最终框架还是帮我们初始化成路由规则,以[Route("/test/home")]为例,相当于生成了以下路由配置:

routes.MapRoute(
    name: "Default",
    template: "test/home",
    defaults: new { controller = "Test", action = "Index" }
);

当然,我们也可以在[Route]配置中使用模板参数,而且依然可以在模板中使用约束,自定义约束也没问题。

[Route("welcome/{name:name}")]
public IActionResult Welcome(string name){
    return Content($"Welcome {name} !");
}

最大的区别就是不能定义默认值了,可能也不需要了,你说是吧。_

六、备注

1、附录

  • 本文代码示例

https://github.com/ken-io/asp.net-core-tutorial/tree/master/chapter-03

  • 本文参考

https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/routing?view=aspnetcore-2.1


本文首发于我的独立博客: https://ken.io/note/asp.net-core-tutorial-mvc-route

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容