entlibの日記 このページをアンテナに追加 RSSフィード

2007-02-21

[] ViewStateをセッションに保存する

ASP.NETアプリケーションでは、ページの状態(プロパティや動的に作成されたコントロール)をViewStateに自動的に保持し、ポストバック時に復元することによって内容の復元や項目変更(イベント発生)の判断を行っている。

しかし、ViewStateに格納した内容はデフォルトではhidden要素としてHTMLに出力されるため、クライアント間のデータ量が増大し、特に帯域が限られている場合にはラウンドトリップ時間への影響が大きくなる。したがって、ViewStateに状態を格納せず、自前で状態の保持および復元を実装することが選択されることがままある。

これを避けるために、ASP.NET1.1までの場合であってもPage.SavePageStateToPersistenceMediumメソッドおよびPage.LoadPageStateFromPersistenceMediumメソッドを実装することによって独自のViewState保持方法を実装することができる。これにより、前述のViewStateによる状態の自動保持を生かしたままHTMLのサイズ増加を防ぐことができる。

protected override object LoadPageStateFromPersistenceMedium()
{
  return Session["ViewState"];
}

protected override void SavePageStateToPersistenceMedium(object state)
{
  Session["ViewState"] = state;
}

このくらいの実装で万事うまくいくのかというと、やはりそんなに簡単ではない。これだと、セッションが共有されている複数のウィンドウを扱う際に、ViewStateが上書きされてしまう。したがって、ページごとに一意なIDを生成して、セッションの読み書きを行う際のキー名はそのIDを使用するという対処が必要になる。この場合、そのIDをどこに保持するかも問題となるが、これは元々のViewStateを使用することになるのだろうか。

private Guid key;

protected override object LoadPageStateFromPersistenceMedium()
{
  Pair p = (Pair)base.LoadPageStateFromPersistenceMedium();
  key = new Guid((string)p.Second);

  return Session["ViewState" + key.ToString()];
}

protected override void SavePageStateToPersistenceMedium(object state)
{
  if (!IsPostBack)
    key = Guid.NewGuid();

  Session["ViewState" + key.ToString()] = state;
  base.SavePageStateToPersistenceMedium(new Pair(null, key.ToString()));
}

しかし、ASP.NET 2.0ではこれを行うためのクラスが標準で提供されている。

SessionPageStatePersisterクラスが、前述のようにViewStateをSessionに(自動的に衝突しないキーを生成して)保持してくれる。

しかも、前述のようにページを変更することなく、ViewStateの保持方法を設定することが可能である。これは、ASP.NETブラウザごとのコントロールの描画方法を外部から指定する機能を使用している。

App_Browsers/BrowserFile.browser

<browsers>
  <browser refID="Mozilla">
    <controlAdapters>
      <adapter 
        controlType="System.Web.UI.Page"                            
        adapterType="Adapter.SamplePageAdapter" />
    </controlAdapters>
  </browser>
</browsers>

SamplePageAdapter.cs

[AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
public class SamplePageAdapter : PageAdapter
{
  public override PageStatePersister GetStatePersister()
  {
    return new SessionPageStatePersister(Page);
  }
}

これで、あとは通常通りにViewStateを使用すれば、ViewStateの内容がSessionを用いて保持されるという仕組み。

うめきちうめきち 2007/05/03 00:25 はじめまして
おもしろそうなのでサンプルを作ってみました。
レスポンスからViewStateが無くなりトラフィック量に制限がある場合とても有用ですね。
ひとつ気になる点があります。
ある時点になるとViewStateの内容が破棄されてしまいます。
同じセッションでA画面とB画面の2画面を開き、A画面で8回ポストバックを発生させると
B画面のViewStateの内容が破棄されます。
何度も確認をし100%の再現性でした。
ポストバックを発生させていたA画面のViewStateは破棄されません。

何かお気づきの点がありますか?設定でもあるのでしょうか?

entlibentlib 2007/05/03 05:38 調査してみます。

entlibentlib 2007/05/03 07:09 調査したところ、SessionPageStatePersisterクラスの挙動だということがわかりました。
このクラスの実装は、Session内部にランダムに生成したキーでViewStateの内容を保持しているのですが、このエントリの保持数が9個で、これを超過した場合に前のViewStateを消しているようです。

で、この数はweb.configのconfiguration/system.web/sessionPageState要素のhistorySize属性を設定することで変更可能です。

うめきちうめきち 2007/05/04 23:20 entlibさんどうもです。
historySize属性ですか、まったく見つけられませんでした。
この制約は複数画面を開く形態のアプリでは厳しいですね。
historySize属性の値を増やしても根本的な解決にはなりませんね・・・
entlibさんの調査能力はさすがですね。
私では日本語MSDNでhistorySize属性を見つけてもこの件と関連があるとは思わなかったかも・・・
やはり英語サイトとかが役に立ちますか?

ちょくちょく覗かせて頂きます、有難うございました。

もげ〜もげ〜 2007/12/12 10:07 はじめまして

SessionにLoad/SaveするときにLoadLosFormatterでシリアライズ/デシリアライズするといいらしいと聞きました。
ただ、もしSessionからViewStateが取得できなかった場合を考えねば。。。

K’sK’s 2010/05/18 11:37 はじめまして。

上記の方法で作成したところ、デバッグ時に「Adapter.SamplePageAdapterを読み込めません。」とエラーになってしまいます。SamplePageAdapter.csを作成する場所は、ソリューション内だったらどこでもいいですよね?

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

Connection: close