Menü Schließen

Using Data Annotations for Validation in WPF

The .NET Framework uses many different approaches for validation. The ASP.NET MVC for example uses Data Annotations to annotate the model classes with the definition of valid ranges, etc. In WPF on the other hand, validation rules are used to validate entries made by the user and display error messages.

This post shows how to combine both approaches. We will then define the validation settings on the model level and validate the user input in the view.

This can be used in POCO objects and is therefore fully compatible with the MVVM pattern and ORM database mapping technology like Entity Framework Code First.

POCO model class:

using System.ComponentModel.DataAnnotations;

public class POC 
{
  [Display(Name = "Vorname")]
  public string FirstName { get; set; }

  [Display(Name = "Nachname"), Required(ErrorMessage="Der Nachname ist erforderlich.")]
  public string LastName { get; set; }
}

Generic validation rule:

This uses reflection to find and check the properties.

internal class GenericValidationRule<T> : ValidationRule
{
  public override ValidationResult Validate(object value, CultureInfo cultureInfo) 
  {
    string result = string.Empty;
    BindingGroup bindingGroup = (BindingGroup)value;
    foreach (var item in bindingGroup.Items.OfType<T>()) 
    {
      Type type = item.GetType();
      // First we find all properties that have attributes.
      foreach (var pi in type.GetProperties()) 
      {
        foreach (var attrib in Attribute.GetCustomAttributes(pi, true)) 
        {
          // We continue if we found a data annotation
          if (attrib is System.ComponentModel.DataAnnotations.ValidationAttribute validationAttribute)
          {
            if (bindingGroup.TryGetValue(item, pi.Name, out object val))
            {
              if (val == DependencyProperty.UnsetValue)
                val = null;
              // We set the error message if the validation of the property fails.
              if (!validationAttribute.IsValid(val)) 
              {
                if (!string.IsNullOrWhiteSpace(result))
                  result += Environment.NewLine;
                if (string.IsNullOrEmpty(validationAttribute.ErrorMessage))
                  result += string.Format("Validation on {0} failed!", pi.Name);
                else
                  result += validationAttribute.ErrorMessage;
              }
            }
          }
        }
      }
    }
    if (!string.IsNullOrWhiteSpace(result))
      return new ValidationResult(false, result);
    else
      return ValidationResult.ValidResult;
  }
}

Concrete validation class:

internal class POCValidationRule : GenericValidationRule<POC> { }

How to use model class and validation in XAML:

<Window x:Class="MyNameSpace.Views.WindowPOC" xmlns:helpers="clr-namespace:Helpers" Validation.ValidationAdornerSite="{Binding ElementName=labelErrors}">

<Window.BindingGroup>
  <BindingGroup>
    <BindingGroup.ValidationRules>
      <helpers:POCValidationRule />
    </BindingGroup.ValidationRules>
  </BindingGroup>
</Window.BindingGroup>

...

  <TextBox Text="{Binding FirstName}" Margin="2" />
  <TextBox Text="{Binding LastName}" Margin="2" />
  <TextBlock x:Name="labelErrors" TextWrapping="Wrap" Margin="5" />

...

</Window>

The properties are bound to TextBoxes like usual. The BindingGroup property of the window is set to a new BindingGroup object that contains our custom validation rule as ValidationRule.

When the validation fails, the error message is automatically shown in the TextBlock with name „labelErrors“.

In code behind I trigger the validation and prevent the window from closing when errors are found:

private void ButtonOK_Click(object sender, RoutedEventArgs e)
{
  if (BindingGroup.CommitEdit())
    DialogResult = true;
}

 

Ähnliche Beiträge

Schreibe einen Kommentar

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