絵で見て理解する ASP.NET シリーズ:Routing の仕組み
さて、今回の「絵で見て理解する ASP.NET シリーズ」(※1)では、.NET Framework 3.5 SP1 で導入された Routing (System.Web.Routing)についてみていきたいと思います。
ASP.NET ではこの Routing を使用することにより、例えば
https://www.example.com/productlist.aspx?id=1214&category=toys&page=3
といったURLに対して、
https://www.example.com/toys/1214/3
といった別表記のURLにてアクセスすることが可能になります。
今回はこの Routing の仕組みを解説したいと思います。
まず最初に主要な登場人物と、物語風の処理のあらすじです。
登場人物:
Route 一家。Routing 情報を知っている Route オブジェクトがいっぱいいる一家。
MyHandler。リクエストに基づき Page(WebForm)を返す。Route 一家と仲が良い。
BuildManager。結構偉い人。MyHandler が返す Page は実は彼が作っている。
Page。MyHandler によって返されるオブジェクト。
物語風あらすじ:
ある日、とあるユーザーから ASP.NET 村に “https://www.example.com/toys/1214/3” への Request が入ります。
この Request を受け取った ASP.NET 村の村長は、村一の物知り一家 Route 家にこの Request を誰に任せるのかよいか訪ねます。
Route 家で Request を良く調べた結果、今回は MyHandler 君に処理を任せるのが一番だと判断し、それを伝えます。
村長は MyHanlder 君を呼び出し、Request の処理を依頼します。
MyHandler 君は Request をもとに BuildManager に Page の作成をお願いし、できあがった Page を村長に渡したのでした。
ということで、概略を理解いただいたうえで、実際の解説に入りたいと思います。
実際の処理概要:
最初に Routing を使用したい Web アプリケーションにおいて、Global.asax に、適切な Route 情報を登録しておきます。
RouteTable.Routes.Add(new Route ( "{category}/{productID}/{page}", new MyHandler("~/productlist.aspx") ) ); |
このRoute 情報は複数登録を行うことができます。
Web アプリケーションに対して、ユーザー(Webアプリケーションの使用者)から Request が投げられると、Web アプリケーションは RouteTable(物語におけるRoute一家) の Route 情報を順番に確認し、Request にマッチする Route があれば、それに関連づいている Handler クラス(実際には IRouteHandler インターフェィスを実装した何らかのクラス)の GetHttpHandler メソッドを呼び出します。
Handler (IRouteHandler インターフェィスを実装した何らかのクラス) の GetHttpHandler メソッドでは Reqest に最適な Page オブジェクトを生成し、返します。
この Page の生成には、BuildManager の CreateInstanceFromVirtualPath メソッドを使用しています。
さて、GetHttpHandler メソッドの戻り値は、IHttpHandler インターフェィスを実装している必要がありますが、ここで生成される Page クラス(WebForm)は System.Web.UI.Pageを継承しており、この System.Web.UI.Page が IHttpHandler インターフェィスを実装しているので特に細工はいりません(※2)
BuildManager の CreateInstanceFromVirtualPath で Page オブジェクトを作成して、さっくり返してあげれば、あとは生成された Page オブジェクトが処理を継続します。
Request から必要な情報の取り出し:
以上で Routing の概略は終了ですが、実際には Request から必要な情報を取り出す処理(①)と、その情報を Pageに渡す処理(②)が必要です。
まず、①の情報の取り出しについてですが、この部分は Routing の基本的な仕組みとしてフレームワークで提供されています。
具体的には、Route 情報の登録の際に指定したパターン(赤字部分)
RouteTable.Routes.Add(new Route ( "{category}/{productID}/{page}", new MyHandler("~/productlist.aspx") ) ); |
に基づいて、Routing のフレームワークが、RequestContext.RouteData にその情報を格納しています。
(パターンで指定された文字(たとえば “category” )をキーに、実際の Request で与えられた文字列を値として保持します)
これを取り出すのは、以下のようにします。
public IHttpHandler GetHttpHandler(RequestContext requestContext) { string category = requestContext.RouteData.Values["category"] as string; …… } |
もしくは GetRequiredString メソッドでもOKです。
public IHttpHandler GetHttpHandler(RequestContext requestContext) { string category = requestContext.RouteData.GetRequiredString("category"); …… } |
これで Request から必要な情報を取り出せました。
次に、②の「Pageに渡す処理」です。
これについては、BuildManager に作成させる Page クラスを拡張することで対応します。
具体的には、IHttpHandlerインターフェイスを継承した新しい Handler インターフェイスを作成し、その中に値を保持するためのプロパティを追加します。
以下のような感じです。
interface IMyHttpHandler : IHttpHandler { string Category { get; set; } string ProductID { get; set; } string Page { get; set; } } |
これにあわせて Page の宣言を変更しましょう。
public partial class ProductList : System.Web.UI.Page, IMyHttpHandler |
最後に、BuildeManager による Page インスタンス作成時に、今回追加を行ったプロパティへの値の設定のためのコードを追加します。
public IHttpHandler GetHttpHandler(RequestContext requestContext) { var page = BuildManager.CreateInstanceFromVirtualPath (VirtualPath, typeof(Page)) as IMyHttpHandler; page.Category = requestContext.RouteData.Values["category"] as string;; …… } |
これで Request (VirtualPath) に含まれている情報を Page に渡すことができるようになりました。
以上で今回の絵で見て理解する ASP.NET シリーズ、「Routing の仕組み」を終了します。
ありがとうございました。
謝辞:
今回のエントリは Routing について調べていたところ、以下の2つの素晴らしい記事に接し、それに触発されて書いたものです。
小山さんと小野さんにお礼申し上げます <(_ _)>
IIS/Windows サーバー徹底解説:ASP.NET ルーティング (ASP.NET Routing)
https://keicode.com/dotnet/aspnet-routing.php
どっとねっとふぁんBlog:ASP.NET ルーティングを実装する
https://dotnetfan.org/blogs/dotnetfanblog/archive/2008/09/18/2809.aspx
今回のエントリでは ASP.NET Routing の実際の使用方法、あるいは web.cofig の設定については触れていませんが、それについてはぜひ上記の記事を参照ください。
※1
思わずネタで書いてしまいましたが、当然そんなシリーズはなく、今回限りのエントリです。しかも、おもったより「絵」の少ないエントリですいません。。。
※2
最初に公開したときに、「Page で IHttpHandler を実装しましょう。といってもクラス宣言で、IHttpHandler を継承すればOKです」といった記述をしていましたが、小野さんから、「Page 自体が IHttpHandler を継承している」ので、「両方を継承する必要はないんじゃ」とコメントいただき、そのとおりですので書き換えました。スイマセン。