petyusa
petyusa

Reputation: 193

ValidationMessage strange behaviour with custom attribute

I have a problem with displaying validation message in a Blazor app.

I have the following model (for testing purposes):

public class MyModel
{
    [Required(ErrorMessage = "Amount is required")]
    public int Amount { get; set; }

    [Required(ErrorMessage = "NullableAmount is required")]
    public int? NullableAmount { get; set; }

    [RequiredIf(nameof(Foo), new[] { "bar" }, ErrorMessage = "Id is required")]
    public int Id { get; set; }

    [RequiredIf(nameof(Foo), new[] { "bar" }, ErrorMessage = "NullableId is required")]
    public int? NullableId { get; set; }

    public string Foo { get; set; }
}

I decorated the properties with the built-in RequiredAttribute, and created a custom RequiredIfAttribute, here's the code for that:

public class RequiredIfAttribute : ValidationAttribute
{
    private readonly string _otherPropertyName;
    private readonly string[] _otherPropertyValues;

    public RequiredIfAttribute(string otherPropertyName, string[] otherPropertyValues)
    {
        _otherPropertyName = otherPropertyName;
        _otherPropertyValues = otherPropertyValues;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var instance = validationContext.ObjectInstance;
        var type = instance.GetType();
        var propertyValue = type.GetProperty(_otherPropertyName)?.GetValue(instance);

        if (!_otherPropertyValues.Contains(propertyValue?.ToString()))
        {
            return ValidationResult.Success;
        }

        if(value == null)
        {
            return new ValidationResult(ErrorMessage);
        }

        if(int.TryParse(value.ToString(), out var intValue))
        {
            if(intValue == 0)
            {
                return new ValidationResult(ErrorMessage);
            }
        }

        return ValidationResult.Success;
    }
}

I takes the name of the other property, the values for that property, and if the value of that other property matches one of the specified values, it checks if the property decorated with RequiredIf is null, or if it is an integer, 0. (I use it for a model with a nullable int property, which should not be null or 0 if the other property has a certain value).

On the WebAPI side it works fine, but in the Blazor form I have some problems. Here's my form:

<EditForm Model="MyModel" OnValidSubmit="OnSubmit">
    <div style="display: flex; flex-direction: column; width: 300px;">
        <DataAnnotationsValidator />
        <ValidationSummary />

        <label>
            Foo:
            <InputText @bind-Value="MyModel.Foo" />
        </label>
        <ValidationMessage For="@(() => MyModel.Foo)" />

        <label>
            Amount:
            <InputNumber @bind-Value="MyModel.Amount" />
        </label>
        <ValidationMessage For="@(() => MyModel.Amount)" />

        <label>
            Null Amount:
            <InputNumber @bind-Value="MyModel.NullableAmount" />
        </label>
        <ValidationMessage For="@(() => MyModel.NullableAmount)" />

        <label>
            Id:
            <InputNumber @bind-Value="MyModel.Id" />
        </label>
        <ValidationMessage For="@(() => MyModel.Id)" />

        <label>
            Null Id:
            <InputNumber @bind-Value="MyModel.NullableId" />
        </label>
        <ValidationMessage For="@(() => MyModel.NullableId)" />

        <button type="submit">submit</button>
    </div>



</EditForm>

I open the form, enter the value "bar" for Foo, than click submit, I get the following result: validation-result after submit button clicked

Amount is valid, as it has the default value 0, and RequiredAttribute only checks for nulls. NullAmount is invalid, it is shown in the ValidationSummary, the input field is also red, as it is invalid, and the validation message is also shown. Id is invalid, as in the RequiredIf attribute I check int value against 0, it has the default 0 value, the error message is shown in the ValidationSummary, and here comes the interesting part, the input field is not red, and the validation message is not shown. The same applies for the Nullable Id field, the error message is shown in the validation summary, but the field is not red, and the validation message is not shown.

Now, if I enter a valid value for Id and NullableId (the fields with my custom attribute), and after that I change Id to 0, and NullableId to empty, this is what happens:

validation-result after changing values

Both input fields change to red, validation message is shown for both properties, but in the validation summary, both error messages are displayed twice.

Now, if I click submit, both input fields change to green, and the validation messages are gone, but still displayed in the summary.

value after submitting again

I'm totally lost, don't know if I have a problem with my custom validation attribute, a problem in the Blazor component, or it is an unknown issue. The built-in Required attribute works fine, I just added it to the code to see if my form works with that.

Do you have any idea?

Upvotes: 0

Views: 1114

Answers (2)

Ryan Faricy
Ryan Faricy

Reputation: 96

Thank you so much @petyusa, I also ran into this issue and it was driving me nuts. I stumbled across your solution and it solved it immediately. However, I had to pass an array of string to the second argument (perhaps because I'm using .NET 5.0) instead of a simple string:

return new ValidationResult(ErrorMessage, new []{validationContext.MemberName});

Upvotes: 1

petyusa
petyusa

Reputation: 193

I've finally found the problem. In the validation attribute, when returning an error result, I have to pass memberName in the constructor.

return new ValidationResult(ErrorMessage, validationContext.MemberName)

Upvotes: 3

Related Questions