每个ASP.NET MVC应用程序都需要路由来定义自己处理请求的方式。路由是MVC应用程序的入口点。路由的核心工作是将一个请求映射到一个操作

路由主要有两种用途:

  • 匹配传入的请求(该请求不匹配服务器文件系统中的文件),并把这些请求映射到控制器操作。
  • 构造传出的URL,用来响应控制器操作

1.特性路由

1.1 路由URL

创建一个ASP.NET MVC Web应用程序项目后,浏览Global.asax.cs文件中的代码中,Application_Start方法中调用了一个名为RegisterRoutes的方法。该方法是集中控制路由的地方,包含在~/App_Start/RouteConfig.cs文件中。

     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 }
); }

修改RegisterRoutes方法中的内容,只通过调用MapMvcAttributeRoutes注册方式让RegisterRoutes方法启用特性路由。修改后的方法如下:

     public static void RegisterRoutes(RouteCollection routes)
{
routes.MapMvcAttributeRoutes();
}

路由的核心工作是将一个请求映射到一个操作。完成这项工作最简单的方法是在一个操作方法上直接使用一个特性:

  //响应URL为 /about的请求
1 public class HomeController : Controller
{
[Route("about")]
public ActionResult About()
{
return View();
}
}

每当收到URL为/about的请求时,这个路由特性就会运行About方法。MVC收到URL,然后运行代码。

如果对于操作有多个URL,就可以使用多个路由特性。例如,想让首页可以通过/、/home和/home/index这几个URL都能访问,可以设置路由如下:

     //响应URL为 /、/home和/home/index三个URL
1 [Route("")]
[Route("home")]
[Route("home/index")]
public ActionResult Index()
{
return View();
}

传入路由特性的字符串叫做路由模版,他就是一个模式匹配规则,决定了这个路由是否是用于传入的请求。如果匹配,MVC就运行路由的操作方法。

1.2 路由值

对于简单的路由,适合刚才的静态路由,但并不是每个URL都是静态的。例如,如果操作显示个人记录的详情,则需要在URL中包含记录的ID。通过添加路由参数可解决这个问题:

     //id作为一个动态参数
1 [Route("Person/{id}")]
public ActionResult Details(int id)
{
return View();
}

通过花括号的id,就可以作为一个占位符。

多个占位符的情况可如下标识:

      //具有多个占位符
1 [Route("{year}/{month}/{day}")]
public ActionResult Index(string year, string month, string day)
{
return View();
}

1.3 控制器路由

之前的讨论了如何把路由特性直接添加到操作方法上,但是很多时候,控制器类中的方法遵循的模式具有相似的路由模版,以HomeController控制器为例:

     public class HomeController : Controller
{
[Route("home/index")]
public ActionResult Index()
{
return View();
}
[Route("home/about")]
public ActionResult About()
{
return View();
}
[Route("home/contact")]
public ActionResult Contact()
{
return View();
}
}

除了URL的最后一段,这些路由是相同的。所以期望能有一个方法能映射到home下的一个URL。

     [Route("home/{action}")]
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
return View();
}
public ActionResult Contact()
{
return View();
}
}

使用控制器类的一个特性代替每个方法上的所有路由特性。在控制器类上定义路由时,可以使用一个叫做action的特殊路由参数,它可以作为任意操作名称的占位符。action参数的作用相当于每个操作方法上单独添加路由,并静态输入操作名:它只是一种更加方便的语法而已。

有时控制器上的某些具有与其他操作稍微不同的路由。此时,我们可以把最通用的路由放到控制器上,然后在具有不同路由模式的操作上重写默认路由。例如,如果我们认为/home/index过于冗长,但是又想支持/home,就可以如下:

     [Route("home/{action}")]
public class HomeController : Controller
{
[Route("home")]
[Route("home/index")]
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
return View();
}
public ActionResult Contact()
{
return View();
}
}

在操作方法级别指定路由特性时,会覆盖控制器级别指定的任何路由特性。在前面的例子中,如果Index方法只有第一个路由特性(home),那么尽管控制器有一个默认路由

home/{action},也不能通过home/index来访问Index方法。如果需要定义某个操作的路由,并且仍希望应用默认的控制器路由,就需要在操作上再次列出控制器的路由。

前面的类仍然带有重复性。每个路由都以home/开头(毕竟,类的名称是HomeController)。通过使用RoutePrefix,可以仅在一个地方指定路由以home/开头:

     [RoutePrefix("home")]
[Route("{action}")]
public class HomeController : Controller
{
[Route("")]
[Route("index")]
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
return View();
}
public ActionResult Contact()
{
return View();
}
}

现在,所有的路由特性都可以省略home/,因为前缀会自动加上home/。这个前缀只是一个默认值,必要时可以覆盖该行为。例如,除了支持/home和/home/index以外,我们还想让HomeController支持/。为此,使用~/作为路由模版的开头,路由前缀就会被忽略

在下面的代码中,HomeController的Index方法支持全部三种URL(/、/home和/home/index):

      //支持URL为 /、/home和/home/index
1 [RoutePrefix("home")]
[Route("{action}")]
public class HomeController : Controller
{
[Route("~/")]
[Route("")] //此处也可以简写 [Route]
[Route("index")]
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
return View();
}
public ActionResult Contact()
{
return View();
}
}

1.4 路由约束

因为方法参数的名称正好位于由路由特性及路由参数名称的下方,所以很容易忽视这两种参数的区别。

     [Route("person/{id}")]
public ActionResult Details(int id)
{
return View();
}

对于这种情况,当收到/person/bob这个URL的请求时,根据路由规则,会将bob作为id参数传入,但bob无法转换为int类型,所以方法不能执行。

如果想同时支持/person/bob和/person/1,并且每个URL运行不同的操作,可以尝试添加具有不同特性路由的方法重载,如下所示:

     [Route("person/{id:int}")]
public ActionResult Details(int id)
{
return View();
}
[Route("person/{name}")]
public ActionResult Details(string name)
{
return View();
}

因为传入的参数存在二义性,1也可以解释为字符串,因此需要添加int约束。路由约束是一种条件,只有满足该条件时,路由才能匹配。这种约束叫做内联约束。

内联路由约束为控制路由何时匹配提供了精细的控制。如果URL看上去相似,但是具有不同的行为,就可以使用路有约束来表达这些URL之间的区别,并把它们映射到正确的操作。

1.5 路由的默认值

     [Route("home/{action}")]
public class HomeController : Controller
{
public Action Index()
{
return View();
}
}

对于以上代码,如果通过URL为 : /home进行访问,根据类定义的路由模版home/{action},以上代码不能运行。因为定义的路由只匹配包含两个段的URL,但是/home只包含一个段。

如果我们想让Index成为默认的action,路由API允许为参数提供默认值,代码如下:

[Route("home/{action=Index}")]
{action=Index}这段代码为{action}参数定义了默认值。此时,该默认情况就允许路由匹配没有action参数的请求。也就是现在既可以匹配具有一个段的URL,也可以匹配具有两个段的URL。
[Route("home/{action=Index}/{id?}")]

这段代码提供默认值Index,以及可选值id。

因为第二个段id是可选值,因此匹配的URL不再必须包含两个段。

2.传统路由

在~/App_Start/RouteConfig.cs文件中存在方法RegisterRoutes,在方法中添加传统路由,代码如下:

 public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("simple", "{first}/{second}/{third}");
}

MapRoute方法的最简单形式是采用路由名称和路由模版。

与特性路由一样,路由模版是一种模式匹配规则,用来决定该路由是否应该处理传入的请求(基于请求的URL决定)。特性路由与传统路由之间最大的区别在于如何将路由链接到操作方法。传统路由依赖于名称字符串而不是特性来完成这种链接。

在操作方法上使用特性路由时,不需要任何参数,路由就可以工作。路由特性被直接放到了操作方法上,当路由匹配时,MVC知道去运行该操作方法。将特性路由放到控制器类上时,MVC知道使用哪个类(因为该类上有路由特性),但是不知道运行哪个方法,所以我们使用特殊的action参数来通过名称指明要运行的方法。

如果针对上面的简单路由请求一个URL(例如a/b/c),回收到一个500错误。因为传统路由不会自动链接控制器或操作。要指定操作,需要使用action参数(就像在控制器类上使用路由特性时所做的那样)。要指定控制器,需要使用一个新参数controller。如果不定义这些参数,MVC不会知道想要运行的操作方法,所以会通过返回一个500错误。

通过修改简单路由,使其包含这些必需参数,可以解决这个问题:

routes.MapRoute("simple","{controller}/{action}");

现在,如果请求一个URL,如/home/index,MVC会认为这是在请求一个名为home的{controller}和一个名为index的{action}。根据约定,MVC会把后缀Controller添加到{controller}路由参数的值上,并尝试定位具有该名称(区分大小写)并实现了System.Web.Mvc.IController接口的类型。

注:特性路由直接绑定到方法和控制器,而不是仅指定名称,这意味着他们更加精确。例如,使用特性路由时,可以随意命名控制器类,只要以Controller后缀结尾即可(名称不需要与URL相关)。在操作方法上直接使用特性,意味着MVC知道运行哪个重载版本,并不需要在同名的多个操作方法中选择。

2.1 路由值

controller和action参数很特殊,因为他们映射到控制器和操作的名称,是必须参数。但是这两个参数并不是可以使用的全部参数。更新路由来包含第三个参数:

routes.MapRoute("simple", "{controller}/{action}/{id}");

对于 /albums/display/123的请求,会导致实例化MVC的类,调用其中的Display方法,同时将123传递给Display方法的参数id。

routes.MapRoute("simple", "site/{controller}/{action}/{id}");

上面的路由指出请求URL的第一段只有以site开头,才能与请求相匹配。因此,上面的路由可以匹配/site/albums/display/123,而不能匹配/albums/display/123。

此外,还有更灵活的路由语法规则:在路径段中允许字面值和路由参数混合在一起。它仅有的限制就是不允许有两个连续的路由参数

 //有效
{language}-{country}/{controller}/{action}
{controller}.{action}.{id}
//无效
{controller}{action}/{id}

只需要记住,除非路由提供了controller和action参数,否则MVC不知道URL运行哪些代码。

2.2 路由默认值

特性路由通过将参数{id}内联修改为{id?},使得id成为可选的参数。传统路由则是将信息放到路由模版后面的单独一个参数中:

 routes.MapRoute("simple", "{controller}/{action}/{id}",
new {id=UrlPatameter.Optional});

第三个参数用于默认值{id=UrlPatameter.Optional},这段代码为{id}参数定义了默认值。

下面的代码为action指明默认值:

  routes.MapRoute("simple",
"{controller}/{action}/{id}",
new {id=UrlPatameter.Optional, action = "index"});

2.3 路由约束

传统路由允许使用正则表达式来限制路由是否匹配请求。而在特性路由中,使用类似于{id:int}的语法在路由模版中内联指定约束,具体代码如下:

 routes.MapRoute("blog", "{year}/{month}/{day}",
new {controller = "blog", action = "index"},
//路由约束
new {year=@"\d{4}", month=@"\d{2}", day=@"\d{2}"});

注:路由机制会自动的使用"^"和“$”符号包装指定的约束表达式。换言之,此处可以匹配参数“1234”,但不匹配“adc1234def”,也不能匹配“/08/05/25”。

3.选择特性路由还是传统路由

选择传统路由:

  • 想要集中配置所有路由
  • 使用自定义约束对象
  • 存在现有可工作的应用对象,而又不想修改应用程序

选择特性路由:

  • 想把路由与操作代码保存在一起
  • 创建新应用程序,或者对现有应用程序进行巨大修改

传统路由的集中配置意味着可以在一个地方理解请求如何映射到操作。传统路由也比特性路由更灵活。例如,向传统路由添加自定义约束对象很容易。C#中的特性只支持特定类型的参数,对于特性路由,这意味着只能在路由模版字符串中指定约束。

特性路由很好的把关于控制器的所有内容放到了一起,包括控制器使用的URL和运行的操作。

4.URL生成详解

  • 开发人员调用像Html.ActionLink或Url.Action之类的方法,这些方法反过来再调用RouteCollection.GetVirtualPath方法,并向它传递一个ResponseContext对象、一个包含值的字典以及用来选择生成URL的路由名称(可选参数)。
  • 路由机制查看要求的路由参数(即没有提供路由参数对默认值),并确保提供的路由值字典为每一个要求的参数提供一个值。否则URL生成程序会立即停止,并返回空值。
  • 一些路有可能包含没有对应路由参数的默认值。例如,路有可能为category键提供默认值“pastries”,但是category不是路有URL的一个参数。这种情况下,如果用户传入的路由值字典为category提供了一个值,那么该值必须匹配category的默认值。
  • 然后路由系统应用路有的约束,如果有的话。
  • 路由匹配成功!现在可以通过查看每一个路由参数,并尝试利用字典中的对应值填充相应参数,进而生成URL

ASP.NET MVC5高级编程 之 路由的更多相关文章

  1. ASP.NET MVC5 高级编程 第2章 控制器

    参考资料<ASP.NET MVC5 高级编程>第5版 第2章 控制器 控制器:响应用户的HTTP 请求,并将处理的信息返回给浏览器. 2.1 ASP.NET MVC 简介 MVC 模式中的 ...

  2. ASP.NET MVC5 高级编程 第5章 表单和HTML辅助方法

    参考资料<ASP.NET MVC5 高级编程>第5版 第5章 表单和HTML辅助方法 5.1 表单的使用 5.1.1 action 和 method 特性 默认情况下,表单发送的是 HTT ...

  3. ASP.NET MVC5 高级编程 第3章 视图

    参考资料<ASP.NET MVC5 高级编程>第5版 第3章 视图 3.1 视图的作用 视图的职责是向用户提供界面. 不像基于文件的框架,ASP.NET Web Forms 和PHP ,视 ...

  4. ASP.NET MVC5 高级编程-学习日记-第一章 入门

    1.1 ASP.NET MVC 简介 ASP.NET是一种构建Web应用程序的框架,它将一般的MVC(Model-View-Controller)模式应用于ASP.NET框架. 1.1.1 MVC模式 ...

  5. ASP.NET MVC5高级编程 之 模型

    1. 为MVC Music Store建模 Models文件夹(右击) --> 添加 --> 类 为类添加对应的属性: public class Album { public virtua ...

  6. ASP.NET MVC5高级编程 之 Ajax

    jQuery不仅支持所有现代浏览器,包括IE.Firefox.Safari.Opera和Chrome等,还可以在编写代码和浏览器API冲突时隐藏不一致性(和错误). 1. jQuery jQuery擅 ...

  7. 学习《ASP.NET MVC5高级编程》——基架

    基架--代码生成的模板.我姑且这么去定义它,在我学习微软向编程之前从未听说过,比如php代码,大部分情况下是我用vim去手写而成,重复使用的代码需要复制粘贴,即使后来我在使用eclipse这样的IDE ...

  8. ASP.NET MVC5高级编程 之 HTML辅助方法

    Html属性调用HTML辅助方法,Url属性调用URL辅助方法,Ajax属性调用Ajax辅助方法. HTML辅助方法 1.Html.BeginForm @using (Html.BeginForm(& ...

  9. ASP.NET MVC5高级编程 之 视图

    1.1理解视图约定 当创建一个项目模版时,可以注意到,项目以一种非常具体的方式包含了一个结构化的Views目录.在每一个控制器的View文件夹中,每一个操作方法都有一个同名的视图文件与其对应.这就提供 ...

随机推荐

  1. [Android] Android 使用Greendao gradle 出现 Error:Unable to find method 'org.gradle.api.tasks.TaskInputs.file(Ljava/lang/Object;)

    Android 使用Greendao gradle 出现 Error:Unable to find method 'org.gradle.api.tasks.TaskInputs.file(Ljava ...

  2. C#中foreach命令的使用

    在Python中,for循环不仅可以用来做指定次数的循环,还可以利用for i in xxx:来实现元素的遍历,遍历的对象几乎可以是任意格式.而在C++以及C#中,除了普通的for循环之外,也提供了这 ...

  3. nativefier - 快速把任意网页生成桌面应用程序

    使用前端技术开发桌面应用的技术已经相当成熟了,像早先的 NW.js,如今很火的 Electron 等,都可以轻松实现.今天给大家分享的 nativefier 就是基于 Electron 封装的,可以帮 ...

  4. 局域网内ping [局域网内ip地址]命令详解

    一.工作过程 主机A向主机B发送一个ICMP请求报文[类型字段为8,代码字段为0],若收到ICMP回复报 文[类型字段为0,代码字段为0]则说明主机B处于活动状态:若超时未收到回复,则可能是 因为(1 ...

  5. HIbernate处理数据更新丢失

    使用乐观锁的机制处理: 第一步: 在持久类中添加version属性,并且添加对应的get.set方法; 第二步: 在全局配置文件中配置节点<version name="version& ...

  6. 在线xss练习平台

    在线xss练习平台 HTTPS://ALF.NU/ALERT1 这个是只要能输出alert1就算赢. No.1第一个就很简单了,什么都没有过滤,只需要闭合前面的标签就可以执行xss了. 1 " ...

  7. 【bzoj 1492】[NOI2007]货币兑换Cash

    Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个实 ...

  8. PHP WeBaCoo后门学习笔记

    PHP WeBaCoo后门学习笔记 - PHP WeBaCoo backdoor learning notes WeBaCoo (Web Backdoor Cookie) 是一款隐蔽的脚本类Web后门 ...

  9. rabbitMQ学习3-RPC远程过程调用

    将一个函数运行在远程计算机上并且等待获取那里的结果,这个称作远程过程调用(Remote Procedure Call)或者 RPC. RPC是一个计算机通信协议. 比喻 将计算机服务运行理解为厨师做饭 ...

  10. 深入理解jQuery中的each方法

    写在前面 我们先回顾一下数组中的forEach方法吧.在数组的实例上有个forEach方法供所有实例使用,forEach里面接收一个回调函数,而且回调函数默认接收三个参数:当前项,索引,数组 .for ...