我正在使用MVVMLight。这是我的Department
模型/ POCO类。我不想以任何方式污染它。
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);
}
}
DepartmentCode
和DepartmentFullName
绑定到用户界面,如下所示。
<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>
在保存部门之前,我需要验证DepartmentCode
和DepartmentFullName
中是否包含一些文字。
我的验证逻辑应该在哪里?在ViewModel本身?如果是这样,我如何将我的验证逻辑解耦,以便它也可以进行单元测试?
答案 0 :(得分: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);
}
}
}
接下来,指定您的TextBoxes应该使用新类的实例来对通过指定Text绑定的ValidationRules属性输入的Text执行验证。如果验证失败,您将获得TextBox边框的额外奖励变为红色。
<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>
最后,如果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>
最后我们在Save按钮
上设置了DisableOnValidationError样式 <Button Grid.Row="2" Content="Save" Width="50" Command="{Binding CreateDepartmentCommand}"
Style="{StaticResource DisableOnValidationError}"/>
现在,如果您的任何一个TextBox未通过验证,TextBox会突出显示,并且“保存”按钮将被禁用。
DepartmentValidationRule与您的业务逻辑完全分开,可重用且可测试。
答案 1 :(得分: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;
}
}
创建一个包装Department模型并实现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而不是Department Model,并将PropertyChanged事件连接到CreateDepartmentCommand,以便在部门验证失败时自动禁用Save按钮,以便显示验证错误。公开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);
}
答案 2 :(得分:1)
如果要使用ValidationRules 类,这会将您的模型与使用验证码进行分解。
这对于单个控件非常有用,但您也可以将此逻辑委派给某些自定义验证类,MvvmValidator framework将对您有所帮助。此框架允许您以规则的形式编写复杂的验证逻辑,这些规则可以在ViewModel级别配置,并可以在提交按钮上触发。它是一种很好的解耦方式,可以在不填充你的domian对象的情况下应用验证。
答案 3 :(得分:0)
在视图模型中添加新方法(Is Valid)和修改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);
}
}
答案 4 :(得分: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
Department
替换ValidDepartment
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 Style
和Validation.ErrorTemplate
以确定验证在UI中的显示方式,例如,通过工具提示:
<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>
中详细了解验证
希望这有帮助
答案 5 :(得分:0)
我也觉得这很烦人,因为它会将您的业务逻辑推入ViewModel
,迫使您接受并将其保留在那里或在Service Layer
或Data Model
中复制它。如果你不介意失去使用注释的一些优点,等等。 This 是我使用过的,也是最常推荐的方法 - 在 ValidationDictionary中添加错误来自服务层。
您还可以将这些与您在服务层中按上述方式处理的业务逻辑以及ViewModel
中注释的仅限UI的相关验证进行混合。
*请注意,我从MVC的角度回答这个问题,但我认为这一切仍然相关。
答案 6 :(得分:0)
我在所有项目中使用流畅的验证,不仅可以解耦,还可以轻松地对我的验证规则进行单元测试。 http://fluentvalidation.codeplex.com/。