リダイレクトをキャンセルするには、レスポンスの出力バッファをクリアして、HTTP ステータスコードに OK ( 200 ) を設定します。
HttpContext context = HttpContext.Current;
context.Response.ClearContent();
context.Response.RedirectLocation = null;
const int HTTP_STATUS_OK = 200;
context.Response.StatusCode = HTTP_STATUS_OK;
RedirectLocation プロパティは別にいじらなくてもいいんですが、どうせだから null にしちゃってます。
[ 参考 ]
HttpResponse.StatusCode プロパティ (System.Web)
HTTP Status Codes (Windows)
例えば "http://hoge:8000/fuga/" に配置された ASP.NET アプリケーション内では、"~/Piyo.aspx" は "/fuga/Piyo.aspx" に変換されます。
以下のコードは、現在のディレクトリ階層に関わらずルートディレクトリの Piyo.aspx へリダイレクトします。
HttpContext.Current.Response.Redirect("~/Piyo.aspx");
ただし、この "~" は、 ASP.NET が解釈して変換しているだけであり、ブラウザが解釈できるわけではありません。ブラウザに "~" から始まるパスが渡されたところでブラウザは単に現在のアドレスを基準とした相対パスとして解釈してしまいます。
だから、ASP.NET がパスを変換してくれないものに対して "~" は使えません。
例えば、ASPX ファイルに記述された [ a ] タグは、基本的にそのままクライアントに送られますので、
<a href="~/Foo.aspx">Foo</a>
なんて書いても、ブラウザにはこのリンクが "http://hoge:8000/fuga/~/Foo.aspx" と解釈されてしまいます。こういう場合は大人しく相対パスで記述しましょう。
余談ですが、ASP.NET アプリケーションのルートディレクトリは HttpRuntime.AppDomainAppVirtualPath プロパティ (System.Web) で取得することができます。最初の例で言えば、このプロパティから "/fuga" という値が取得できます。
※ 注意 ※
モバイルページでセッションステートを Cookie なしで使用する場合は問題が発生する可能性があるそうです。詳しくは [ 参考 ] のリンク先ページを読んでください。
[ 参考 ]
VirtualPathUtility クラス (System.Web)
ASP.NET Web サイトのパス
CodeZine に「ASP.NETのセッションをタイプセーフに取り扱うクラスの作成」という記事が載ってましたが、この方法は僕的にちょっと馴染めなかったもので。
以下、現時点での考えをメモしときます。
セッションステートに保存するオブジェクトは、セッションステートで管理されることを目的として定義されたクラスだけにします。
だから、セッションステートの項目キーはクラスの完全修飾名でいいと考えています。
文字列とかドメインオブジェクト、データセットなんかを直接保存したりはしません。
まず、クラスの完全修飾名を使ってセッションステートを利用するための補助を行う静的クラスを定義しておきます。
項目が見つからなかったり、セッションステートが使えない時 ( IRequiresSessionState インターフェイスを実装していない HTTP ハンドラからの呼び出し等 ) には例外を発生させるようにもします。
SessionStateAdapter クラス
using System;
using System.Web;
using System.Web.SessionState;
/// <summary>
/// 型の完全修飾名を項目キーとしてセッションステートを利用するアダプターです。
/// </summary>
/// <typeparam name="T">セッションステートに保存するインスタンスの型。この型の完全修飾名がセッションステートの項目キーになります。</typeparam>
public static class SessionStateAdapter<T>
{
#region public static T GetItem()
/// <summary>
/// ジェネリックパラメータ T のインスタンスを現在のセッションステートから取得します。
/// </summary>
/// <returns>ジェネリックパラメータ T のインスタンス</returns>
/// <exception cref="NotFoundSessionItemException">指定した項目がセッションステート内に存在しません。</exception>
/// <exception cref="System.InvalidOperationException">セッションステートが利用できません。</exception>
public static T GetItem()
{
HttpSessionState currentSession = SessionStateAdapter<T>.GetSessionState();
object result = currentSession[typeof(T).FullName];
if ((result == null) || !(result is T))
{
throw new NotFoundSessionItemException(typeof(T).FullName);
}
return (T)result;
}
#endregion
#region public static void SetItemt(T target)
/// <summary>
/// ジェネリックパラメータ T のインスタンスを現在のセッションステートに設定します。
/// </summary>
/// <param name="target">ジェネリックパラメータ T のインスタンス。</param>
/// <exception cref="System.InvalidOperationException">セッションステートが利用できません。</exception>
/// <exception cref="System.InvalidOperationException">セッションステートが読み取り専用です。</exception>
/// <exception cref="System.ArgumentNullException">引数 target が null です。</exception>
public static void SetItem(T target)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
HttpSessionState currentSession = SessionStateAdapter<T>.GetSessionState();
if (currentSession.IsReadOnly)
{
throw SessionStateAdapter<T>.CreateExceptionForSessionStateIsReadOnly();
}
currentSession[typeof(T).FullName] = target;
}
#endregion
#region public static void RemoveItem()
/// <summary>
/// ジェネリックパラメータ T のインスタンスを現在のセッションステートから削除します。
/// </summary>
/// <exception cref="System.InvalidOperationException">セッションステートが利用できません。</exception>
/// <exception cref="System.InvalidOperationException">セッションステートが読み取り専用です。</exception>
public static void RemoveItem()
{
HttpSessionState currentSession = SessionStateAdapter<T>.GetSessionState();
if (currentSession.IsReadOnly)
{
throw SessionStateAdapter<T>.CreateExceptionForSessionStateIsReadOnly();
}
currentSession.Remove(typeof(T).FullName);
}
#endregion
#region public static bool ItemIsExists()
/// <summary>
/// ジェネリックパラメータ T のインスタンスが現在のセッションステートに存在するかどうかを表す値を返します。
/// </summary>
/// <returns>ジェネリックパラメータ T のインスタンスが現在のセッションステートに存在するかどうかを表す値。</returns>
/// <exception cref="System.InvalidOperationException">セッションステートが利用できません。</exception>
public static bool ItemIsExists()
{
HttpSessionState currentSession = SessionStateAdapter<T>.GetSessionState();
return (currentSession[typeof(T).FullName] is T);
}
#endregion
#region private static HttpSessionState GetSessionState()
/// <summary>
/// 現在のセッションステートを取得します。
/// </summary>
/// <returns>現在のセッションステート。</returns>
/// <exception cref="System.InvalidOperationException">セッションステートが利用できません。</exception>
private static HttpSessionState GetSessionState()
{
HttpContext currentContext = HttpContext.Current;
HttpSessionState currentSession;
try
{
currentSession = currentContext.Session;
}
catch (Exception ex)
{
throw new InvalidOperationException("セッションステートが利用できません。", ex);
}
if (currentSession == null)
{
throw new InvalidOperationException("セッションステートが利用できません。");
}
return currentSession;
}
#endregion
#region private static Exception CreateExceptionForSessionStateIsReadOnly()
/// <summary>
/// セッションステートが読み取り専用であることを示す例外を生成します。
/// </summary>
/// <returns>セッションステートが読み取り専用であることを示す例外。</returns>
private static Exception CreateExceptionForSessionStateIsReadOnly()
{
return new InvalidOperationException("セッションステートが読み取り専用です。");
}
#endregion
}
セッションステート内に項目が見つからなかった場合にスローされるNotFoundSessionItemException クラスは以下のコードになります。
追加情報として項目のキーを持つことができるようにしてます。
NotFoundSessionItemException クラス
using System;
using System.Runtime.Serialization;
using System.Security.Permissions;
/// <summary>
/// セッションステート内に特定の項目が見つからないことを表す例外。
/// </summary>
[Serializable]
public class NotFoundSessionItemException : Exception
{
#region Fields
#region private readonly string _key
/// <summary>
/// 例外の原因となった項目キーを取得します。
/// </summary>
private readonly string _key;
#endregion
#endregion
#region Properties
#region public string Key
/// <summary>
/// 例外の原因となった項目キーを取得します。
/// </summary>
public string Key
{
get
{
return this._key;
}
}
#endregion
#endregion
#region Constructors
#region public NotFoundSessionItemException()
/// <summary>
/// NotFoundSessionItemException クラスの新しいインスタンスを初期化します。
/// </summary>
public NotFoundSessionItemException()
: this(null)
{
}
#endregion
#region public NotFoundSessionItemException(string key)
/// <summary>
/// この例外の原因である項目のキーを指定して、NotFoundSessionItemException クラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="key">例外の原因となった項目キー。</param>
public NotFoundSessionItemException(string key)
: this(key, "指定した項目がセッションステート内に存在しません。")
{
}
#endregion
#region public NotFoundSessionItemException(string key, string message)
/// <summary>
/// エラー メッセージ、およびこの例外の原因である項目のキーを指定して、NotFoundSessionItemException クラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="key">例外の原因となった項目キー。</param>
/// <param name="message">エラーを説明するメッセージ。</param>
public NotFoundSessionItemException(string key, string message)
: this(key, message, null)
{
}
#endregion
#region public NotFoundSessionItemException(string key, string message, Exception innerException)
/// <summary>
/// エラー メッセージ、項目キー、およびこの例外の原因である内部例外への参照を使用して、NotFoundSessionItemException クラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="key">例外の原因となった項目キー。</param>
/// <param name="message">例外の原因を説明するエラー メッセージ。</param>
/// <param name="innerException">現在の例外の原因である例外。内部例外が指定されていない場合は、null 参照 (Visual Basic の場合は Nothing)。</param>
public NotFoundSessionItemException(string key, string message, Exception innerException)
: base(message, innerException)
{
this._key = key;
}
#endregion
#region protected NotFoundSessionItemException(SerializationInfo info, StreamingContext context)
/// <summary>
/// シリアル化したデータを使用して、NotFoundSessionItemException クラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="context">転送元または転送先に関するコンテキスト情報を含んでいる System.Runtime.Serialization.StreamingContext。</param>
/// <param name="info">スローされている例外に関するシリアル化済みオブジェクト データを保持している System.Runtime.Serialization.SerializationInfo。</param>
/// <exception cref="System.Runtime.Serialization.SerializationException">クラス名が null であるか、または System.Exception.HResult が 0 です。</exception>
/// <exception cref="System.ArgumentNullException">info パラメータが null です。</exception>
protected NotFoundSessionItemException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
this._key = info.GetString("key");
}
#endregion
#endregion
#region Methods
#region public override void GetObjectData(SerializationInfo info, StreamingContext context)
/// <summary>
/// パラメータ名と追加の例外情報を使用して System.Runtime.Serialization.SerializationInfo オブジェクトを設定します。
/// </summary>
/// <param name="context">転送元または転送先に関するコンテキスト情報。</param>
/// <param name="info">シリアル化されたオブジェクト データを保持するオブジェクト。</param>
/// <exception cref="System.ArgumentNullException">info オブジェクトが null 参照 (Visual Basic の場合は Nothing) です。</exception>
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
base.GetObjectData(info, context);
// 追加の例外情報がある場合、ここでパラメータ名と例外情報を info に追加します。
info.AddValue("key", this._key, typeof(string));
}
#endregion
#endregion
}
ここからが、肝心の「セッションステートで管理されることを目的として定義されたクラス」になります。
業務アプリケーションは大抵の場合、情報の検索だとか、情報の登録、物品の貸出等の複数の機能を持っています。
ここでは、これら一つ一つを "業務" と呼びます。(こうやって呼ぶのって一般的なのかな?)
僕は、画面にビジネスロジックをできるだけ持ち込まないために、ファサードパターンを適用して各業務毎にクラスを用意します。
例えば、ほげ業務には HogeBusiness クラス、ふが業務には FugaBusiness クラスを用意します。
そして、この業務クラスをセッションに保存しておき、複数画面に渡って業務を行います。
そこで、BusinessManager という名前の「セッションステートで管理されることを目的として定義されたクラス」を一つ用意し、そのクラスのフィールドで全ての業務を管理します。
BusinessManager は、Current という読み取り専用のスタティックプロパティを持ちます。
この Current プロパティは、セッションステートを使用してシングルトンパターンのような動きをします。
BusinessManager クラス
using System;
/// <summary>
/// 業務を管理します。
/// </summary>
[Serializable]
public class BusinessManager
{
#region Static Members
#region public static BusinessManager Current
/// <summary>
/// 現在のセッションにて管理されている BusinessManager オブジェクトを取得します。
/// </summary>
/// <exception cref="System.InvalidOperationException">セッションステートが利用できません。</exception>
/// <exception cref="System.InvalidOperationException">セッションステートが読み取り専用です。</exception>
public static BusinessManager Current
{
get
{
BusinessManager currentBusinessManager;
bool isExists = SessionStateAdapter<BusinessManager>.ItemIsExists();
if (isExists)
{
currentBusinessManager = SessionStateAdapter<BusinessManager>.GetItem();
}
else
{
currentBusinessManager = new BusinessManager();
SessionStateAdapter<BusinessManager>.SetItem(currentBusinessManager);
}
return currentBusinessManager;
}
}
#endregion
#endregion
#region Fields
#region private HogeBusiness _hogeBusiness
/// <summary>
/// ほげ業務を取得または設定します。
/// </summary>
private HogeBusiness _hogeBusiness;
#endregion
#region private FugaBusiness _fugaBusiness
/// <summary>
/// ふが業務を取得または設定します。
/// </summary>
private FugaBusiness _fugaBusiness;
#endregion
#endregion
#region Properties
#region public HogeBusiness HogeBusiness
/// <summary>
/// ほげ業務を取得します。
/// </summary>
/// <exception cref="System.InvalidOperationException">ほげ業務が開始されていません。</exception>
public HogeBusiness HogeBusiness
{
get
{
if (this._hogeBusiness == null)
{
throw this.CreateExceptionForNotStartedBusiness("ほげ");
}
return this._hogeBusiness;
}
}
#endregion
#region public FugaBusiness FugaBusiness
/// <summary>
/// ふが業務を取得します。
/// </summary>
/// <exception cref="System.InvalidOperationException">ふが業務が開始されていません。</exception>
public FugaBusiness FugaBusiness
{
get
{
if (this._fugaBusiness == null)
{
throw this.CreateExceptionForNotStartedBusiness("ふが");
}
return this._fugaBusiness;
}
}
#endregion
#endregion
#region Constructors
#region private BusinessManager()
/// <summary>
/// BusinessManager クラスの新しいインスタンスを初期化します。
/// </summary>
private BusinessManager()
{
this.InitializeFields();
}
#endregion
#endregion
#region Methods
#region public void StartHogeBusiness()
/// <summary>
/// ほげ業務を開始します。
/// </summary>
public void StartHogeBusiness()
{
this._hogeBusiness = new HogeBusiness();
}
#endregion
#region public void EndHogeBusiness()
/// <summary>
/// ほげ業務を終了します。
/// </summary>
/// <returns></returns>
public void EndHogeBusiness()
{
this._hogeBusiness = null;
}
#endregion
#region public void StartFugaBusiness()
/// <summary>
/// ふが業務を開始します。
/// </summary>
public void StartFugaBusiness()
{
this._fugaBusiness = new FugaBusiness();
}
#endregion
#region public void EndFugaBusiness()
/// <summary>
/// ふが業務を終了します。
/// </summary>
/// <returns></returns>
public void EndFugaBusiness()
{
this._fugaBusiness = null;
}
#endregion
#region public void EndAllBusiness()
/// <summary>
/// 全ての業務を終了します。
/// </summary>
public void EndAllBusiness()
{
this.InitializeFields();
}
#endregion
#region private void InitializeFields()
/// <summary>
/// フィールドを初期化します。
/// </summary>
private void InitializeFields()
{
this._hogeBusiness = null;
this._fugaBusiness = null;
}
#endregion
#region private void CreateExceptionForNotStartedBusiness(string businessName)
/// <summary>
/// 業務が開始されていないことを表す例外を生成します。
/// </summary>
/// <param name="businessName">業務名。</param>
private Exception CreateExceptionForNotStartedBusiness(string businessName)
{
string message = string.Format("{0}業務は開始されていません。", businessName);
return new InvalidOperationException(message);
}
#endregion
#endregion
}
セッションには BusinessManager のインスタンスが一つだけ格納され、そのインスタンスの各フィールドで業務クラスが管理されます。
セッションで扱う情報は業務クラス以外にもありますので、それらにはまた別の「セッションステートで管理されることを目的として定義されたクラス」を用意します。
それらもまた、Current という読み取り専用のスタティックプロパティを定義し、セッションステートを使用したシングルトンパターンのように実装します。
とりあえず、今の考えはこんな感じです。
他にも、「セッションステートで管理されることを目的として定義されたクラス」を各業務ごとに用意するという方法も考えていて、それはまた少し異なる仕組みとなります ( シングルトンパターンではなくなります ) 。
// コード修正履歴
2007/09/17
・セッションステートが読み取り専用の時は、SessionStateAdapter<T>.SetItem(T) メソッド、SessionStateAdapter<T>.RemoveItem(T) メソッドが例外をスローするよう修正。
セッションの格納方法別に挙動をまとめてみました。 ( SQL Server モードは未検証ですが。。。 )
[ In Process モード ]
未処理例外発生時の HTTP リクエスト処理より以前にセッションデータが格納されていた場合、セッションはクリアされません。
未処理例外発生時の HTTP リクエスト処理より以前にセッションデータが格納されていなかった場合、未処理例外発生時の HTTP リクエスト処理中に格納されたセッションは、未処理例外発生時の HTTP リクエスト処理が終了した時点でクリアされます。
[ State Server モード ]
未処理例外発生時の HTTP リクエスト処理中に格納されたセッションデータは、未処理例外発生時の HTTP リクエスト処理が終了すると無効化され、未処理例外発生時の HTTP リクエスト処理以前の状態が復元されます。 ( 新しくキーを生成した場合は削除され、既存のキーに対してなんらかの処理を行った場合は、その処理が全て取り消される。 )
[ SQL Server モード ]
検証してません。 ( State Server モードと同じ動作な予感。 )
どのみち、Global.asax の Error イベントハンドラでリダイレクトすればセッションは保持されます。Global.asax で ClearError メソッドを呼び出すことで例外が「処理」されるため、上記動作が作動しないということですね。
# この検証をしている時に ClearError メソッドを呼び忘れて検証していたため、「Global.asax でリダイレクトさせても同様の現象が発生する!」って内容の記事を30分程公開していたのは内緒 ^^; もしその時に記事を読んでしまった方がいらっしゃいましたら、本当にすみませんでした m( _ _;)m
Web.config で上記のようにして defaultRedirect を設定した場合、未処理の例外が発生したら自作エラーページである Error.aspx にリダイレクトされます。
僕の場合は、今まで Global.asax の Error イベントハンドラ ( Application_Error メソッド ) で自作エラーページへのリダイレクト処理を行っていました。なので気づかなかったんですが、Web.config に設定した defaultRedirect によってエラーページに遷移した場合、セッションがクリアされます。
Global.asax の Error イベントハンドラでリダイレクトさせるより defaultRedirect でリダイレクトさせた方がいいんじゃないか?と疑問に思って ( ※ ) 検証していたら、これに気づきました。いや、気づくまでしばらくハマってました。
今後も Global.asax の Error イベントハンドラでリダイレクトしていこうと心に決めましたね。
※ defaultRedirect でリダイレクトさせる場合、mode 属性に RemoteOnly 指定するだけで簡単に開発者向けのエラーページを表示するように切り替えられるからです。
[ 関連記事 ]
未処理例外発生時のセッションの動作
このアカウントはファイル・ディレクトリへのアクセス権などが制限されていて、ログファイルを出力することすらできません。
これを解決するには、対象のファイル・ディレクトリへ適切な権限を付加する必要があります。
インストーラで、この権限付加の処理を行いたい場合、インストーラのカスタム動作でこれを行います。
以下に例を示します。
//using System;
//using System.Configuration.Install;
//using System.IO;
//using System.Security.AccessControl;
[RunInstaller(true)]
public partial class CustomInstaller : Installer
{
public CustomInstaller()
{
InitializeComponent();
}
public override void Install(System.Collections.IDictionary stateSaver)
{
string assemblyPathName = this.Context.Parameters["assemblypath"];
int fileDivisionIndex = assemblyPathName.LastIndexOf('\\');
string installDirectoryPathName = assemblyPathName.Remove(fileDivisionIndex);
DirectoryInfo installDirectoryInfo = new DirectoryInfo(installDirectoryPathName);
// インストール先ディレクトリの ACL ( アクセス制御リスト ) を取得
DirectorySecurity installDirectoryACL = installDirectoryInfo.GetAccessControl();
// Network Service アカウントの書き込み権限を表すオブジェクトを生成
FileSystemAccessRule writingAuthorityOfWorkerProcess = new FileSystemAccessRule("NT AUTHORITY\\NETWORK SERVICE", FileSystemRights.Write, AccessControlType.Allow);
// ACL に、Network Service アカウントの書き込み権限を追加する
installDirectoryACL.AddAccessRule(writingAuthorityOfWorkerProcess);
// インストール先ディレクトリの ACL を設定
installDirectoryInfo.SetAccessControl(installDirectoryACL);
base.Install(stateSaver);
}
}
この例では、インストール先ディレクトリに対して、Network Serviceアカウントが書き込みを行うための権限を付加しています。
これにより、Web アプリがインストール先ディレクトリ内にログファイルを出力することが可能となります。
インストーラ、カスタム動作自体の説明はここでは省略します。これについては以下のチュートリアルを参考にしてください。
Windows インストーラでの配置に関するチュートリアル
プロジェクトファイル開いて [ Project ] - [ PropertyGroup ] セクションに [ DocumentationFile ] セクションを追加してその中にファイル名指定してるのに出力されない orz
ファイル名の指定方法も色々 ( 絶対パスやら相対パスやら何やら ) 試したけど出力されない orz
Web.config に [ system.codedom ] - [ compilers ] - [ compiler ] セクションを追加して /doc:file オプションを指定してやる場合は出力されるのに orz
いや、まぁこれで充分なんだけど・・・。
orz
Visual Studio 2005 Web Deployment Projects
これは中々便利かもしれません。
以下、機能の概要をホワイトペーパーから抜粋します。
・ビルド プロセスの一部としての ASP.NET 2.0 プリコンパイル
・Web プロジェクトからコンパイル済みのアセンブリを生成する際の柔軟なオプション。次の選択肢があります。
・Web サイト全体に対して単一のアセンブリを生成する。
・コンテンツ フォルダごとに 1 つのアセンブリを生成する。
・すべての UI コンポーネントに対して単一のアセンブリを生成する。
・Web サイト内のコンパイル済みファイルごとに 1 つのアセンブリを生成する。
・アセンブリの署名オプション
・カスタムのビルド前アクションおよびビルド後アクションの定義
・ビルドからのフォルダの除外
・Visual Studio のビルド構成に基づいた、Web.config ファイル (<connectionString> 要素など) の設定の変更
・セットアップ プロジェクトでの .msi ファイルの作成のサポート
執筆はMicrosoft デベロッパーエバンジェリストの松崎剛さんです。
ASP.NET AJAXを理解する − @IT
「.NET開発者への道 ~ Web開発編」を2月5日(月)に開始
これに伴って、ASP.NET Developer Centerがリニューアルされました。
ASP.NET Developer Center
さらに、MSDNフォーラムにASP.NETのフォーラムが開設されました。
ASP.NET - MSDNフォーラム
[ 情報元 ]
Hiroyasu Kitagawa's Blog : .NET開発者への道 ~ Web開発編
「.NET開発者への道〜Web開発編」が本日からスタート。



