我们可以使用路由为应用程序创建URL模板,这些路由模板匹配进入的请求并且分发这些请求到应用程序的终结点,终结点负责处理这些请求,在ASP.NET Core MVC中大多数的终结点是Controllers
在ASP.NET Core 中支持两种类型的路由:
1 基于契约路由 – 在Program.cs 类中使用
2 基于Attribute的路由 – 路由作为C# 特性使用在Controllers和action方法上
我们通过一个例子来了解ASP.NET Core 路由是如何工作
1 ASP.NET Core MVC 路由例子
在Visual Studio 创建一个新的ASP.NET Core MVC项目,名字为URLRouting
创建之后,在解决方案中打开Program.cs文件,将显示应用程序默认的路由:
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:
using AspNetCore.URLRouting.Models;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
namespace AspNetCore.URLRouting.Controllers
{
public class HomeController : Controller
{
private readonly ILogger
_logger; public HomeController(ILogger
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 = ;
}
@{
ViewData["Title"] = "Routing";
}
'Home' Controller, 'Index' View
运行应程序,你将看到Index视图显示到浏览器,图片如下:
https://localhost:7134 我们没有指定Controller或Action,但是在路由中定义了一个默认的Controller和Actioin分别是"HomeController"和"Index",因此请求能映射到HomeController的Action 方法,我们可以使用下面地址达到相同效果,访问 –
https://localhost:7134/Home/Index
在解释ASP.NET Core 路由如何工作之前,首先解释一下URL中的路由段
因此,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
app.UseEndpoints(endpoints =>
{
// Default route
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
注意:如果没有指定URL段,会使用默认路由模板中提供的值
当运行应用程序时,你会发现Home控制器中的Index视图被调用,尽管在URL中没有包含段(https://localhost:7134/),这是使用了路由模板中的默认值( Home和Index)
我们可以看到当前路由在URL中匹配3个段,如果大于3个段将失败并给404错误消息
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action}");
因此这个路由将匹配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 | 无法匹配 |
3 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方法
app.MapControllerRoute(
name: "news2",
pattern: "News{controller}/{action}");
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/Clothes, /Shopping/Electronics, /Shopping/Grocery 会用HomeController替换ShoppingController
3.2 Controller和Action 在路由中的默认值
如果您只想要一个特定的控制器和Action应该映射到一个URL,那么您必须提供Controller和Action的默认值,如下面的路由所示:
app.MapControllerRoute(
name: "old",
pattern: "Shopping/Old",
defaults: new { controller = "Home", action = "Index" });
3.3 ASP.NET Core 多个路由
路由按照在Program类中定义的顺序使用,ASP.NET Core 路由尝试将传入的 URL 与第一个定义的路由进行匹配,在没有匹配成功时才继续到下一个路由, 因此,你必须定义最优的路线
让我们添加下面2个路由在Program.cs 类中:
app.MapControllerRoute(
name: "old",
pattern: "Shopping/Old",
defaults: new { controller = "Home", action = "Index" });
app.MapControllerRoute(
name: "shop",
pattern: "Shopping/{action}",
defaults: new { controller = "Home" });
public IActionResult Old()
{
return View();
}
@{
Layout = ;
}
'Home' Controller, 'Old' View
现在,我们更改一下路由的顺序,将第二个路由放到第一个路由之前:
app.MapControllerRoute(
name: "shop",
pattern: "Shopping/{action}",
defaults: new { controller = "Home" });
app.MapControllerRoute(
name: "old",
pattern: "Shopping/Old",
defaults: new { controller = "Home", action = "Index" });
在Program类中改变了路由顺序之后,路由系统针对URL发现了不同的匹配,我们记住将更具体的路由放在第一位,否则,路由系统将做错误的映射
移除所有的路由,在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 = ;
}
'Home' Controller, 'Check' View
Id value is: @ViewBag.ValueofId
运行程序进入
https://localhost:7134/Home/Check/cSharp
ASP.NET Core 路由系统将匹配第三段的值在URL,作为id变量的值,你将看到id值是cSharp被显示到浏览器上:
如果你请求
https://localhost:7134/Home/Check,URL中第三段没有值,路由系统没有发现与之匹配的路由,因此在浏览器中会显示404错误
5 在Action方法的参数中获取路由变量
如果我们在Action方法中添加一个参数使用和URL相同名字,dotnet将获取url变量中的值传递到action方法参数
app.MapControllerRoute(
name: "MyRoute",
pattern: "{controller=Home}/{action=Index}/{id}");
public IActionResult Check(int id)
{
ViewBag.ValueofId = id;
return View();
}
我们注意到在方法内部,我们仅仅将id变量的值赋值给ViewBag变,访问
https://localhost:7134/Home/Check/100
你会看到100显示在视图上:
类似,我们将action方法中参数类型修改为string或者DateTime,dotnet将自动转换id的值到指定的类型
例如:我们把check方法参数的类型从int改为string
public IActionResult Check(string id)
{
ViewBag.ValueofId = id;
return View();
}
6 ASP.NET Core 路由可选参数
app.MapControllerRoute(
name: "MyRoute1",
pattern: "{controller=Home}/{action=Index}/{id?}");
当你没有给可选的参数提供值时,路由也会被匹配成功,只不过id值会被设置成
public IActionResult Check()
{
ViewBag.ValueofId = RouteData.Values["id"];
return View();
}
这是因为路由没有在可选的参数中发现id的值,因此空值会显示在浏览器
接下来,在HomeController中修改Check方法:
public IActionResult Check(string id)
{
ViewBag.ValueofId = id ?? " Value";
return View();
}
将参数id类型修改为string类型,现在代码ViewBag.ValueofId = id ?? " Value" 表示如果id的值为,会将 Value的值赋值给ViewBag变量,否则,将id的值赋值给ViewBag变量
这时你可以添加一个新的路由
app.MapControllerRoute(
name: "MyRoute2",
pattern: "{controller=Home}/{action=Index}/{id?}/{idtwo?}");
这很乏味而且会有大量代码重复,可以在路由中使用*catchall ,在url段的变量中它将扮演通配符的角色
app.MapControllerRoute(
name: "MyRoute2",
pattern: "{controller=Home}/{action=Index}/{id?}/{*catchall}");
我们已经添加了{*catchall}作为在路由中作为第四段,前3段分别映射到controller, action & id 变量
如果URL包含的段大于3,catchall变量将捕获所有的段,即:从第4段直到最后
下面表格中显示了,catchall匹配的路由
段 | URL | 映射 |
0 | / | |
1 | /Home | |
2 | /Home/CatchallTest | |
3 | /Home/CatchallTest/Hello | |
4 | ||
5 | ||
6 |
public IActionResult CatchallTest(string id, string catchall)
{
ViewBag.ValueofId = id;
ViewBag.ValueofCatchall = catchall;
return View();
}
接下来添加CatchallTest视图在View->Home文件夹使用下面代码:
@{ Layout = ; }
'Home' Controller, 'CatchallTest' View
Id value is: @ViewBag.ValueofId
Catchall value is: @ViewBag.ValueofCatchall
视图将会显示ViewBag中变量的值,运行程序且进入
URL-https://localhost:7134/Home/CatchallTest/Hello/How/Are/U你会发现catchall的值为How/Are/U显示在视图中
总结
源代码地址
参考文献
[1]https://www.yogihosting.com/aspnet-core-url-routing/