这章呢,我们开始讲ASP.NET MVC5中的路由机制,在这之前,先提一下URL(Uniform Resource Locator)-- 统一资源定位符。需要注意的是,这里的“资源”这个词,是一个抽象的概念,既可以指一个文件,也可以指一个方法、一个类或是一段代码。由此我们引出了路由的主要用途:
ASP.NET MVC5中有:特性路由和传统路由。
ASP.NET MVC5中的路由机制图解:
ASP.NET路由在两个地方设置:
1 :在应用程序Web.config文件中四个节点与路由有关:
sytem.web.httpModules,system.web.httpHandlers节,system.webserver.modules节,system.webserver.handlers节。
因为没有它们路由将不能工作。
2 :在应用程序的Global.asax文件中包含一个路由表,路由表在Application Start事件期间创建,当一个MVC应用程序首次运行时,会调用Application_Start()方法,这个方法随后调用RegisterRoutes()方法,RegisterRoutes()方法创建路由表:
创建一个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的请求
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
[Route("")]
[Route("home")]
[Route("home/index")]
public ActionResult Index()
{
return View();
}
传入路由特性的字符串叫做路由模版,他就是一个模式匹配规则,决定了这个路由是否是用于传入的请求。如果匹配,MVC就运行路由的操作方法。
对于简单的路由,适合刚才的静态路由,但并不是每个URL都是静态的。例如,如果操作显示个人记录的详情,则需要在URL中包含记录的ID。通过添加路由参数可解决这个问题:
//id作为一个动态参数
[Route("Person/{id}")]
public ActionResult Details(int id){
return View();
}
通过花括号的id,就可以作为一个占位符。
多个占位符的情况可如下标识:
//具有多个占位符
之前的讨论了如何把路由特性直接添加到操作方法上,但是很多时候,控制器类中的方法遵循的模式具有相似的路由模版,以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
[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();
}
}
因为方法参数的名称正好位于由路由特性及路由参数名称的下方,所以很容易忽视这两种参数的区别。
对于这种情况,当收到/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之间的区别,并把它们映射到正确的操作。
[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不再必须包含两个段。
URL模式及模式匹配:
URL模式是路由系统的核心,相当于表示URL的一个公式。
URL模式的表现形式:{controller}/{action};
应用系统由若干条路由组织成,每条路由都有一个URL模式;
与模式匹配的URL可能有多条;
路由系统对应用的一个URL请求进行服务时,要查看这个URL请求与哪个URL模式相匹配,然后用这个模式对应的路由对这个URL请求进行处理;
URL匹配:
URL可以被分成除主机名(域名)和查询字符串以外的。以“/”分割的各个部分,每个部分是一个片段值;
URL=http:// localhost/home/index
URL模式:{controller}/{action}
例子:
http:// localhost/home/index,Controller=home, action=index
http://mysite.com/admin/index, Controller=admin, action=index
例如: localhost/home/index,localhost是域名, 所以首先要去掉域名部分, 所以能够识别出 Controller=home, action=index, id没有则为默认值""。
Url路由实例讲解:
URL= /Home/Index/3 调用Index()方法,此时Id被忽略。
URL= /Home 调用Index()方法,并使用空字符串作为Id参数的值。
URL= /Home/Index/3 调用Index()方法,id=“3”。
此时Index() 方法拥有一个可空整数参数。
URL= /Home 调用Index()方法,并使用 NULL 作为Id参数的值。
URL= /Home/Index/3 调用Index()方法,id=3。
URL= /Home 调用Index()方法,但会引发一个异常,因为id为null。
默认路由:
Routes.MapRoute(
"Default", // 路由名称
"{controller}/{action}/{id}", // 带有参数的URL
new { controller = "Home", action = "Index",
id = UrlParameter.Optional } // 参数默认值——默认路由
);
试想:如果不定义默认路由,URL将会如何进行匹配?
结论:只匹配与模式具有相同片段数的URL。
带有静态片段(固定值)的URL:
routes.MapRoute( "", "hotels/{controller}/{action}", new { controller = "Hotel", action = "default"} );
例子:
http://myDemo.com/hotels/Home/index
http://myDemo.com/hotels/Hotel/default
Routes.MapRoute(“”,”X{controller}/{action}”);
例子:
http://myDemo.com/Xhome/index --(X就是固定值)
片段变量的访问:
使用RouteData.Values属性,可以在动作方法中访问任何一个片段变量。
Eg:“{controller}/{action}/{id}”
RouteData.Values(controller})
RouteData.Values(action})
RouteData.Values(id})
定义可选URL:
可选URL:用户不需要指定但又未指定默认值的片段。
Routes.MapRoute(
"Default", // 路由名称
"{controller}/{action}/{id}", // 带有参数的URL
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 默认路由
);
匹配不管是否提供id的URL:URL= /Home 和URL= /Home/Index/3 都匹配。
定义可变长路由(任意长度的URL):
作用:能够在一个单一的路由中对任意长度的URL进行路由。
定义方法:通过指定一个叫做“catchall”的片段变量并以“*”作为其前缀,去除前面路径,后面所有的值都是catchall变量的值。
Eg:“{controller}/{action}/{id}/{*catchall}”
路由约束:
目的:实现对路由片段的值进行约束
方法:通过正则表达式、将一条路由约束到一组指定的值、约束使用HTTP方法的路由。
通过正则表达式约束路由:
routes.MapRoute
( "酒店列表页",
"hotels/{action}-{city}-{price}-{star}",
new { controller = "Hotel", action = "list", city = "beijing",
price="-1,-1", star="-1" },
new { city=@"[a-zA-Z]*",price=@"(\d)+\,(\d)+", star="[-1-5]"}
);
URL= localhost/hotels/list-beijing-100,200-3 ,会访问酒店频道的列表页,并传入查询参数。
将一条路由约束到一组指定的值:
通过“|”将指定的一组值分开,结合正则表达式使用:
Eg:routes.MapRoute ( “MyRoute",
“{controller}/ {action}/ {id}/{*catchall}",
new { controller = "Home", action = “index", id=
UrlParameter.Optional },
new { controller=“^H.*",action=“^Index$“|”^About$”}
); //只匹配action= Index或者About的路由
约束使用HTTP方法的路由:
目标:对匹配的URL使用的HTTP方法进行约束
Eg:routes.MapRoute ( “MyRoute",
“{controller}/ {action}/ {id}/{*catchall}",
new { controller = "Home", action = “index", id=
UrlParameter.Optional },
new { controller=“^H.*",action=“^Index$“|”^About$”}
httpMethod = new HttpMethodConstraint(“GET”)
); //把路由限制到GET请求
对磁盘文件的请求进行路由:
并不是MVC应用程序的所有请求都针对控制器和动作,MVC路由提供对内容进行服务。
服务开关:RouteCollection.RouteExistingFiles =true/false
Eg:routes.MapRoute ( “FileRoute”, “Content/test.html”,
new{controller=“Account”,action=“Logon”},
new{customConstraint = new UserAgentConstraint(“IE”)}
// 该路由把Content/test.html的URL请求映射到Account控制器的Logon动作方法, IE浏览器的用户将得到Account控制器的响应,而其他用户将看到test.html静态页面的内容。
Url路由实例讲解1:
Url路由实例讲解2:
生成输出URL的两种方法:在视图中生成(多数情况下)、在动作方法中生成。
在视图中生成输出URL:
在视图页面中通过调用ActionLink辅助器方法。
Eg:@Html. ActionLink(“链接文本”,”目标动作方法名”)
至于和哪个控制器进行绑定,取决于视图是通过哪个控制器的请求进行的渲染
可以用一个匿名类型为片段变量传递值
Eg:@Html. ActionLink(“链接文本”,”index”, new{id=“myId”})
在动作方法中生成输出URL:
Url.Action(“index”, new{id=“myId”})
Url.RouteUrl(new{controller=“home”,action=“index”})
RedirectToAction(“路由名称”)
URL匹配规则总结
(1)默认路由:
带静态片段的路由:
可选路由:
可变长路由:
(2)Routing规则有顺序(按照添加时的顺序), 如果一个URL匹配了多个Routing规则, 则按照第一个匹配的Routing规则执行。
(3) {*values} 表示片段变量可以是任意的内容。
(4) 通过正则表达式等方法可实现自定义路由约束。
参考文章:
https://www.cnblogs.com/imstrive/p/6496187.html
本系列文章所有实例代码GitEE地址:
https://gitee.com/jahero/mvc