史上最全的ASP.NET MVC路由配置

部落格園發表於2014-08-16

繼續延續坑爹標題系列。其實只是把apress.pro.asp.net.mvc.4.framework裡的CHAPTER 13翻譯過來罷了,當做自己總結吧。內容看看就好,排版就不要吐槽了,反正我知道你也不會反對的。

XD 首先說URL的構造。 其實這個也談不上構造,只是語法特性吧。

命名引數規範+匿名物件

routes.MapRoute( name: “Default”, url: “{controller}/{action}/{id}”, defaults: new { controller = “Home”, action = “Index”, id = UrlParameter.Optional } );

構造路由然後新增

Route myRoute = new Route(“{controller}/{action}”, new MvcRouteHandler()); routes.Add(“MyRoute”, myRoute);

直接方法過載+匿名物件

routes.MapRoute(“ShopSchema”, “Shop/{action}”, new { controller = “Home” });

個人覺得第一種比較易懂,第二種方便除錯,第三種寫起來比較效率吧。各取所需吧。本文行文偏向於第三種。

 1.預設路由(MVC自帶)

routes.MapRoute( “Default”, // 路由名稱 “{controller}/{action}/{id}”, // 帶有引數的 URL new { controller = “Home”, action = “Index”, id = UrlParameter.Optional } // 引數預設值 (UrlParameter.Optional-可選的意思) );

2.靜態URL段

routes.MapRoute(“ShopSchema2″, “Shop/OldAction”, new { controller = “Home”, action = “Index” });

routes.MapRoute(“ShopSchema”, “Shop/{action}”, new { controller = “Home” }); <br><br>routes.MapRoute(“ShopSchema2″, “Shop/OldAction.js”, new { controller = “Home”, action = “Index” });

沒有佔位符路由就是現成的寫死的。

比如這樣寫然後去訪問http://localhost:XXX/Shop/OldAction.js,response也是完全沒問題的。 controller , action , area這三個保留字就別設靜態變數裡面了。

3.自定義常規變數URL段(好吧這翻譯暴露智商了)

routes.MapRoute(“MyRoute2″, “{controller}/{action}/{id}”, new { controller = “Home”, action = “Index”, id = “DefaultId” });

這種情況如果訪問 /Home/Index 的話,因為第三段(id)沒有值,根據路由規則這個引數會被設為DefaultId

這個用viewbag給title賦值就能很明顯看出

ViewBag.Title = RouteData.Values["id"];

圖不貼了,結果是標題顯示為DefaultId。 注意要在控制器裡面賦值,在檢視賦值沒法編譯的。

4.再述預設路由

然後再回到預設路由。 UrlParameter.Optional這個叫可選URL段.路由裡沒有這個引數的話id為null。 照原文大致說法,這個可選URL段能用來實現一個關注點的分離。剛才在路由裡直接設定引數預設值其實不是很好。照我的理解,實際引數是使用者發來的,我們做的只是定義形式引數名。但是,如果硬要給引數賦預設值的話,建議用語法糖寫到action引數裡面。比如:

public ActionResult Index(string id = “abcd”) { ViewBag.Title = RouteData.Values["id"]; return View(); }

5.可變長度路由

routes.MapRoute(“MyRoute”, “{controller}/{action}/{id}/{*catchall}”, new { controller = “Home”, action = “Index”, id = UrlParameter.Optional });

在這裡id和最後一段都是可變的,所以 /Home/Index/dabdafdaf 等效於 /Home/Index//abcdefdjldfiaeahfoeiho 等效於 /Home/Index/All/Delete/Perm/…..

6.跨名稱空間路由

這個提醒一下記得引用名稱空間,開啟IIS網站不然就是404。這個非常非主流,不建議瞎搞。

routes.MapRoute(“MyRoute”, “{controller}/{action}/{id}/{*catchall}”, new { controller = “Home”, action = “Index”, id = UrlParameter.Optional }, new[] { “URLsAndRoutes.AdditionalControllers”, “UrlsAndRoutes.Controllers” });

但是這樣寫的話陣列排名不分先後的,如果有多個匹配的路由會報錯。 然後作者提出了一種改進寫法。

routes.MapRoute(“AddContollerRoute”, “Home/{action}/{id}/{*catchall}”, new { controller = “Home”, action = “Index”, id = UrlParameter.Optional }, new[] { “URLsAndRoutes.AdditionalControllers” });

routes.MapRoute(“MyRoute”, “{controller}/{action}/{id}/{*catchall}”, new { controller = “Home”, action = “Index”, id = UrlParameter.Optional }, new[] { “URLsAndRoutes.Controllers” });

這樣第一個URL段不是Home的都交給第二個處理 最後還可以設定這個路由找不到的話就不給後面的路由留後路啦,也就不再往下找啦。

Route myRoute = routes.MapRoute(“AddContollerRoute”, “Home/{action}/{id}/{*catchall}”, new { controller = “Home”, action = “Index”, id = UrlParameter.Optional }, new[] { “URLsAndRoutes.AdditionalControllers” }); <br><br>myRoute.DataTokens["UseNamespaceFallback"] = false;

7.正規表示式匹配路由

routes.MapRoute(“MyRoute”, “{controller}/{action}/{id}/{*catchall}”,
new { controller = “Home”, action = “Index”, id = UrlParameter.Optional },
new { controller = “^H.*”},
new[] { “URLsAndRoutes.Controllers”});

約束多個URL

routes.MapRoute(“MyRoute”, “{controller}/{action}/{id}/{*catchall}”,
new { controller = “Home”, action = “Index”, id = UrlParameter.Optional },
new { controller = “^H.*”, action = “^Index$|^About$”},
new[] { “URLsAndRoutes.Controllers”});

8.指定請求方法

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”) },

new[] { “URLsAndRoutes.Controllers” });

9.最後還是不爽的話自己寫個類實現 IRouteConstraint的匹配方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;
/// <summary>
/// If the standard constraints are not sufficient for your needs, you can define your own custom constraints by implementing the IRouteConstraint interface.
/// </summary>
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);
}
}

routes.MapRoute(“ChromeRoute”, “{*catchall}”,

new { controller = “Home”, action = “Index” },

new { customConstraint = new UserAgentConstraint(“Chrome”) },

new[] { “UrlsAndRoutes.AdditionalControllers” });

比如這個就用來匹配是否是用谷歌瀏覽器訪問網頁的。

10.訪問本地文件

routes.RouteExistingFiles = true;

routes.MapRoute(“DiskFile”, “Content/StaticContent.html”, new { controller = “Customer”, action = “List”, });

瀏覽網站,以開啟 IIS Express,然後點顯示所有應用程式-點選網站名稱-配置(applicationhost.config)-搜尋UrlRoutingModule節點

<add name=”UrlRoutingModule-4.0″ type=”System.Web.Routing.UrlRoutingModule” preCondition=”managedHandler,runtimeVersionv4.0″ />

把這個節點裡的preCondition刪除,變成

<add name=”UrlRoutingModule-4.0″ type=”System.Web.Routing.UrlRoutingModule” preCondition=”" />

11.直接訪問本地資源,繞過了路由系統

routes.IgnoreRoute(“Content/{filename}.html”);

檔名還可以用 {filename}佔位符。

IgnoreRoute方法是RouteCollection裡面StopRoutingHandler類的一個例項。路由系統通過硬-編碼識別這個Handler。如果這個規則匹配的話,後面的規則都無效了。 這也就是預設的路由裡面routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”);寫最前面的原因。

路由測試(在測試專案的基礎上,要裝moq)

PM> Install-Package Moq

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Web;
using Moq;
using System.Web.Routing;
using System.Reflection;
[TestClass]
public class RoutesTest
{
private HttpContextBase CreateHttpContext(string targetUrl = null, string HttpMethod = “GET”)
{
// create the mock request
Mock<HttpRequestBase> mockRequest = new Mock<HttpRequestBase>();
mockRequest.Setup(m => m.AppRelativeCurrentExecutionFilePath)
.Returns(targetUrl);
mockRequest.Setup(m => m.HttpMethod).Returns(HttpMethod);
// create the mock response
Mock<HttpResponseBase> mockResponse = new Mock<HttpResponseBase>();
mockResponse.Setup(m => m.ApplyAppPathModifier(
It.IsAny<string>())).Returns<string>(s => s);
// create the mock context, using the request and response
Mock<HttpContextBase> mockContext = new Mock<HttpContextBase>();
mockContext.Setup(m => m.Request).Returns(mockRequest.Object);
mockContext.Setup(m => m.Response).Returns(mockResponse.Object);
// return the mocked context
return mockContext.Object;
}

private void TestRouteMatch(string url, string controller, string action, object routeProperties = null, string httpMethod = “GET”)
{
// Arrange
RouteCollection routes = new RouteCollection();
RouteConfig.RegisterRoutes(routes);
// Act – process the route
RouteData result = routes.GetRouteData(CreateHttpContext(url, httpMethod));
// Assert
Assert.IsNotNull(result);
Assert.IsTrue(TestIncomingRouteResult(result, controller, action, routeProperties));
}

private bool TestIncomingRouteResult(RouteData routeResult, string controller, string action, object propertySet = null)
{
Func<object, object, bool> valCompare = (v1, v2) =>
{
return StringComparer.InvariantCultureIgnoreCase
.Compare(v1, v2) == 0;
};
bool result = valCompare(routeResult.Values["controller"], controller)
&& valCompare(routeResult.Values["action"], action);
if (propertySet != null)
{
PropertyInfo[] propInfo = propertySet.GetType().GetProperties();
foreach (PropertyInfo pi in propInfo)
{
if (!(routeResult.Values.ContainsKey(pi.Name)
&& valCompare(routeResult.Values[pi.Name],
pi.GetValue(propertySet, null))))
{
result = false;
break;
}
}
}
return result;
}

private void TestRouteFail(string url)
{
// Arrange
RouteCollection routes = new RouteCollection();
RouteConfig.RegisterRoutes(routes);
// Act – process the route
RouteData result = routes.GetRouteData(CreateHttpContext(url));
// Assert
Assert.IsTrue(result == null || result.Route == null);
}

[TestMethod]
public void TestIncomingRoutes()
{
// check for the URL that we hope to receive
TestRouteMatch(“~/Admin/Index”, “Admin”, “Index”);
// check that the values are being obtained from the segments
TestRouteMatch(“~/One/Two”, “One”, “Two”);
// ensure that too many or too few segments fails to match
TestRouteFail(“~/Admin/Index/Segment”);//失敗
TestRouteFail(“~/Admin”);//失敗
TestRouteMatch(“~/”, “Home”, “Index”);
TestRouteMatch(“~/Customer”, “Customer”, “Index”);
TestRouteMatch(“~/Customer/List”, “Customer”, “List”);
TestRouteFail(“~/Customer/List/All”);//失敗
TestRouteMatch(“~/Customer/List/All”, “Customer”, “List”, new { id = “All” });
TestRouteMatch(“~/Customer/List/All/Delete”, “Customer”, “List”, new { id = “All”, catchall = “Delete” });
TestRouteMatch(“~/Customer/List/All/Delete/Perm”, “Customer”, “List”, new { id = “All”, catchall = “Delete/Perm” });
}

}

最後還是再推薦一下Adam Freeman寫的apress.pro.asp.net.mvc.4這本書。稍微熟悉MVC的從第二部分開始讀好了。前面都是入門(對我來說是扯淡)。但總比國內某些寫書的人好吧——把個開源專案的原始碼下載下來帖到書上面來,然後標題起個深入解析XXXX,然後淨瞎扯淡。最後一千多頁的鉅著又誕生了。Adam Freeman的風格我就很喜歡,都是例項寫作,然後還在那邊書裡面專門寫了大量的測試。

相關文章