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

さて属性ベースのバリデーションとだが、バリデーションに使う属性はSystem.ComponentModel.DataAnnotationsネームスペース下に既に存在しており、基本的なバリデーションはこれを利用することで殆ど事足りてしまうだろう。

System.ComponentModel.DataAnnotations下にあるバリデーション用属性

CustomValidationAttribute.cs
DataTypeAttribute.cs
EnumDataTypeAttribute.cs
RangeAttribute.cs
RegularExpressionAttribute.cs
RequiredAttribute.cs
StringLengthAttribute.cs
TimestampAttribute.cs
ValidationAttribute.cs

それぞれ名前を見ただけでバリデーションのルールが解るものからちょっと解らないものまであるが、今はその内容どうでも良い。重要なのはこれらの属性をどのように利用するかである。

案1 メタデータとして使用する属性の型を指定する

最初に考えたのは、ViewModelを生成する際に生成するクラスのメタデータとして利用したPropertyDeclにバリデーション属性のメタデータを含めることである。バリデーション用の属性はプロパティに対して記述するので、元々プロパティ情報を生成するのに使用している同属性を使うのは自然だと考えた。

  • 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 Type validatorType;
        public string validatorOption;
        〜
    }

記述する時は以下のようにする。

  • MyViewModel.cs
    [PropertyDecl("Name", typeof(String), validatorType = typeof(RequiredAttribute), validatorOption = "ErrorMessage=\"名前は必須です\"")]
    [PropertyDecl("Age", typeof(int), validatorType = typeof(RangeAttribute), validatorOption = "1, 150")]
    public partial class MyViewModel
    {}


生成されるViewModelは以下のようになるだろう。

  • MyViewModel_generated.cs
    public partial class MyViewModel: Mandarine.MVVM.ViewModel.ViewModel
    {
        #region 

        private String _name ;
        [RequiredAttribute( ErrorMessage="名前は必須です" )]
        public String Name
        {
            get { return _name ; }
            set 
            { 
                ValidateProperty(() => Name, value);
                if ( _name == value ) 
                    return;
                _name = value; 
                NotifyPropertyChanged(() => Name);
            }
        }

        #endregion
    :略

これで行けると思ったのだが、一つ問題があった。元々バリデーション用属性は複数の属性を"重ねて"記述することでより複雑なルールを与えることができるのだ。

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

    [RequiredAttribute( ErrorMessage="名前は必須です" )]
    [StringLengthAttribute(20, ErrorMessage="名前は20桁以内で入力してください")]
    public String Name


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

うーん。 どうしたものかなぁ。