RadzenRequiredValidator, ValidationMessage Issues

I have included a trimmed down version of a page that is shown in a dialog and a CustomValidation class below that. The CustomValidation instance is created in the form markup. I initially chose to use a RadzenRequiredValidator for the email address. It worked fine on the client side. We also send back a list of errors from the server in the form of a Dictionary<string, List> produced by FluentValidation. If errors exist we call customValidation?.DisplayErrors(response.Errors); This works with the standard ValidationMessage tag (seen in the page code below). The RadzenRequiredValidator does not show the errors that come back from the server. I added both a RadzenRequiredValidator and a ValidationMessage to do some testing and what happened surprised me a bit. Clearing the email text box and submitting caused the client validation to fail and both tags showed the error. I wasn't expecting the ValidationMessage to do anything.

Questions

  1. Why doesn't the RadzenRequiredValidator show the error after the call to customValidation?.DisplayErrors(response.Errors)? (You can see the code for DisplayErrors() in the included class.)

  2. Why does the ValidationMessage suddenly start showing a message when a RadzenRequiredValidator is declared for the same edit box?

  3. Is there a simple way to get the RadzenRequiredValator to show an error message returned from the server? (I would like to do this and eliminate the ValidationMessage completely)

Any thoughts on how to validate from both the client and server side using Radzen components is welcomed.

If you got this far, thanks for taking the time to read it.

image

Razor Page

@page "/editUser"

@using Radzen
@using System.Collections;

@inject DialogService DialogService
@inject NotificationService NotificationService
@inject IUserService UserService
@inject IRoleService RoleService

<RadzenTemplateForm @ref="form" Data="@user" Context="CurrentEditContext" Submit="@(async (EditUserRequest user) => { await Submit(user); })">
				<!-- Custom Validation created here. -->
    <CustomValidation @ref="customValidation" />
				<div class="row">
								<div class="col-lg-6">
												<RadzenLabel Text="Username" />
								</div>
								<div class="col-lg-6">
												<RadzenLabel Text="Full Name" />
								</div>
				</div>
				<div class="row pb-2">
								<div class="col-lg-6">
												<RadzenTextBox class="w-100" Name="UserName" @bind-Value="user.UserName" />
												<ValidationMessage class="rz-message rz-messages-error" For="() => user.UserName" />
								</div>
								<div class="col-lg-6">
												<RadzenTextBox class="w-100" Name="FullName" @bind-Value="user.FullName" />
								</div>
				</div>
				<div class="row">
								<div class=" col-lg-12">
												<RadzenLabel Text="Email" />
								</div>
				</div>
				<div class="row pb-2">
								<div class="col-lg-12">
												<RadzenTextBox class="w-100" Name="Email" @bind-Value="user.Email" />
												<!-- Validators -->
												<RadzenRequiredValidator Component="Email" Text="Email is required" Style="display:block" />
												<ValidationMessage class="rz-message rz-messages-error" For="() => user.Email" />
								</div>
				</div>
    <div class="row">
        <div class="ml-auto pr-3">
            <RadzenButton Text="Validate" Click="@(args => form.EditContext.Validate())" />
            <RadzenButton Variant="Variant.Flat" Text="Delete" Style="width: 7.5rem" Click="@(() => DeleteClick(user))" Disabled="user != null ? user.Id.Equals(Guid.Empty) : false" />
            <RadzenButton Click="@((args) => DialogService.Close(false))" Variant="Variant.Flat" ButtonStyle="ButtonStyle.Light" Text="Cancel" Style="width: 7.5rem" Class="me-1" />
            <RadzenButton ButtonType="ButtonType.Submit" Variant="Variant.Flat" Text="Save" Style="width: 7.5rem" />
        </div>
    </div>
</RadzenTemplateForm>

@code {
    [Parameter]
    public Guid Id { get; set; }

    private EditUserRequest user = new();
    private CustomValidation? customValidation;
    private RadzenTemplateForm<EditUserRequest>? form = null;

    private async Task Submit(EditUserRequest user)
    {
        Console.WriteLine($"Submit() User: {user.UserName} {user.FullName}");

        customValidation?.ClearErrors();

        if (!Id.Equals(default))
        {
            var response = await UserService.UpdateUser(user);
            if (response.Success)
            {
                DialogService.Close(true);
                NotificationService.ShowSuccessNotification("User Updated");
            }
            else if (response.Invalid)
            {
                customValidation?.DisplayErrors(response.Errors);
            }
            else
                NotificationService.ShowErrorNotification("An error has ocurred");
        }
        else
        {
            var response = await UserService.CreateUser(user);
            if (response.Success)
            {
                DialogService.Close(true);
            }
            else if (response.Invalid)
            {
                customValidation?.DisplayErrors(response.Errors);
            }
            else
                NotificationService.ShowErrorNotification("An error has ocurred");
        }
    }
}

CustomValidator.cs

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;

namespace Test.Client.Services
{
    public class CustomValidation : ComponentBase
    {
        private ValidationMessageStore? messageStore;

        [CascadingParameter]
        private EditContext? CurrentEditContext { get; set; }

        protected override void OnInitialized()
        {
            if (CurrentEditContext is null)
            {
                throw new InvalidOperationException(
                    $"{nameof(CustomValidation)} requires a cascading " +
                    $"parameter of type {nameof(EditContext)}. " +
                    $"For example, you can use {nameof(CustomValidation)} " +
                    $"inside an {nameof(EditForm)}.");
            }

            messageStore = new(CurrentEditContext);

            CurrentEditContext.OnValidationRequested += (s, e) =>
                messageStore?.Clear();
            CurrentEditContext.OnFieldChanged += (s, e) =>
                messageStore?.Clear(e.FieldIdentifier);
        }

        public void DisplayErrors(Dictionary<string, List<string>> errors)
        {
            if (CurrentEditContext is not null)
            {
                foreach (var err in errors)
                {
                    messageStore?.Add(CurrentEditContext.Field(err.Key), err.Value);
                }

                CurrentEditContext.NotifyValidationStateChanged();
            }
        }

        public void ClearErrors()
        {
            messageStore?.Clear();
            CurrentEditContext?.NotifyValidationStateChanged();
        }
    }
}

Hi @usedbyanr,

The RadzenRequiredValidator works only with the built-in form validation via EditContext. You can check the implementation in ValidatorBase and RadzenRequiredValidator. It would answer all your questions.