blazorblazor-server-side

ValidationMessageStore.Add() - not a field issue


If I get an exception saving the data in a form to a database, I want to display that error to the user. The easiest way is to call ValidationMessageStore.Add() with the appropiate message.

However, the first parameter to Add() is a FieldIdentifier object. Since this is not a filed specific issue, what should I pass?

thanks - dave


Solution

  • Trying to add to ValidationMessageStore is the wrong approach here. There are numerous posts on the web showing how to do so, but all of them are very complicated which is generally a sign that it's the wrong approach. And in this case it is.

    This is easy to accomplish, with a CustomValidation element. This is what it's designed for and it looks identical in the displayed validation issues.

    Step 1, add the file CustomValidation.cs (I put it in Shared). I got this code from the Microsoft documentation.

    // from https://learn.microsoft.com/en-us/aspnet/core/blazor/forms-and-input-components?view=aspnetcore-7.0

    using Microsoft.AspNetCore.Components;
    using Microsoft.AspNetCore.Components.Forms;
    
    namespace LouisHowe.web.Shared
    {
        public class CustomValidation : ComponentBase, IDisposable
        {
            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 += CurrentEditContext_OnValidationRequested;
                CurrentEditContext.OnFieldChanged += CurrentEditContext_OnFieldChanged;
            }
    
            private void CurrentEditContext_OnValidationRequested(object? sender, ValidationRequestedEventArgs e)
            {
                messageStore?.Clear();
            }
    
            private void CurrentEditContext_OnFieldChanged(object? sender, FieldChangedEventArgs 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();
            }
    
            /// <inheritdoc />
            public void Dispose()
            {
                if (CurrentEditContext != null)
                {
                    CurrentEditContext.OnValidationRequested -= CurrentEditContext_OnValidationRequested;
                    CurrentEditContext.OnFieldChanged -= CurrentEditContext_OnFieldChanged;
                }
            }
        }
    }
    

    Step 2, add CustomValidation to your .razor (example here uses DevExpress):

    <EditForm Model="@RegisterPageModel" OnValidSubmit="HandleValidSubmitAsync" Context="EditFormContext">
        <DataAnnotationsValidator/>
        <CustomValidation @ref="customValidation" />
        <DxFormLayout>
    

    Step 3, add the following to your razor page's code:

    private CustomValidation? customValidation;
    // ...
    async Task HandleValidSubmitAsync(EditContext editContext)
        {
        customValidation?.ClearErrors();
        // ...
        if (error)
            customValidation?.DisplayErrors(errors);
    

    Update: h/t to @MrCakaShaunCurtis, added in the Dispose() functionality.