CustomValidator

Hello,
First I would like to say that Radzden components are really great job. I have already tested lots of them and practically no issues :slight_smile:

Unfortunatelly I got first one today I cannot figure out how to make it work. I am doing component with CustomValidator component. I am testing it with the Blazor WASM (.NET 8).

The trouble is that Validator does not display the text. Main validator function works fine. When I place breakpoint inside of the validator custom function it jumps there and logic works. Just the warning text does not show up. I tried to add the Popup parameter with "false" value but it did not help.

I tried it first without the FieldSet, Row and Column, Then I tried to add those because I thought it might be related, but it still does not work. Could you help me what is wrong please?

Here is the code of the component:


@using System.Text.RegularExpressions
@inject NotificationService NotificationService

<div class="rz-p-12 @getAlign()">
    <RadzenFieldset Text="DevEUI">
        <RadzenRow>
            <RadzenColumn>
                <RadzenTextBox Name="DevEUI" Value=@Value ValueChanged="@OnValueChangedHandler" Placeholder="Enter DevEUI" Style="display: block; width: 100%;" />
                <RadzenRequiredValidator Component="DevEUI" Text="DevEUI is required" />
                <RadzenCustomValidator Component="DevEUI" Text="Invalid DevEUI. Must be 16 hex characters." Validator=@(() => ValidateDevEUI(Value)) />
            </RadzenColumn>
        </RadzenRow>
    </RadzenFieldset>
</div>

@code {
    [Parameter] public string Value { get; set; } = string.Empty;
    [Parameter] public EventCallback<string> ValueChanged { get; set; }
    [Parameter] public string Align { get; set; } = "center";

    public bool ValidateDevEUI(object value)
    {
        string input = value as string;
        var res = !string.IsNullOrEmpty(input) && Regex.IsMatch(input, @"^[0-9A-Fa-f]{16}$");
        if (!res)
        {
            NotificationService.Notify(
            new NotificationMessage
                {
                    Severity = NotificationSeverity.Error,
                    Summary = "Wrong DevEUI Format",
                    Detail = $"DevEUI must be 16 hex characters. Do not use '-' between hexs.",
                    Duration = 2000
                });
        }
        return res;
    }

    private async Task OnValueChangedHandler(string value)
    {
        Value = value;
        if (ValidateDevEUI(value))
        {
            await ValueChanged.InvokeAsync(value);
        }
    }

    public string getAlign()
    {
        if (Align == "left")
            return "rz-text-align-start";
        else if (Align == "right")
            return "rz-text-align-end";
        else
            return "rz-text-align-center";
    }
}

You must use @bind-Value in order for validation to work (or find a way to set ValueExpression). Setting Value and ValueChanged will not initialize ValueExpression as opposed to @bindValue. And ValueExpression is what Blazor uses to validate.

Also here is the relevant code from Blazor itself: aspnetcore/src/Components/Web/src/Forms/InputBase.cs at main ยท dotnet/aspnetcore ยท GitHub

UPDATE: Found a relevant thread in StackOverflow:

<div>
    <p>My custom input wrapper</p>
    @* If you pass @bind-Value it won't work*@
    @* You need to pass the properties that are used in the bind*@
    <RadzenTextBox Value="@Value" ValueChanged="@ValueChanged" ValueExpression="@ValueExpression" />
</div>

@code {    
    [Parameter]
    public virtual string Value { get; set; }

    [Parameter]
    public EventCallback<string> ValueChanged { get; set; }

    [Parameter]
    public Expression<Func<string>> ValueExpression { get; set; }        
}

Thank you for info, but it does not help. Even if I use the bind-Value it still does not display the info about validation not passed. Btw, it should work even with ValueChanged because if I set the Value manually inside of my ValueChanged handler it actually call the "Set" function of the public property so it is equivalent. My trouble is the that CustomValidator component does not display the label. It works in field of the validation, just label is missing.

No, it will not work unless ValueExpression is set. I have provided links to the actual implementation (which I am the author of - at least in Radzen.Blazor).

It tried to change it like this (if I understand right what you are reffering to). But it still does not work:

Here is new start part of the component:


@using System.Text.RegularExpressions
@using System.Linq.Expressions;
@inject NotificationService NotificationService

<div class="rz-p-12 @getAlign()">
    <RadzenFieldset Text="DevEUI">
        <RadzenRow>
            <RadzenColumn>
                <RadzenTextBox Name="DevEUI" @bind-Value=@Value
                               Placeholder="Enter DevEUI" 
                               Style="@getStyle()" />
                <RadzenRequiredValidator Component="DevEUI" Text="DevEUI is required" />
                <RadzenCustomValidator Component="DevEUI" Text="Invalid DevEUI. Must be 16 hex characters." Validator=@(() => ValidateDevEUI(Value)) Popup="@true" />
            </RadzenColumn>
        </RadzenRow>
    </RadzenFieldset>
</div>

@code {
    private string text = string.Empty;
    [Parameter] public string Value
    {
        get => text;
        set
        {
            if (text != value)
            {
                text = value;
                if (ValueChanged.HasDelegate)
                {
                    if (ValidateDevEUI(value))
                        ValueChanged.InvokeAsync(value);
                }
            }
        }
    }

    [Parameter] public EventCallback<string> ValueChanged { get; set; }
    [Parameter] public Expression<Func<string>> ValueExpression { get; set; }

I provided a code snippet which shows what you should do. Please check my reply again - also the linked source and stackoverflow question and answer.

Sorry for my stupid questions, but I probably still do not understand how does it work. I changed it now based on your snippet and still no luck. It even stopped to jump into ValidateDevEUI function now.

@using System.Text.RegularExpressions
@using System.Linq.Expressions;
@inject NotificationService NotificationService

<div class="rz-p-12 @getAlign()">
    <RadzenFieldset Text="DevEUI">
        <RadzenRow>
            <RadzenColumn>
                <RadzenTextBox Name="DevEUI" Value=@Value ValueChanged="@ValueChanged" ValueExpression="@ValueExpression"
                               Placeholder="Enter DevEUI" 
                               Style="@getStyle()" />
                <RadzenRequiredValidator Component="DevEUI" Text="DevEUI is required" />
                <RadzenCustomValidator Component="DevEUI" Text="Invalid DevEUI. Must be 16 hex characters." Validator=@(() => ValidateDevEUI(Value)) Popup="@true" />
            </RadzenColumn>
        </RadzenRow>
    </RadzenFieldset>
</div>

@code {
    [Parameter] public virtual string Value { get; set; }
    [Parameter] public EventCallback<string> ValueChanged { get; set; }
    [Parameter] public Expression<Func<string>> ValueExpression { get; set; }

Here is a minimal example that works:

@using System.Linq.Expressions;

<RadzenTextBox Value=@Value ValueChanged=@ValueChanged ValueExpression=@ValueExpression Name=@Name />
<RadzenRequiredValidator Component=@Name Text="@($"{Name} is required")" />
<RadzenCustomValidator Component=@Name Text="@($"{Name} should be at least 2 characters")" Validator=@(Validate)  />

@code {
    [Parameter]
    public string Name {get; set;}
    [Parameter]
    public string Value { get; set; }

    [Parameter]
    public EventCallback<string> ValueChanged { get; set; }

    [Parameter]
    public Expression<Func<string>> ValueExpression { get; set; }


    private bool Validate()
    {
        var value = ValueExpression.Compile().Invoke();

        return value?.Length >= 2;
    }
}

Used like this

 <MyTextBox Name="FirstName" @bind-Value=@model.FirstName />

The important nuance is getting the current value via ValueExpression.Compile().Invoke(). If you just use Value it won't work as it wouldn't have been updated yet at the time the Validate method is invoked.

Thank you for this example. I finally understand how to combine this ValueExpression with it. Unfortunatelly, I have tested your latest minimalistic example in the Blazor WASM and it does not work. Could it be some trouble with using it in WASM?

Seems to work just fine in WASM. There is nothing server-side specific in the implementation after all.
custom-validator

It is probably better to share the recording of demo because it looks you do not trust me that I really have the trouble. This is copy paste of minimalistic demo you have shared:

radzen-customvalidator

Could you please help me to understand why it does not work? I use .NET 8 Blazor WASM.

Radzen.Blazor form validation requires RadzenTemplateForm. Your page does not seem to have one.

oh, ok. That is what I was missing whole time. It looks it works now as you have shown. Thanks you very much for this analysis/help.
If may I suggest you could add this info into docs page: Blazor CustomValidator Component | Free UI Components by Radzen