Livet ViewModel のValidation で ValidationGroup を作成する
LivetというかWPFでの単項目のチェックの実装について悩んでいた時、Nine Works: WPFでのデータ検証 1:IDataErrorInfoを使ったデータ検証 というエントリを見つけたので参考にさせて頂きました。
しかし、実案件に導入した際、ASP.NET の ValidationGroup のようにValidation対象をグループ化したいという要望が出てきました。しかし、DataAnotation にはValidationGroup に該当するものを見つける事ができませんでした。そのため、DiplayAttribute に存在する GroupName を ValidationGroup として利用する機能を追加します。
環境
- Visual Studio 2010
- Livet 1.0.4
補足
- DisplayAttribute のメンバである、Groupnameを利用しますが、このプロパティの説明「UI でのフィールドのグループ化に使用される値を取得または設定します。」に則った利用法なのか調べたかわかりませんでした。利用時は、十分検証してくださいい。
追加した機能
- プロパティをグループ化し、検証時指定グループの検証結果だけ取得する。エラーメッセージも同様。
- 1つのプロパティに複数グループの指定もカンマ区切りで指定可能。
- isValidメソッドを利用した検証時、Errorプロパティに検証結果のメッセージが設定される
検証を行うViewModelクラス
// using 省略 namespace LivetWPFApplication.ViewModels { /// <summary> /// DataAnnotationによるプロパティの検証を可能とするViewModelです。 /// </summary> public class ValidationViewModel : ViewModel, IDataErrorInfo { protected Dictionary<string, string> _errors = new Dictionary<string, string>(); /// <summary> /// オブジェクトに関する間違いを示すエラー メッセージを取得します。 /// </summary> public string Error { get { return GetErrorMessage(String.Empty); } } /// <summary> /// DataAnnotationsを使ってデータ検証を行い /// 指定した名前のプロパティに関するエラー メッセージを取得します。 /// </summary> /// <param name="propName"></param> /// <returns></returns> public string this[string propName] { get { var result = new List<ValidationResult>(); if (Validator.TryValidateProperty( GetType().GetProperty(propName).GetValue(this, null), new ValidationContext(this, null, null) { MemberName = propName }, result)) { // エラー無し _errors[propName] = null; } else { _errors[propName] = result.First().ErrorMessage; } return _errors[propName]; } } /// <summary> /// 指定したグループに属するプロパティの値が妥当かを返します。 /// </summary> /// <param name="validationGroupName">検証を行うグループ</param> /// <returns></returns> public bool isValid(string validationGroupName) { var errorMessages = GetError(validationGroupName); // エラーメッセージの更新通知 RaiseErrorChanged(); return errorMessages.Count() == 0; } /// <summary> /// 指定したグループに属するプロパティのエラーメッセージのサマリを取得します。 /// </summary> /// <param name="validationGroupName">検証を行うグループ</param> /// <returns></returns> public string GetErrorMessage(string validationGroupName) { var errorMessages = GetError(validationGroupName); return String.Join(System.Environment.NewLine, errorMessages.Values); } /// <summary> /// オブジェクトのすべてのプロパティの値が妥当かを返します。 /// </summary> /// <returns></returns> public bool isValid() { return isValid(String.Empty); } // Errorの変更通知 protected virtual void RaiseErrorChanged() { RaisePropertyChanged("Error"); } /// <summary> /// 指定したグループのエラーを取得します。 /// </summary> /// <param name="validationGroupName"></param> /// <returns>エラーのプロパティ名、エラーメッセージ</returns> private Dictionary<string, string> GetError(string validationGroupName) { IEnumerable<KeyValuePair<string, string>> errorMessages; if (String.IsNullOrEmpty(validationGroupName)) { errorMessages = _errors.Where(x => !String.IsNullOrEmpty(x.Value)); } else { errorMessages = _errors.Where(x => IsContaindValidationGroup(x.Key, validationGroupName) && _errors[x.Key] != null); } return errorMessages.ToDictionary(x => x.Key, x => x.Value); } /// <summary> /// 対象プロパティが、指定したグループに属するかの結果を返します。 /// </summary> /// <param name="propertyName">対象プロパティ名</param> /// <param name="validationGroupName">指定を行うグループ</param> /// <returns></returns> private bool IsContaindValidationGroup(string propertyName, string validationGroupName) { var attributes = Attribute.GetCustomAttributes(GetType().GetProperty(propertyName), typeof(DisplayAttribute)); if (attributes.Count() == 0) { return false; } var displayAttr = attributes[0] as DisplayAttribute; return displayAttr.GetGroupName().Split(',').ToList().Exists(x => x.Trim() == validationGroupName); } } }
Validationを行うViewModelの実装例
public class TargetVM : ValidationViewModel { #region Value1変更通知プロパティ private string _Value1; [Display(Name = "値1", GroupName = "grp1")] [Required] public string Value1 { get { return _Value1; } set { if (_Value1 == value) return; _Value1 = value; RaisePropertyChanged("Value1"); } } #endregion #region Value2変更通知プロパティ private int? _Value2; [Required] [Display(Name = "値2", GroupName = "grp2")] public int? Value2 { get { return _Value2; } set { if (_Value2 == value) return; _Value2 = value; RaisePropertyChanged("Value2"); } } #endregion #region Value3変更通知プロパティ private string _Value3; [Required] [Display(GroupName="grp3,grp4")] public string Value3 { get { return _Value3; } set { if (_Value3 == value) return; _Value3 = value; RaisePropertyChanged("Value3"); } } #endregion public bool Click() { return isValid("grp1"); } public bool ClickGroup2() { return isValid("grp2"); } public bool ClickGroup3() { return isValid("grp3"); } public bool ClickGroup4() { return isValid("grp4"); } public bool ClickGroupAll() { return isValid(); } }