2013-09-23 31 views
9

Tôi đang sử dụng MVVMLight. Đây là mẫu Department/POCO của tôi. Tôi không muốn làm ô nhiễm nó bằng bất kỳ phương tiện nào.Làm thế nào để bạn decouple xác nhận tài sản ViewModel của bạn từ ViewModel?

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

Đây là 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); 
    } 
} 

Các DepartmentCodeDepartmentFullName được liên kết với giao diện người dùng như hình dưới đây.

<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> 

Trước khi lưu các Sở, tôi cần phải xác nhận rằng cả hai DepartmentCodeDepartmentFullName có một số văn bản trong đó.

Logic hợp lệ của tôi nên ở đâu? Trong chính ViewModel? Nếu vậy, làm thế nào để tôi decouple logic xác nhận của tôi để nó cũng là đơn vị testable?

+0

Tôi nghĩ rằng cách tốt nhất là thực hiện 'IDataErrorInfo' trong lớp Model của bạn. Điều này có thể 'gây ô nhiễm' nó một chút nhưng nó là kiểm tra thực hành tốt nhất [this] (http://blogs.msdn.com/b/wpfsdk/archive/2007/10/02/data-validation-in-3-5 .aspx) post –

+0

Đó là những gì tôi không muốn làm. Tôi không muốn gây ô nhiễm cho Mô hình của mình vì nó được chia sẻ với dự án MVC. Có cách tiếp cận nào khác không? – NoobDeveloper

+2

Tôi không thể nhập một ví dụ ngay bây giờ, nhưng tại sao lại liên kết trực tiếp với mô hình của bạn? Chỉ cần bọc 2 thuộc tính đó hoặc như vậy trong ViewModel của bạn, có ViewModel của bạn thực hiện IDataErrorInfo (thay vì thay đổi Model của bạn) và thực hiện logic hợp lệ ở đó. Sau đó, liên kết với ViewModel.DepartmentCode không ViewModel.Department.DepartmentCode. Hoặc logic nghiệp vụ xác nhận của bạn cần phải nằm trong Mô hình của bạn hoặc ViewModel ... Vì bạn không muốn thay đổi mô hình, gói nó trong ViewModel là tùy chọn khác. – Alan

Trả lời

4

tôi đã tìm thấy cách dễ nhất để thực hiện điều này là sử dụng một

System.Windows.Controls.ValidationRule 

Nó chỉ mất 3 bước thẳng về phía trước.

Trước tiên, bạn tạo một ValidationRule. Đây là một lớp hoàn toàn riêng biệt tồn tại bên ngoài cả Mô hình của bạn và ViewModel và xác định cách dữ liệu Văn bản phải được xác thực. Trong trường hợp này, kiểm tra String.IsNullOrWhiteSpace đơn giản.

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); 
     } 
    } 
} 

Tiếp theo, xác định rằng các hộp thoại của bạn nên sử dụng một thể hiện của lớp mới của bạn để thực hiện xác nhận trên Văn bản nhập bằng việc xác định tài sản của các văn bản ràng buộc ValidationRules. Bạn nhận được tiền thưởng thêm của đường viền TextBox chuyển sang màu đỏ nếu Xác thực không thành công.

<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> 

Cuối cùng, tạo Kiểu để tắt nút Lưu nếu TextBox không xác thực. Chúng ta thực hiện điều này bằng cách liên kết với thuộc tính Validation.HasError của Textbox mà chúng ta đã ràng buộc với quy tắc Validation. Chúng tôi sẽ đặt tên cho phong cách này là DisableOnValidationError để làm rõ mọi thứ.

<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> 

Và cuối cùng, chúng tôi thiết lập các phong cách DisableOnValidationError vào nút Save

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

Bây giờ, nếu một trong hai textbox của bạn bị lỗi Validation TextBox được nhấn mạnh và nút Save sẽ bị vô hiệu.

DepartmentValidationRule hoàn toàn tách biệt khỏi logic nghiệp vụ của bạn và có thể sử dụng lại và kiểm tra được.

0

Thêm phương pháp mới trong mô hình quan điểm của bạn (có giá trị) và Sửa đổi các phương pháp CanExecte, bạn có thể dễ dàng kiểm tra điều này bằng cách kiểm tra các phương pháp 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

Bạn có thể làm cho lớp Model của bạn thực hiện giao diện IDataErrorInfo.

Nếu bạn không muốn làm ô nhiễm mẫu, bạn có thể tạo ra một lớp mới kế thừa từ nó, và làm xác nhận có

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 
} 

Trong ViewModel bạn thay Department với ValidDepartment

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

Trong số View đặt ValidatesOnDataErrors=True của bạn để kiểm soát ràng buộc của bạn

<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> 

Set TextBox StyleValidation.ErrorTemplate để xác định cách xác nhận của bạn sẽ xuất hiện trong giao diện người dùng, ví dụ, thông qua Tooltip:

<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> 

Bạn có thể tìm hiểu thêm về xác nhận trong WPF here, và here

Hy vọng điều này giúp

+0

Bạn có thể cho tôi biết cách/nơi tôi có thể sử dụng ValidDepartment không? – NoobDeveloper

+0

@Nexus Đã cập nhật câu trả lời của tôi. Về cơ bản, 'ViewModel' của bạn hoặc bất kỳ người tiêu dùng nào khác của' Department' sẽ cần xác thực sẽ sử dụng 'ValidDepartment', tất cả người tiêu dùng khác sẽ tiếp tục sử dụng' Department' –

+0

Ok. Hãy để tôi cụ thể. ViewModel của tôi tiêu thụ ValidDepartment như thế nào? Làm cách nào để hiển thị thông báo lỗi trong Chế độ xem? – NoobDeveloper

1

Điều gì về cách sử dụng ValidationRules lớp học, điều này sẽ ngăn cản mô hình của bạn xuất hiện bằng mã xác thực.

Điều này sẽ hoạt động tốt cho các điều khiển riêng lẻ nhưng bạn cũng có thể ủy quyền logic này cho một số lớp xác thực tùy chỉnh, MvvmValidator framework sẽ giúp bạn. Khung công tác này cho phép bạn viết logic xác thực phức tạp dưới dạng các quy tắc và các quy tắc này có thể được cấu hình ở cấp ViewModel và có thể được kích hoạt trên nút gửi. một cách decouple tốt đẹp của nó áp dụng xác nhận hợp lệ mà không cần cư trú đối tượng domian của bạn.

+0

Làm cách nào để xác thực điều gì đó phụ thuộc vào 2 hoặc nhiều thuộc tính? Ví dụ: Trên chế độ xem, người dùng cần nhập Số điện thoại di động hoặc số Trang chủ. Nếu bất kỳ một số nào được nhập thì dữ liệu sẽ được lưu, nếu không, sẽ hiển thị thông báo lỗi. – NoobDeveloper

+0

@Nexus: Vui lòng xem câu trả lời cập nhật của tôi – TalentTuner

0

Tôi cũng thấy điều này gây phiền nhiễu vì nó khiến bạn kinh doanh logic vào ViewModel buộc bạn phải chấp nhận điều đó và để nó ở đó hoặc sao chép nó trong Service Layer hoặc Data Model. Nếu bạn không nhớ mất một số lợi thế của việc sử dụng chú thích, v.v.This là phương pháp tôi đã sử dụng và được xem nhiều nhất được đề xuất - thêm lỗi vào ValidationDictionary từ lớp dịch vụ.

Bạn cũng có thể kết hợp chúng, với logic nghiệp vụ được xử lý như trên trong lớp dịch vụ của bạn và xác thực hợp lệ chỉ có giao diện người dùng được chú thích trong ViewModel của bạn.

* Lưu ý rằng tôi trả lời câu hỏi này từ quan điểm MVC, nhưng tôi nghĩ rằng tất cả vẫn có liên quan.

2

Tạo một lớpValidator, một đơn vị sẽ dễ dàng được kiểm tra. Ngoài ra, lớp này sẽ cho phép bạn loại bỏ trùng lặp xác thực trong các kịch bản phía máy chủ và giao diện người dùng.

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; 
    } 
} 

Tạo một DepartmentViewModel rằng kết thúc tốt đẹp mô hình cục của bạn và thực hiện IDataErrorInfo, do đó bạn có thể kiểm soát chi tiết hơn và có thể hiển thị lỗi xác nhận sử dụng tiêu chuẩn Validation Templates.

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)); 
     } 
    } 
} 

Phơi bày DepartmentViewModel, chứ không phải là cục Model, và treo lên các sự kiện PropertyChanged đến CreateDepartmentCommand để nút Save của bạn sẽ tự động bị vô hiệu hóa khi bộ phận này không xác nhận và do đó bạn có thể hiển thị lỗi xác nhận. Hiển thị thuộc tính 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); 
} 

Trước khi lưu Cục, bạn có thể muốn xác thực lại.

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

    bool success = departmentService.SaveDepartment(_department); 
} 
Các vấn đề liên quan