中央集権なコントローラ

先日Windows Developer Magazineという雑誌を初めて買いました.そこで『Webアプリケーションへの応用 HTTPモジュールでコード変換処理を横取りして混在を整理』という記事があったんですが,HTTPモジュールって最近も見たような……そうだ,菊池さんの
http://www.ailight.jp/blog/kazuk/archive/2005/11/11/10063.aspx
でした.


この手の話は
.NETによるエンタープライズソリューションパターン (Patterns & practices)
に詳しく書かれているわけですが,例によって飛ばし読みでろくに頭に残ってなかったので、この機会に真面目に復習してみました.

HTTPハンドラとHTTPモジュール

エンタープライズソリューションパターンには

  • フロントコントローラ
  • 受信フィルタ

が紹介されています.
フロントコントローラはStrutsみたいに一箇所でリクエストを受け取って,必要な処理を(Commandパターンとかで)行った後でServer.Transferでページ遷移します.結構大仰な仕掛けになるので,本の中でもあまりオススメではないような?
受信フィルタはそれほど大仰ではなく,イベントフックによって処理を追加することでページコントローラの前処理を行うというものです.
で,フロントコントローラは,拡張子が.aspxのものを全てIHttpHandlerインタフェースを実装するクラスが受け取って処理しますが,受信フィルタはIHttpModuleインタフェースを実装するクラスを必要なだけイベントハンドラに結びつけて処理を組み立てます.

菊池さんのコード解析

菊池さんの示されたコードは,IHttpModuleを利用しています.基本的なアイデアはページコントローラ(通常の分離コード)に処理が移る前に必要な処理をし,表示に必要なデータを作ってあげてHttpContext.Itemsに入れておく,分離コードはHttpContext.Itemsからデータを取り出し,表示するだけにする,というものです.Itemsに入れるオブジェクトはなんでもよいのですが,菊池さんの示されたコードはIDisposableを実装するようにしています.
(ここから示すコードは全て,菊池さんの示されたコードから一部加筆修正しています)

namespace SimpleSite
{
  public class HogeAppContext : IDisposable
  {
    const string ContextItemName = "HogeAppContext";
    public HogeAppContext(HttpContext httpContext)
    {
      httpContext.Items[ContextItemName] = this;
    }
    public static HogeAppContext Current
    {
      get
      {
        return (HogeAppContext)(HttpContext.Current.Items[ContextItemName]);
      }
    }
    public DataSet DataProperty;

    public void LoadData()
    {
      // DataPropertyにDBからデータを取り込む
    }

    #region IDisposable メンバ

    public void Dispose()
    {
      HttpContext.Current.Items[ContextItemName] = null;
    }

    #endregion
  }
}

コンストラクタでHttpContext.Itemsに自分自身を格納します.キー名はconstで持っています.利用する際にはHttpContext.Itemsからキー名を使って値を取得すればよいのですが,Currentプロパティを用意してHogeAppContext.Currentで値を取得できるようにしてあります.


HogeAppContextをどのようにイベントにフックさせるかが今回の仕掛けの肝です.IHttpModuleを実装したHogeAppContextManagerを用意して,Initメソッド内でBeginRequest/EndRequest/PostAuthorizeRequestイベントに対してイベントハンドラを結び付けます.BeginRequestでHogeAppContextコンストラクタ呼び出し(=httpContext.Itemsに格納),EndRequestで削除してます.PostAuthorizeRequestでは,データ取り出し等の業務ロジックを実施しています.

namespace SimpleSite
{
  public class HogeAppContextManager : IHttpModule
  {
    private HttpApplication Application;

    void Application_BeginRequest(object sender, EventArgs e)
    {
      //  new HogeAppContext(Context);
      new HogeAppContext(Application.Context);
    }

    void Application_EndRequest(object sender, EventArgs e)
    {
      //  ApplicationContext.Current.Dispose();
      HogeAppContext.Current.Dispose();
    }

    void Application_PostAuthorizeRequest(object sender, EventArgs e)
    {
      HogeAppContext.Current.LoadData();
    }

    #region IHttpModule メンバ

    public void Init(HttpApplication context)
    {
      Application = context;
      Application.BeginRequest += new EventHandler(Application_BeginRequest);
      Application.EndRequest += new EventHandler(Application_EndRequest);
      Application.PostAuthorizeRequest += new EventHandler(Application_PostAuthorizeRequest);
    }

    public void Dispose()
    {
      //throw new Exception("The method or operation is not implemented.");
    }

    #endregion
  }
}

HogeAppContextManagerのASP.NETランタイムへの登録はweb.configで行います.

<system.web>
  <httpModules>
    <add name="HogeAppContextManager" type="SimpleSite.HogeAppContextManager"/>
  </httpModules>
</system.web>

(nameは何でもいいらしい?typeはクラス名と,必要に応じてアセンブリ名)


分離コード側はHttpContext.Itemsからデータを取り出し表示するだけなので

void Page_Load( object sender,EventArgs e )
{
  GridView1.DataSource = HogeAppContext.Current.DataProperty;
  GridView1.DataBind();
}

だけでOK

まとめ

といった感じで,受信フィルタ(IHttpModule)については大分イメージが掴めてきました.
あとは実プロジェクトにどう適用するかですが……フロントコントローラから比べると確かに手頃で使いやすそうです.ただ,ページコントローラでこれまでやっていた処理を全て受信フィルタで処理できるかというと……どうなんでしょう?
そうしたほうがいいような?悪いような?だったらフロントコントローラのほうがいいような?
今の自分にはよく分かりません.
なんとなく受信フィルタはページ横断的な機能の実現に向いてそうな気がするんですけどね.