0

I have a WPF application that uses the ObservableValidator to handle validation of properties using data annotations. This works great for string properties. For example:

public class LoginViewModel : ObservableValidator
{
    [ObservableProperty]
    [NotifyDataErrorInfo]
    [Required]
    [StringLength(64, MinimumLength = 8)]
    private string username = String.Empty;

    [RelayCommand]
    private void LogIn()
    {
        ValidateAllProperties();

        if(HasErrors)
        {
            return;
        }
        
        // Log in the user
    }

    // ...
}

When I bind the Username property to a text box using <TextBox Text="{Binding Username, ValidatesOnNotifyDataErrors=True}" />, I automatically get validation messages in my view that appear around the textbox!

However, I don't know how I'm supposed to handle cases where I need to validate non-string properties. For example:

public class User : ObservableValidator
{
    [ObservableProperty]
    [NotifyDataErrorInfo]
    [Required]
    [Range(0, 200)]
    private int age = 10;
}

If I used the same approach of <TextBox Text="{Binding Age, ValidatesOnNotifyDataErrors=True}" /> and the user enters a value that's not an integer like "12aaa", the default value converter on the binding throws an exception saying that "12aaa" cannot be converted into an integer. These value conversion exceptions can't be detected from my view model since the binding engine never updates the property value.

Thus, calling ValidateAllProperties() sets HasError to false, even though the user entered invalid data!

I see a few ways to handle these impossible-to-detect errors:

  1. Prevent the user from ever entering invalid data. This seems feasible at first, but becomes harder with more complicated types (e.g., a user input for a TimeSpan).
  2. Add string fields for each of the non-string properties. Even if you do this, I don't know how to propagate validation errors from the typed properties to the non-typed counterparts so they show up around the appropriate text box.
  3. Probably some other options I haven't thought of.

Are there any recommended ways of handling conversion errors for non-string properties with the MVVM Toolkit's ObservableValidator? Thanks in advance for your help!

2
  • 1
    Int isn't a string. Don't set it to strin.empty. Maybe make it int? And null. But that's not valid of course. Maybe int and 0 initially. Commented Jun 16, 2023 at 17:13
  • @Andy good catch--that was just a typo when I copied things over to StackOverflow. Commented Jun 16, 2023 at 17:16

3 Answers 3

2

This is a runtime exception and not thrown by the library you use. The exception is thrown before the value is assigned to the source property (when the binding engine tries to convert from target type string to the source type int). The exception is not related to data validation.

TextBox.Text is of type string. User.Age is of type int. The exception is thrown because the binding tries to assign a string value from the TextBox to an int property. There is no implicit cast from string to int. In case the source property type differs from the target property type (and there is no implicit type conversion) Binding markup extension will use a default converter so that e.g. an int value can be send/assigned to a string binding target property. But this only works if there exists a default conversion.

For example, "123" is purely numeric and can be converted to int using a default converter. But "123abc" is alphanumeric and the standard conversion to int fails and the binding engine throws the exception.

You can:

  1. Make User.Age of type string
  2. Add a User.AgeText property of type string to bind to the TextBox and convert it to int in your data model for example using int.TryParse from the property setter
  3. Implement an IValueConverter to convert the binding target value from string to int e.g. with the help of int.TryParse.
  4. Extend TextBox and implement an e.g. NumericTextBox where the input is converted from string to int or the input is validated to ensure a valid numeric value (so that the default converter can handle it). The validation of the value itself (e.g. if the numeric value is within a particular range) is still implemented in the data source (view model class)
Sign up to request clarification or add additional context in comments.

2 Comments

I believe option 4 is the best approach for my application. After I implement the fix, I'll add it onto the original post. Thanks for your help!
I agree. I personally think a NumericTextBox is the most convenient reusable solution.
2

How about using CustomValidation?

public partial class LoginViewModel : ObservableValidator
{
    public LoginViewModel()
    {
        ValidateAllProperties();
    }

    [ObservableProperty]
    [NotifyDataErrorInfo]
    [Required]
    [StringLength(64, MinimumLength = 8)]
    private string? username = String.Empty;

    [ObservableProperty]
    [NotifyDataErrorInfo]
    [Required]
    [Range(0, 200)]
    [CustomValidation(typeof(LoginViewModel), nameof(ValidateAge))]
    private string? age = "10";

    public static ValidationResult ValidateAge(string age, ValidationContext context)
    {
        if (int.TryParse(age, out int number))
        {
            return ValidationResult.Success;
        }

        return new("Validation failed/show error message");
    }

    [RelayCommand]
    private void LogIn()
    {
        ValidateAllProperties();

        if (HasErrors)
        {
            return;
        }

        // Log in the user
    }
}

1 Comment

Note that the original problem is not related to the data validation. It's an invalid cast exception that is thrown because the TextBox.Text string property is bound to an int property (Age). The exception is thrown before the value is assigned to the source property (and therefore before the value is eligible for validation).
0

You should try custom validation methods or custom validation attributes.

1 Comment

The value will not transfer to the viewmodel at all.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.