探索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処理に任せる.