Convention Routing VS Attribute Routing






4.94/5 (9投票s)
本文将详细介绍基于约定的路由和属性路由。欢迎提出您的反馈。
引言
路由是ASP.NET MVC管道中的第一个也是最重要的现象。在这里,您将学习路由、其工作原理及其变体。
背景
上周,我的一位朋友问了这个问题:“我们能创建带有某些约束条件的自定义路由吗?什么是属性路由?”我将本文献给他。希望他会喜欢。
将要涵盖的主题是
- 路由及其工作原理
- 路由的组成部分
- 默认路由和自定义路由
IgnoreRoute()
方法的目的是什么?- 如何为自定义路由应用约束?
- 什么是属性路由?
- 默认参数和可选参数
- 默认操作和RoutePrefix
- Name和Order属性
- 属性路由(包括约束)
路由
路由是MVC管道中的第一步,它是URL中具体、物理文件的替代。换句话说,路由是一种现象,其中控制器和操作执行,而不是具体的物理文件。
为什么我们需要路由?
传统上,浏览器的URL代表物理文件。文件可以是HTML文件、ASPX页面等。让我们来理解这种传统方式
www.abcd.com/images/john.aspx?id=2
此URL显示首先转到“images”文件夹,然后访问id为2的“john.aspx”文件。因此,将您的宝贵信息暴露给浏览器URL并不是一个好方法,因为通过这些URL,站点可能被黑客攻击。因此,路由开始发挥作用,解决了这种传统基于文件系统的问。路由的实现从路由表开始。路由表是所有可能的、正确的路由的集合,这些路由可用于映射HTTP请求URL。让我们详细了解RouteTable
和路由的工作原理。
路由是一种系统,通过使用MapRoute
方法,将URL模式与RouteTable
中定义的URL模式进行匹配。当应用程序启动时,路由过程开始发挥作用,首先将路由注册到RouteTable
。在RouteTable
中注册路由至关重要,因为这是路由引擎如何处理请求URL的唯一依据。路由在应用程序的App_Start
文件中的RouteConfig
类中注册。让我们看一下
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
Global.asax
文件的样子是
protected void Application_Start()
{
//Some other code is removed for clarity.
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
该图说明了路由的工作原理,并展示了路由引擎如何处理URL请求并向浏览器提供响应。
路由引擎是负责处理HTTP请求的模块。当HTTP请求到来时,UrlRoutingModule
开始在RouteTable
中匹配最完美的URL模式,一旦成功找到,路由引擎会将请求转发给RouteHandler
。在RouteHandler
中,其接口IRouteHandler
发挥作用,并调用其GetHttpHandler
方法。该方法如下所示
public interface IRouteHandler
{
IHttpHandler GetHttpHandler(RequestContext requestContext);
}
当上述所有过程成功完成后,将调用ProcessRequest()
方法,如上图所示;否则,如果请求的URL与RoutTable
中的任何模式都不匹配,用户将被重定向到HTTP 404错误页面。
路由的组成部分
当您要注册路由时,必须在RouteConfig
类中使用重载的MapRoute()
方法。MapRoute()
方法有6个重载版本,最后一个包含所有参数的方法将在下面解释
public static class RouteCollectionExtensions
{
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces);
}
上面的MapRoute()
方法包含以下参数
- 路由的名称
- 路由的URL模式
- 路由的默认值
- 路由的约束
- 路由的命名空间
让我们一一了解!
路由名称
首先,我想说,社区中关于路由名称的观点非常模糊。因此,我强烈建议仔细阅读本节,并请留下您的评论,因为我是在自学。所以,如果有错误,请理性地纠正我。
在RouteTable
中注册的路由必须有一个唯一的名称,以便与其他路由区分开来。此名称引用RouteTable
中的特定URL模式。最重要的是,此名称仅用于URL生成。因此,我得出结论,路由不是基于名称发生的,而是基于其URL模式发生的。事实上,URL模式告诉UrlRoutingModule
如何处理请求,而不是路由的名称。那么问题来了,为什么它必须是唯一的呢?它之所以应该是唯一的,是因为它是创建URL生成唯一性的东西。让我们实际理解它。
- 创建一个包含两个操作方法的控制器。
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Student()
{
return View();
}
}
- 创建用于理解路由名称的路由。
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Specific Route
routes.MapRoute(
name: "SecondRoute",
url: "Home/Student",
defaults: new { controller = "Home", action = "Student" }
);
//Generic Route
routes.MapRoute(
name: "FirstRoute",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
- 现在我们需要创建视图。
Student.cshtml
@{
ViewBag.Title = "Second Route";
}
<h2>Second Route</h2>
This is our 2nd Route.
Index.cshtml
@{
ViewBag.Title = "Second Route";
}
<h2>First Route</h2>
<a href="@Url.RouteUrl("SecondRoute")">This URL will direct you to Second Route.</a>
- 现在,当我们执行应用程序时,默认的通用路由将开始生效,如图所示
- 当我们点击URL时,它将重定向到第二个路由的视图,如图所示
正如您所看到的,我们在锚标签中传递了路由名称“Second Route”,当我们点击它时,它将重定向到名称为该路由的路由。
因此,结论是,路由名称用于URL生成。现在的问题是,为什么路由名称必须是唯一的?所以,如果路由名称不唯一,UrlRoutingModule
将如何知道Second Route,它将抛出一个运行时错误,如下所示
路由的URL模式:路由的URL模式由文字值和变量占位符组成。这些变量也称为URL参数。所有这些文字和占位符都由斜杠(/)分隔,称为段(Segments)。
文字(Literal)表示“固定”值,变量占位符(variable placeholder)表示“值的替换”。在URL模式中,您可以使用大括号{}来定义占位符。您可以通过文字在段中定义多个占位符。例如,请看下图!
因此,如果您想在一个段中添加多个占位符,您必须在这些占位符之间使用一个文字。
路由的默认值
当您定义一个路由时,您可以为URL的每个段分配默认值。当URL中没有提供参数时,这些默认值将生效。您可以通过创建RouteValueDictionary
类的匿名对象来设置路由的默认值。
路由的约束:
简单来说,约束是对路由参数的限制。当您为路由设置任何约束时,如果URL参数不满足约束要求,则该路由将不起作用。然后请求将转到具有默认参数的路由。您添加约束到路由是为了确保它能根据您的应用程序需求工作。您将在本主题中看到更多详细信息。
路由中的命名空间
您可以为Web应用程序设置自己的命名空间。可以通过创建RouteValueDictionary
类的对象将命名空间添加到路由中。当您想将URL重定向到具有特定命名空间的特定控制器时,将使用此参数。
默认路由和自定义路由
默认路由
MVC中的默认路由是一个通用路由,它以“defaults”的名称保存在RouteTable
中。有趣的是,默认路由只有三个由斜杠分隔的段
{controller}/{action}/{id}
图示了一个路由将要注册到RouteCollection
(routes.RouteTable
)中,它有一个唯一的名称“Defaults”,一个URL模式,其中controller、action、id都是占位符,然后有一个defaults属性,负责在请求URL中不包含controller和action时自动初始化它们。
自定义路由
让我们创建一个自定义路由来理解路由。我们有一个HomeContoller
和一个Index
操作方法。让我们创建另一个名为Student
的操作方法
public class HomeController : Controller
{
public string Index()
{
return "This is Index action method";
}
public string Student(int rollno)
{
return $"Roll Number is {rollno}”;
}
}
现在我们需要在RoutConfig
类中创建一个自定义路由
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "FirstRoute",
url: "Home/{action}/{rollno}",
defaults: new { controller = "Home", action = "Student", rollno = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
在执行应用程序之前,我想告诉您关于路由一个非常重要的事情,那就是:“始终从具体到通用定义您的路由”。在上面的代码中,默认路由是最通用的,所以它应该在RouteTable
的最后。而我们最具体的路由是我们之前定义的第一个路由。路由流程如下
现在,当您执行应用程序并输入如下URL时,输出如下
表格清晰地展示了URL模式及其输出。第二个URL中的错误是由于URL中缺少参数值。另一方面,当您在第三个URL中传递参数值时,该路由将生效并显示输出。这种在URL中传递数据的方式称为查询字符串方法。
查询字符串在其URL中有一个问号(?)。但这种传递数据的方式并不是一个好方法,因为通过这种方法,您的数据可能会暴露给黑客或渗透者。比查询字符串更好的传递数据方式是这样的
https://Server/Home/Student/12345
IgnoreRoute()
方法的目的是什么?
在上面的RouteConfig
类代码中,您可以看到IgnoreRoute()
方法,如下所示
如您所知,URL模式{controller}/{action}/{id}默认由UrlRoutingModule
处理。但有趣的是,如果您真的希望RoutingModule
处理上述模式,则必须将RouteCollection
的RouteExistingFiles
属性设置为true。
public class RouteCollection : Collection<RouteBase>
{
//other code is removed for clarity
public bool RouteExistingFiles { get; set; }
}
您可以在上面的代码中看到,RouteExistingFiles
属性的数据类型是bool
,所以它可以是true或false。当它为true时,RoutingModule
将处理所有与RouteConfig
类中定义的URL模式匹配的请求。否则,当此属性设置为false时,UrlRoutingModule
将永远不会处理任何请求。
现在,问题是,当您将RouteExistingFiles
属性设置为true时,路由模块会处理所有请求,包括您的Web资源文件。所以,当您想阻止浏览器访问某些路由时,您该如何做?
这可以通过使用IgnoreRoute()
方法来实现。IgnoreRoute
方法将阻止对IgnoreRoute
参数中定义的路由的访问。方法如下
public static void IgnoreRoute(string url);
这将从RouteTable
中的可用路由列表中忽略指定的URL路由。
那么,在RouteConfig
类中,以下代码是什么意思?
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
上面的IgnoreRoute()
代码表明resource.axd
文件被禁止访问。最后一点是,这些.axd
文件不在我们的项目中,它们是为HTTP处理保留的。
如何为自定义路由应用约束?
约束是对路由参数的限制。当您为路由设置任何约束时,如果URL包含不满足约束要求的参数值,则该路由将不起作用。
让我们以实际方式来理解它,假设您想将学生的卷号限制为只有5位数,那么您该如何做?
这可以通过约束来实现。让我们来试试。
routes.MapRoute(
name: "FirstRoute",
url: "Home/{action}/{rollno}",
defaults: new { controller = "Home", action = "Student", rollno = UrlParameter.Optional },
constraints: new { rollno = @"\d{5}" }
);
如上所示,卷号数字必须是5。当我们执行以下URL时
输出:卷号是12345
“\d{5}”有一个@符号作为前缀,这被称为逐字字面量(verbatim literal)。它的反面且糟糕的方式是使用转义序列(Escape sequences)。转义序列及其用法定义在下面的图中
让我们了解两者的区别
使用转义序列:“C:\\Computer\\Lectures\\ASP.NET”
使用逐字字面量:@“C:\Computer\Lectures\ASP.NET”
正如我们所见,逐字字面量提供了更好的可读性。因此,通过这种方式,您可以为自定义路由创建自己的约束。
什么是属性路由?
属性路由是ASP.NET MVC 5中的一种新型路由。根据名称属性路由,人们可以认为这种方法将使用属性来定义路由。是的!让我们详细了解。
为什么我们需要属性路由?
首先,看看我们之前的自定义路由
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "FirstRoute",
url: "Home/{action}/{rollno}",
defaults: new { controller = "Home", action = "Student", rollno = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
我们需要属性路由是因为
- 在上面的代码中,只有两个路由是使用
MapRoute
定义的。但是当我们的应用程序足够大,有超过两个路由时,单独创建每个路由将变得非常复杂。对于每个路由,您需要写5行代码,这会占用您的时间并干扰您的开发时间。 - 您需要同时关注
RouteConfig
类中的URL模式以及处理请求的Controller
。因此,在这些文件夹之间来回移动会给您带来复杂性。 - 这会减少出错的可能性,因为在
RouteConfig
类中,创建新自定义路由时可能会出现任何错误。 - 它易于使用,并有助于将同一个操作方法映射到两个路由。
- 您不必关心路由流程,即从最具体到最通用。所有这些都在您在操作方法上方使用的属性。
现在,让我们开始使用属性路由。
首先,您必须启用属性路由的访问。因此,RouteConfig
类将变成
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// below line of code will enable the attribute routing.
routes.MapMvcAttributeRoutes();
}
}
现在,让我们进入控制器,看看它的工作原理。[Route]
属性用于操作方法的顶部,如下所示
public class HomeController : Controller
{
[Route("Home/Student/{rollno}")]
public string Student(int rollno)
{
return $"Name is {rollno}";
}
}
输出是
如何在属性路由中创建可选参数并设置参数的默认值?
如果您想在属性路由中使参数可选,只需在参数末尾加上问号即可,如下所示
public class HomeController : Controller
{
[Route("Home/Student/{rollno ?}")]
public string Student(int rollno)
{
return $"Name is {rollno}";
}
}
如果您想为任何参数设置默认值,您必须使用此模式
ParameterName = Value,如下所示
public class HomeController : Controller
{
[Route("Home/Student/{rollno = 23}")]
public string Student(int rollno)
{
return $"Name is {rollno}";
}
}
什么是RoutePrefix?
您可能会看到很多路由从开头部分相同,意味着它们的prefix是相同的。例如
Home/Student
Home/Teacher
以上两个URL都有相同的prefix,即Home。
所以,我们使用RoutePrefix
属性而不是一遍又一遍地输入相同的prefix。此属性设置在控制器级别。如下所示
[RoutePrefix("Home")]
public class HomeController : Controller
{
[Route("Student/{rollno= 23}")]
public string Student(int rollno)
{
return $"Roll Number {rollno}";
}
[Route("Teacher")]
public string Teacher()
{
return "Teacher’s method";
}
}
在上面的代码中,RoutePrefix
设置在控制器级别,在操作方法中,我们不必反复使用Home prefix。输出如下
RoutePrefix
控制着整个控制器,所有操作方法都绑定使用该prefix。但是,当您想执行一个不应将prefix作为控制器prefix的操作时,该怎么办?
因此,当您想覆盖路由prefix时,请使用~符号,如下所示
[RoutePrefix("Home")]
public class HomeController : Controller
{
[Route("Student/{rollno= 23}")]
public string Student(int rollno)
{
return $"Roll Number is {rollno}";
}
[Route("~/Home2/Teacher")]
public string Teacher()
{
return "Teacher’s method";
}
}
正如您在基于约定的路由中看到的,我们可以设置默认操作方法,现在问题来了,
我们可以在属性路由中设置默认操作方法吗?
是的!您可以通过在控制器顶部使用[Route]
属性来实现。请看下面的代码
[RoutePrefix("Home")
[Route("{action = Teacher}")]
public class HomeController : Controller
{
public string Teacher()
{
return "Teacher’s method";
}
}
控制器顶部的第二个Route属性为操作方法设置了默认值。
正如您在路由部分所看到的,我们可以为路由设置名称。所以,
我们可以在属性路由中为特定URL设置名称吗?
是的!在这里,名称也仅用于URL生成。我们可以这样设置名称
语法
[Route("URLPath", Name = "RouteName")]
[Route("Home/Student", Name = "StudentRoute")]
public ActionResult Student()
{
return View();
}
上面的代码显示了如何在属性路由中设置路由名称。在阅读了基于约定的路由的路由名称部分后,您可以在此Route属性中重复URL生成的过程。
正如您在路由部分所看到的,我们应该从更具体到更通用的流程创建我们的路由。那么,如何在属性路由中做到这一点?
我们可以在属性路由中设置路由的优先级吗?
是的!您需要使用路由属性中的Order
参数。假设我们有一个控制器,其中有两个操作方法,分别命名为First和Second。它们分别设置了Order参数为2和1。
为操作方法设置顺序后,操作方法将按照FCFS(先来先服务)执行,其值可以是从-2,147,483,648到2,147,483,647(int的大小)。
public class HomeController : Controller
{
[Route(Order = 2)]
public string First()
{
return "This is First action method having order 2";
}
[Route(Order = 1)]
public string Second()
{
return "This is Second action method having order 1";
}
}
当我们运行此代码时,Second操作方法将执行,因为它具有更高的顺序。
但是,如果在Route属性中未指定顺序,则操作方法将按照路由在RouteTable
中注册的顺序执行。
正如您在基于约定的路由部分所看到的,我们可以为它设置约束。
我们可以在属性路由中设置约束吗?如果可以,如何设置?
是的!您可以在属性路由中设置约束。语法如下
[Route(URLPath/{parameterName: constraint})]
首先,让我们看看可以在属性路由中使用的不同约束。
让我们看一个实际的例子。
public class HomeController : Controller
{
[Route("Home/Student/{rollno:maxlength(2)}")]
public string Student(int rollno)
{
return $"Roll Number's length is {rollno}";
}
}
当我们传入URL时
https://:50656/Home/Student/12
输出如下
当我们传入卷号长度为3时,如下所示
https://:50656/Home/Student/123
输出如下
我们也可以同时使用多个约束
[Route("Home/Student/{rollno:maxlength(2):range(9,10)}")]
public string Student(int rollno)
{
return $"Roll Number's length is {rollno}";
}
因此,这些是使用属性路由对我们的路由的限制。
结论
这些是关于路由和属性路由的首要内容,我希望本文能帮助您理解所有概念。如果您有任何疑问,请随时在评论中与我联系。同时,请提供反馈,无论是正面的还是负面的,它将帮助我写出最好的文章,并增加我分享知识的热情。