探索ASP.NET MVCフレームワークのルーティングシステム
21924 ワード
引用する
ASPについてNET MVCのルーティングシステムは、皆さんがよく知っていると信じています.今日はASPに深く入ります.NETのフレームワークの内部で,ルーティングシステムがどのように我々が与えたアドレス(例えば,/Home/Index)を通じてControllerとActionを解析するかを見てみよう.今日のこの文章はフレームワークの内部に深く入り込んで、中の流れを見てみましょう.
UrlRouteModuleの紹介
ASP.NET MVCは本質的にIHttpModuleとIHttpHandlerの2つのコンポーネント対ASPを通過する.NETフレームワークを拡張して実現した.ASP.NET要求処理プロセスは、複数のHttpModuleとHttpHandlerからなるパイプモデルに基づく、ASP.NETはhttpリクエストを順次パイプ内の各HttpModuleに渡し,最終的にHttpHandlerに処理され,処理が完了した後,再びパイプ内のHTTPモジュールを経由し,結果をクライアントに返す.各HttpModuleで要求の処理プロセスに関与することができます.
ASP.NET MVCは、カスタムIHttpModuleを介してHttp要求を成功させたASPである.NET処理パイプでMVCフレームを引き継ぐ.マイクロソフト自身がこのカスタムIHttpModuleを実現しました.これが私たちが今日紹介するUrlRouteModuleです.このクラスはSystemです.Web.Routing.dllの.ILSpyでソースコードを確認します.ソースコードは次のとおりです(ソースコードは適切にフィルタされています).
1 public class UrlRoutingModule : IHttpModule
2 {
3 private static readonly object _contextKey = new object();
4 private RouteCollection _routeCollection;
5 public RouteCollection RouteCollection
6 {
7 get
8 {
9 if (this._routeCollection == null)
10 {
11 this._routeCollection = RouteTable.Routes;
12 }
13 return this._routeCollection;
14 }
15 [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
16 set
17 {
18 this._routeCollection = value;
19 }
20 }
21 public UrlRoutingModule()
22 {
23 }
24
25 protected virtual void Init(HttpApplication application)
26 {
27 if (application.Context.Items[UrlRoutingModule._contextKey] != null)
28 {
29 return;
30 }
31 application.Context.Items[UrlRoutingModule._contextKey] = UrlRoutingModule._contextKey;
32 application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
33 }
34
35 public virtual void PostResolveRequestCache(HttpContextBase context)
36 {
37 RouteData routeData = this.RouteCollection.GetRouteData(context);
38 if (routeData == null)
39 {
40 return;
41 }
42 IRouteHandler routeHandler = routeData.RouteHandler;
43 if (routeHandler == null)
44 {
45 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
46 }
47 if (routeHandler is StopRoutingHandler)
48 {
49 return;
50 }
51 RequestContext requestContext = new RequestContext(context, routeData);
52 context.Request.RequestContext = requestContext;
53 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
54 if (httpHandler == null)
55 {
56 throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[]
57 {
58 routeHandler.GetType()
59 }));
60 }
61 if (!(httpHandler is UrlAuthFailureHandler))
62 {
63 context.RemapHandler(httpHandler);
64 return;
65 }
66 if (FormsAuthenticationModule.FormsAuthRequired)
67 {
68 UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
69 return;
70 }
71 throw new HttpException(401, SR.GetString("Assess_Denied_Description3"));
72 }
73 private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
74 {
75 HttpApplication httpApplication = (HttpApplication)sender;
76 HttpContextBase context = new HttpContextWrapper(httpApplication.Context);
77 this.PostResolveRequestCache(context);
78 }
79
80 protected virtual void Dispose()
81 {
82 }
83 }
UrlRouteModuleがIHttpModuleインタフェースから継承されているのを見ました.このインタフェースは非常に簡単で,InitメソッドとDisposeメソッドのみである.私たちはASPを知っています.NETパイプラインでは、要求の処理中に19のイベントがプログラマに拡張をカスタマイズさせ、あるステップを実行した後、関連する操作を要求することができます.UrlRouteModuleは、Initメソッドを使用してPostResolveRequestCacheイベント処理関数を登録し、パイプラインをPostResolveRequestCacheに処理するには、コールバック関数OnApplicationPostResolveRequestCacheを呼び出します.次に、このコールバック関数(コードの73行から)をよく分析します.
PostResolveRequestCacheメソッドの解析
ソースコードの赤い部分がコールバック関数の主体であることがわかります.1行目のコードは次のとおりです.
1 RouteData routeData = this.RouteCollection.GetRouteData(context);
現在のリクエストのコンテキストに基づいて、RouteDataオブジェクトを取得します.GetRouteDataに入って、その論理を見てみましょう.ソースコードを見てください.
1 public RouteData GetRouteData(HttpContextBase httpContext)
2 {
3 using (this.GetReadLock())
4 {
5 foreach (RouteBase current in this)
6 {
7 RouteData routeData = current.GetRouteData(httpContext);
8 if (routeData != null)
9 {
10 RouteData result;
11 if (!current.RouteExistingFiles)
12 {
13 if (!flag2)
14 {
15 flag = this.IsRouteToExistingFile(httpContext);
16 }
17 if (flag)
18 {
19 result = null;
20 return result;
21 }
22 }
23 result = routeData;
24 return result;
25 }
26 }
27 }
28 return null;
29 }
このメソッドの内部では,GetRouteDataメソッド(コードの7行目)を順次呼び出すためにループが使用されていることを示した.明らかにこちらのthisは私たちがプログラムで構成したルーティングテーブルを指しています.PostResolveRequestCacheメソッドのthisを覚えています.RouteCollectionですか?最初のコードクリップの11行目に戻ると、次のコードが表示されます(赤い部分に注意してください).
1 public RouteCollection RouteCollection
2 {
3 get
4 {
5 if (this._routeCollection == null)
6 {
7 this._routeCollection = RouteTable.Routes;
8 }
9 return this._routeCollection;
10 }
11 [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
12 set
13 {
14 this._routeCollection = value;
15 }
16 }
これを見て、RouteCollectionに何が保存されているのかわかるでしょう.中には構成されたすべてのルートが格納されています.我々はcurrentから探索を続けた.GetRouteData(httpContext)から、currentオブジェクト(タイプはRouteBase)のGetRouteDataオブジェクトに再び入ります.RouteBaseのソースコードを見ました.
1 public abstract class RouteBase
2 {
3 private bool _routeExistingFiles = true;
4 public bool RouteExistingFiles
5 {
6 get
7 {
8 return this._routeExistingFiles;
9 }
10 set
11 {
12 this._routeExistingFiles = value;
13 }
14 }
15 public abstract RouteData GetRouteData(HttpContextBase httpContext);
16 public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
17 }
RouteBaseは抽象クラスであり,GetRouteDataメソッドの実装は特定の継承タイプの実装に基づいている.では、RouteBaseから受け継がれているタイプはどれですか?考えてみようRouteTableRoutesに格納されているのはすべてルーティングオブジェクトです.では、ルーティングオブジェクトを追加するときに、RouteBaseタイプから継承された派生クラスを追加する必要があります.これはきっとみんなよく知らないに違いない.ルーティングを追加する場合はMapRouteメソッドの他にAddメソッドも使用できます.次のようになります.
1 routes.MapRoute("StaticRoute", "Content/CustomerJS.js",
2 new { controller = "Home", action = "Index" },
3 new string[] { "MyFirstMvcProject.Controllers" });
4
5 routes.Add("first", new Route("{controller}/{action}", new MvcRouteHandler()));
MVCフレームワークの内部ではRouteを使用してRouteBaseを継承していることがわかります.それは明らかだGetRouteDataが呼び出すのは派生クラスのメソッドに違いありません.このメソッドの具体的な実装をRouteオブジェクトに見てみましょう.ソースコードは次のとおりです.
1 public override RouteData GetRouteData(HttpContextBase httpContext)
2 {
3 string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
4 RouteValueDictionary routeValueDictionary = this._parsedRoute.Match(virtualPath, this.Defaults);
5 if (routeValueDictionary == null)
6 {
7 return null;
8 }
9 RouteData routeData = new RouteData(this, this.RouteHandler);
10 if (!this.ProcessConstraints(httpContext, routeValueDictionary, RouteDirection.IncomingRequest))
11 {
12 return null;
13 }
14 foreach (KeyValuePair<string, object> current in routeValueDictionary)
15 {
16 routeData.Values.Add(current.Key, current.Value);
17 }
18 if (this.DataTokens != null)
19 {
20 foreach (KeyValuePair<string, object> current2 in this.DataTokens)
21 {
22 routeData.DataTokens[current2.Key] = current2.Value;
23 }
24 }
25 return routeData;
26 }
この方法では、RouteDataオブジェクトが作成され、URLパスが解析されていることを示します(this._parsedRoute.Match(virtualPath,this.Defaults).さらに,ルーティングルールの正規表現とネーミング空間も検証した.また、RouteDataの多くの属性値がここに追加されていることも見られます.RouteDataのRouteHandlerもRouteのRouteHandlerプロパティから取得されます.
ここまで、RouteDataオブジェクトがどのように作成され、属性値がどのように割り当てられているかが明らかになりました.私たちはやはりUrlRouteModuleに戻ります.次のコードを見てみましょう.
1 IRouteHandler routeHandler = routeData.RouteHandler;
2 if (routeHandler == null)
3 {
4 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
5 }
6 if (routeHandler is StopRoutingHandler)
7 {
8 return;
9 }
10 RequestContext requestContext = new RequestContext(context, routeData);
11 context.Request.RequestContext = requestContext;
12 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
次のステップは、RouteDataに保存されているRouteHandlerを取得し、RouteHandlerによって次の処理要求のHandlerを見つけることです.私たちは今回routeを通過しました.MapRouteメソッドで探索します.ルーティングを登録するときによく使うMapRouteの内部実装を見てみましょう.ソースコードを見てください.
1 public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
2 {
3 if (routes == null)
4 {
5 throw new ArgumentNullException("routes");
6 }
7 if (url == null)
8 {
9 throw new ArgumentNullException("url");
10 }
11
12 Route route = new Route(url, new MvcRouteHandler())
13 {
14 Defaults = CreateRouteValueDictionary(defaults),
15 Constraints = CreateRouteValueDictionary(constraints),
16 DataTokens = new RouteValueDictionary()
17 };
18
19 if ((namespaces != null) && (namespaces.Length > 0))
20 {
21 route.DataTokens["Namespaces"] = namespaces;
22 }
23
24 routes.Add(name, route);
25
26 return route;
27 }
呼び出したMapRouteがRouteオブジェクト(元のRouteTable.Routesに格納されていたものはすべてRouteタイプのインスタンス(RouteはRouteBaseオブジェクトから継承されている))を作成しているのを見ました.もちろんIHttpHandlerはMvcRouteHandlerに設定されています.デフォルト値default、制約、ネーミングスペースの値をRouteValueDictionaryタイプとして保存します.では、RouteDataのRouteHandlerがMvcRouteHandlerであることは明らかです.
RouteDataのRouteHandlerはIrouteHandlerタイプであり,MvcRouteHandlerはIrouteHandlerの具体的な実装であることを知っている.MvcRouteHandlerのソースコードを見てみましょう.
1 public class MvcRouteHandler : IRouteHandler
2 {
3 private IControllerFactory _controllerFactory;
4
5 public MvcRouteHandler()
6 {
7 }
8
9 public MvcRouteHandler(IControllerFactory controllerFactory)
10 {
11 _controllerFactory = controllerFactory;
12 }
13
14 protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
15 {
16 requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
17 return new MvcHandler(requestContext);
18 }
19
20 }
ソースコードにより,MvcRouteHandlerがIrouteHandlerインタフェースのGetHttpHandler法を実装することを示した.我々がUrlRouteModuleでRouteDataのRouteHandler属性からHttpHandlerを取得するには,実際にMvcRouteHandlerのGetHttpHandlerメソッドが呼び出される.最終的にはMvcHandlerタイプが返されるのを見た.ここまで,最終的に返されるIHttpHandlerタイプがMvcHandlerであることが分かった.リクエストの後続操作はこのHttpHandler処理に任せる.
関連のまとめ
1、私たちがよく使うMapRouteはRouteCollectionが持っている方法ではなく、MVCソースコードで提供している拡張方法です.拡張クラス名は、RouteCollectionExtensionsです.
2、RouteはRouteBase抽象クラスから継承されます.RouteDataを取得する方法では、RouteTableを巡回する.Routesコレクションは、現在要求されているURLとルーティングテンプレートを一致させ、本質的にRouteタイプのGetRouteDataメソッドを呼び出します.
3、RouteDataの関連属性とRouteHandlerは、いずれもRouteオブジェクトから取得されます.
4、ルーティングシステムが最終的に返すIHttpHandlerタイプはMvcHandlerタイプであり、要求された後続の操作はこのHttpHandler処理に任せる.