Customising Error Messages with INotifyDataErrorInfo Validation for XAML Applications (UWP, WinUI, Uno)

In my previous post, Adding Validation to a XAML Control Using INotifyDataErrorInfo, I walked through adding validation to a view model using the INotifyDataErrorInfo interface and the Community Toolkit. This post will pick up where that post left off and look at how you can customise the error message that gets displayed. This will improve the way you surface errors in your XAML based application, whether you use Windows UI, UWP or even Uno.

As a quick recap, this is how the error message currently looks:

The challenges we want to address are:

  • The name of the property is being displayed as “First” which is the name of the underlying property on our view model. This needs to be changed to perhaps “First Name”
  • The error message doesn’t handle localization.

We’ll tackle these two challenges in order.

DisplayAttribute

Changing the string that is generated for the name of the Property can be done by applying the DisplayAttribute.

Name

We’ll start by setting the Name property, for example:

[Required()]
[MinLength(2)]
[MaxLength(100)]
[Display(Name ="First Name")]
public string First { get => firstName; set => SetProperty(ref firstName, value, true); }

This improves the error message for the first name field.

ResourceType

Setting the Name on the DisplayAttribute works for the static scenario. However, if you want to provide a more dynamic response, for example to change the value based on different language, you need to set the ResourceType property. Setting the ResourceType offloads the responsibility of working out the correct property name to a different class. Let’s take a look at a simple example.

[Display(Name ="FirstName", ResourceType =typeof(DisplayResolver))]
public string First { get => firstName; set => SetProperty(ref firstName, value, true); }

public static class DisplayResolver
{
    public static string FirstName => "Custom First Name";
}

In this example we’ve set the ResourceType to be a simple class called DisplayResolver. The important thing is that this class has a static property with the same name as the Name property set on the DisplayAttribute (reflection is used to look up the property based on the Name property value).

Note that here we’re only returning a static value for the FirstName property in the DisplayResolver class – we’ll come back to this in a subsequent post and look at how we can load language resources.

ValidationAttribute

In order to customise how the error message is displayed, including handling different languages, the validation attributes (which inherit from ValidationAttribute) exposes a couple of key properties that can be set.

ErrorMessage

The first place to start is to set the ErrorMessage property on the validation attribute. For example we can set the ErrorMessage property on the MinLength attribute.

[MinLength(2, ErrorMessage = "{0} needs to be at least {1} characters")]

This updates the display to the following

You’ll noticed that in the ErrorMessage we specified two placeholders, {0} and {1}, which were replaced with the property name and the minimum length we set. The placeholders vary depending on which validation attribute you’re using. The built in validation attributes all have the first placeholder being used for the property name. Not all validation attributes have more than one placeholders; for example the Required attribute only has a single placeholder in the error string (source):

  <data name="RequiredAttribute_ValidationError" xml:space="preserve">
    <value>The {0} field is required.</value>
  </data>

ErrorMessageResourceType and ErrorMessageResourceName

Setting the ErrorMessage works for the basic case of needing to customise the error message with a static string. However, it doesn’t cater for more dynamic handling, for example where you need to specify a string based on language. The ErrorMessageResourceType and ErrorMessageResourceName properties work similar to the ResourceType property on the DisplayAttribute we saw earlier. We can set these in order for the error message to be retrieved from a static property on the specified class.

[Required()]
[MinLength(2,  
    ErrorMessageResourceType = typeof(ErrorResolver), 
    ErrorMessageResourceName = nameof(ErrorResolver.CustomMinLength))]
[MaxLength(100)]
[Display(Name ="FirstName", ResourceType =typeof(DisplayResolver))]
public string First { get => firstName; set => SetProperty(ref firstName, value, true); }

public static class ErrorResolver
{
    public static string CustomMinLength => "{0} min length of {1}";
}

In this example the error message template is retrieved from the CustomMinLength property (i.e. the ErrorMessgeResourceName property) on the ErrorResolver class (i.e. the ErrorMessageResourceType property). Our error message now looks like this:

The name of the property is coming from the FirstName property on the DisplayResolver class; the error message template coming from the CustomMinLength property on the ErrorResolver class.

As you can see there are a number of options available for customising how the error message for validation errors with the INotifyDataErrorInfo implementation will appear. We’ll come back to this again in a subsequent post to take this to the final step of looking up language based resources.

Leave a comment