Menü Schließen

ViewModel Validation in MVVM

In WPF IValidatableObject, IDataErrorInfo interface implementations can be used to validate user input against validation rules.

This blow shows a different approach that defines a IValidatingViewModel as interface for ViewModels that should use a validation. This is a very simple approach without relying on some black box third party technology.

Validation Interface

This is the general interface, which all ViewModels need to implement that use the validation:

public interface IValidatingViewModel
{
  string ErrorMessage { get; }
  bool HasErrors { get; }
  bool Validate();
}

It has a Validate function. When this is called, the ErrorMessage is set and also the HasErrors property.

ViewModel

This is the abstract base class of ViewModels implementing the IValidatingViewModel interface:

public abstract class ValidatingViewModel<T> : ViewModel, IValidatingViewModel
{
  #region Validation

  public T CurrentItem { get; set; }

  protected abstract bool InternalValidate(T item);

  public bool Validate()
  {
    ClearErrors();
    return InternalValidate(CurrentItem);
  }

  protected void AddError(string message)
  {
    if (!string.IsNullOrWhiteSpace(ErrorMessage))
    {
      ErrorMessage += Environment.NewLine;
    }

    ErrorMessage += message;
    OnPropertyChanged(nameof(ErrorMessage));
    OnPropertyChanged(nameof(HasErrors));
  }

  protected void ClearErrors()
  {
    ErrorMessage = null;
    OnPropertyChanged(nameof(ErrorMessage));
    OnPropertyChanged(nameof(HasErrors));
  }

  public string ErrorMessage { get; private set; } = null;

  public bool HasErrors
  {
    get { return !string.IsNullOrWhiteSpace(ErrorMessage); }
  }

  #endregion
}

It inherits from my own ViewModel base class. This only implements the INotifyPropertyChanged interface. See my other posts for more details on that. The class is abstract and is a Generic. The item which is supposed to be validated is stored in the CurrentItem property. Whenever the Validate method is called, it will call the InternalValidate method.

This is supposed to be overridden in derived classes. This is the place where the actual validation happens.

This class also contains two helper methods: AddError and ClearErrors that can be used in the derived class to add an error to the error message and to clear the errors. The ClearErrors method is already called when a validation happens. All required property changed events are triggered by the AddError and ClearErrors methods.

Concrete ViewModel Class

public class ProjectViewModel : ValidatingViewModel<Project>
{
  #region Validation

  protected override bool InternalValidate(Project item)
  {
    if (item == null)
      return true;

    ClearErrors();

    if (!string.IsNullOrWhiteSpace(item.Name))
    {
      AddError("The name is required.");
    }

    return !HasErrors;
  }

  #endregion
}

This is a concrete implementation of the ViewModel class. It derives from ValidatingViewModel and overrides the InternalValidate method.

View

This is a short example that shows how the information from the ViewModel is bound to a TextBlock to display the error messages.

<TextBlock Text="{Binding ErrorMessage}" Visibility="{Binding HasErrors, Converter={StaticResource VisibilityConverter}}" TextWrapping="Wrap" Margin="5" />

Validation

The validation happens in code behind of the view class whenever the OK button is pressed to accept the values and close the dialog.

public ViewModel ViewModel { get; set; }

public void ButtonOK_Click(object sender, RoutedEventArgs e)
{
  if (DataContext is IValidatingViewModel && (DataContext as IValidatingViewModel).Validate() != false) {
    this.DialogResult = true;
  }
}

We will check if the DataContext actually contains an IValidatingViewModel and call the Validate method afterwards. If the result of the validation is false (validation failed), we’ll prevent the closing of the dialog.

The validation message is automatically displayed when the data bindings are correct.

Ähnliche Beiträge

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert