連載:ASP.NET MVC入門【バージョン3対応】

第4回 検証属性の自作とクラス・レベルのモデル検証

山田 祥寛(http://www.wings.msn.to/
2011/06/17
Page1 Page2 Page3

 前回は、登録フォームの実装には欠かせないアノテーション検証について解説した。アノテーション検証を利用すれば、モデル(エンティティ)のプロパティに決められた属性を付与するだけで、検証機能を実装できる。検証の実施にあたって、コントローラやビュー・スクリプトにほとんど影響を及ぼさないのも、アノテーション検証のよいところだろう。

 もっとも、前回説明したのは、奥深いアノテーション検証のほんの触りの部分に過ぎない。今回は、さらに突っ込んで、前回紹介しきれなかった検証属性の自作、クラス・レベルの(複数項目にまたがる)検証について解説していく。

自作の検証属性を定義する(サーバサイド編)

 前回見たように、ASP.NET MVC 3は、標準でも実にさまざまな検証属性を提供している。しかし、本格的なアプリケーションを構築するうえでは、標準の検証機能だけではカバーできない部分もあるはずだ。そのような場合には、検証属性の自作を検討するとよい。

■InArray属性の使い方

 さっそく具体的な実装例を挙げてみよう。以降で示すのは、入力値が候補リストに含まれるかどうかをチェックするInArray属性の記述例だ。例えば、以下のように利用できる。

[DisplayName("出版社")]
[InArray("翔泳社,技術評論社,秀和システム,毎日コミュニケーションズ,日経BP社,インプレスジャパン")]
public string Publish { get; set; }
<DisplayName("出版社")>
<InArray("翔泳社,技術評論社,秀和システム,毎日コミュニケーションズ,日経BP社,インプレスジャパン")>
Public Property Publish() As String
リスト1 BookエンティティにInArray検証を適用した例(上:Book.cs、下:Book.vb)

 InArray属性のパラメータである値リストは、カンマ区切りのテキストとして設定できる。リスト1では、Publish(出版社)列の値が「翔泳社、技術評論社、秀和システム、毎日コミュニケーションズ、日経BP社、インプレスジャパン」のいずれでもない場合に検証エラーとなるわけだ。

 以下に、InArray検証による検証エラーの表示例も示しておく。


図1 自作のInArray属性による検証エラーの表示例

■InArray属性の実装コード

 このようなInArray属性の定義は以下のようなコードになる。

using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;

namespace MvcApp.Models
{
  // 値リストとの比較検証を行うInArray属性を定義
  [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
  public class InArrayAttribute : ValidationAttribute
  {
    // 値リストを表すプライベート変数
    private string _opts;

    // コンストラクタ(値リストとエラー・メッセージを設定)
    public InArrayAttribute(string opts)
    {
      this._opts = opts;
      this.ErrorMessage = "{0}は{1}のいずれかで指定してください。";
    }

    // プロパティの表示名と値リストでエラー・メッセージを整形
    public override string FormatErrorMessage(string name)
    {
      return String.Format(CultureInfo.CurrentCulture,
                           ErrorMessageString, name, _opts);
    }

    // 検証の実処理(値リストに入力値が含まれているかをチェック)
    public override bool IsValid(object value)
    {
      // 入力値が空の場合は検証をスキップ
      if (value == null) { return true; }

      // カンマ区切りテキストを分解し、入力値valueと比較
      if (Array.IndexOf(_opts.Split(','), value) == -1)
      {
        return false;
      }
      return true;
    }
  }
}
Imports System.ComponentModel.DataAnnotations
Imports System.Globalization

' 値リストとの比較検証を行うInArray属性を定義
<AttributeUsage(AttributeTargets.Property, AllowMultiple:=False)>
Public Class InArrayAttribute : Inherits ValidationAttribute

    ' 値リストを表すプライベート変数
    Private _opts As String

    ' コンストラクタ(値リストとエラー・メッセージを設定)
    Public Sub New(ByVal opts As String)
        Me._opts = opts
        Me.ErrorMessage = "{0}は{1}のいずれかで指定してください。"
    End Sub

    ' プロパティの表示名と値リストでエラー・メッセージを整形
    Public Overrides Function FormatErrorMessage(ByVal name As String) As String
        Return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, _opts)
    End Function

    ' 検証の実処理(値リストに入力値が含まれているかをチェック))
    Public Overrides Function IsValid(ByVal value As Object) As Boolean

        ' 入力値が空の場合は検証をスキップ
        If value Is Nothing Then Return True

        ' カンマ区切りテキストを分解し、入力値valueと比較
        If Array.IndexOf(_opts.Split(","c), value) = -1 Then
            Return False
        End If
        Return True
    End Function
End Class
リスト2 InArray検証を実装したコード(上:InArrayAttribute.cs、下:InArrayAttribute.vb)

 やや長めのコードであるが、ほとんどが決まりきった書き方であるので、ルールさえ覚えてしまえば、何ら難しいことはない。ポイントを押さえつつ、コードを読み解いていこう。

ValidationAttributeクラスを継承する

 検証属性は、ValidationAttributeクラス(System.ComponentModel.DataAnnotations名前空間)の派生クラスとして定義し、「<検証名>Attribute」の形式で命名すること。

 クラス先頭のAttributeUsage属性は、属性の用途を制限するための属性である。指定してなくとも最低限の動作に支障はないが、属性の用途を明確にするという意味でも、きちんと宣言しておくのが望ましい。

 以下は、AttributeUsage属性の主なプロパティだ。

プロパティ 概要
ValidOn*1 属性を指定できるプログラム要素(Class、Property、Methodなど)
AllowMultiple 同一の属性を複数回指定できるか
Inherited 派生メンバで属性を継承できるか
表1 AttributeUsage属性の主なプロパティ

*1 サンプルでは、コンストラクタの引数として指定している。

 リスト1の例であれば、InArray属性がプロパティに対して付与でき、重複の指定はできないことを意味している。

エラー・メッセージと検証パラメータを設定する(コンストラクタ)

 コンストラクタでは、必須の検証パラメータ(ここでは値リストを表す変数「_opts」)と、エラー・メッセージを設定しておこう。エラー・メッセージ(ErrorMessageプロパティ)には、{0}、{1}……のようにプレイスホルダを埋め込める点に注目してほしい。プレイスホルダへの具体的な値の割り当てはあらためて説明するので、まずは“{0}”がプロパティの表示名を、“{1}”が検証に必要なパラメータ(ここでは値リスト)を表すとだけ理解しておいてほしい。

エラー・メッセージを整形するのはFormatErrorMessageメソッド

 FormatErrorMessageメソッドは、引数としてname(プロパティの表示名)を受け取り、整形済みのエラー・メッセージを返すためのメソッドだ。デフォルトでは、プレイスホルダ“{0}”に対して表示名を割り当てるだけであるので、そのほかのパラメータ値を割り当てたい場合には、自分でオーバーライドして整形ルールを変更する必要がある。

 例えばリスト1の例では、エラー・メッセージにプレイスホルダである{0}、{1}が含まれていることを想定しているので、String.Formatメソッドでも対応する値として、name(表示名)、_opts(値リスト)を渡しているわけだ。

 なお、整形すべきエラー・メッセージは、(ErrorMessageプロパティではなく)ErrorMessageStringプロパティで取得している点にも注意してほしい。ErrorMessageStringプロパティでは、ErrorMessageプロパティ(エラー・メッセージ)、または、ErrorMessageResourceType/ErrorMessageResourceNameプロパティ(エラー・メッセージを表すリソースの型、キー名*2)のいずれかを評価することで、適切なエラー・メッセージを取り出している。ErrorMessageプロパティに直接アクセスしてしまうと、リソース・ファイルを利用している場合に正しくメッセージが反映されなくなってしまうので注意されたい。

*2 ErrorMessageResourceType/ErrorMessageResourceNameプロパティについては、後日、国際化対応の回であらためて解説の予定だ。

検証の実処理を定義するのはIsValidメソッド

 検証の実処理を定義するのは、IsValidメソッドの役割だ。引数として入力値(value)を受け取り、検証結果をブール値(検証成功はtrue)で返すようにすればよい。

 リスト1の例では、まず で入力値valueが空(null)の場合は無条件に検証を成功させている。一般的には未入力の場合に値チェックを継続するのは意味がないので、 の記述は必須と覚えておけばよいだろう。

 後は、 で値リストをカンマ分割し、入力値と比較するだけだ。Array.IndexOfメソッドが-1を返す(=値リストに入力値が存在しない)場合に、検証エラー(false)を返している。

■InArray属性の動作確認

 以上を理解したら、さっそくInArray検証の動作を確認してみよう。もっとも、自作の検証属性を適用するのに何ら特別な手続きは必要ない。リスト1でも見たように、標準の検証属性と同じ要領で、対象のプロパティに検証属性を付与するだけだ。

 ただし、この例を試す際には、もう1つ、Web.configでクライアントサイド検証を無効化しておくとよい*3。現時点では、InArray検証はサーバサイドのコードしか実装していないので、デフォルトではクライアント検証が優先して働いてしまうためだ。つまり、ほかの検証が正しく通過するように値を入力しないと、InArray検証のエラーを確認できない。

*3 ブラウザのJavaScript機能を無効にしても構わない。

 クライアントサイド検証を無効にしているのは、以下のコードだ。

<appSettings>
  <add key="ClientValidationEnabled" value="false"/>
  <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>
リスト3 クライアントサイド検証を無効にするための設定(Web.config)

 アプリケーション設定のClientValidationEnabledキーをfalseにすることで、クライアントサイド検証は無効化される。

 サンプルを実行し、あえて誤った値を入力することで、先ほどの図1のような結果が得られることを確認してほしい。また、サンプルの正しい実行が確認できたら、Web.configは元の状態に戻しておくこと。

[参考]控えめなJavaScript

 ASP.NET MVC 3では、「Unobtrusive JavaScript(控えめなJavaScript)」というポリシーが導入された。これは、要はHTMLコードの随所に出しゃばらない――JavaScriptコードがHTMLコードから完全に分離しているという意味だ。

 例えば、以下はCreate.cshtmlによる出力コードの一部である。

<input class="text-box single-line" data-val="true" data-val-number="フィールド 価格 には数字を指定してください。" data-val-range="価格は100〜10000の間で入力してください。" data-val-range-max="10000" data-val-range-min="100" id="Price" name="Price" type="text" value="" />
リスト4 Create.cshtmlの一部

 <input>要素に見慣れない「data-xxxxx属性」が列挙されているのが見て取れるはずだ。これらはいずれもクライアントサイド検証に必要なパラメータやエラー・メッセージである。ASP.NET MVC 3では、このようにdata-xxxxx属性を利用することで、検証コード(JavaScriptコード)とパラメータ(HTMLコード)を明確に分離しているわけだ。

 ちなみに、アプリケーション設定のUnobtrusiveJavaScriptEnabledキー(リスト3参照)をfalseにすることで、控えめなJavaScriptは無効にすることもできる。

 出力コードを確認してみると、今度は、パラメータ情報がJavaScriptのコードとして出力されていることが確認できるはずだ(リスト5)。

<input class="text-box single-line" id="Price" name="Price" type="text" value="" />

……中略……

<script type="text/javascript">
//<![CDATA[
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":...FieldName":"Price", "ReplaceValidationMessageContents":true, "ValidationMessageId":"Price_validationMessage", "ValidationRules":[{"ErrorMessage":"価格は100〜10000の間で入力してください。", "ValidationParameters":{"min":100,"max":10000}, "ValidationType":"range"}, {"ErrorMessage":"フィールド 価格 には数字を指定してください。", "ValidationParameters":{}, "ValidationType":"number"}]},...});
//]]>
</script>
リスト5 UnobtrusiveJavaScriptEnabledキーをfalseにしたCreate.cshtml

 

 INDEX
  ASP.NET MVC入門【バージョン3対応】
  第4回 検証属性の自作とクラス・レベルのモデル検証
  1.自作の検証属性を定義する(サーバサイド編)
    2.自作の検証属性を定義する(クライアントサイド編)
    3.複数プロパティをまたがった検証を実装する
 
インデックス・ページヘ  「ASP.NET MVC入門【バージョン3対応】」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間