首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >仅在提交时使用Blazor验证表单

仅在提交时使用Blazor验证表单
EN

Stack Overflow用户
提问于 2021-12-20 10:15:32
回答 2查看 1.4K关注 0票数 3

我最近开始使用Blazor。是否有一种方法只在提交时触发表单模型验证,而不是针对每个更改进行活动?

为了澄清一下,假设我有这样的事情:

代码语言:javascript
复制
<EditForm Model="this" OnValidSubmit="SubmitForm">
    <DataAnnotationsValidator />
    <ValidationSummary />
            <Label For="Name">Name</Label>
            <InputText id="Name" name="Name" class="form-control" @bind-Value="Name"/>
    <button type="submit">Save</button>
</EditForm>

@code {
    [StringLength(10, ErrorMessage="Name too long")]
    public string Name { get; set; }

    private async Task SubmitForm()
    {
        // ...
        // send a POST request
    }
}

默认情况下,字段的有效性和ValidationSummary中显示的错误消息在每次文本输入更改时都会重新评估(例如,当我从输入中删除第11个字符时,“太长”消息就消失了)。

我希望显示的消息保持冻结状态,直到单击Submit按钮。

我认为可以通过删除ValidationSummary组件并实现自定义解决方案来实现它(例如,显示仅在提交时刷新的错误消息列表),但我想知道是否有一些我不知道的惯用解决方案。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-12-21 18:17:35

@enet的答案引发了另一个答案。构建您自己的DataAnnotationsValidator。

这是EditContext扩展代码。它是原始MS代码的一个修改版本,带有一些额外的控制参数。

代码语言:javascript
复制
using Microsoft.AspNetCore.Components.Forms;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;

namespace StackOverflowAnswers;

public static class EditContextCustomValidationExtensions
{
    public static IDisposable EnableCustomValidation(this EditContext editContext, bool doFieldValidation, bool clearMessageStore)
        =>  new DataAnnotationsEventSubscriptions(editContext, doFieldValidation, clearMessageStore);

    private static event Action? OnClearCache;

    private static void ClearCache(Type[]? _)
        =>  OnClearCache?.Invoke();

    private sealed class DataAnnotationsEventSubscriptions : IDisposable
    {
        private static readonly ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo?> _propertyInfoCache = new();

        private readonly EditContext _editContext;
        private readonly ValidationMessageStore _messages;
        private bool _doFieldValidation;
        private bool _clearMessageStore;

        public DataAnnotationsEventSubscriptions(EditContext editContext, bool doFieldValidation, bool clearMessageStore)
        {
            _doFieldValidation = doFieldValidation;
            _clearMessageStore = clearMessageStore;
            _editContext = editContext ?? throw new ArgumentNullException(nameof(editContext));
            _messages = new ValidationMessageStore(_editContext);

            if (doFieldValidation)
                _editContext.OnFieldChanged += OnFieldChanged;
            _editContext.OnValidationRequested += OnValidationRequested;

            if (MetadataUpdater.IsSupported)
            {
                OnClearCache += ClearCache;
            }
        }
        private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs)
        {
            var fieldIdentifier = eventArgs.FieldIdentifier;
            if (TryGetValidatableProperty(fieldIdentifier, out var propertyInfo))
            {
                var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model);
                var validationContext = new ValidationContext(fieldIdentifier.Model)
                {
                    MemberName = propertyInfo.Name
                };
                var results = new List<ValidationResult>();

                Validator.TryValidateProperty(propertyValue, validationContext, results);
                _messages.Clear(fieldIdentifier);
                foreach (var result in CollectionsMarshal.AsSpan(results))
                {
                    _messages.Add(fieldIdentifier, result.ErrorMessage!);
                }

                // We have to notify even if there were no messages before and are still no messages now,
                // because the "state" that changed might be the completion of some async validation task
                _editContext.NotifyValidationStateChanged();
            }
        }

        private void OnValidationRequested(object? sender, ValidationRequestedEventArgs e)
        {
            var validationContext = new ValidationContext(_editContext.Model);
            var validationResults = new List<ValidationResult>();
            Validator.TryValidateObject(_editContext.Model, validationContext, validationResults, true);

            // Transfer results to the ValidationMessageStore
            _messages.Clear();
            foreach (var validationResult in validationResults)
            {
                if (validationResult == null)
                {
                    continue;
                }

                var hasMemberNames = false;
                foreach (var memberName in validationResult.MemberNames)
                {
                    hasMemberNames = true;
                    _messages.Add(_editContext.Field(memberName), validationResult.ErrorMessage!);
                }

                if (!hasMemberNames)
                {
                    _messages.Add(new FieldIdentifier(_editContext.Model, fieldName: string.Empty), validationResult.ErrorMessage!);
                }
            }

            _editContext.NotifyValidationStateChanged();
        }

        public void Dispose()
        {
            if (_clearMessageStore)
                _messages.Clear();
            if (_doFieldValidation)
                _editContext.OnFieldChanged -= OnFieldChanged;
            _editContext.OnValidationRequested -= OnValidationRequested;
            _editContext.NotifyValidationStateChanged();

            if (MetadataUpdater.IsSupported)
            {
                OnClearCache -= ClearCache;
            }
        }

        private static bool TryGetValidatableProperty(in FieldIdentifier fieldIdentifier, [NotNullWhen(true)] out PropertyInfo? propertyInfo)
        {
            var cacheKey = (ModelType: fieldIdentifier.Model.GetType(), fieldIdentifier.FieldName);
            if (!_propertyInfoCache.TryGetValue(cacheKey, out propertyInfo))
            {
                // DataAnnotations only validates public properties, so that's all we'll look for
                // If we can't find it, cache 'null' so we don't have to try again next time
                propertyInfo = cacheKey.ModelType.GetProperty(cacheKey.FieldName);

                // No need to lock, because it doesn't matter if we write the same value twice
                _propertyInfoCache[cacheKey] = propertyInfo;
            }

            return propertyInfo != null;
        }

        internal void ClearCache()
            => _propertyInfoCache.Clear();
    }
}

CustomValidation组件:

代码语言:javascript
复制
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;

namespace StackOverflowAnswers;

public class CustomValidation : ComponentBase, IDisposable
{
    private IDisposable? _subscriptions;
    private EditContext? _originalEditContext;

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

    [Parameter] public bool DoEditValidation { get; set; } = false;

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

        _subscriptions = CurrentEditContext.EnableCustomValidation(DoEditValidation, true);
        _originalEditContext = CurrentEditContext;
    }

    /// <inheritdoc />
    protected override void OnParametersSet()
    {
        if (CurrentEditContext != _originalEditContext)
        {
            // While we could support this, there's no known use case presently. Since InputBase doesn't support it,
            // it's more understandable to have the same restriction.
            throw new InvalidOperationException($"{GetType()} does not support changing the " +
                $"{nameof(EditContext)} dynamically.");
        }
    }

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

    void IDisposable.Dispose()
    {
        _subscriptions?.Dispose();
        _subscriptions = null;

        Dispose(disposing: true);
    }
}

你可以这样使用它:

代码语言:javascript
复制
<EditForm EditContext=this.editContext OnValidSubmit=OnValidSubmit>
    <CustomValidation DoEditValidation=false/>
    @*<DataAnnotationsValidator/>*@
    <div class="row">
        <div class="col-2">
            Date:
        </div>
        <div class="col-10">
            <InputDate @bind-Value=this.Record.Date></InputDate>
        </div>
    </div>
.......
票数 2
EN

Stack Overflow用户

发布于 2021-12-20 18:37:49

当验证发生时,由您正在使用的Validator控制。

您可以从EditContext接收两个事件:

在调用OnValidationRequested时或作为表单提交过程的一部分调用EditContext.Validate

每次更改字段值时都会调用OnFieldChanged

验证器使用这些事件触发它的验证过程,并将结果输出到EditContext的ValidationMessageStore。

无论何时调用事件,DataAnnotationsValidator都会连接事件并触发验证。

还有其他的验证器,而且编写自己的也不太困难。除了那些通常的控制供应商之外,还有其他的,或者是我的。我的记录在这里- https://shauncurtis.github.io/articles/Blazor-Form-Validation.html。它有一个DoValidationOnFieldChange设置!

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70420295

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档