ASP.NET コード内で別ページにリダイレクトする場合の留意点

SPECIAL


System.Threading.ThreadAbortException

ASP.NET で Response.Redirect メソッドを実行した際に、次のエラーが Visual Studio の出力ウィンドウに表示される場合があります。

'System.Threading.ThreadAbortException' の初回例外が mscorlib.dll で発生しました。

型 'System.Threading.ThreadAbortException' の例外が mscorlib.dll で発生しましたが、ユーザー コード内ではハンドルされませんでした

これは、Response.Redirect 実行時に Response.End メソッドが実行されてしまうのが原因だそうです。他にも Response.End メソッドや Server.Transfer メソッドを実行した場合にも同様の例外エラーが発生するとのことです。詳しい事は分らないのですが、これらのメソッドでは内部で Response.End メソッドが実行され、それ以降のコードが実行されなくなるために、このようなエラーに繋がるような感じになるようです。

 

これを回避するためには、Response.Redirect(url) という表記を、次のように修正します。

Response.Redirect(url, false)

第二引数に false を指定した Response.Redirect を呼ぶことで、Response.End が実行されなくなり、例外エラーが発生しなくなるとのことでした。ただし、それ以降のコードも処理の対象となるので注意が必要です。

 

Server.Transfer メソッドを使用している場合は、次のように修正します。

Server.Execute(aspfile)

Server.Transfer メソッドというのは、別の ASP, ASPX に処理を移行し、元のページの以降の処理は捨てるといったもののようです。Server.Transfer の前に Response.Clear 命令を呼ぶことで Response.Redirect に動作が似てくるものの、変数の状態などの環境はそのまま引き継がれるため、Response.Redirect のサーバー版を期待して利用するものではなさそうです。

こちらも、Server.Execute に変更することで、それ以降のコードも処理の対象となるので注意が必要です。

 

 Response.End の場合は、次のように置き換えれば良いそうです。

HttpContext.Current.ApplicationInstance.CompleteRequest

このようにすることで、OnEndRequest に制御が移るとのことでした。但し、以降のコードも処理の対象となるので注意が必要です。

 

コードを修正する際の注意

重ねてになりますが、上記のように修正を行う際には、動作が大きく異なる命令に差し替えることになるので、十分注意が必要です。

Response.End を ASP ページ自体の break や Exit 処理的に利用しているコードを多く見るような気がしますが、そのような場合は、単純に上記に置き換えるだけでは、動作の不具合に繋がることになります。

もっとも個人的には、在る条件下の処理が終わった段階で処理を打ち切り、それ以外の条件の処理はそれ以降に記されているという書き方はプログラムの構造上、保守性の低下や制御不能を招く、間違った制御方法だと考えています。ですので、正しい流れでコードを制御をする上でも、今回のエラーは正しい修正を促すように思います。

 

考察

ところで ASP の頃なら Response.End なのだからそれで中断されても当たり前のような気もしますが、ASP.NET では、最後まで実行しないと行けないということになるのでしょうか。

ASP.NET の場合、処理の過程で自動的に呼び出されるメソッドが幾つも存在しているので、その呼び出しの流れを追って、実際の様子を確かめてみることにしました。

通常の場合

まず、何も制御の流れを変更しない場合です。

Application_OnBeginRequest

Application_OnAuthenticateRequest

Application_OnAuthorizeRequest

Application_OnResolveRequestCache

Session_Start

Application_OnAcquireRequestState

Application_OnPreRequestHandlerExecute

 

>> Page_Load: Enter

<< Page_Load: Leave

 

Application_OnPostRequestHandlerExecute

Application_OnReleaseRequestState

Application_OnUpdateRequestCache

Application_OnEndRequest

Application_OnPreSendRequestHeaders

Application_OnPreSendRequestContent

このような順でメソッドが呼び出されて行くような感じでした。

ページの処理は Application_OnPreRequestHandlerExecute と Application_OnPostRequestHandlerExecute の間で行われている感じです。

 

Response.End を使用した場合

ページ内で Response.End を実行した場合は、次のような感じになります。

Application_OnBeginRequest

Application_OnAuthenticateRequest

Application_OnAuthorizeRequest

Application_OnResolveRequestCache

Session_Start

Application_OnAcquireRequestState

Application_OnPreRequestHandlerExecute

 

>> Page_Load: Enter

 

*** System.Threading.ThreadAbortException ***

 

Application_OnEndRequest

Application_OnPreSendRequestHeaders

Application_OnPreSendRequestContent

Response.End が呼び出された後に System.Threading.ThreadAbortException 例外が発生しています。

その後、Application_OnPostRequestHandlerExecute, Application_OnReleaseRequestState, Application_OnUpdateRequestCache の 3 メソッドが呼び出されないまま、Application_OnEndRequest に制御が遷っている様子です。

 

CompleteRequest を使用した場合

ページ内の Response.End を HttpContext.Current.ApplicationInstance.CompleteRequest に置き換えてみます。

Application_OnBeginRequest

Application_OnAuthenticateRequest

Application_OnAuthorizeRequest

Application_OnResolveRequestCache

Session_Start

Application_OnAcquireRequestState

Application_OnPreRequestHandlerExecute

 

>> Page_Load: Enter

<< Page_Load: Leave

 

Application_OnEndRequest

Application_OnPreSendRequestHeaders

Application_OnPreSendRequestContent

この場合、確かにそれ以降のイベント処理がスキップされて Application_OnEndRequest に制御が遷っている様子が伺えますが、Response.End の対処法として Microsoft 社が掲げるには、いかにもサーバー側の都合といった感じにも思えます。なんとも、局所的なページ内の制御が継続するか断たれるかなど、どうでも良いと言いたそうな雰囲気です。

Server.Transfer を使用した場合

ついでに、Server.Transfer を使用した場合の流れについて記してみようと思います。

Application_OnBeginRequest

Application_OnAuthenticateRequest

Application_OnAuthorizeRequest

Application_OnResolveRequestCache

Session_Start

Application_OnAcquireRequestState

Application_OnPreRequestHandlerExecute

 

>> Page_Load: Enter (最初のページ)

>> Page_Load: Enter (Transfer で呼ばれたページ)

<< Page_Load: Leave (Transfer で呼ばれたページ)

 

*** System.Threading.ThreadAbortException ***

 

 

Application_OnEndRequest

Application_OnPreSendRequestHeaders

Application_OnPreSendRequestContent

ひと通りのメソッドが呼ばれてページ処理に遷った後、Server.Transfer が呼ばれたからといって、再度各種メソッドが呼ばれる訳ではないようです。また、読み込まれたページの処理が終わった後に例外エラーが発生しています。

Response.Redirect(url) を使用した場合

Response.Redirect を用いた場合は、次のような感じです。

Application_OnBeginRequest

Application_OnAuthenticateRequest

Application_OnAuthorizeRequest

Application_OnResolveRequestCache

Session_Start

Application_OnAcquireRequestState

Application_OnPreRequestHandlerExecute

 

>> Page_Load: Enter (呼び出されたページ)

 

*** System.Threading.ThreadAbortException ***

 

Application_OnEndRequest

Application_OnPreSendRequestHeaders

Application_OnPreSendRequestContent

Application_OnBeginRequest

Application_OnAuthenticateRequest

Application_OnAuthorizeRequest

Application_OnResolveRequestCache

Session_Start

Application_OnAcquireRequestState

Application_OnPreRequestHandlerExecute

 

>> Page_Load: Enter (リダイレクト先のページ)

<< Page_Load: Leave (リダイレクト先のページ)

 

Application_OnEndRequest

Application_OnPreSendRequestHeaders

Application_OnPreSendRequestContent

ページがそれぞれひとつずつ呼び出されたのと同じ感じ、といいますか、まさにそのような動作となります。ここから見ても、Server.Transfer と Response.Redirect の動作の違いが明らかに分かる感じがします。

Response.Redirect(url, false) を使用した場合

Response.Redirect(url, false) というように、第二引数に false を指定した形で呼び出してみると、次のような流れになりました。

Application_OnBeginRequest

Application_OnAuthenticateRequest

Application_OnAuthorizeRequest

Application_OnResolveRequestCache

Session_Start

Application_OnAcquireRequestState

Application_OnPreRequestHandlerExecute

 

>> Page_Load: Enter (呼び出されたページ)

<< Page_Load: Leave (呼び出されたページ)

 

Application_OnPostRequestHandlerExecute

Application_OnReleaseRequestState

Application_OnUpdateRequestCache

Application_OnEndRequest

Application_OnPreSendRequestHeaders

Application_OnPreSendRequestContent

Application_OnBeginRequest

Application_OnAuthenticateRequest

Application_OnAuthorizeRequest

Application_OnResolveRequestCache

Session_Start

Application_OnAcquireRequestState

Application_OnPreRequestHandlerExecute

 

>> Page_Load: Enter (リダイレクト先のページ)

<< Page_Load: Leave (リダイレクト先のページ)

 

Application_OnPostRequestHandlerExecute

Application_OnReleaseRequestState

Application_OnUpdateRequestCache

Application_OnEndRequest

Application_OnPreSendRequestHeaders

Application_OnPreSendRequestContent

これはまさに、通常の流れで正常に処理されたページが 2 つ、呼び出されたのと同じ流れです。Response.Redirect(url, false) 形式の場合、CompleteRequest を使用したときのような、イベント処理の打ち切りは行われない感じのようです。

Server.Execute を使用した場合

最後に、Server.Execute を使用した場合について見てみます。

Application_OnBeginRequest

Application_OnAuthenticateRequest

Application_OnAuthorizeRequest

Application_OnResolveRequestCache

Session_Start

Application_OnAcquireRequestState

Application_OnPreRequestHandlerExecute

 

>> Page_Load: Enter (呼び出し元のページ)

<< Page_Load: Leave (呼び出し元のページ)

>> Page_Load: Enter (Execute で指定したページ)

<< Page_Load: Leave (Execute で指定したページ)

 

Application_OnPostRequestHandlerExecute

Application_OnReleaseRequestState

Application_OnUpdateRequestCache

Application_OnEndRequest

Application_OnPreSendRequestHeaders

Application_OnPreSendRequestContent

この場合、ページ処理前のイベントが実行された後、ページ内に記載されたコードが順次実行され、最後にページ処理後のイベントが実行されるといった、とてもスマートな流れになりました。

 

このような形で、どれが旧来のコードの代役にふさわしいとかいうものではありませんけど、こういった制御の違いを考慮に入れながら、適切なコードと制御に直してみると良さそうです。