MVVM – 驗證



本章將學習驗證。我們還將研究一種利用 WPF 繫結已支援的功能但將其與 MVVM 元件結合起來進行驗證的簡潔方法。

MVVM 中的驗證

  • 當您的應用程式開始接受來自終端使用者的的資料輸入時,您需要考慮驗證該輸入。

  • 確保它符合您的整體要求。

  • WPF 在繫結系統中具有一些出色的內建功能和特性,用於驗證輸入,並且在使用 MVVM 時您仍然可以利用所有這些特性。

  • 請記住,支援您的驗證並定義哪些規則適用於哪些屬性的邏輯應該是模型或 ViewModel 的一部分,而不是檢視本身。

您仍然可以使用 WPF 資料繫結支援的所有表達驗證的方式,包括:

  • 在設定屬性時丟擲異常。
  • 實現 IDataErrorInfo 介面。
  • 實現 INotifyDataErrorInfo。
  • 使用 WPF 驗證規則。

一般來說,建議使用 INotifyDataErrorInfo,它是在 WPF .NET 4.5 中引入的,它支援查詢物件的與屬性關聯的錯誤,並且它還修復了所有其他選項的一些缺陷。具體來說,它允許非同步驗證。它允許屬性與多個錯誤關聯。

新增驗證

讓我們來看一個示例,在這個示例中,我們將向我們的輸入檢視新增驗證支援,在大型應用程式中,您可能需要在應用程式的許多地方使用此功能。有時在檢視上,有時在 ViewModel 上,有時在這些圍繞模型物件的包裝器幫助器物件上。

將驗證支援放在您可以從中繼承不同場景的公共基類中是一個好習慣。

基類將支援 INotifyDataErrorInfo,以便在屬性更改時觸發驗證。

建立一個名為 ValidatableBindableBase 的新類。由於我們已經有一個用於屬性更改處理的基類,讓我們從它派生基類,並實現 INotifyDataErrorInfo 介面。

以下是 ValidatableBindableBase 類的實現。

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 

//using System.ComponentModel.DataAnnotations; 
using System.Linq; 
using System.Runtime.CompilerServices; 
using System.Text;
using System.Threading.Tasks; 
using System.Windows.Controls;

namespace MVVMHierarchiesDemo { 

   public class ValidatableBindableBase : BindableBase, INotifyDataErrorInfo { 
      private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

      public event EventHandler<DataErrorsChangedEventArgs> 
         ErrorsChanged = delegate { };

      public System.Collections.IEnumerable GetErrors(string propertyName) {
		
         if (_errors.ContainsKey(propertyName)) 
            return _errors[propertyName]; 
         else 
            return null; 
      }
      
      public bool HasErrors { 
         get { return _errors.Count > 0; } 
      }
		
      protected override void SetProperty<T>(ref T member, T val, 
         [CallerMemberName] string propertyName = null) {
		
         base.SetProperty<T>(ref member, val, propertyName);
         ValidateProperty(propertyName, val);
      }
		
      private void ValidateProperty<T>(string propertyName, T value) {
         var results = new List<ValidationResult>();
			
         //ValidationContext context = new ValidationContext(this); 
         //context.MemberName = propertyName;
         //Validator.TryValidateProperty(value, context, results);

         if (results.Any()) {
            //_errors[propertyName] = results.Select(c => c.ErrorMessage).ToList(); 
         } else { 
            _errors.Remove(propertyName); 
         }
			
         ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); 
      } 
   } 
}

現在在各自的資料夾中新增 AddEditCustomerView 和 AddEditCustomerViewModel。以下是 AddEditCustomerView.xaml 的程式碼。

<UserControl x:Class = "MVVMHierarchiesDemo.Views.AddEditCustomerView"
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <Grid.RowDefinitions> 
         <RowDefinition Height = "Auto" /> 
         <RowDefinition Height = "Auto" />
      </Grid.RowDefinitions>
		
      <Grid x:Name = "grid1" 
         HorizontalAlignment = "Left" 
         DataContext = "{Binding Customer}" 
         Margin = "10,10,0,0" 
         VerticalAlignment = "Top">
			
         <Grid.ColumnDefinitions> 
            <ColumnDefinition Width = "Auto" /> 
            <ColumnDefinition Width = "Auto" /> 
         </Grid.ColumnDefinitions>
		
         <Grid.RowDefinitions> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
         </Grid.RowDefinitions>
		
         <Label Content = "First Name:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "0" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "firstNameTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "0" 
            Text = "{Binding FirstName, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Last Name:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "1" 
            VerticalAlignment = "Center" /> 
			
         <TextBox x:Name = "lastNameTextBox"
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "1" 
            Text = "{Binding LastName, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Email:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "2" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "emailTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "2" 
            Text = "{Binding Email, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Phone:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "3" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "phoneTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "3" 
            Text = "{Binding Phone, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
      </Grid> 

      <Grid Grid.Row = "1"> 
         <Button Content = "Save" 
            Command = "{Binding SaveCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "25,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" />
		
         <Button Content = "Add" 
            Command = "{Binding SaveCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "25,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
		
         <Button Content = "Cancel" 
            Command = "{Binding CancelCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "150,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
      </Grid>
		
   </Grid> 
	
</UserControl>

以下是 AddEditCustomerViewModel 的實現。

using MVVMHierarchiesDemo.Model;

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.ViewModel { 

   class AddEditCustomerViewModel : BindableBase { 
	
      public AddEditCustomerViewModel() {
         CancelCommand = new MyIcommand(OnCancel); 
         SaveCommand = new MyIcommand(OnSave, CanSave); 
      } 
		
      private bool _EditMode; 
		
      public bool EditMode { 
         get { return _EditMode; } 
         set { SetProperty(ref _EditMode, value);} 
      }
		
      private SimpleEditableCustomer _Customer;
		
      public SimpleEditableCustomer Customer { 
         get { return _Customer; } 
         set { SetProperty(ref _Customer, value);} 
      }

      private Customer _editingCustomer = null;
		
      public void SetCustomer(Customer cust) {
         _editingCustomer = cust; 
			
         if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged; 
         Customer = new SimpleEditableCustomer();
         Customer.ErrorsChanged += RaiseCanExecuteChanged;
         CopyCustomer(cust, Customer); 
      }
		
      private void RaiseCanExecuteChanged(object sender, EventArgs e) { 
         SaveCommand.RaiseCanExecuteChanged(); 
      }

      public MyIcommand CancelCommand { get; private set; }
      public MyIcommand SaveCommand { get; private set; }

      public event Action Done = delegate { };
		
      private void OnCancel() { 
         Done(); 
      }

      private async void OnSave() { 
         Done(); 
      }
		
      private bool CanSave() { 
         return !Customer.HasErrors; 
      }  
   } 
}

以下是 SimpleEditableCustomer 類的實現。

using System;
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.Model { 

   public class SimpleEditableCustomer : ValidatableBindableBase { 
      private Guid _id; 
		
      public Guid Id { 
         get { return _id; } 
         set { SetProperty(ref _id, value); } 
      }
		
      private string _firstName; 
      [Required]
		
      public string FirstName { 
         get { return _firstName; } 
         set { SetProperty(ref _firstName, value); } 
      }
		
      private string _lastName; 
      [Required] 
		
      public string LastName {  
         get { return _lastName; } 
         set { SetProperty(ref _lastName, value); } 
      }
		
      private string _email; 
      [EmailAddress] 
		
      public string Email {
         get { return _email; } 
         set { SetProperty(ref _email, value); } 
      }
		
      private string _phone; 
      [Phone] 
		
      public string Phone { 
         get { return _phone; } 
         set { SetProperty(ref _phone, value); } 
      } 
   } 
}

編譯並執行上述程式碼後,您將看到以下視窗。

MVVM Validations MainWindow1

當您按下“新增客戶”按鈕時,您將看到以下檢視。當用戶留下任何欄位為空時,它將被高亮顯示,並且儲存按鈕將被停用。

MVVM Validations MainWindow2
廣告