• .NET Core MVC中的路由功能
  • 发布于 2个月前
  • 231 热度
    0 评论
我们可以使用路由为应用程序创建URL模板,这些路由模板匹配进入的请求并且分发这些请求到应用程序的终结点,终结点负责处理这些请求,在ASP.NET Core MVC中大多数的终结点是Controllers。
路由主要完成下面工作:
1 匹配进入的URL到Controllers和Actions

2 根据定义的路由生成URL链接


在ASP.NET Core 中支持两种类型的路由:
1 基于契约路由 – 在Program.cs 类中使用
2 基于Attribute的路由 – 路由作为C# 特性使用在Controllers和action方法上

我们通过一个例子来了解ASP.NET Core 路由是如何工作。
一.ASP.NET Core MVC 路由例子
在Visual Studio 创建一个新的ASP.NET Core MVC项目,名字为URLRouting

创建之后,在解决方案中打开Program.cs文件,将显示应用程序默认的路由:
// 堆代码 duidaima.com
app.UseRouting();
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
我们配置app.UseRouting() 路由中间件,接着为应用程序添加一个default路由app.MapControllerRoute()方法,这个路由根据url指定的模板映射到终结点,这意味着当应用程序接收url匹配 {controller=Home}/{action=Index}/{id?} 会调用Home控制器中的Index方法。

注意: {controller=Home}意味着如果没有指定控制器,将使用HomeController作为默认控制器,类似的{action=Index} 意味着如果action没有被具体指定,将会调用Index作为默认的action,{id?} - id 参数是可选的,在该参数后面使用"?"

这个路由模板匹配如下URL:
1 /
2 /Home
3 /Home/Index
4 /Products/Details
5 /Products/Details/3
ASP.NET Core 应用程序中有一个HomeController.cs,该控制器里面包含一个Index方法,代码如下:
using AspNetCore.URLRouting.Models;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
namespace AspNetCore.URLRouting.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }
        public IActionResult Index()
        {
            return View();
        }
        public IActionResult Privacy()
        {
            return View();
        }
        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}
现在,修改Index.cshtml 视图代码(Views->Home)文件夹内:
@{
    Layout = null;
}
@{
    ViewData["Title"] = "Routing";
}
<h1>'Home' Controller, 'Index' View</h1>
运行应程序,你将看到Index视图显示到浏览器,图片如下:

https://localhost:7134 我们没有指定Controller或Action,但是在路由中定义了一个默认的Controller和Actioin分别是"HomeController"和"Index",因此请求能映射到HomeController的Action 方法,我们可以使用下面地址达到相同效果,访问 – https://localhost:7134/Home/Index 。

在解释ASP.NET Core 路由如何工作之前,首先解释一下URL中的路由段。

二. 什么是路由段
段是URL的一部分,除了HostName和查询字符串,使用"/"字符进行分割,下面图片解释:

因此,URL可以有多个段,但是大多数应用程序需要的段不会大于3个。当HTTP 请求进入应用程序时,ASP.NET Core 路由匹配 URL 并从中提取每个段的值,在Program.cs类中,我们能看到默认契约路由最多能匹配3个段
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
2.1 .NET Core 5.0 和 ASP.NET Core 3.0
如果你使用的这个版本,检查Startup类中的Configure()方法,会添加一个默认路由("default"是一个名字,你可以使用别的名字),使用了UseEndpoints 方法,我们使用MapControllerRoute()方法来创建路由,代码如下:
app.UseEndpoints(endpoints =>
{
    // Default route
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});
花括号{}内部代表一个段,默认路由有3个段:
1 Home是默认的Controller
2 Index是默认的Action
3 id是一个可选的参数
注意:如果没有指定URL段,会使用默认路由模板中提供的值

当运行应用程序时,你会发现Home控制器中的Index视图被调用,尽管在URL中没有包含段(https://localhost:7134/),这是使用了路由模板中的默认值( Home和Index)。我们可以看到当前路由在URL中匹配3个段,如果大于3个段将失败并给404错误消息。

现在移除默认的Controller和Action 段,并且从路由移除id段:
app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action}");
运行应用程序,你将获取404错误,因为路由系统不能映射到任何Controller的URL,如下图所示:

打开https://localhost:7134/Home/Index 并添加Controller和Action的名字,这次你将看到Home控制器中的Index方法被调用。因此这个路由将匹配2个段在URL,别的数量的段,像 0,1,3,4....它将失败并给予404错误消息。

下面的表给予了所有可能匹配的URL:

URL
映射到
0
/
无法匹配
1 /Home
无法匹配
2 /Home/Index
controller=Home action=Index
2
/Home/Show/4
无法匹配
3
/Home/Show/4
无法匹配


三.ASP.NET Core 静态路由
ASP.NET Core 支持静态路由,为了理解它,移除默认路由并添加一个路由静态文本:
app.MapControllerRoute(
    name: "news1",
    pattern: "News/{controller=Home}/{action=Index}");
路由包含一个静态文本-News,运行应用程序并在浏览器中打开如下地址https://localhost:7134/News,你会发现Home控制器的Index方法被调用。让我们理解发生了什么,路由匹配3个段,第一个是News一个静态文本,第二个和第三个段Controller和Action是可选的,如果省略,默认会访问Home控制器的Index方法。

你也可以在静态文本中结合变量段(像Controller或者action),使用下面路由模板:
app.MapControllerRoute(
    name: "news2",
    pattern: "News{controller}/{action}");
这里我们添加了静态文本News结合Controller段的值,现在运行应用程序并且进入https://localhost:7134/NewsHome/Index,你会发现Home控制器中的Index方法被调用

3.1 保留旧的路由
通过使用静态路由我们能保留旧的URL,假设我们有一个Shopping的Controller,现在你可以使用HomeController替换Shopping(即/Shopping/Clothes, /Shopping/Electronics, /Shopping/Grocery等)
你在Program类中添加这个路由:
app.MapControllerRoute(
    name: "shop",
    pattern: "Shopping/{action}",
    defaults: new { controller = "Home" });
路由匹配两个段,第一个段是Shopping,第二个段是action方法,URL模板没有包含Controller段,因此这时会使用默认值,默认参数提供了一个HomeController的值
/Shopping/Clothes, /Shopping/Electronics, /Shopping/Grocery 会用HomeController替换ShoppingController 。

打开URL-https://localhost:7134/Shopping/Index 在浏览器,你将发现HomeController中的Index方法被调用, 如下图所示:

3.2 Controller和Action 在路由中的默认值
如果您只想要一个特定的控制器和Action应该映射到一个URL,那么您必须提供Controller和Action的默认值,如下面的路由所示:
app.MapControllerRoute(
    name: "old",
    pattern: "Shopping/Old",
    defaults: new { controller = "Home", action = "Index" });
https://localhost:7134/Shopping/Old 会映射到Home中的 Index方法,如下图所示:


3.3 ASP.NET Core 多个路由
路由按照在Program类中定义的顺序使用,ASP.NET Core 路由尝试将传入的 URL 与第一个定义的路由进行匹配,在没有匹配成功时才继续到下一个路由, 因此,你必须定义最优的路线。

让我们添加下面2个路由在Program.cs 类中:
//堆代码 duidaima.com
app.MapControllerRoute(
    name: "old",
    pattern: "Shopping/Old",
    defaults: new { controller = "Home", action = "Index" });

app.MapControllerRoute(
    name: "shop",
    pattern: "Shopping/{action}",
    defaults: new { controller = "Home" });
在HomeController中添加一个新的Action方法Old, 代码如下:
 public IActionResult Old()
 {
     return View();
 }
下一步,在Views->Home文件夹添加Old视图,内容如下:
@{
    Layout = null;
}
<h1>'Home' Controller, 'Old' View</h1>
运行应用程序并打开https://localhost:7134/Shopping/Old 在浏览器中,这次你将发现HomeController中的Index方法被调用,看如下图片:     

当我们请求https://localhost:7134/Shopping/Old地址时,路由系统开始从第一个路由匹配url,第一个路由被成功匹配,所以Home控制器中的Index方法被调用。
现在,我们更改一下路由的顺序,将第二个路由放到第一个路由之前:
app.MapControllerRoute(
    name: "shop",
    pattern: "Shopping/{action}",
    defaults: new { controller = "Home" });

app.MapControllerRoute(
    name: "old",
    pattern: "Shopping/Old",
    defaults: new { controller = "Home", action = "Index" });
我们再次请求https://localhost:7134/Shopping/Old地址时,这次你会发现Home控制器中的Old方法被调用,如下图所示:

在Program类中改变了路由顺序之后,路由系统针对URL发现了不同的匹配,我们记住将更具体的路由放在第一位,否则,路由系统将做错误的映射

四. 客户自定义段
到目前为止,我们已经在路由中看到controller和action段变量,你可以在路由中添加你自己的变量段,让我们看一下具体如何做
移除所有的路由,在ASP.NET Core应用程序的Program.cs中添加下面代码:
app.MapControllerRoute(
    name: "MyRoute",
    pattern: "{controller=Home}/{action=Index}/{id}");
在这个路由中我添加了自定义的段叫id,接下来在HomeController中添加一个新的Action叫Check:
public IActionResult Check()
{
    ViewBag.ValueofId = RouteData.Values["id"];
    return View();
}
这个action方法使用RouteData.Values属性获取路由模板中客户自定义变量id的值,并且将值存储在ViewBag变量,创建一个check 视图在Home->Check文件夹下,Check视图的代码如下:
@{
    Layout = null;
}
<h1>'Home' Controller, 'Check' View</h1>
<h2>Id value is: @ViewBag.ValueofId</h2>
运行程序进入https://localhost:7134/Home/Check/cSharp 。
ASP.NET Core 路由系统将匹配第三段的值在URL,作为id变量的值,你将看到id值是cSharp被显示到浏览器上:

如果你请求https://localhost:7134/Home/Check,URL中第三段没有值,路由系统没有发现与之匹配的路由,因此在浏览器中会显示404错误

五 在Action方法的参数中获取路由变量
如果我们在Action方法中添加一个参数使用和URL相同名字,dotnet将获取url变量中的值传递到action方法参数。
我们使用和上面相同的路由:
app.MapControllerRoute(
    name: "MyRoute",
    pattern: "{controller=Home}/{action=Index}/{id}");
为了能获取url中id的值,我们在action方法中添加一个id参数,因此我们在action方法中添加一个id的参数:
public IActionResult Check(int id)
{
      ViewBag.ValueofId = id;
      return View();
}
我们注意到在方法内部,我们仅仅将id变量的值赋值给ViewBag变,访问https://localhost:7134/Home/Check/100 。
你会看到100显示在视图上:

注意:我们将id参数声明为为Int类型,dotnet会尝试将URL中100转换到action方法的参数所声明的类型,这里使用了模型绑定相关技术技术。类似,我们将action方法中参数类型修改为string或者DateTime,dotnet将自动转换id的值到指定的类型。
例如:我们把check方法参数的类型从int改为string
public IActionResult Check(string id)
{
    ViewBag.ValueofId = id;
    return View();
}

当请求URL-https://localhost:7134/Home/Check/hello 或者https://localhost:7134/Home/Check/100,框架会自动将客户自定义id变量的值(hello 和100)转化成string 。


六. ASP.NET Core 路由可选参数
如果路由段是可选类型的参数,在调用过程中你不需要指定该参数,通过使用问号(?)来指定可选参数,在你的应用程序移除所有的路由,添加下面代码,我们使用?将id段标记为可选参数:
app.MapControllerRoute(
    name: "MyRoute1",
    pattern: "{controller=Home}/{action=Index}/{id?}");
当你没有给可选的参数提供值时,路由也会被匹配成功,只不过id值会被设置成null 。
我们在HomeController的check方法测试一下:
public IActionResult Check()
{
    ViewBag.ValueofId = RouteData.Values["id"];
    return View();
}
运行程序,访问URL-https://localhost:7134/Home/Check
你会看到空值显示在浏览器中:

这是因为路由没有在可选的参数中发现id的值,因此空值会显示在浏览器。接下来,在HomeController中修改Check方法:
public IActionResult Check(string id)
{
    ViewBag.ValueofId = id ?? "Null Value";
    return View();
}
将参数id类型修改为string类型,现在代码ViewBag.ValueofId = id ?? "Null Value" 表示如果id的值为null,会将Null Value的值赋值给ViewBag变量,否则,将id的值赋值给ViewBag变量。运行程序并且访问-https://localhost:7134/Home/Check 地址,你会发现Null Value显示在浏览器:

七. 路由通配符的*catchall
到目前为止,我们使用的路由的URL最多只有3个段与之匹配,如果你的URL中断的数量大于3个,我们应该怎么办呢?一种方法针对大于3个段添加新的路由,例如:匹配4个段URL-https://localhost:7134/Home/Check/Hello/World,这时你可以添加一个新的路由
app.MapControllerRoute(
    name: "MyRoute2",
    pattern: "{controller=Home}/{action=Index}/{id?}/{idtwo?}");
如果第5个段出现,你不得不添加另外一个新的路由来匹配5段,这很乏味而且会有大量代码重复,可以在路由中使用*catchall ,在url段的变量中它将扮演通配符的角色.我们将路由的第四段修改为 {*catchall} , 它将是:
app.MapControllerRoute(
    name: "MyRoute2",
    pattern: "{controller=Home}/{action=Index}/{id?}/{*catchall}");
我们已经添加了{*catchall}作为在路由中作为第四段,前3段分别映射到controller, action & id 变量,如果URL包含的段大于3,catchall变量将捕获所有的段,即:从第4段直到最后

下面表格中显示了,catchall匹配的路由


URL

映射

0

/

controller = Homeaction = Index id = Null

1

/Home

controller = Homeaction = Index id = Null

2

/Home/CatchallTest

controller = Homeaction = CatchallTest Id = Null

3

/Home/CatchallTest/Hello

controller = Homeaction = catchallTest id = Hello

4

/Home/CatchallTest/Hello/How controller = Homeaction = catchallTest id = Hello catchall = How

5

/Home/CatchallTest/Hello/How/Are controller = Homeaction = catchallTest id = Hello catchall = How/Are

6

/Home/CatchallTest/Hello/How/Are/U controller = Homeaction = catchallTest id = Hello catchall = How/Are/You
为了测试,在HomeController代码中添加新的Action方法,名字为CatchallTest,代码如下:
public IActionResult CatchallTest(string id, string catchall)
{
    ViewBag.ValueofId = id;
    ViewBag.ValueofCatchall = catchall;
    return View();
}
我们在action方法中添加了一个新的参数名字为cacthall,这个参数的值是URL中除了前3段剩余所有段的值,我们将cacthall的值存储到ViewBag的ValueofCatchall变量中。
接下来添加CatchallTest视图在View->Home文件夹使用下面代码:
@{ Layout = null; }
<h1>'Home' Controller, 'CatchallTest' View</h1>
<h2>Id value is: @ViewBag.ValueofId</h2>
<h2>Catchall value is: @ViewBag.ValueofCatchall</h2>
视图将会显示ViewBag中变量的值,运行程序且进入URL-https://localhost:7134/Home/CatchallTest/Hello/How/Are/U你会发现catchall的值为How/Are/U显示在视图中


总结
这节我们主要讲解在Program.cs类中如何创建路由
用户评论