読者です 読者をやめる 読者になる 読者になる

しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

WebPages 2 で使えるカスタムバリデータを実装する

WebMatrix 2 というか、WebPages 2 ではバリデーション周り非常に使いやすくなっていいですね。下手に Fluent Interface で定義させるよりわかりやすいと思いました。

しかし、標準で使えるバリデータは思ったより多くありません。正規表現使えるので大体のことはできますが、ちょっと複雑なことをしようとすると困りますね。なので、今回はカスタムバリデータを作ってみたいと思います。

とりあえず ASP.NET MVC 3 で独自の検証属性を作成して、クライアントサイド検証を行う - しばやん雑記 でクライアントサイド検証を実装した時のように CERO のバリデータを作ります。

バリデータは IValidator インターフェースを実装したクラスです。じゃあこれを実装すれば何でも作れる!と思ったら、ちょっとインターフェースが難しいです。

public interface IValidator
{
    ModelClientValidationRule ClientValidationRule { get; }
    ValidationResult Validate(ValidationContext validationContext);
}

今まで MVC で開発してきて ModelClientValidationRule と ValidationContext とか見ないですよね。このあたりは綺麗にフレームワークが隠してくれてるので、我々は適当に使っておけばよかったのですが、今回は逃げることが出来ません。

とは言っても、ClientValidationRule プロパティはその名の通りクライアントサイド検証用なので、クライアントサイド検証を使わないのなら null 返しとけば問題ありません。

とりあえず完成したコードをいつも通り載せておきます。

public class CeroValidator : IValidator
{
    public CeroValidator(char rating, string errorMessage = null)
    {
        _rating = rating;
        _errorMessage = errorMessage;
    }

    private char _rating;
    private string _errorMessage;

    public ModelClientValidationRule ClientValidationRule
    {
        get { return null; }
    }

    public ValidationResult Validate(ValidationContext validationContext)
    {
        // ObjectInstance には HttpContext が入ってるので、キャストして取得
        var context = (HttpContextBase)validationContext.ObjectInstance;

        // メンバー名はフォーム要素名なので、コレクションから値を直接取得
        var value = context.Request.Form[validationContext.MemberName];

        // バリデータでは null or 空文字列の場合は成功にしないとダメ(required があるので)
        if (string.IsNullOrEmpty(value))
        {
            return ValidationResult.Success;
        }

        var result = false;
        
        // 数字の時だけ検証する
        if (value.IsInt())
        {
            var age = value.AsInt();

            switch (_rating)
            {
                case 'Z':
                    result = age >= 18;
                    break;

                case 'D':
                    result = age >= 17;
                    break;

                case 'C':
                    result = age >= 15;
                    break;

                case 'B':
                    result = age >= 12;
                    break;

                case 'A':
                    result = true;
                    break;
            }
        }

        if (result)
        {
            // 成功したら ValidationResult.Success を返す
            return ValidationResult.Success;
        }

        // 検証に失敗したらエラーメッセージとメンバー名で初期化した ValidationResult を返す
        return new ValidationResult(_errorMessage, new[] { validationContext.MemberName });
    }
}

流れはコードとコメントでわかってください、お願いします。基本的には MVC 向けに検証属性作るのとあまり変わらないですが、入力値が文字列なので気を付けてください。

クライアントサイド検証を使いたい場合には MVC 3 と同じように ModelClientValidationRule を作って返せばいいです。JavaScript 側を忘れないように気を付けてくださいね。

そしてサンプルコードは以下のような感じ。Validator.ほげほげ と指定していた部分を new CeroValidator に変えただけです。

Validation.RequireFields("name");
Validation.Add("age", new CeroValidator('Z', "検証に失敗しました"));

if (IsPost)
{
    Validation.Validate();
}

実際に試してみたところ、Z 指定なので 18 才未満の場合にはちゃんとエラーが出ました。入力値が分からないので確認しようがないですね(汗