ASP.NET MVC Routing、Areas、URLs
webForm页面运行起来url一般是这样的:localhost:****/index.aspx,这个过程就是当你运行页面的时候,vs开发工具自带的微型服务器会打开你存在硬盘上的这个文件然后显示在浏览器上,所以url是后半部分是页面的名字(index.aspx),但是在mvc中却是这样的:localhost:****/index,因为mvc中有一整套路由机制来控制浏览器的请求。
看看Global.asax文件里路由的定义:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // 路由名称 "{controller}/{action}/{id}", // 带有参数的 URL new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 参数默认值 ); }
注释掉这个默认的路由,自己重新定义一个路由:
根据vs的只能提示可以看出参数一个是路由名称,一个是路由的模式,定义为这样:
routes.MapRoute("MyRoute", "{controller}/{action}");
有了路由了就可以去创建控制器了,新建一个HomeController:
public class HomeController : Controller { public ActionResult Index() { return View(); } }
再去创建一个对应的View:
@{ Layout = null; } <!DOCTYPE html> <html> <head> <title>Index</title> </head> <body> <div> HomeController下的Index Action响应的同名视图Index </div> </body> </html>
直接运行程序发现报404错误,找不到页面。查看下路由就知道了问题:这个路由指定的是url模式是{controller}/{action}的方式,而浏览器里的url却是localhost:****,既没有写Controller,也没有写Action,显然不符合路由的定义,自然得报404错了。把Url补充完整就可以找到页面了:localhost:****/home/index,当然如果在url后面少一级或者多加一级都会报404错:localhost:****/home、localhost:****/home/index/1,可见Url必须严格根据路由的定义来。
当然,也可以在路由定义的时候就给默认值:
routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home", action = "Index" });
这样运行程序就不报错了:localhost:****,根据路由给的默认值就相当于:localhost:****/home/index
再看这两条路由的定义:
routes.MapRoute("", "Customers/{controller}/{action}"); routes.MapRoute("", "X{controller}/{action}");
第一条路由对应的Url是这样的:localhost:****/Customers/Home/Index
第二条是:localhost:****/XHome/Index
区别:第一条是静态的路由,第一级目录必须是路由里指定了的Customers,而第二条路由则是动态可变的,第一级只要以X开头都可以和本路由匹配上
路由的顺序:
如果同时定义了这么两条路由:
routes.MapRoute("MyRoute", "{controller}/{action}"); routes.MapRoute("", "X{controller}/{action}");
现在浏览器有这么条Url请求过来了:localhost:****/XHome/Index 看到XHome好像得使第二条路由了,但是不是。这条Url同时符合两条路由就优先使用前面的路由。根据第一条路由,这条Url过去就是找XHome这个控制器Controller里的Index Action
混合的静态路由,带默认值:
routes.MapRoute("ShopSchema", "Shop/{action}", new { controller = "Home" });
这条名为ShopSchema的路由没有定义Controller,而是给了一个默认值Home,但是如果直接请求localhost:****/home/index 虽然有HomeController,但是还是会报404,因为路由已经限制了Controller部分必须是Shop,所以url得是这样:localhost:****/shop/index,虽然没有ShopController,但是同样会导向到HomeController下。
注:为了演示路由效果,防止前面定义的路由影响到Url请求的控制器,所以得先把之前的所有路由都注释了再运行查看效果。
routes.MapRoute("ShopSchema2", "Shop/OldAction", new { controller = "Home", action = "Index" });
Url:localhost:1042/shop/OldAction 找的同样是HomeController下的Index这个Action
定义额外的参数:
路由当然不光可以定义controller和action,同时也可以定义之后的参数,比如:
routes.MapRoute("MyRoute", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "10086" });
在控制器下可以这么拿到传进来的id参数:ViewBag.CustomVariable = RouteData.Values["id"];
视图View中这么调用:@ViewBag.CustomVariable
{controller}/{action}/{id} 对应的url是这样的:localhost:****/home/index/10010 视图中输出:10010
当然,因为路由给了默认值,所有一下url都可以:
localhost:****
localhost:****/home
localhost:****/home/index 输出的都是:10086
可选参数:
routes.MapRoute("MyRoute", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional });
Optional这个在EF配置实体之间的一对一、一对多等关系里演示过,表示可选的意思,这里的路由也是一样,表示id部分就是可选的:
localhost:**** => controller = Home action = Index
localhost:****/Customer => controller = Home action = Index
localhost:****/Customer/List => controller = Home action = Index
localhost:****/Customer/List/All => controller = Home action = Index id = All
可变长度的路由:
routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}", new { controller = "Home", action = "Index", id = UrlParameter.Optional });
localhost:**** => controller = Home action = Index
localhost:****/Customer => controller = Home action = Index
localhost:****/Customer/List => controller = Home action = Index
localhost:****/Customer/List/All => controller = Home action = Index id = All
localhost:****/Customer/List/All/Delete => controller = Home action = Index id = All catchall = Delete
localhost:****/Customer/List/All/Delete/Perm => controller = Home action = Index id = All catchall = Delete/Perm
指定命名空间:
routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home", action = "Index" });
这是一个极普通的路由,项目运行起来,默认找的是HomeController下的Index这个Action,但是现在如果项目下同个命名空间有两个HomeController怎么办,那自然要报错的:
可以指定命名空间:
routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home", action = "Index" }, new[] { "RouteDemo4Blog.Controllers" });
正则表达式路由:
routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home", action = "Index" }, new { controller = "^H.*" });
可适配所有以字母H开头的Controller 再来一个:
routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home", action = "Index" }, new { controller = "^H.*", action = "^Index$|^About$" });
元字符^和$分别匹配字符串的开始和结束 | 表示或,所有这个路由匹配名为Index或者About的Action
指定url请求方式:
routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home", action = "Index" }, new { controller = "^H.*", action = "Index|About", httpMethod = new HttpMethodConstraint("GET") });
只响应GET请求(默认)
传递额外的值:
@Html.ActionLink("About this application", "About", new { id = "MyID" })
生成的HTML就是:
<a href="/Home/About/MyID">About this application</a>
点击页面上这个a标签自然区找的就是对应的路由了。
@Html.ActionLink("Click me", "List", "Catalog", new {page=789}, null)
生成的HTML:
<a href="/Catalog/List/Purple/789">Click me</a>
对应的路由自然就是这样的:
routes.MapRoute("MyRoute", "{controller}/{action}/{color}/{page}");
指定css:
@Html.ActionLink("About this application", "Index", "Home", null, new {id = "myAnchorID", @class = "myCSSClass"})
<a class="myCSSClass" href="/" id="myAnchorID">About this application</a>
生成完整的url:
之前生成的都是相对路径的Url,现在看看绝对路径的:
@Html.ActionLink("About this application", "Index", "Home", "https", "myserver.mydomain.com", " myFragmentName", new { id = "MyId"}, new { id = "myAnchorID", @class = "myCSSClass"})
生成的HTML:
<a class="myCSSClass" href="https://myserver.mydomain.com/Home/Index/MyId#myFragmentName" id="myAnchorID">About this application</a>
生成Url:
之前的@Html.ActionLink方法生成的都是a标签,看看如何生成url:
@Url.Action("Index", "Home", new { id = "MyId" })
/Home/Index/MyId
Areas
Areas官方的解释是为了划分功能使用的,比如区分:管理员、订单、客户等功能,适合大项目使用,同时方便程序员之间协同开发。来一个试试:右键解决方案 - 添加 - 区域 - Admin - 确定
可以看出每个区域Areas里都是个mini的MVC项目,Controller、Models、Views一个都不缺,还多了一个AdminAreaRegistration类,看看其中的代码:
public class AdminAreaRegistration : AreaRegistration { public override string AreaName { get { return "Admin"; } } public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( "Admin_default", "Admin/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional } ); } }
可以看出其中已经有一个默认的路由了,跟普通的MVC项目是一样的,只不过前面多了一个Admin。向这个Areas里添加一个Controller试试:右键Controllers目录 - 添加 - 控制器 - HomeController 同时添加对应的视图Index:
@{ ViewBag.Title = "Index"; } <h2> Admin Area Index</h2>
运行下程序,url定位到:localhost:1042/admin/home/index就能显示这个视图了。但是如果直接请求localhost:1042会报错:
很明显是因为默认的路由和Areas下的路由冲突了,这个之前演示过。解决方法就是加上命名空间:'
routes.MapRoute( "Default", // 路由名称 "{controller}/{action}/{id}", // 带有参数的 URL new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // 参数默认值 new[] { "RouteDemo4Blog.Controllers" } );
当然也可以从一个Areas跳转到另一个Areas:
@Html.ActionLink("跳转到别的Areas", "Index", new { area = "Support" })
生成的HTML:
<a href="/Support/Home">跳转到别的Areas</a>
跳转到顶级Controller:
@Html.ActionLink("Click me to go to another area", "Index", new { area = "" })