MVVM - Walidacje

W tym rozdziale dowiemy się o walidacji. Przyjrzymy się również czystemu sposobowi sprawdzania poprawności z tym, co już obsługują powiązania WPF, ale wiążąc je ze składnikami MVVM.

Walidacja w MVVM

  • Kiedy aplikacja zaczyna akceptować dane wejściowe od użytkowników końcowych, należy rozważyć walidację tych danych wejściowych.

  • Upewnij się, że jest zgodny z ogólnymi wymaganiami.

  • WPF ma kilka świetnych kompilacji i funkcji w systemie powiązań do sprawdzania poprawności danych wejściowych i nadal można wykorzystać wszystkie te funkcje podczas wykonywania MVVM.

  • Należy pamiętać, że logika, która obsługuje walidację i definiuje, jakie reguły istnieją dla właściwości, które powinny być częścią modelu lub ViewModel, a nie samego widoku.

Nadal można używać wszystkich sposobów wyrażania walidacji, które są obsługiwane przez powiązanie danych WPF, w tym -

  • Zgłaszanie wyjątków dla właściwości jest ustawione.
  • Implementacja interfejsu IDataErrorInfo.
  • Implementacja INotifyDataErrorInfo.
  • Użyj reguł walidacji WPF.

Ogólnie rzecz biorąc, INotifyDataErrorInfo jest zalecane i zostało wprowadzone do WPF .net 4.5 i obsługuje zapytania obiektu pod kątem błędów związanych z właściwościami, a także naprawia kilka niedociągnięć we wszystkich innych opcjach. W szczególności umożliwia walidację asynchroniczną. Dzięki temu właściwościom można przypisać więcej niż jeden błąd.

Dodawanie walidacji

Spójrzmy na przykład, w którym dodamy obsługę walidacji do naszego widoku wejściowego, aw dużej aplikacji prawdopodobnie będziesz potrzebować tego w wielu miejscach w swojej aplikacji. Czasami w widokach, czasami w ViewModels, a czasami w tych obiektach pomocniczych wokół obiektów modelu znajdują się opakowania.

Jest to dobra praktyka umieszczania obsługi walidacji we wspólnej klasie bazowej, którą można następnie dziedziczyć z różnych scenariuszy.

Klasa bazowa będzie obsługiwać INotifyDataErrorInfo, dzięki czemu ta walidacja zostanie wyzwolona po zmianie właściwości.

Utwórz dodaj nową klasę o nazwie ValidatableBindableBase. Ponieważ mamy już klasę bazową do obsługi zmiany właściwości, wyprowadźmy z niej klasę bazową, a także zaimplementujmy interfejs INotifyDataErrorInfo.

Poniżej przedstawiono implementację klasy 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)); 
      } 
   } 
}

Teraz dodaj AddEditCustomerView i AddEditCustomerViewModel w odpowiednich folderach. Poniżej znajduje się kod 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>

Poniżej znajduje się implementacja 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; 
      }  
   } 
}

Poniżej przedstawiono implementację klasy 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); } 
      } 
   } 
}

Kiedy powyższy kod zostanie skompilowany i wykonany, zobaczysz następujące okno.

Po naciśnięciu przycisku Dodaj klienta zobaczysz następujący widok. Gdy użytkownik pozostawi dowolne pole puste, zostanie ono podświetlone, a przycisk zapisywania zostanie wyłączony.