AvaloniaUI –Validation in ViewModel

Veröffentlicht von

How to implement validation with INotifyDataErrorInfo in AvaloniaUI.

When it comes to programming, I hate nothing more than input and data validation. But anyway, it needs to be done. For my side project with AvaloniaUI, I needed validation for some dialogs.

Base Validation ViewModel with INotifyDataErrorInfo

First I created a base ViewModel class, which derived from my default ViewModel base class.

Here it is:

// <summary>
/// ViewModelBase for ViewModels that need validation
/// </summary>
public class ValidateableViewModelBase : ViewModelBase, INotifyDataErrorInfo
{
    private static readonly string[] NO_ERRORS = Array.Empty<string>();
    private readonly Dictionary<string, List<string>> _errorsByPropertyName = new Dictionary<string, List<string>>();
    
    /// <inheritdoc />
    public virtual IEnumerable GetErrors(string? propertyName)
    {
        if (_errorsByPropertyName.TryGetValue(propertyName, out var errorList))
        {
            return errorList;
        }
        return NO_ERRORS;
    }
    
    /// <inheritdoc />
    public bool HasErrors => _errorsByPropertyName.Count > 0;
    
    /// <inheritdoc />
    public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
    
    protected void AddError(string propertyName, string error)
    {
        if (_errorsByPropertyName.TryGetValue(propertyName, out var errorList))
        {
            if (!errorList.Contains(error))
            {
                errorList.Add(error);
            }
        }
        else
        {
            _errorsByPropertyName.Add(propertyName, new List<string>{ error });
        }
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    }
    
    protected void RemoveError(string propertyName)
    {
        if (_errorsByPropertyName.ContainsKey(propertyName))
        {
            _errorsByPropertyName.Remove(propertyName);
            ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
        }
    }
    
    protected bool ValidateString(string propertyName, string? value, int minLength = 1, int maxLength = 1000)
    {
        if (string.IsNullOrEmpty(value))
        {
            AddError(propertyName, $"The text must not be empty.");
            return false;
        }
        if (value.Length < minLength)
        {
            AddError(propertyName, $"The text must be at least {minLength} characters long.");
            return false;
        }
        if (value.Length > maxLength)
        {
            AddError(propertyName, $"The text must be at most {maxLength} characters long.");
            return false;
        }
        RemoveError(propertyName);
        return true;
    }
}

The class holds implements „INotitfyDataErrorInfo“, contains a list of errors, if there are any and adds some helper methods for adding and removing the error. Since I currently only need string validation, a helper method for that is there as well. There you can see, that there are also some generic error messages provided, these will show up in the UI.

ViewModel of the dialog

My dialog ViewModel now derives from that class.

public class EditCookieWindowViewModel : ValidateableViewModelBase

I created a method for validating the entire dialog / view model. In my case, I check two values. The „CanExecute“ is used by the OK button. It also returns the value, that the method can be also used in If statements.

private bool ValidateAll()
{
  var result = true;
  result &= ValidateString(nameof(Title), Title);
  result &= ValidateString(nameof(Description), Description);
  CanExecute = result; 
  return result;
}

In my property’s setter, I also added the validation. When one value has changed, the validation is executed and if it fails this will now show up in the UI.

public string Title
{
  get => _title;
  set
  {
    this.RaiseAndSetIfChanged(ref _title, value);
    ValidateAll();
  }
}

There was one small problem in the end. When the dialog was empty, everything was showing up as OK, since no validation was performed. However is was fixed by calling the „ValidateAll“ method in the ViewModels constructor.

Have I said, that I hate validation? It is so boring!

Kommentar hinterlassen

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