您好,登錄后才能下訂單哦!
ASP.NET中怎么實現Routing路由,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
Routing框架的主要流程
基本的Routing框架是基于Middleware來實現的,這樣就可以將其添加到HTTP的請求Pipeline中了,它可以喝其它任意Middleware一起進行組合使用,如靜態文件處理程序、錯誤頁、或者SignalR服務器。
在使用Routing框架之前,首要要了解Routing的作用,作用很簡單:
對于HTTP請求,Routing系統負責找出與之匹配的route,創建route數據,并將該請求派送到該route對于的處理程序(Handler)上。
Controller和Action的選擇,只是MVC的Handler的一個具體實現,該實現使用route數據和HTTP請求中的其它信息來選擇要執行的Controller和Action。在新版的MVC6中,該處理程序的名稱為MvcRouteHandler。
路由系統的執行流程如下:
ASP.NET 5監聽到一個HTTP請求。然后Routing Middleware就會嘗試將route集合中的route匹配該請求。
一旦成功匹配一個請求,就找出該route對應的handler。
調用該handler上的RouteAsync方法(因為所有的handler都要實現該接口方法)。
RoutingContext有一個IsHandled標記,如果該標記設置為true,則意味著該請求已經被這個handler成功處理了;如果設置為false,則意味著該handler無法處理該請求,系統會再為此匹配一個route。
和之前的Routing系統有點不同的是,老版的Routing系統一旦成功匹配一個路由,就將其交由其對應的Handler,不管對應的Handler能不能處理該請求,所以就會出現route匹配成功了,但是找不到對應的action,此時就會出現404錯誤,而新版對此作出了上述第4步驟的改進(重新將控制權交回給Routing系統,進行重新匹配),看起來還是非常不錯的。
Route參數和約束條件的改進
在之前的route設置中,要約束一個參數的數據類型的話,我們需要使用類型如下代碼:
routes.MapRoute(
"Product",
"Product/{productId}",
defaults: new { controller = "Product", action = "Details" },
constraints: new { productId = @"\d+" });
而在新版route中,就可以直接設置Product/{productId:int}了,約束條件遵守如下約定:
{parameter:constraint}
目前支持的約束如下:
而對于可選參數,則值需要在約束類型后面加一個問號即可,示例如下:
routes.MapRoute(
"Product",
"Product/{productId:long?}",
new { controller = "Product", action = "Details" });
如果參數是必填的,需要保留一個默認值的話,則可以按照如下示例進行設置:
routes.MapRoute(
"Product",
"Product/{productId:long=1000}",
new { controller = "Product", action = "Details" });
通用Routing
關于示例使用,我們先不從MVC開始,而是先從普通的Routing使用方式開始,新版route添加的時候默認添加的是TemplateRoute實例,并且在該實例實例化的時候要設置一個Handler。
舉例來說,我們先創建一個空的ASP.NET 5項目,并在project.json文件的dependencies節點中添加程序集"Microsoft.AspNet.Routing": "1.0.0-beta3",,在Startup.cs的Configure方法里添加如下代碼:
public void Configure(IApplicationBuilder app)
{
RouteCollection routes = new RouteCollection();
routes.Add(new TemplateRoute(new DebuggerRouteHandler("RouteHandlerA"), "", null));
routes.Add(new TemplateRoute(new DebuggerRouteHandler("RouteHandlerB"), "test/{a}/{b:int}", null));
routes.Add(new TemplateRoute(new DebuggerRouteHandler("RouteHandlerC"), "test2", null));
app.UseRouter(routes); // 開啟Routing功能
}
在這里,我們設置HTTP請求處理的的Handler為DebuggerRouteHandler,該類繼承于IRouter,實例代碼如下:
public class DebuggerRouteHandler : IRouter { private string _name; public DebuggerRouteHandler(string name) { _name = name; } public string GetVirtualPath(VirtualPathContext context) { throw new NotImplementedException(); } public async Task RouteAsync(RouteContext context) { var routeValues = string.Join("", context.RouteData.Values); var message = String.Format("{0} Values={1} ", _name, routeValues); await context.HttpContext.Response.WriteAsync(message); context.IsHandled = true; } }
上述類,繼承IRouter以后,必須實現一個RouteAsync的方法,并且如果處理成功,則將IsHandled設置為true。
訪問如下網址即可查看相應的結果:
正常:`http://localhost:5000/`
正常:`http://localhost:5000/test/yyy/12`
404 :`http://localhost:5000/test/yyy/s`
正常:`http://localhost:5000/test2`
404 :`http://localhost:5000/test3`
注意:TemplateRoute和DebuggerRouteHandler都繼承于IRouter,是實現前面所述的不出現404錯誤(繼續匹配下一個路由)的核心。
MVC中的Routing
在MVC示例程序中,我們只需要配置在調用app.UseMVC方法的時候,使用委托中的MapRoute方法來定義各種route就可以了。在這里我們以空白項目為例,來看看MVC的route如何使用。
***步:在project.json文件的dependencies節點中引用程序集"Microsoft.AspNet.Mvc": "6.0.0-beta3",
第二部:添加MVC的Middleware,并使用MVC,然后添加一條默認的路由,代碼如下:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc(routeBuilder =>
{
routeBuilder.MapRoute(
name: "default",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "Home", action = "Index" });
});
}
第三步:分別創建如下如下三種Controller,其中ProductsController繼承于Microsoft.AspNet.Mvc下的Controlle
public class ProductsController : Controller { public IActionResult Index() { return Content("It Works with Controller Base Class!"); } } public class DemoController { public IActionResult Index() { return new ObjectResult("It Works without Controller Base Class!"); } } public class APIController { public object Index() { return new { Code = 100000, Data = "OK" }; } }
訪問http://localhost:5000/products和http://localhost:5000/demo,均能顯示正常的輸出結果;而訪問http://localhost:5000/api的時候返回的則是json數據。
這就是我們在前面ASP.NET5新特性中所講的MVC和API合二為一了,并且也可以不繼承于Controller基類(但類名要以Controller結尾)。這種技術的核心是Controller的查找機制,關于如何在一個項目中查找合適的程序集,請參考《Controller與Action》章節。
新版MVC在判定Controller的時候,有2個條件:要么繼承于Controller,要么是引用MVC程序集并且類名以Controller結尾。所以,在創建MVC Controller和Web API Controller的時候,如果你不需要相關的上下文(如HTTPContext、ActionContext等)的話,則可以不必繼承于Controller基類;但推薦都繼承于Controller,因為可以多多利用基類的方法和屬性,因為不管繼承不繼承,你定義的所有Controller類都要走MVC的各個生命周期,我們通過ActionFilter來驗證一下:
***步:在project.json文件的dependencies節點中引用程序集"Microsoft.AspNet.Server.WebListener": "1.0.0-beta3"。
第二步:創建一個Aciton Filter,分別在Action執行前和執行后輸出一行文字,代碼如下:
public class ActionFilterTest : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { var typeName = context.Controller.GetType().FullName; Console.WriteLine(typeName + "." + context.ActionDescriptor.Name + ":Start"); } public void OnActionExecuted(ActionExecutedContext context) { var typeName = context.Controller.GetType().FullName; Console.WriteLine(typeName + "." + context.ActionDescriptor.Name + ":END"); } }
第三步:在ConfigureServices方法里注冊該Action Filter
services.Configure(options => { options.Filters.Add(typeof(ActionFilterTest)); });
運行程序,并訪問響應的路徑,三種類型的代碼均會按計劃輸出內容,輸出內容如下:
RouterTest.ProductsController.Index:Start RouterTest.ProductsController.Index:End RouterTest.DemoController.Index:Start RouterTest.DemoController.Index:End RouterTest.APIController.Index:Start RouterTest.APIController.Index:End
普通的ASP.NET5程序和MVC程序是可以在一起混合使用Routing功能的。
自定義Route
ASP.NET 5和MVC6都提供了豐富的Route自定義功能,關于普通Route的自定義,可以參考前面小節的DebuggerRouteHandler,這種方式需要實現自己的HTTP輸出,相當于原來輕量級的IHttpHandler一樣。本節,我們將這種在基于MVC的Route自定義功能,即定義的Route的Handler處理程序都是MvcRouteHandler。
在之前版本的MVC中,要自定義Route,一般都是繼承于RouteBase基類或Route類;而在新版的MVC6中,要實現自定義Route,有三種方式,分別如下:
繼承于TemplateRoute
實現IRouter
實現INamedRouter(注:INamedRouter和IRouter的唯一區別是多了一個名稱)
本例中,我們以繼承繼承于TemplateRoute為例,首先創建一個繼承于該類的子類PromoTemplateRoute,該類只匹配/promo目錄下的路徑
public class PromoTemplateRoute : TemplateRoute
{
public PromoTemplateRoute(IRouter target, string routeTemplate, IInlineConstraintResolver inlineConstraintResolver)
: base(target, routeTemplate, inlineConstraintResolver: inlineConstraintResolver)
{
}
public PromoTemplateRoute(IRouter target,
string routeTemplate,
IDictionary
為了方便使用,我們也比葫蘆畫瓢,創建一些擴展方法,示例如下:
public static class RouteBuilderExtensions { public static IRouteBuilder MapPromoRoute(this IRouteBuilder routeCollectionBuilder, string name, string template) { MapPromoRoute(routeCollectionBuilder, name, template, defaults: null); return routeCollectionBuilder; } public static IRouteBuilder MapPromoRoute(this IRouteBuilder routeCollectionBuilder, string name, string template, object defaults) { return MapPromoRoute(routeCollectionBuilder, name, template, defaults, constraints: null, dataTokens: null); } public static IRouteBuilder MapPromoRoute(this IRouteBuilder routeCollectionBuilder, string name, string template, object defaults, object constraints, object dataTokens) { var inlineConstraintResolver = routeCollectionBuilder.ServiceProvider.GetService(); routeCollectionBuilder.Routes.Add( new PromoTemplateRoute( routeCollectionBuilder.DefaultHandler, name, template, ObjectToDictionary(defaults), ObjectToDictionary(constraints), ObjectToDictionary(dataTokens), inlineConstraintResolver)); return routeCollectionBuilder; } private static IDictionary
使用的時候,則很簡單,和之前的方式非常類似,示例如下:
routes.MapPromoRoute( name: "default2", template: "promo/{controller}/{action}/{id?}", defaults: new { controller = "Home", action = "Index" });
通過這種方式,我們可以在符合路由匹配條件的時候,使用PromoTemplateRoute類來處理一些自定義邏輯,比如添加一些額外的文件頭信息等等。
基于Attribute的Routing
基于Attribute的Routing功能一直是MVC所期待的功能,在Web API已經通過RoutePrefix(Controller上使用)和Route(Action上使用)來實現了。該特性在MVC 6中進行了重寫和增強,并且由于MVC和Web API合二而一了,所以在這兩種Controller上都可以使用該特性。
舉例來說:
[Route("bookhome")]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[Route("about")]
public IActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
[Route("contactus")]
public IActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
在上述Controller上定義一個bookhome前綴,并且在About和Contact上又分別定義了action名稱,所以上述3個Action的訪問地址則是如下這種形式
/bookhome /bookhome/about /bookhome/contactus
在這里,我們需要注意,Controller和Action使用的Attribute都是Route,同時,在這些路由模板字符串中,依然可以使用內聯參數,比如,我們可以定義類似這樣的路由:
[Route("products/{productId:int}")]
Controller和Action標記位
另外,針對Route的模板字符串,不僅支持內聯參數,還支持Controller和Action的標記位,即不用寫死該Controller或Action的名稱,使用一個[controller]或[action]的字符即可表示該Controller或Action的名稱。比如,我們可以在Controller上定義這樣的一個路由(Action上什么都不定義):
[Route("book/[controller]/[action]")]
這樣訪問首頁的地址就變成了:/book/Home/Index。
Web API的等價Route定義
在Web API中,我們一般還要定義GET、POST這樣的請求方式,為了方便,新版的HTTPGET等一系列方法都集成了Route功能,直接在構造函數傳入Route模板即可,示例如下:
[HttpGet("products/{productId:int}")]
上述Route的定義,即表明,既要符合products/{productId:int}的路由規則,又要是GET請求。
其實HTTPGET這一系列Attribute也可以在普通的MVC Controller上使用,因為在MVC6中,MVC Controller和Web API Controller本身就是同一個東西,只不過MVC的返回類型都是IActionResult而已。
Route定義,不僅僅支持GET請求,還支持POST等其它類型的請求,即不限制請求方式。
在HttpXXX系列特性中,也是支持內聯參數和[controller]、[action]標記位的,大可放心使用。
目前可用的特性類有:HttpGet、HttpPost、HttpPut、HttpDelete、HttpPatch。
非要重要Route定義規則
基于Attribute的Route定義很方便,但也很危險,具體規則和危險性如下。
規則1:Controller上定義了Route特性很危險
一旦在Controller上定義了Route特性,該Controller下的所有路由規則都不受其它規則控制了,比如,如果你定義了類似這樣的
[Route("book")]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
}
那么,上述2個Action你都再也沒辦法訪問了,因為默認的action的名稱根本就不會起作用,即/book/index和/book/about這兩個路徑無法路由到對應的Action方法上。而且/book也訪問不了,因為有兩個以上的Action,系統無法定位到其中一個Action上。
所以要讓上述Action能訪問,必須要在其中一個Action上定義再Route,例如:
[Route("book")]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[Route("about")]
public IActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
}
這樣,就可以通過/book/about來訪問About方法了,而訪問/book則可以訪問默認的index方法了,因為該index方法是默認唯一一個沒有定義路由的方法,所以他就是/book路由規則的默認Action。如果,有3個Action的話,則必須要至少給兩個Action定義Route,示例如下:
[Route("book")]
public class HomeController : Controller
{
[Route("index")]
public IActionResult Index()
{
return View();
}
[Route("about")]
public IActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public IActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
此時,Contact方法就是默認/book路由的Action了,訪問/book路徑的話,就會顯示Contact對應的頁面。
規則2:Route和HttpGet可以一起使用,但也很危險
我們前面提到,在Action上即可以使用Route特性,也可以使用HttpGet特性,兩者之間的不同,就是多了一個Http Method。很多同學可以要問兩個特性在一起使用的時候會有問題么?
其實,這兩個特性是可以在一起使用的,示例如下:
[Route("book")]
public class HomeController : Controller
{
[Route("Contact")]
[HttpGet("home/Contact2")]
public IActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
這樣/book/contact和/book/home/contact2這兩個網址,都可以訪問了。但如果這里定義HttpGet,情況就不一樣了,示例如下:
[Route("Contact")]
[HttpPost("home/Contact2")]
此時,訪問該Action的方式,要么是以GET的方式訪問/book/contact地址,要么是以POST的方式訪問/book/home/contact2。所以為了避免出錯,建議使用的時候不要講兩者混用,即便是要同時支持GET和POST,那也是建議用同類型的HttpXXX來定義這些路由,例如:
[HttpGet("Contact")] [HttpPost("home/Contact2")]
這樣,看起來就清晰多了。
規則3:多個Route和多個HttpXXX也可以一起使用,但也很危險
在如下示例中,我們為HomeController定義了2個Route特性,而Contact定義了2個Route特性和1個HttpPost特性。
[Route("book")]
[Route("tom")]
public class HomeController : Controller
{
[Route("Contact")]
[Route("ContactUS")]
[HttpPost("home/Contact2")]
public IActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
那么,在上述代碼生效后,我們將有六種訪問來訪問該Action,這六種方式分布如下:
GET:/book/contact GET:/book/contactus GET:/tom/contact GET:/tom/contactus POST:/book/home/contact2 POST:/tom/home/contact2
但是,在視圖文件中,通過@Html.ActionLink("Contact", "Contact", "Home")生成鏈接地址的話,則默認會使用***個定義的Route,如果要強制指定順序,則可以使用Order屬性來定義排序值,默認會優先使用最小的值。示例如下:
[Route("book", Order = 1)] [Route("tom", Order = 0)] public class HomeController : Controller { [Route("Contact", Order = 1)] [Route("ContactUS", Order = 0)] [HttpPost("home/Contact2", Order = 2)] public IActionResult Contact() { ViewBag.Message = "Your contact page."; return View(); } }
自定義內聯參數約束
在前面的介紹中,我們知道任意類型的路由在定義的時候都支持不同的內聯參數約束,因為這些約束是基于ASP.NET 5的,而不是基于MVC6的,并且這些約束還是可以擴展的,本節我們就來看看如何自定義一些擴展。
無參數約束
首先,我們來看一個比較簡單的約束,即無參數約束,類似于{productId:int}這樣的類型約束,假設我們要實現一個AABBCC字符串限定的約束,示例如下:
[Route("index/{productId:aabbcc}")]
為了確保/index/112233和/index/aabbcc是符合約束的,而/index/aabbccdd是不符合約束的,我們首先要自定義一個約束類AABBCCRouteConstraint,并實現IRouteConstraint接口,示例如下:
public class AABBCCRouteConstraint : IRouteConstraint { public bool Match(HttpContext httpContext, IRouter route, string routeKey, IDictionary
在該實現類中,要實現Match方法,根據傳入的各種參數,判斷是否符合定義的約束,并返回true或false,Match方法的參數中,其中routeKey是約束{productId:aabbcc}對應的參數名稱(本例中是productId),values集合中會有該productId所對應的數字(如112233),在該方法通過響應的判斷返回true和false。
下一步,就是要將該約束類注冊到Routing系統的約束集合中,在Startup.cs的ConfigureServices方法中,執行如下語句:
services.Configure(opt => { opt.ConstraintMap.Add("aabbcc", typeof(AABBCCRouteConstraint)); });
注意,這里注冊的aabbcc就是前面我們所指定約束名稱,完成上述步驟以后,即可實現類似{productId:int}的功能了。
有參數約束
一般情況下,有些時候可能需要定義一些約束的值,比如Length(1,10)來表示1-10之間的字符串長度,舉例來說,加入我們要定義一個4個參數的約束規則,如abcd(1,10,20,30)來表示一個特殊的驗證項,則需要聲明有4個參數的構造函數,示例如下:
public class ABCDRouteConstraint : IRouteConstraint
{
public int A { get; private set; }
public int B { get; private set; }
public int C { get; private set; }
public int D { get; private set; }
public ABCDRouteConstraint(int a, int b, int c, int d)
{
A = a;B = b;C = c;D = d;
}
public bool Match(HttpContext httpContext, IRouter route, string routeKey, IDictionary
假如你在Action上了定義了如下約束:
[Route("index/{productId:abcd(1,20,30,40)}")]
那么,在注冊該約束類型以后,系統啟動厚掃描所有的Route進行注冊的時候,會分析你定義的這4個值,然后會將這4個值賦值給該路由對應的約束實例上的A、B、C、D四個屬性上,以便在HTTP請求過來的時候,分析URL上的值,看是否符合Match里定義的規則(在驗證的時候就可以使用這4個屬性值)。
默認約束的所有代碼可以參考:https://github.com/aspnet/Routing/tree/dev/src/Microsoft.AspNet.Routing/Constraints另外,如果定義了4個參數的約束,那么在action上定義路由的時候則必須符合參數的數據類型,如果不符合,系統啟動的時候就會出錯,示例錯誤如下:
[Route("index/{productId:abcd}")] //沒有為該對象定義無參數的構造函數
[Route("index/{productId:abcd(a)}")]
[Route("index/{productId:abcd('a')}")] //輸入字符串的格式不正確
[Route("index/{productId:abcd(1,2,3)}")] //構造函數的參數個數和定義的參數個數不一致。
如果你定義的參數類型是字符串類型,則下面2種形式的定義都是合法的:
[Route("index/{productId:abcd(a,b,c,d)}")]
[Route("index/{productId:abcd('a','b','c','d')}")]
關于ASP.NET中怎么實現Routing路由問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。