ASP.NET MVC5高级编程 ——(5)路由

这章呢,我们开始讲ASP.NET MVC5中的路由机制,在这之前,先提一下URL(Uniform Resource Locator)-- 统一资源定位符。需要注意的是,这里的“资源”这个词,是一个抽象的概念,既可以指一个文件,也可以指一个方法、一个类或是一段代码。由此我们引出了路由的主要用途

  • 匹配传入的请求(该请求不匹配服务器文件系统中的文件),并把这些请求映射到控制器操作。(controller/action--简单来说:Routing路由作用--确定Controller、确定Action、确定其他参数、根据识别出来的数据, 将请求传递给Controller和Action。)
  • 构造传出的URL,用来响应控制器操作

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()方法创建路由表

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的请求

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就运行路由的操作方法。

1.2 路由值

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

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

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

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

      //具有多个占位符

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

[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 路由约束

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

对于这种情况,当收到/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.传统路由

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

原文发布于微信公众号 - 浩Coding(gh_c4a2e63d2ca7)

原文发表时间:2018-12-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券