是否可以检查 Blazor ValidationMessageStore 是否有任何错误消息

Is it possible to check if Blazor ValidationMessageStore has ANY error message

我根据此模式使用 ValidationMessageStore 验证我的 Blazor 表单输入:

https://docs.microsoft.com/en-us/aspnet/core/blazor/forms-validation?view=aspnetcore-5.0#basic-validation-1

然后我在每个控件上输出 ValidationMessage。

但它是一个长表单,所以我还想在提交按钮附近的某个地方向用户表明存在一些需要修复的错误,这就是我们尚未接受输入的原因。

我知道我可以使用 ValidationSummary,但我不想重复所有可能的错误,请注意。

ValidationMessageStore 显然将所有消息保存在一个内部集合中,但它们不可访问。是否可以通过某种方式检查是否有任何错误消息?

查看 ValidationSummary 代码 - 验证消息存储可用。它不是很复杂,因此您应该能够自己构建一个类似但更简单的组件来显示您想要的内容。

代码在这里:https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web/src/Forms/ValidationSummary.cs

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components.Rendering;

namespace Microsoft.AspNetCore.Components.Forms
{
    // Note: there's no reason why developers strictly need to use this. It's equally valid to
    // put a @foreach(var message in context.GetValidationMessages()) { ... } inside a form.
    // This component is for convenience only, plus it implements a few small perf optimizations.

    /// <summary>
    /// Displays a list of validation messages from a cascaded <see cref="EditContext"/>.
    /// </summary>
    public class ValidationSummary : ComponentBase, IDisposable
    {
        private EditContext? _previousEditContext;
        private readonly EventHandler<ValidationStateChangedEventArgs> _validationStateChangedHandler;

        /// <summary>
        /// Gets or sets the model to produce the list of validation messages for.
        /// When specified, this lists all errors that are associated with the model instance.
        /// </summary>
        [Parameter] public object? Model { get; set; }

        /// <summary>
        /// Gets or sets a collection of additional attributes that will be applied to the created <c>ul</c> element.
        /// </summary>
        [Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; }

        [CascadingParameter] EditContext CurrentEditContext { get; set; } = default!;

        /// <summary>`
        /// Constructs an instance of <see cref="ValidationSummary"/>.
        /// </summary>
        public ValidationSummary()
        {
            _validationStateChangedHandler = (sender, eventArgs) => StateHasChanged();
        }

        /// <inheritdoc />
        protected override void OnParametersSet()
        {
            if (CurrentEditContext == null)
            {
                throw new InvalidOperationException($"{nameof(ValidationSummary)} requires a cascading parameter " +
                    $"of type {nameof(EditContext)}. For example, you can use {nameof(ValidationSummary)} inside " +
                    $"an {nameof(EditForm)}.");
            }

            if (CurrentEditContext != _previousEditContext)
            {
                DetachValidationStateChangedListener();
                CurrentEditContext.OnValidationStateChanged += _validationStateChangedHandler;
                _previousEditContext = CurrentEditContext;
            }
        }

        /// <inheritdoc />
        protected override void BuildRenderTree(RenderTreeBuilder builder)
        {
            // As an optimization, only evaluate the messages enumerable once, and
            // only produce the enclosing <ul> if there's at least one message
            var validationMessages = Model is null ?
                CurrentEditContext.GetValidationMessages() :
                CurrentEditContext.GetValidationMessages(new FieldIdentifier(Model, string.Empty));

            var first = true;
            foreach (var error in validationMessages)
            {
                if (first)
                {
                    first = false;

                    builder.OpenElement(0, "ul");
                    builder.AddMultipleAttributes(1, AdditionalAttributes);
                    builder.AddAttribute(2, "class", "validation-errors");
                }

                builder.OpenElement(3, "li");
                builder.AddAttribute(4, "class", "validation-message");
                builder.AddContent(5, error);
                builder.CloseElement();
            }

            if (!first)
            {
                // We have at least one validation message.
                builder.CloseElement();
            }
        }

        /// <inheritdoc/>
        protected virtual void Dispose(bool disposing)
        {
        }

        void IDisposable.Dispose()
        {
            DetachValidationStateChangedListener();
            Dispose(disposing: true);
        }

        private void DetachValidationStateChangedListener()
        {
            if (_previousEditContext != null)
            {
                _previousEditContext.OnValidationStateChanged -= _validationStateChangedHandler;
            }
        }
    }
}

如果您在构建组件方面需要更多帮助,请在问题中添加您想要的更多详细信息。

我找到了一个更简单的解决方案来解决我的问题。在 EditContext 上,我找到了一个名为 GetValidationMessages 的方法。

@if (editContext.GetValidationMessages().Any())
{
    <div class="alert alert-danger">
        Some input was incomplete. Please review detailed messages above.
    </div>
}

我有几乎相同的问题。根据 Jakob Lithner 的回答,这是我想出的解决方案。

为了访问 ValidationSummary 是否有任何错误消息,您可以将 EditForm 绑定到 EditContext 而不是 Model。这样您就可以以编程方式直接引用上下文。这是一个使用模型对象和 razor 文件的简单示例,它将在每个已验证的表单下显示验证消息,并且如果任一表单无效,将显示一般错误消息。

ExampleModel.cs

using System.ComponentModel.DataAnnotations;

namespace Example.Pages;

public class ExampleModel
{
    [Required(ErrorMessage = "The object must have a name")]
    [StringLength(100, ErrorMessage = "Object names cannot exceed 100 characters")]
    public string Name { get; set; } = "New Example";
    [StringLength(1000, ErrorMessage = "Description cannot exceed 1000 characters")]
    public string Description { get; set; }
}

ExamplePage.razor

@page "/ExamplePage"

<EditForm EditContext="@EditContext" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator/>

    @* Where the edit context is checked for validation messages. *@
    @* This can go anywhere you want it. *@
    @* An alternative to "alert alert-danger" is "validation-message", 
        which contains the style for validation messages *@
    @if (EditContext is not null && EditContext.GetValidationMessages().Any())
    {
        <p class="alert alert-danger text-center">
            One or more errors must be fixed before changes can be saved
        </p>
    }
    
    <div class="mb-3">
        <div class="input-group">
            <span class="input-group-text">Name</span>
            <InputText class="form-control" @bind-Value="ExampleModel.Name"/>
        </div>
        <ValidationMessage For="@(() => ExampleModel.Name)"/>
    </div>

    <div class="mb-3">
        <label class="form-label" for="queryDescription">Object Description</label>
        <InputTextArea 
            class="form-control" 
            id="queryDescription" rows="3" 
            @bind-Value="ExampleModel.Description"/>
        <ValidationMessage For="() => ExampleModel.Description"/>
    </div>

    <div class="btn-group">
        <a class="btn btn-warning" href="/ExamplePage">Cancel</a>
        @* By signifying the type as submit, this is the button that triggers
            the validation event *@
        <button class="btn btn-success" type="submit">Create</button>
    </div>
</EditForm>

@code {
    private ExampleModel ExampleModel { get; set; } = new();
    private EditContext? EditContext { get; set; }

    protected override Task OnInitializedAsync()
    {
        // This binds the Model to the Context
        EditContext = new EditContext(ExampleModel);
        return Task.CompletedTask;
    }

    private void HandleValidSubmit()
    {
        // Process the valid form
    }
}

有关如何使用 Context 而不是 ModelEditForm 的更多详细信息,请参阅 Binding a form 上的表单和验证文档部分。