ASP.NETのライフサイクルの仕組み
概要
この記事ではIISがリクエストを受け取ったときにASP.NETで発生する2つのイベントとライフサイクルの仕組みについて解説します。 2つのイベントというのはHttpHandlerHttpModuleというクラスによって発生するイベントのことです。 これらのイベントを利用することでWEBアプリケーションのカスタマイズを様々なかたちで行うことが可能です。 まずは全体の概要の説明ということでIISにリクエストが到達したときに内部で行われる処理について解説します。 全体的な処理の概要を下の図で示します。
リクエストがIISに到達したとき
ユーザーがリクエストを送信してIISがそれを受け取るとIISは拡張子に基づいて適切なDLLに処理を委譲します。 例えば.aspxという拡張子のリクエストを受信したときにはそのリクエストはaspnet_isapi.dllにリクエストが委譲され処理されます。
AppDomainの作成
受信したリクエストがそのWEBサイトにとって初めてのリクエストのときはApplicationManagerクラスによってWEBサイトを実行するための AppDomainが作成されます。ご存知のようにAppDomainはWEBアプリケーションごとに分離して作成されます。 結果としてあるWEBアプリケーションがクラッシュしても他のアプリケーションには影響しないようになっています。
ホストする環境の作成
新しく作成されたAppDomainはHttpRuntimeという環境をホストするためのオブジェクトを作成します。 またHttpContext,HttpRequest,HttpResponseといったASP.NETで不可欠なオブジェクトも生成されます。
HttpApplicationの作成
ASP.NETで必要な各種のオブジェクトが作成されたあとにHttpApplicationが作成され、リクエストを処理していきます。 もしあなたが作成したWEBアプリケーションにGlobal.asaxファイルがあるならばGlobalクラスはHttpApplicationを継承しているので Globalクラスが使用されます。HttpApplicationは新たに作成されることもあればプールから再利用されることもあります。
HttpApplicationの処理の開始
作成されたHttpApplicationにASP.NETの各オブジェクトがセットされてリクエストに対するレスポンスの作成処理が実行されます。 HttpApplicationはHttpModuleの各イベントを発生させた後、適切なHttpHandlerに処理を委譲します。
IHttpHandlerFactoryによるHttpHandlerの作成
IHttpHandlerFactoryによってそれぞれの拡張子やGET,POSTなどに応じて適切なHttpHandlerが作成されて処理が実行されます。
HttpApplication
HttpApplicationの指定はGloabl.asaxファイルで行います。Global.asaxファイルの中身は以下のようになっています。
<%@ Application CodeBehind="Global.aspx.cs" Inherits="MyApp1.Global" Language="C#" %>
Globalクラスは以下のようなかたちになりHttpApplicationを継承しているのがわかります。
public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        //アプリケーションの初期化時に1回だけ実行されるメソッド
    }
    protected void Application_Error(object sender, EventArgs e)
    {
        //アプリケーションで補足されなかったエラーが実行されるメソッド
    }
    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        //リクエストの開始時に実行されるメソッド
    }
    protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {
        //認証時に実行されるメソッド
    }
}
HttpApplicationオブジェクト(Globalオブジェクト)は毎リクエストごとに生成されてそれぞれのメソッドが実行されます。 全てのメソッドが毎リクエストごとに呼ばれるわけではなく中には特定の条件のときだけ呼び出されるメソッドもあります。 以下のメソッドがそのメソッドです。 上記のメソッドの特性はHttpModuleでは実現できないのでHttpApplicationを継承したクラスを使用することになります。
"Application_イベント名"で作成したメソッドは後述のHttpModuleのイベントのタイミングで呼び出され実行されます。 これらのメソッドは毎リクエストごとに実行されることになります。 これらの処理はHttpModuleで実行することも可能です。
HttpModule
HttpModuleが発生させるイベントには以下のようなものがあり表に並べている順にイベントが発生します。 以下のイベントは全てのリクエストごとに毎回発生します。
BeginRequest リクエストを受信して最初に発生するイベントです。
AuthenticateRequest ASP.NETがユーザーの認証処理の準備が完了したときに発生します。
このイベントを使用してユーザーの認証処理をカスタマイズすることが可能です。
AuthorizeRequest 権限の承認処理の準備が完了したときに発生します。
このイベントを使用してユーザーへの権限の承認処理をカスタマイズすることが可能です。
ResolveRequestCache リクエストに対してキャッシュからレスポンスを生成するのかレスポンスを1から生成するのかを決定します。
このイベントを使用してキャッシュの振る舞いをカスタマイズすることが可能です。
AcquireRequestState セッション変数の準備処理を行います。
このイベントを使用して独自のセッション変数の機構を組み込むことが可能です。
PreRequestHandlerExecute 各ハンドラーへ処理を委譲する直前に発生します。
ハンドラーの実行直前に何かしらの処理をしたい場合はこのイベントを使用します。
PostRequestHandlerExecute 各ハンドラーへ処理の実行直後に発生します。
ハンドラーの実行直後に何かしらの処理をしたい場合はこのイベントを使用します。
ReleaserequestState セッション変数などの値を更新・保存するためのイベントです。
UpdateRequestCache リクエストキャッシュの更新処理を行うためのイベントです。
EndRequest クライアントのブラウザへデータを送信する直前に発生します。
自分用のHttpModuleを作成例としては以下のようになります。
public class MyHttpModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        var cx = context;

        cx.BeginRequest += this.ValidateDosAttack;
        cx.PreRequestHandlerExecute += this.RegisterHandlerStartTime;
        cx.PostRequestHandlerExecute += this.RegisterHandlerEndTime;
    }
    void ValidateDosAttack(object sender, EventArgs a)
    {
        //DOS攻撃かどうかを検証
    }
    void RegisterHandlerStartTime(object sender, EventArgs a)
    {
        //ハンドラーの実行開始時刻を記録
    }
    void RegisterHandlerEndTime(object sender, EventArgs a)
    {
        //ハンドラーの実行終了時刻を記録
    }
}
HttpModuleのイベントの登録は上記で示したデリゲートで登録する方法と特定の規約に従ってメソッド名をつける方法があります。 "Onイベント名"というかたちでメソッドを作成するとそのメソッドは対応するイベントのときに自動で呼ばれます。
public class MyHttpModule : IHttpModule
{
    void OnBeginRequest(object sender, EventArgs a)
    {
        //DOS攻撃かどうかを検証
    }
    void OnPreRequestHandlerExecute(object sender, EventArgs a)
    {
        //ハンドラーの実行開始時刻を記録
    }
    void OnPostRequestHandlerExecute(object sender, EventArgs a)
    {
        //ハンドラーの実行終了時刻を記録
    }
}
作成したHttpModuleをweb.configで登録します。
<configuration>
    <system.web>
      <httpModules>
        <add name="MyHttpModule" type="MyHttpModule" />
      </httpModules>
    </system.web>
</configuration>
これで任意のイベントのタイミングで独自の処理を組み込むことが可能です。一般的に組み込む処理としては などがあげられます。HttpApplicationではなくHttpModuleで独自の処理を記述することにより 異なるWEBサイト間で処理を共通化してプログラムを再利用することが可能です。
Machine.configファイルには既定のHttpModuleが登録されています。これらのHttpModuleは全てのWEBサイトで実行されることになります。 不要なものがあれば解除しておくのも良いでしょう。
<httpModules> 
    <add name="OutputCache" type="System.Web.Caching.OutputCacheModule"/>
    <add name="Session" type="System.Web.SessionState.SessionStateModule"/>
    <add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule"/>
    <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule"/>
    <add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule"/>
    <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule"/>
    <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule"/>
    <add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, 
        System.Web.Mobile, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
</httpModules>
HttpModuleを使用することで全てのリクエストに対して特定のタイミングで処理を実行することが可能です。
HttpHandler
処理を分岐するための仕組みとしてHttpHandlerがあります。 拡張子とHttpメソッド(GET、POST、DELETE…など)で使用するハンドラーを分岐可能です。 machine.configには既定のHttpHandlerが登録されています。
<httpHandlers>
    <add verb="*" path="*.vjsproj" type="System.Web.HttpForbiddenHandler"/>
    <add verb="*" path="*.java" type="System.Web.HttpForbiddenHandler"/>
    <add verb="*" path="*.jsl" type="System.Web.HttpForbiddenHandler"/>
    <add verb="*" path="trace.axd" type="System.Web.Handlers.TraceHandler"/>
    <add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>
    <add verb="*" path="*.ashx" type="System.Web.UI.SimpleHandlerFactory"/>
    <add verb="*" path="*.asmx" type="System.Web.Services.Protocols.WebServiceHandlerFactory, 
        System.Web.Services, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" validate="false"/>
    <add verb="*" path="*.rem" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, 
        System.Runtime.Remoting, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false"/>
    <add verb="*" path="*.soap" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, 
        System.Runtime.Remoting, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" validate="false"/>
    <add verb="*" path="*.asax" type="System.Web.HttpForbiddenHandler"/>
    <add verb="*" path="*.ascx" type="System.Web.HttpForbiddenHandler"/>
    <add verb="GET,HEAD" path="*.dll.config" type="System.Web.StaticFileHandler"/>
    <add verb="GET,HEAD" path="*.exe.config" type="System.Web.StaticFileHandler"/>
    <add verb="*" path="*.config" type="System.Web.HttpForbiddenHandler"/>
    <add verb="*" path="*.cs" type="System.Web.HttpForbiddenHandler"/>
    <add verb="*" path="*.csproj" type="System.Web.HttpForbiddenHandler"/>
    <add verb="*" path="*.vb" type="System.Web.HttpForbiddenHandler"/>
    <add verb="*" path="*.vbproj" type="System.Web.HttpForbiddenHandler"/>
    <add verb="*" path="*.webinfo" type="System.Web.HttpForbiddenHandler"/>
    <add verb="*" path="*.asp" type="System.Web.HttpForbiddenHandler"/>
    <add verb="*" path="*.licx" type="System.Web.HttpForbiddenHandler"/>
    <add verb="*" path="*.resx" type="System.Web.HttpForbiddenHandler"/>
    <add verb="*" path="*.resources" type="System.Web.HttpForbiddenHandler"/>
    <add verb="GET,HEAD" path="*" type="System.Web.StaticFileHandler"/>
    <add verb="*" path="*" type="System.Web.HttpMethodNotAllowedHandler"/>
</httpHandlers>
例えばMyPage1.aspxへのリクエストが来た場合、System.Web.UI.PageHandlerFactoryによってハンドラーが生成され 処理を行います。画像などの静的ファイルの場合はSystem.Web.StaticFileHandlerが処理を実行します。 閲覧させたくないファイルに対してはSystem.Web.HttpForbiddenHandlerが設定されておりこれによって.cs、.csprojなどの リクエストが来たとしても無視するようになっています。
例えば画像をDBから取得してクライアントに送信する独自のハンドラーを作成してみましょう。 まずは以下のようにIHttpHandlerインターフェースを実装する以下のようなクラスを定義します。 (※実際にはSQLインジェクションされないようにもうひと工夫する必要があります。)
<%@ WebHandler Language="C#" Class="ImageHandler" %> 

using System;
using System.Web;
using System.Configuration;
using System.Data.SqlClient;

namespace MyApp1
{     
    public class ImageHandler : IHttpHandler 
    {
        public void ProcessRequest (HttpContext context) 
        { 
            string imageid = context.Request.QueryString["ImageID"];
            using (SqlConnection cn = new SqlConnection("..."))
            {
                connection.Open();
                SqlCommand command = new SqlCommand("select ImageData from ImageTable where ImageID=" + imageid, cn);
                SqlDataReader dr = command.ExecuteReader();
                dr.Read();
                context.Response.BinaryWrite((Byte[])dr[0]);
                connection.Close();
            }
            context.Response.End();              
        }
        public bool IsReusable 
        { 
            get { return false; } 
        }
    }
}
独自のハンドラを作成するにはIHttpHandlerインターフェースを実装してProcessRequestメソッドとIsReusableプロパティを 作成するかたちになります。 すると以下のようなURLでアクセスすると画像のダウンロードができるようになります。
http://www.myapp1.com/ImageHandler.ashx?ImageID=001
上記のURLだと拡張子が.ashxなのでSystem.Web.UI.SimpleHandlerFactoryクラスがどのハンドラを生成するのか判断することになります。 リクエストのURLを元にMyApp1.ImageHandlerを使用すると判断し、ImageHandlerオブジェクトを生成して処理を行う形になります。
Create at 2013/02/13 LastUpdate 2013/02/14