生成したViewModelとバリデーション その4

バリデート属性の型をPropertyDeclで指定して、それを元にプロパティに属性を記述する方法にはちょっと無理があった

例)必須で尚かつ、桁数が20桁以内であること

    [RequiredAttribute( ErrorMessage="名前は必須です" )]
    [StringLengthAttribute(20)]
    public String Name

このように記述するにはPropertyDeclに複数のバリデート属性とそのパラメタを含めなければならず、分かり易く記述するのが難しくなってしまう。
うーん。どうしたものかなぁ。

少し考えてみたが、次の案は「複数記述するのが難しいなら一つに集約すればいいじゃない」というものだ。

メタデータには同じくPropertyDeclAttributeクラスを使用するが、今度はバリデータの型の指定は持たず、プロパティに対してバリデーションをするか否かだけを情報として持たせることにする。(オプションパラメタは前回同様に文字列で与えられるようにしておく)

新たなPropertyDeclAttributeは以下にようになるだろう。

PropertyDeclAttribute.cs
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public class PropertyDeclAttribute : Attribute
    {
        public string name;
        public Type type;
        public object defaultValue;
        public Type converterType;
        public bool notifyPropertyChanged = true;
        public bool generateValidate;
        public string validateOption;

        public PropertyDeclAttribute() 
            : base()
        {}
        public PropertyDeclAttribute(string name, Type type)
            :this()
        {
            this.name = name;
            this.type = type;
        }
    }


生成元となるクラスには以下のように記述する。

MyViewModel.cs
    [PropertyDecl("Name", typeof(String), generateValidate = true, validateOption = "ErrorMessage=\"名前は必須です\"")]
    public partial class MyViewModel
    {
    }

ViewModelGenerate T4 Templateにより生成される型は以下のようになる。

MyViewModel_Generated.cs
    public partial class MyViewModel: Mandarine.MVVM.ViewModel.ViewModel
    {
        private String _name ;
        [ValidateName( ErrorMessage="名前は必須です" )]
        public String Name
        {
            get { return _name ; }
            set 
            { 
                ValidateProperty(() => Name, value);
                if ( _name == value ) 
                    return;
                _name = value; 
                NotifyPropertyChanged(() => Name);
            }
        }
    : 略

前回と変わったのはバリデーション用属性が ValidateName という名前に変わったことだ。これはPropertyDeclから生成されたプロパティ名のプレフィクスに"Validate"を付加した名前だが、実はこの属性クラス、本クラスを生成した時にはまだどこにも存在しない、なのでテンプレートの変換処理を実行した直後はコンパイルエラーになる。

エラー 2 型または名前空間名 'ValidateName' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。 E:\.Mandarine\DataBindApp1\DataBindApp1\ViewModel\MyViewModel_Generated.cs 22 10 DataBindApp1

MyViewModel_Generated.csを生成したプログラマはどのプロパティをバリデーションするかを知っているはずなので、このコンパイルエラーが出たならば(別に前もって用意しておいても全然構わないが)、生成元のMyViewModelに属性の実装を記述すれば良い。

バリデーション属性の実装を追加した MyViewModel.cs
    [PropertyDecl("Name", typeof(String), generateValidate = true, validateOption = "ErrorMessage=\"名前は必須です\"")]
    public partial class MyViewModel
    {}
    public class ValidateName : RequiredAttribute
    {}

ValidateNameはSystem.ComponentModel.DataAnnotations.RequiredAttributeを単純に実装した属性クラス、つまり必須属性にしている。
単なる必須属性で済まない場合はこのクラスの検証メソッド(IsValid)をオーバライドしてやればよい。