[ASP.NET
MVC 小牛之路]07 - URL Routing
我們知道在ASP.NET Web Forms中,一個URL請求往往對應一個aspx頁面,一個aspx頁面就是一個物理文件,它包含對請求的處理。
而在ASP.NET MVC中,一個URL請求是由對應的一個Controller中的Action來處理的,由URL Routing來告訴MVC如何定位到正確的Controller和Action。
籠統的講,URL Routing包含兩個主要功能:解析URL 和 生成URL,本文將圍繞這兩個大點進行講解。
本文目錄
URL Routing 的定義方式
讓我們從下面這樣一個簡單的URL開始:
http://mysite.com/Admin/Index
在域名後面,默認使用“//Index
在域名的後面,來對URL進行分段。路由系統透過類似 {controller}/{action} 格式的字串可以知道這個URL的 Admin 和 Index 兩個片段分別對應Controller和Action的名稱。
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 } ); }
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); }
public static void RegisterRoutes(RouteCollection routes) { Route myRoute = new Route("{controller}/{action}", new MvcRouteHandler()); routes.Add("MyRoute", myRoute); }
public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("MyRoute", "{controller}/{action}"); }這種方法簡潔易讀,一般我們都會用這種方法定義路由。 範例準備
作為演示,我們先來準備一個Demo。建立一個標準的MVC應用程序,然後添加三個簡單的Controller,分別是HomeController、CustomerController和AdminController,代碼如下:
HomeController
CustomerController
/Ad為這三個Controller加上一個共享的名為ActionName.cshtml 的View,程式碼如下:
ActionName.cshtml
我們把RouteConfig.cs檔案中項目自動產生的URL Rounting的定義刪了,然後根據前面講的路由定義知識,我們自己寫一個最簡單的:
public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("MyRoute", "{controller}/{action}"); }
這個Demo輸出的是被調用的Controller和Action名稱。
給片段變數定義預設值
在上面我們必須把URL定位到特定Controller和Action,否則程式會報錯,因為MVC不知道去執行哪個Action。 我們可以透過指定預設值來告訴MVC當URL沒有給出對應的片段時使用某個預設的值。如下給controller和action指定預設值:这个Demo输出的是被调用的Controller和Action名称。 在上面我们必须把URL定位到特定Controller和Action,否则程序会报错,因为MVC不知道去执行哪个Action。 我们可以通过指定默认值来告诉MVC当URL没有给出对应的片段时使用某个默认的值。如下给controller和action指定默认值: 这时候如果在URL中不提供action片段的值或不提供controller和action两个片段的值,MVC将使用路由定义中提供的默认值: 它的各种匹配情况如下表所示:
注意,对于上面的URL路由的定义,我们可以只给action一个片段指定默认值,但是不能只给controller一个片段指定默认值,即如果我们给Controller指定了默认值,就一定也要给action指定默认值,否则URL只有一个片段时,这个片段匹配给了controller,action将找不到匹配。
定义静态片段 并不是所有的片段都是用来作为匹配变量的,比如,我们想要URL加上一个名为Public的固定前缀,那么我们可以这样定义: 这样,请求的URL也需要一个Public前缀与之匹配。我们也可以把静态的字符串放在大括号以外的任何位置,如: 在一些情况下这种定义非常有用。比如当你的网站某个链接已经被用户普遍记住了,但这一块功能已经有了一个新的版本,但调用的是不同名称的controller,那么你把原来的controller名称作为现在controller的别名。这样,用户依然使用他们记住的URL,而导向的却是新的controller。如下使用Shop作为Home的一个别名: 这样,用户使用原来的URL可以访问新的controller:
自定义片段变量 自定义片段变量的定义和取值 contrlloer和action片段变量对MVC来说有着特殊的意义,在定义一个路由时,我们必须有这样一个概念:contrlloer和action的变量值要么能从URL中匹配得到,要么由默认值提供,总之一个URL请求经过路由系统交给MVC处理时必须保证contrlloer和action两个变量的值都有。当然,除了这两个重要的片段变量,我们也可从通过自定义片段变量来从URL中得到我们想要的其它信息。如下自定义了一个名为Id的片段变量,而且给它定义了默认值: 我们在HomeController中增加一个名为CustomVariable的ACtion来演示一下如何取自定义的片段变量: 可以通过 RouteData.Values[segment] 来取得任意一个片段的变量值。 将URL定位到 /Home/CustomVariable/Hello 将得到如下结果: 自定义的片段变量用处很大,也很灵活,下面介绍一些常见的用法。 将自定义片段变量作为Action方法的参数 我们可以将自定义的片段变量当作参数传递给Action方法,如下所示: 效果和上面是一样的,只不过这样省去了用 RouteData.Values[segment] 的方式取自定义片段变量的麻烦。这个操作背后是由模型绑定来做的,模型绑定的知识我将在后续博文中进行讲解。 指定自定义片段变量为可选 指定自定片段变量为可选,即在URL中可以不用指定片段的值。如下面的定义将Id定义为可选: 定义为可选以后,需要对URL中没有Id这个片段值的情况进行处理,如下: 当Id是整型的时候,参数的类型需要改成可空的整型(即int? id)。 这样其实就是和使用下面这样的方式定义路由是一样的: 定义可变数量的自定义片段变量 我们可以通过 catchall 片段变量加 * 号前缀来定义匹配任意数量片段的路由。如下所示: 这个路由定义的匹配情况如下所示: 使用*catchall,将匹配的任意数量的片段,但我们需要自己通过“/”分隔catchall变量的值来取得独立的片段值。 正则表达式约束 通过正则表达式,我们可以制定限制URL的路由规则,下面的路由定义限制了controller片段的变量值必须以 H 打头: 这个定义,限制了action片段值只能是Index或About,不区分大小写。 Http请求方式约束 我们还可以限制路由只有当以某个特定的Http请求方式才能匹配。如下限制了只能是Get请求才能进行匹配: 通过创建一个 HttpMethodConstraint 类的实例来定义一个Http请求方式约束,构造函数传递是允许匹配的Http方法名。这里的httpMethod属性名不是规定的,只是为了区分。 自定义路由约束 如果标准的路由约束满足不了你的需求,那么可以通过实现 IRouteConstraint 接口来定义自己的路由约束规则。 这里实现IRouteConstraint的Match方法,返回的bool值告诉路由系统请求是否满足自定义的约束规则。我们的UserAgentConstraint类的构造函数接收一个浏览器名称的关键字作为参数,如果用户的浏览器包含注册的关键字才可以访问。接一来,我们需要注册自定的路由约束: 下面分别是IE10和Chrome浏览器请求的结果: 定义请求磁盘文件路由 并不是所有的URL都是请求controller和action的。有时我们还需要请求一些资源文件,如图片、html文件和JS库等。 我们看到,是可以直接访问一静态资源文件的。 设置了routes.RouteExistingFiles = true后,还需要对IIS进行设置,这里我们以IIS Express为例,右键IIS Express小图标,选择“显示所有应用程序”,弹出如下窗口: 点击并打开配置文件,Control+F找到UrlRoutingModule-4.0,将这个节点的preCondition属性改为空,如下所示: 然后我们运行程序,再把URL定位到之前的静态文件:
这样,路由系统通过定义的路由去匹配RUL,如果路由中没有定义该静态文件的匹配,则会报上面的错误。 这个路由匹配Content/StaticContent.html的URL请求为controller = Customer, action = List。我们来看看运行结果: 这样做的目的是为了可以在Controller的Action中控制对静态资源的请求,并且可以阻止对一些特殊资源文件的访问。 这样,只要是请求Content目录下的任何html文件都能被直接返回。这里的IgnoreRoute方法将创建一个RouteCollection的实例,这个实例的Route Handler 为 StopRoutingHandler,而不是 MvcRouteHandler。运行程序定位到Content/StaticContent.html,我们又看到了之前的静态面面了。 前面讲的都是解析URL的部分,现在我们来看看如何通过路由系统在View中生成URL。 生成指向当前controller的action链接 在View中生成URL的最简单方法就是调用Html.ActionLink方法,如下面在 Views/Shared/ActionName.cshtml 中的代码所示: 这里的Html.ActionLink方法将会生成指向View对应的Controller和第二个参数指定的Action,我们可以看看运行后页面是如何显示的: 经过查看Html源码,我们发现它生成了下面这样的一个html链接: 这样看起来,通过Html.ActionLink生成URL似乎并没有直接在View中自己写一个标签更直接明了。 但它的好处是,它会自动根据路由配置来生成URL,比如我们要生成一个指向HomeContrller中的CustomVariable Action的连接,通过Html.ActionLink方法,只需要给出对应的Controller和Action名称就行,我们不需要关心实际的URL是如何组织的。举个例子,我们定义了下面的路由: 运行程序,我们发现它会自动生成下面这样的连接: 所以我们要生成指向某个Action的链接时,最好使用Html.ActionLink方法,否则你很难保证你手写的连接就能定位到你想要的Action。 生成其他controller的action链接 上面我们给Html.ActionLink方法传递的第二个参数只告诉了路由系统要定位到当前View对应的Controller下的Action。Html.ActionLink方法可以使用第三个参数来指定其他的Controller,如下所示: 它会自动生成如下链接: 生成带有URL参数的链接 有时候我们想在连接后面加上参数以传递数据,如 ?id=xxx 。那么我们可以给Html.ActionLink方法指定一个匿名类型的参数,如下所示: 它生成的Html如下: 指定链接的Html属性 通过Html.ActionLink方法生成的链接是一个a标签,我们可以在方法的参数中给标签指定Html属性,如下所示: 这里的class加了@符号,是因为class是C#关键字,@符号起到转义的作用。它生成 的Html代码如下: 生成完整的标准链接 前面的都是生成相对路径的URL链接,我们也可以通过Html.ActionLink方法生成完整的标准链接,方法如下:
生成URL字符串 用Html.ActionLink方法生成一个html链接是非常有用而常见的,如果要生成URL字符串(而不是一个Html链接),我们可以用 Url.Action 方法,使用方法如下: 它显示到页面是这样的:
根据指定的路由名称生成URL 我们可以根据某个特定的路由来生成我们想要的URL,为了更好说明这一点,下面给出两个URL的定义: 始终会生成这样的链接: 也就是说,永远无法使用第二个路由来生成App前缀的链接。这时候我们需要通过另一个方法Html.RouteLink来生成URL了,方法如下: 它会生成如下链接: 在Action方法中生成URL 通常我们一般在View中才会去生成URL,但也有时候我们需要在Action中生成URL,方法如下: 其中 myActionUrl 和 myRouteUrl 将会被分别赋值 /Home/Index/MyID 和 / 。 RedirectToAction的返回结果是一个RedirectToRouteResult类型,它使MVC触发一个重定向行为,并调用指定的Action方法。RedirectToAction也有一些重载方法,可以传入controller等信息。也可以使用RedirectToRoute方法,该方法传入的是object匿名类型,易读性强,如: URL方案最佳实践 下面是一些使用URL的建议: 以上就是[ASP.NET MVC 小牛之路]07 - URL Routing的内容,更多相关内容请关注PHP中文网(www.php.cn)!routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home", action = "Index" });
给片段变量定义默认值routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home", action = "Index" });
routes.MapRoute("", "Public/{controller}/{action}", new { controller = "Home", action = "Index" });
routes.MapRoute("", "X{controller}/{action}", new { controller = "Home", action = "Index" });
routes.MapRoute("ShopSchema", "Shop/{action}", new { controller = "Home" });
routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
new {
controller = "Home",
action = "Index",
id = "DefaultId"
});
public ActionResult CustomVariable() {
ViewBag.Controller = "Home";
ViewBag.Action = "CustomVariable";
ViewBag.CustomVariable = RouteData.Values["id"];
return View("ActionName");
}
再稍稍改一下ActionName.cshtml 来看一下我们取到的自定义片段变量的值:...
<p>The controller is: @ViewBag.Controller</p>
<p>The action is: @ViewBag.Action</p>
<p>The custom variable is: @ViewBag.CustomVariable</p>
...
public ActionResult CustomVariable(string id) {
ViewBag.Controller = "Home";
ViewBag.Action = "CustomVariable";
ViewBag.CustomVariable = id;
return View("ActionName");
}
routes.MapRoute("MyRoute", "{controller}/{action}/{id}", new {
controller = "Home",
action = "Index", id = UrlParameter.Optional});
public ActionResult CustomVariable(string id) {
ViewBag.Controller = "Home";
ViewBag.Action = "CustomVariable";
ViewBag.CustomVariable = id == null ? "<no value>" : id;
return View("ActionName");
}
为了省去判断参数是否为空,我们也可以把Action方法的id参数也定义为可选,当没有提供Id参数时,Id使用默认值,如下所示:public ActionResult CustomVariable(string id = "DefaultId") {
ViewBag.Controller = "Home";
ViewBag.Action = "CustomVariable";
ViewBag.CustomVariable = id;
return View("ActionName");
}
routes.MapRoute("MyRoute", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "DefaultId" });
routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
路由约束routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, new { controller = "^H.*" });
定义路由约束是在MapRoute方法的第四个参数。和定义默认值一样,也是用匿名类型。
我们可以用正则表达式约束来定义只有指定的几个特定的片段值才能进行匹配,如下所示:routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { controller = "^H.*", action = "^Index$|^About$" }
);
routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { controller = "^H.*", httpMethod = new HttpMethodConstraint("GET") }
);
这种约束也可以通过HttpGet或HttpPost过滤器来实现,后续博文再讲到滤器的内容。
我们来做一个限制浏览器版本访问的路由约束。在MVC工程中添加一个文件夹,取名Infrastructure,然后添加一个 UserAgentConstraint 类文件,代码如下:public class UserAgentConstraint : IRouteConstraint {
private string requiredUserAgent;
public UserAgentConstraint(string agentParam) {
requiredUserAgent = agentParam;
}
public bool Match(HttpContextBase httpContext, Route route, string parameterName,
RouteValueDictionary values, RouteDirection routeDirection) {
return httpContext.Request.UserAgent != null
&& httpContext.Request.UserAgent.Contains(requiredUserAgent);
}
}
public static void RegisterRoutes(RouteCollection routes) {
routes.MapRoute("ChromeRoute", "{*catchall}",
new { controller = "Home", action = "Index" },
new { customConstraint = new UserAgentConstraint("Chrome") }
);
}
我们先来看看能不能直接请求一个静态Html文件。在项目的Content文件夹下,添加一个html文件,内容随意。然后把URL定位到该文件,如下图:
默认情况下,路由系统先检查URL是不是请求静态文件的,如果是,服务器直接返回文件内容并结束对URL的路由解析。我们可以通过设置 RouteCollection的 RouteExistingFiles 属性值为true 让路由系统对静态文件也进行路由匹配,如下所示:public static void RegisterRoutes(RouteCollection routes) {
routes.RouteExistingFiles = true;
routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional
});
}
<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition=""/>
一旦定义了routes.RouteExistingFiles = true,我们就要为静态文件定义路由,如下所示:public static void RegisterRoutes(RouteCollection routes) {
routes.RouteExistingFiles = true; routes.MapRoute("DiskFile", "Content/StaticContent.html",
new { controller = "Customer", action = "List", });
routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
设置了RouteExistingFiles属性为true后,我们要为允许用户请求的资源文件进行路由定义,如果每种资源文件都去定义相应的路由,就会显得很繁琐。
我们可以通过RouteCollection类的IgnoreRoute方法绕过路由定义,使得某些特定的静态文件可以由服务器直接返回给给浏览器,如下所示:public static void RegisterRoutes(RouteCollection routes) {
routes.RouteExistingFiles = true; routes.IgnoreRoute("Content/{filename}.html");
routes.MapRoute("DiskFile", "Content/StaticContent.html",
new { controller = "Customer", action = "List", });
routes.MapRoute("MyRoute", "{controller}/{action}/{id}/{*catchall}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
生成URL(链接)...
<p>The controller is: @ViewBag.Controller</p>
<p>The action is: @ViewBag.Action</p>
<p>
@Html.ActionLink("This is an outgoing URL", "CustomVariable")
</p>
<a href="/Home/CustomVariable">This is an outgoing URL</a>
public static void RegisterRoutes(RouteCollection routes) {
routes.MapRoute("NewRoute", "App/Do{action}", new { controller = "Home" });
routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
<a href="/App/DoCustomVariable">This is an outgoing URL</a>
<p>
@Html.ActionLink("This targets another controller", "Index", "Admin")
</p>
<a href="/Admin">This targets another controller</a>
<p>
@Html.ActionLink("This is an outgoing URL", "CustomVariable", new { id = "Hello" })
</p>
<a href="/Home/CustomVariable/Hello">This is an outgoing URL</a>
<p>
@Html.ActionLink("This is an outgoing URL", "Index", "Home", null,
new {id = "myAnchorID", @class = "myCSSClass"})
</p>
<a class="myCSSClass" href="/" id="myAnchorID">This is an outgoing URL</a>
<p>
@Html.ActionLink("This is an outgoing URL", "Index", "Home",
"https", "myserver.mydomain.com", " myFragmentName",
new { id = "MyId"},
new { id = "myAnchorID", @class = "myCSSClass"})
</p>
这是Html.ActionLink方法中最多参数的重载方法,它允许我们提供请求的协议(https)和目标服务器地址(myserver.mydomain.com)等。它生成的链接如下:<a class="myCSSClass" id="myAnchorID"
href="https://myserver.mydomain.com/Home/Index/MyId#myFragmentName" >
This is an outgoing URL</a>
<p>This is a URL:
@Url.Action("Index", "Home", new { id = "MyId" })
</p>
public static void RegisterRoutes(RouteCollection routes) {
routes.MapRoute("MyRoute", "{controller}/{action}");
routes.MapRoute("MyOtherRoute", "App/{action}", new { controller = "Home" });
}
对于这样的两个路由,对于类似下面这样的写法:@Html.ActionLink("Click me", "Index", "Customer")
<a href="/Customer/Index">Click me</a>
@Html.RouteLink("Click me", "MyOtherRoute","Index", "Customer")
<a Length="8" href="/App/Index?Length=5">Click me</a>
这个链接指向的是HomeController下的Index Action。但需要注意,通过这种方式来生成URL是不推荐的,因为它不能让我们从直观上看到它生成的URL指向的controller和action。所以,非到万不得已的情况才会这样用。public ViewResult MyActionMethod() {
string myActionUrl = Url.Action("Index", new { id = "MyID" });
string myRouteUrl = Url.RouteUrl(new { controller = "Home", action = "Index" });
//... do something with URLs...
return View();
}
更多时候我们会在Action方法中将客户端浏览器重定向到别的URL,这时候我们使用RedirectToAction方法,如下:public RedirectToRouteResultMyActionMethod() {
return RedirectToAction("Index");
}
public RedirectToRouteResult MyActionMethod() {
return RedirectToRoute(new { controller = "Home", action = "Index", id = "MyID" });
}
最好能直观的看出URL的意义,不要用应用程序的具体信息来定义URL。比如使用 /Articles/Report 比使用 /Website_v2/CachedContentServer/FromCache/Report 好。
使用内容标题比使用ID好。比如使用 /Articles/AnnualReport 比使用 /Articles/2392 好。如果一定要使用使用ID(比如有时候可能需要区分相同的标题),那么就两者都用,如 /Articles/2392/AnnualReport ,它看起来很长,但对用户更友好,而且更利于SEO。
对于Web页面不要使用文件扩展名(如 .aspx 或 .mvc)。但对于特殊的文件使用扩展名(如 .jpg、.pdf 和 .zip等)。
尽可能使用层级关系的URL,如 /Products/Menswear/Shirts/Red,这样用户就能猜到父级URL。
不区分大小写,这样方便用户输入。
正确使用Get和Post。Get一般用来从服务器获取只读的信息,当需要操作更改状态时使用Post。
尽可能避免使用标记符号、代码、字符序列等。如果你想要用标记进行分隔,就使用中划线(如 /my-great-article),下划线是不友好的,另外空格和+号都会被URL编码。
不要轻易改变URL,尤其对于互联网网站。如果一定要改,那也要尽可能长的时间保留原来的URL。
尽量让URL使用统一的风格或习惯。