代码改变世界

WPF中的数据验证

2010-06-03 21:55  Franz  阅读(1772)  评论(1编辑  收藏  举报

WPF提供了非常好的数据验证模式。

 具体不理解的请参考《Validation in Windows Presentation Foundation

如果让你设计在V-VM-M 中数据的验证 ,你更喜欢把它放到那一层呢?当然如果我们可以说那一层都无所谓,比较数据都是通过View输入的。最终的反馈也是在View层显示,那View层是最直接最方便的。 那WPF设计者的确为之中方式提供了很方便的编写方式。只要在Binding中使用ValidationRules指定具体的验证规则就OK了,而且一般来说我们要验证的数据不会太多,无非是不能为空,或者满足某个正则的约定。所以这个已经非常方便了。

 

 由于某些原因有很多时候我们的Model层可能本身就有了ValidationAttribute特性。那我们应该去应用这个特性。当然还有一个理由就是我们的数据对象被多出创建,或者说我们不想在XAML中每次都填写验证方式。

 

那么我们可以这样去写。

代码
public abstract class ValidationViewModelBase : ViewModelBase, IDataErrorInfo
  {
    
private readonly TypesContent _typesContent;

    
private readonly Type _thisType;

    
protected ValidationViewModelBase()
    {
      
this._thisType = GetType();
      
this._typesContent = new TypesContent(this._thisType);
    }

    
#region Implementation of IDataErrorInfo

    
public string this[string columnName]
    {
      
get
      {
        PropertyInfo info 
= this._typesContent.GetPropertyInfo(
          
this._thisType, columnName);

        var validationAttributes 
=
          
this._typesContent.GetValidationAttribute(this._thisType, columnName);

        var value 
= info.GetValue(thisnull);

        var errors 
= new List<string>();

        
foreach (var validationAttribute in validationAttributes)
        {
          
if (!validationAttribute.IsValid(value))
          {
            errors.Add(validationAttribute.ErrorMessage);
          }
        }
        
        OnPropertyChanged(
"Error");

        
return string.Join(Environment.NewLine, errors.ToArray());
      }
    }

    
public string Error
    {
      
get
      {
        var propertyInfos 
= this._typesContent.GetPropertInfos(this._thisType);
        var errorlist 
= new List<string>();

        
foreach (var info in propertyInfos)
        {
          var validationAttributes 
=
            
this._typesContent.GetValidationAttribute(
              
this._thisType, info.Name);

          var value 
= info.GetValue(thisnull);

          var errors 
= validationAttributes.Where(v => !v.IsValid(value))
            .Select(v 
=> v.ErrorMessage).ToArray();
          errorlist.AddRange(errors);
        }

        
return string.Join(Environment.NewLine, errorlist.ToArray());
      }
    }

    
#endregion

    
private class TypePropertyContent
    {
      
private readonly Type _type;

      
private readonly Dictionary<string, PropertyInfo> _propertyInfos =
        
new Dictionary<string, PropertyInfo>();

      
private readonly Dictionary<string, ValidationAttribute[]> _validators =
        
new Dictionary<string, ValidationAttribute[]>();

      
public TypePropertyContent(Type type)
      {
        
this._type = type;
        
this.LoadData();
      }

      
private void LoadData()
      {
        var classType 
= this._type;
        var properties 
= classType.GetProperties();
        
foreach (var propertyInfo in properties)
        {
          var customAttributes 
=
            propertyInfo.GetCustomAttributes(
              
typeof(ValidationAttribute), true);
          
if (customAttributes.Length > 0)
          {
            
this._validators.Add(
              propertyInfo.Name, (ValidationAttribute[]) customAttributes);
            
this._propertyInfos.Add(propertyInfo.Name, propertyInfo);
          }
        }
      }

      
public PropertyInfo GetPropertInfo(string propertyName)
      {
        
return this._propertyInfos.ContainsKey(propertyName)
              
? this._propertyInfos[propertyName]
              : 
null;
      }

      
public PropertyInfo[] GetPropertInfos()
      {
        
return this._propertyInfos.Values.ToArray();
      }

      
public ValidationAttribute[] GetValidationAttribute(string propertyName)
      {
        
return this._validators.ContainsKey(propertyName)
              
? this._validators[propertyName]
              : 
null;
      }
    }

    
private class TypesContent
    {
      
private static readonly Dictionary<Type, TypePropertyContent> _types =
        
new Dictionary<Type, TypePropertyContent>();

      
public TypesContent(Type type)
      {
        
if (!_types.ContainsKey(type))
        {
          _types.Add(type, 
new TypePropertyContent(type));
        }
      }

      
public PropertyInfo GetPropertyInfo(Type type, string propertyName)
      {
        
if (_types.ContainsKey(type))
        {
          var propertyContent 
= _types[type];
          
return propertyContent.GetPropertInfo(propertyName);
        }

        
return null;
      }

      
public ValidationAttribute[] GetValidationAttribute(
        Type type, 
string propertyName)
      {
        
if (_types.ContainsKey(type))
        {
          var typePropertyContent 
= _types[type];
          
return typePropertyContent.GetValidationAttribute(propertyName);
        }

        
return null;
      }

      
public PropertyInfo[] GetPropertInfos(Type type)
      {
        
return _types.ContainsKey(type) ? _types[type].GetPropertInfos() : null;
      }
    }
  }
  
public class ViewModelBase : INotifyPropertyChanged
  {
    
#region INotifyPropertyChanged 

    
public event PropertyChangedEventHandler PropertyChanged = delegate { };

    
protected virtual void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
    {
      
this.PropertyChanged(this, eventArgs);
    }

    
protected virtual void OnPropertyChanged(string propertyName)
    {
      
this.PropertyChanged(thisnew PropertyChangedEventArgs(propertyName));
    }

    
#endregion
  }
  
  
//ViewModel
   
 
public class PersonViewModel : ValidationViewModelBase
  {
    
private string _name;

    [RegularExpression(
@"^\w+$", ErrorMessage = "format error")]
    
public string Name
    {
      
get
      {
        
return this._name;
      }

      
set
      {
        
this._name = value;
        OnPropertyChanged(
"Name");
      }
    }
  }
//View like this:

<TextBox>
  
<TextBox.Text>
    
<Binding Path=".Name"
         Mode
="TwoWay"
         UpdateSourceTrigger
="PropertyChanged"
         ValidatesOnDataErrors
="True"
         ValidatesOnExceptions
="True"
         NotifyOnValidationError
="True">
    
</Binding>
  
</TextBox.Text>
</TextBox>