2013-09-23 31 views
9

我正在使用MVVMLight。这是我的Department模型/ POCO类。我不想以任何方式污染它。如何从ViewModel中分离ViewModel属性验证?

public partial class Department 
    { 
     public int DepartmentId { get; set; } 
     public string DepartmentCode { get; set; } 
     public string DepartmentFullName { get; set; } 
    } 

这里是CreateDepartmentViewModel

public class CreateDepartmentViewModel : ViewModelBase 
{ 
    private IDepartmentService departmentService; 
    public RelayCommand CreateDepartmentCommand { get; private set; } 

    public CreateDepartmentViewModel(IDepartmentService DepartmentService) 
    { 
     departmentService = DepartmentService; 
     this.CreateDepartmentCommand = new RelayCommand(CreateDepartment, CanExecute); 
    } 

    private Department _department = new Department(); 
    public Department Department 
    { 
     get 
     { 
      return _department; 
     } 
     set 
     { 
      if (_department == value) 
      { 
       return; 
      } 
      _department = value; 
      RaisePropertyChanged("Department"); 
     } 
    } 

    private Boolean CanExecute() 
    { 
     return true; 
    } 
    private void CreateDepartment() 
    { 
     bool success = departmentService.SaveDepartment(_department); 
    } 
} 

DepartmentCodeDepartmentFullName被结合(如下所示)的用户界面。

<Grid> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="Auto" /> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition Height="Auto"/> 
     </Grid.RowDefinitions> 
     <TextBlock Text="Department Code" Grid.Row="0"/> 
     <TextBox Grid.Row="0" Text="{Binding Department.DepartmentCode, Mode=TwoWay}" Margin="150,0,0,0"/> 

     <TextBlock Text="Department Name" Grid.Row="1"/> 
     <TextBox Grid.Row="1" Text="{Binding Department.DepartmentFullName, Mode=TwoWay}" ToolTip="Hi" Margin="150,0,0,0"/> 

     <Button Grid.Row="2" Content="Save" Width="50" Command="{Binding CreateDepartmentCommand}"/> 
    </Grid> 

之前保存处,我需要验证两个DepartmentCodeDepartmentFullName中有一些文字。

我的验证逻辑应该放在哪里?在ViewModel本身?如果是这样,我如何分离我的验证逻辑,以便它也是单元可测试的?

+0

我认为最好的方法是在你的模型类中实现'IDataErrorInfo'。这可能会“污染”一点点,但最好练习检查[this](http://blogs.msdn.com/b/wpfsdk/archive/2007/10/02/data-validation-in-3-5 .aspx)post –

+0

这就是我不想做的事情。我不想污染我的模型,因为它与MVC项目共享。还有其他方法吗? – NoobDeveloper

+2

我现在无法输入示例,但为何直接绑定到您的模型?只需在ViewModel中包装这两个属性,让ViewModel实现IDataErrorInfo(而不是更改模型)并在其中实现验证逻辑。然后绑定到ViewModel.DepartmentCode而不是ViewModel.Department.DepartmentCode。您的验证业务逻辑需要驻留在您的Model或ViewModel中......因为您不想更改模型,所以将其包装在ViewModel中是另一种选择。 – Alan

回答

4

我发现做到这一点最简单的方法是使用

System.Windows.Controls.ValidationRule 

只需要3直线前进的步骤。

首先创建一个ValidationRule。这是一个完全独立的类,它存在于Model和ViewModel之外,并定义了Text数据应该如何验证。在这种情况下,一个简单的String.IsNullOrWhiteSpace检查。

public class DepartmentValidationRule : System.Windows.Controls.ValidationRule 
{ 
    public override System.Windows.Controls.ValidationResult Validate(object value, CultureInfo ultureInfo) 
    { 
     if (String.IsNullOrWhiteSpace(value as string)) 
     { 
      return new System.Windows.Controls.ValidationResult(false, "The value is not a valid"); 
     } 
     else 
     { 
      return new System.Windows.Controls.ValidationResult(true, null); 
     } 
    } 
} 

接下来,指定您的文本框应该使用新的类的实例来对specifing文本结合的ValidationRules属性中输入文本进行验证。如果验证失败,您将获得文本框边框变红的额外奖励。

<TextBlock Text="Department Code" Grid.Row="0"/> 
    <TextBox Name="DepartmentCodeTextBox" Grid.Row="0" Margin="150,0,0,0"> 
     <TextBox.Text> 
      <Binding Path="Department.DepartmentCode" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"> 
       <Binding.ValidationRules> 
        <local:DepartmentValidationRule/> 
       </Binding.ValidationRules> 
      </Binding> 
     </TextBox.Text> 
    </TextBox> 
    <TextBlock Text="Department Name" Grid.Row="1"/> 
    <TextBox Name="DepartmentNameTextBox" Grid.Row="1" ToolTip="Hi" Margin="150,0,0,0"> 
     <TextBox.Text> 
      <Binding Path="Department.DepartmentFullName" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"> 
       <Binding.ValidationRules> 
        <local:DepartmentValidationRule/> 
       </Binding.ValidationRules> 
      </Binding> 
     </TextBox.Text> 
    </TextBox> 

最后,创建一个样式来禁用保存按钮,如果任一文本框验证失败。我们通过绑定到我们将Validation规则绑定到的Textbox的Validation.HasError属性来完成此操作。我们将这种风格命名为DisableOnValidationError,以使事情变得明显。

<Grid.Resources> 
     <Style x:Key="DisableOnValidationError" TargetType="Button"> 
      <Style.Triggers> 
       <DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=DepartmentCodeTextBox}" Value="True" > 
        <Setter Property="IsEnabled" Value="False"/> 
       </DataTrigger> 
       <DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=DepartmentNameTextBox}" Value="True" > 
        <Setter Property="IsEnabled" Value="False"/> 
       </DataTrigger> 
      </Style.Triggers> 
     </Style> 
    </Grid.Resources> 

最后我们设置的保存按钮

<Button Grid.Row="2" Content="Save" Width="50" Command="{Binding CreateDepartmentCommand}" 
      Style="{StaticResource DisableOnValidationError}"/> 

的DisableOnValidationError风格现在,如果你的任何文本框的验证失败的文本框被高亮显示并保存按钮将被禁用。

DepartmentValidationRule与您的业务逻辑完全分离,可重复使用且可测试。

0

在您的视图模型添加新的方法(有效),并修改CanExecte方法,你可以很容易地通过测试CanExecute方法测试:

public class CreateDepartmentViewModel : ViewModelBase 
{ 
private IDepartmentService departmentService; 
public RelayCommand CreateDepartmentCommand { get; private set; } 

public CreateDepartmentViewModel(IDepartmentService DepartmentService) 
{ 
    departmentService = DepartmentService; 
    this.CreateDepartmentCommand = new RelayCommand(CreateDepartment, CanExecute); 
} 

private Department _department = new Department(); 
public Department Department 
{ 
    get 
    { 
     return _department; 
    } 
    set 
    { 
     if (_department == value) 
     { 
      return; 
     } 
     _department = value; 
     RaisePropertyChanged("Department"); 
    } 
} 
private bool IsValid() 
{ 
return !string.IsNullOrEmpty(this.Department.DepartmentCode) && !string.IsNullOrEmpty(this.Department.DepartmentFullName); 
} 

private Boolean CanExecute() 
{ 
    return this.IsValid(); 
} 
private void CreateDepartment() 
{ 
    bool success = departmentService.SaveDepartment(_department); 
} 
} 
0

你可以让你Model类实现IDataErrorInfo接口。

如果你不想你的污染型,您可以创建一个新的类从它的继承,并在那里做

public class ValidDepartment : Department, IDataErrorInfo 
{ 
    #region IDataErrorInfo Members 

    public string Error 
    { 
     get { return null; } 
    } 

    public string this[string name] 
    { 
     get 
     { 
      if (name == "DepartmentCode") 
      { 
       if (string.IsNullOrEmpty(DepartmentCode) 
        return "DepartmentCode can not be empty"; 
      } 

      if (name == "DepartmentFullName") 
      { 
       if (string.IsNullOrEmpty(DepartmentFullName) 
        return "DepartmentFullName can not be empty"; 
      } 

      return null; 
     } 
    } 

    #endregion 
} 

验证在你ViewModel代之以DepartmentValidDepartment

private ValidDepartment _department = new ValidDepartment(); 
public ValidDepartment Department 
{ 
    get 
    { 
     return _department; 
    } 
    set 
    { 
     if (_department == value) 
     { 
      return; 
     } 
     _department = value; 
     RaisePropertyChanged("Department"); 
    } 
} 

在您的View集合ValidatesOnDataErrors=True到您的绑定控件

<TextBox Grid.Row="1" ToolTip="Hi" Margin="150,0,0,0"> 
    <TextBox.Text> 
     <Binding Path="Department.DepartmentFullName" 
       Mode="TwoWay" 
       ValidatesOnDataErrors="True"> 
     </Binding> 
    </TextBox.Text> 
</TextBox> 

设置TextBox StyleValidation.ErrorTemplate以确定您的验证将如何出现在用户界面,例如,通过工具提示:

<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}"> 
    <Style.Triggers> 
    <Trigger Property="Validation.HasError" Value="true"> 
      <Setter Property="ToolTip" 
        Value="{Binding RelativeSource={x:Static RelativeSource.Self}, 
        Path=(Validation.Errors)[0].ErrorContent}"/> 
    </Trigger> 
    </Style.Triggers> 
</Style> 

您可以了解更多有关验证在WPF herehere

希望这帮助

+0

你能告诉我如何/在哪里可以使用ValidDepartment? – NoobDeveloper

+0

@Nexus更新了我的答案。基本上,你需要验证的'ViewModel'或任何其他使用'Department'的消费者将使用'ValidDepartment',所有其他消费者将继续使用'Department' –

+0

好的。让我具体一点。我的ViewModel如何使用ValidDepartment?如何在View中显示错误消息? – NoobDeveloper

1

怎么样使用ValidationRules类,这将解耦您的模型与验证代码poppluting它。

这对个人控制非常有用,但您也可以将此逻辑委派给一些自定义验证类,MvvmValidator framework将帮助您。这个框架允许您以规则的形式编写复杂的验证逻辑,这些规则可以在ViewModel级别配置,并可以在提交按钮上触发。它是一个很好的分离方式,可以在不填充你的domian对象的情况下应用验证。

+0

我如何验证取决于2个或更多属性的东西?例如,在视图上,用户需要输入手机号码或家庭号码。如果输入任何一个数字,则应保存数据,否则显示错误消息。 – NoobDeveloper

+0

@Nexus:请看我更新的答案 – TalentTuner

0

我也觉得这恼人的,因为它推动你的业务逻辑到ViewModel强迫你接受并保持不变或Service LayerData Model复制它。如果您不介意丢失使用注释等的一些优势,This是我使用过的最常见的方法 - 将错误添加到服务层的ValidationDictionary中。

您还可以将这些与您的服务层中按上述方式处理的业务逻辑以及在ViewModel中注释的仅UI相关验证混合使用。

*注意我从MVC的角度回答了这个问题,但我认为它仍然是相关的。

2

创建一个DepartmentValidator类,该类很容易进行单元测试。此外,这个类将允许您消除服务器端和UI场景中验证的重复。

public class DepartmentValidator 
{ 
    private class PropertyNames 
    { 
     public const string DepartmentFullName = "DepartmentFullName"; 
     public const string DepartmentCode = "DepartmentCode"; 
    } 

    public IList<ValidationError> Validate(Department department) 
    { 
     var errors = new List<ValidationError>(); 

     if(string.IsNullOrWhiteSpace(department.DepartmentCode)) 
     { 
      errors.Add(new ValidationError { ErrorDescription = "Department code must be specified.", Property = PropertyNames.DepartmentCode}); 
     } 

     if(string.IsNullOrWhiteSpace(department.DepartmentFullName)) 
     { 
      errors.Add(new ValidationError { ErrorDescription = "Department name must be specified.", Property = PropertyNames.DepartmentFullName}); 
     } 

     if (errors.Count > 0) 
     { 
      return errors; 
     } 

     return null; 
    } 
} 

创建一个包装贵处的模型,并实现IDataErrorInfo的,让你有更精细的控制,可以显示使用标准验证模板验证错误DepartmentViewModel。

public class DepartmentViewModel : IDataErrorInfo, INotifyPropertyChanged 
{ 
    private Department _model; 

    public DepartmentViewModel(Department model) 
    { 
     _model = model; 
     Validator = new DepartmentValidator(); 
    } 

    public DepartmentValidator Validator { get; set; } 

    public string DepartmentFullName 
    { 
     get 
     { 
      return _model.DepartmentFullName; 
     } 
     set 
     { 
      if(_model.DepartmentFullName != value) 
      { 
       _model.DepartmentFullName = value; 
       this.OnPropertyChanged("DepartmentFullName"); 
      } 
     } 
    } 

    public string DepartmentCode 
    { 
     get 
     { 
      return _model.DepartmentCode; 
     } 
     set 
     { 
      if(_model.DepartmentCode != value) 
      { 
       _model.DepartmentCode = value; 
       this.OnPropertyChanged("DepartmentCode"); 
      } 
     } 
    } 

    public int DepartmentId 
    { 
     get 
     { 
      return _model.DepartmentId; 
     } 
    } 

    public string this[string columnName] 
    { 
     get 
     { 
      var errors = Validator.Validate(_model) ?? new List<ValidationError>(); 
      if (errors.Any(p => p.Property == columnName)) 
      { 
       return string.Join(Environment.NewLine, errors.Where(p => p.Property == columnName).Select(p => p.ErrorDescription)); 
      } 
      return null; 
     } 
    } 

    public string Error 
    { 
     get 
     { 
      var errors = Validator.Validate(_model) ?? new List<ValidationError>(); 
      return string.Join(Environment.NewLine, errors); 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 

    protected virtual void OnPropertyChanged(string propertyName) 
    { 
     PropertyChangedEventHandler handler = this.PropertyChanged; 
     if (handler != null) 
     { 
      handler(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 
} 

揭露DepartmentViewModel,而不是系车型,以及挂钩PropertyChanged事件的CreateDepartmentCommand让您保存按钮时,将自动部门验证失败而禁用,这样可以显示验证错误。公开一个ValidationErrors属性。

public CreateDepartmentViewModel(IDepartmentService DepartmentService) 
{ 
    departmentService = DepartmentService;   
    _department = new DepartmentViewModel(new Department()); 
    this.CreateDepartmentCommand = new RelayCommand(CreateDepartment, CanExecute); 

    _department.PropertyChanged += (s,a) => 
    { 
     ValidationErrors = Department.Errors; 
     RaisePropertyChanged("ValidationErrors"); 
     this.CreateDepartmentCommand.RaiseCanExecuteChanged(); 
    } 
} 

public DepartmentViewModel Department 
{ 
    get 
    { 
     return _department; 
    } 
    set 
    { 
     if (_department == value) 
     { 
      return; 
     } 
     _department = value; 
     RaisePropertyChanged("Department"); 
    } 
} 

public string ValidationErrors {get; set;} 

private Boolean CanExecute() 
{ 
    return string.IsNullOrEmpty(ValidationErrors); 
} 

保存部门之前,您可能需要再次验证。

private void CreateDepartment() 
{ 
    if(Department.Error!=null) 
    { 
     ValidationErrors = Department.Error; 
     RaisePropertyChanged("validationErrors"); 
     return; 
    } 

    bool success = departmentService.SaveDepartment(_department); 
}