Validation Application BlockのASP.NET連携

前項でも書いたとおり、Validation Application Block(以下VAB)ではオブジェクトに対する値の検証を行うのですが、さらにフロント層での検証を行うこともできます。
ASP.NET, Windows Forms, WCFがサポートされていますが、ここではASP.NETとの連携について触れます。
検証対象のクラスは前項のとおりに用意します。属性による指定でも構成ファイルによる指定でも同様なので、本例では属性による指定を行います。

public class TestData
{
  ...
  [StringLengthValidator(10)]
  public string Data1
  {
    get { return data1; }
    set { data1 = value; }
  }

  [StringLengthValidator(5)]
  [RegexValidator("^a")]
  public string Data2
  {
    get { return data2; }
    set { data2 = value; }
  }
  ...
}

これに対して、ASP.NETによるページを用意します。

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Namespace="Microsoft.Practices.EnterpriseLibrary.Validation.Integration.AspNet"
    Assembly="Microsoft.Practices.EnterpriseLibrary.Validation.Integration.AspNet"
    TagPrefix="entlib" %>
  ...
  <asp:TextBox ID="TextBox1" runat="server" />
  <entlib:PropertyProxyValidator ID="Text1Proxy" runat="server"
    ControlToValidate="TextBox1" SourceTypeName="Library.TestData" PropertyName="Data1" />
  <asp:TextBox ID="TextBox2" runat="server" />
  <entlib:PropertyProxyValidator ID="Text2Proxy" runat="server"
    ControlToValidate="TextBox2" SourceTypeName="Library.TestData" PropertyName="Data2" />
  ...

PropertyProxyValidatorコントロールは、VABでの検証を行い、結果はASP.NET標準のValidatorとして扱う(すなわち、Page.IsValidに反映される)コントロールです。下記のプロパティを設定しています。

ControlToValidate
ASP.NET標準のValidatorと同様に、検証対象のコントロールIDを指定します。
SourceTypeName
検証を行う際に参照する型を指定します。この型に対する検証内容を入力コントロールの値に対して行います。
PropertyName
SourceTypeNameで指定した型内のプロパティ名を指定します。入力コントロールの値を用いて、このプロパティに対して設定された検証を行います。

この設定で、プレゼンテーション層での検証とビジネス層での検証を単一の設定で行うことが可能になります。

Validation Application Block

EntLib3から追加されたValidation Application Block(以下VAB)について。

  • 用途

VABは値の検証を行うためのApplication Blockです。オブジェクトのプロパティ、メンバ、メソッドの実行結果に対して検証を行います。
検証の対象となるオブジェクトと、検証を行う側に分けて説明します。

検証の設定方法

検証の設定方法として、下記の方法が用意されています。

  • 属性による設定
  • 構成ファイルによる設定
  • 検証メソッドによる設定
属性による設定

検証対象のクラスに含まれるフィールド、プロパティ、メソッドに対してMicrosoft.Practices.EnterpriseLibrary.Validation.Validators.ValidatorAttributeから派生した検証属性を付加します。検証属性はひとつの項目に複数付加することも可能です。

public class TestData
{
  ...
  [StringLengthValidator(10)]
  public string Data1
  {
    get { return data1; }
    set { data1 = value; }
  }

  [StringLengthValidator(5)]
  [RegexValidator("^a")]
  public string Data2
  {
    get { return data2; }
    set { data2 = value; }
  }
  ...
}

上記の例では、Data1が10文字以下、Data2がaで始まる5文字以下という検証を行うことになります。

構成ファイルによる設定

属性で設定するかわりに、構成ファイルを使用して検証を設定することも可能です。
上記属性と同様の設定を行った際のイメージを挙げます。

対象クラス、ルールセット名、対象メンバ、検証の順にノードを設定します。

検証メソッドによる設定

もっと複雑な検証や、複数メンバ間の検証に対しては、検証メソッドを指定することも可能です。

[HasSelfValidation]
public class TestData
{
  ...
  [SelfValidation]
  public void ValidateThis(ValidationResults results)
  {
    if (string.Compare(Data1, Data2) < 0)
      results.AddResult(new ValidationResult("Data1がData2より小さい", null, null, null, null));                
  }
  ...
}

HasSelfValidation属性をクラスに、SelfValidation属性を検証メソッドに付加することにより、検証メソッドを用いた検証が行われます。検証に失敗した際に、ValidationResultsに失敗の内容を表すValidationResultオブジェクトを追加します。
上記の例では、Data1がData2より小さいときに検証が失敗します。

検証方法

検証は、簡単なコードを記述することによって行うことができます。

TestData data;
... // dataに値を設定する
ValidationResults r = Validation.Validate<TestData>(data);
if (r.IsValid) {
...

上記例で出てくるTestDataクラスのオブジェクトを検証しています。

EntLib3 Feb CTPをインストールする際に

自分の環境だけかもしれないけれど(そんなことはないと信じているが)、以前の(Dec?)CTPのインストールされていた環境にFeb CTPをインストールすると、Application Block Software FactoryのGuidが変わっているためにエラーが発生する。

A package with name Application Block Software Factory already exists with a different Guid. Unregister that package before registering a new package with the same name and a different GUID, or use both the same GUID and the same name in new package.

推測するに、CTPのアンインストール時にABSFのアンインストールを行っていないようだ。

さて、どうしたものか。

  1. 一旦前のCTPを入れなおし、Guidance Package ManagerでアンインストールしてからCTPのアンインストールを行う
  2. 強引にレジストリから削除する
  3. CTP内のGuidance PackageのGUIDを揃える
  4. CTP内のGuidance Packageの名前を変える

やはり1かなぁ。

      • -

と書いて、色々と調べているうちに思い出したけど、Dec CTPではABSFはソースからビルドしないとインストールされなかった気がする。
ソースは別インストールだったため残っていた。簡単にUnregisterできた。

Vistaへのインストールでエラー2869

Guidance Automation ToolkitをVista環境にインストールすると下記のエラーとなる。

The installer has encountered an unexpected error installing this package. This may indicate a problem with this package. The error code is 2869.

対処方法としては、

  1. [スタート メニュー]内の[コマンド プロンプト]を右クリックして[管理者として実行]する
  2. msiファイルのあるディレクトリに移動する
  3. msiexec /i GuidanceAutomationToolkit.msi

でいいらしい。動作から推測すると、インストーラは管理者権限に昇格するものの、内部で実行するアプリケーションに権限が不足しているのか。

Enterprise Library 3.0 February CTP

http://blogs.msdn.com/tomholl/archive/2007/02/28/just-released-enterprise-library-3-0-february-2007-ctp.aspx

日本時間では3/1ですが。
Policy Injection Application Blockも加わり、これで全機能が出揃った形となります。後はリリースを待つばかりとなりました。
3.0によって、今までの「使う」から「作る」「広げる」「育てる」が容易になります。
EntLibの機能はConfiguration Consoleで設定していたのにアプリ独自設定はXMLを直接編集しなければいけないとか、ObjectBuilderを使用するのにXMLを(以下同文)とかから解放される日も近いですね。

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を用いて保持されるという仕組み。