在 blazor razor 页面中使用 editform 时如何重置自定义验证错误

Ole*_*ers 7 validation razor blazor blazor-server-side

我有一个使用编辑上下文的编辑表单:

    <EditForm OnValidSubmit="HandleValidSubmit" EditContext="_editContext" Context="auth">
      <DataAnnotationsValidator />
      <input type="time" @bind-value="_foodTruck.EndDelivery" @onkeydown="@(q=>ResetValidation("EndDelivery"))" >
        <ValidationMessage For="() => _foodTruck.EndDelivery" />
      <input type="time" @bind-value="_foodTruck.StartDelivery" @onkeydown="@(q=>ResetValidation("StartDelivery"))" >
        <ValidationMessage For="() => _foodTruck.StartDelivery" />
      <input class="btn btn-default" type="submit" value="save" />
    </EditForm>
Run Code Online (Sandbox Code Playgroud)

我在 HandleValidSubmit 中做了一些自定义验证:

EditContext _editContext = new EditContext(_foodTruck);
private async void HandleValidSubmit()
{
  var messageStore = new ValidationMessageStore(_editContext);
  if (_foodTruck.StartDelivery >= _foodTruck.EndDelivery)
  {
    messageStore.Add(_editContext.Field("EndDelivery"), "Bad time entered");
    _editContext.NotifyValidationStateChanged();
  }
 if (!_editContext.Validate()) return;
}
Run Code Online (Sandbox Code Playgroud)

现在发生的是我的自定义错误(“输入的错误时间”)显示在正确的位置。唯一的问题是:当我更改值时,该错误不会消失。因此,如果我单击提交按钮,则永远不会再次调用 HandleValidSubmit。

我还尝试在修改字段时清空验证错误:

   protected void ResetValidation(string field)
    {
        var messageStore = new ValidationMessageStore(_editContext);        
        messageStore.Clear(_editContext.Field(field));
        messageStore.Clear();
        _editContext.NotifyValidationStateChanged();
    }
Run Code Online (Sandbox Code Playgroud)

这由 调用onkeydown。但这似乎也没有效果。Errormessage 不会消失,因此HandleValidSubmit也不会被调用。

Xam*_*Xam 9

我遇到了与原始海报相同的问题,因此我决定研究一下 EditContext 的源代码(谢谢 source.dot.net!)。因此,我提出了一种解决方法,该解决方法应该足以满足 Blazor 团队在未来版本中正确解决该问题的要求。

/// <summary>
/// Contains extension methods for working with the <see cref="EditForm"/> class.
/// </summary>
public static class EditFormExtensions
{
    /// <summary>
    /// Clears all validation messages from the <see cref="EditContext"/> of the given <see cref="EditForm"/>.
    /// </summary>
    /// <param name="editForm">The <see cref="EditForm"/> to use.</param>
    /// <param name="revalidate">
    /// Specifies whether the <see cref="EditContext"/> of the given <see cref="EditForm"/> should revalidate after all validation messages have been cleared.
    /// </param>
    /// <param name="markAsUnmodified">
    /// Specifies whether the <see cref="EditContext"/> of the given <see cref="EditForm"/> should be marked as unmodified.
    /// This will affect the assignment of css classes to a form's input controls in Blazor.
    /// </param>
    /// <remarks>
    /// This extension method should be on EditContext, but EditForm is being used until the fix for issue
    /// <see href="https://github.com/dotnet/aspnetcore/issues/12238"/> is officially released.
    /// </remarks>
    public static void ClearValidationMessages(this EditForm editForm, bool revalidate = false, bool markAsUnmodified = false)
    {
        var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

        object GetInstanceField(Type type, object instance, string fieldName)
        {                
            var fieldInfo = type.GetField(fieldName, bindingFlags);
            return fieldInfo.GetValue(instance);
        }

        var editContext = editForm.EditContext == null
            ? GetInstanceField(typeof(EditForm), editForm, "_fixedEditContext") as EditContext
            : editForm.EditContext;

        var fieldStates = GetInstanceField(typeof(EditContext), editContext, "_fieldStates");
        var clearMethodInfo = typeof(HashSet<ValidationMessageStore>).GetMethod("Clear", bindingFlags);

        foreach (DictionaryEntry kv in (IDictionary)fieldStates)
        {
            var messageStores = GetInstanceField(kv.Value.GetType(), kv.Value, "_validationMessageStores");
            clearMethodInfo.Invoke(messageStores, null);
        }

        if (markAsUnmodified)
            editContext.MarkAsUnmodified();

        if (revalidate)
            editContext.Validate();
    }
}
Run Code Online (Sandbox Code Playgroud)


Ole*_*ers 8

我通过在验证重置时创建一个新的 EditContext 解决了这个问题。所以我只是在 ResetValidation-Method 中添加了以下行:

  _editContext = new EditContext(_foodTruck);
Run Code Online (Sandbox Code Playgroud)

但老实说:那感觉不对。因此,我将对此保持开放,以便获得更好的答案(希望如此)。


Mee*_*eer 5

我有同样的问题。我找不到简单的解决方案。类似于下面的解决方法对我有用。

修改 EditForm 如下 -

<EditForm EditContext="_editContext" OnSubmit="HandleSubmit">
Run Code Online (Sandbox Code Playgroud)

@代码块

EditContext _editContext;

ValidationMessageStore msgStore;

FoodTruck _foodTruck= new FoodTruck();

protected override void OnInitialized()
{
    _editContext = new EditContext(_foodTruck);
    msgStore = new ValidationMessageStore(_editContext);
}

void HandleSubmit()
{
    msgStore.Clear();
    if(_editContext.Validate()) // <-- Model Validation
    {
        if (_foodTruck.StartDelivery >= _foodTruck.EndDelivery) //<--Custom validation
        {
            msgStore = new ValidationMessageStore(_editContext);
            msgStore.Add(_editContext.Field("EndDelivery"), "Bad time entered");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


MrC*_*tis 5

由于这个问题仍然出现在搜索中并且人们正在引用它,因此这个答案解释了问题存在的原因并展示了如何解决它。

让我们看看各种答案并消除一些都市神话和巫术:

  1. 重置EditContext不是答案,只是一个巫术修复。它破坏的东西比修复的东西多:EditContext除非你真的知道自己在做什么,否则永远不应该重置。

  2. 调用StateHasChanged通常是基本逻辑设计有缺陷时强制UI更新的无奈之举。如果你必须编码,那么StateHasChanged你需要认真问自己:为什么?

  3. 其他答案都是黑客。它们在某些情况下可以工作,但不能保证您的设计。

问题的根本原因是对aValidationMessageStore是什么以及如何使用和管理它的误解。

ValidationMessageStore比第一次出现的情况要复杂一些。它不是一个保存从各种来源记录的所有验证消息的存储:_messageStore = new ValidationMessageStore(_editContext);应该是一个线索,特别是new。您应该在实例化组件时获取实例,然后向该实例添加消息并清除该实例中的消息。每次调用方法时都创建一个根本行不通。您只是创建一个新的空ValidationMessageStore.

这是问题中代码的工作版本:

@page "/"

<PageTitle>Index</PageTitle>

@if (loaded)
{
    <EditForm OnValidSubmit="HandleValidSubmit" EditContext="_editContext" Context="auth">
        <DataAnnotationsValidator />
        <div class="p-2">
            End Delivery
            <input type="time" @bind-value="_foodTruck.EndDelivery" @onkeydown="@(()=>ResetValidation("EndDelivery"))">
            <ValidationMessage For="() => _foodTruck.EndDelivery" />
        </div>
        <div class="p-2">
            Start Delivery
            <input type="time" @bind-value="_foodTruck.StartDelivery" @onkeydown="@(()=>ResetValidation("StartDelivery"))">
            <ValidationMessage For="() => _foodTruck.StartDelivery" />

        </div>
        <div class="p-2 text-end">
            <input class="btn btn-primary" type="submit" value="save" />
        </div>
        <div class="p-2 text-end">
            Counter: @counter
        </div>
    </EditForm>
}

@code {
    private FoodTruck _foodTruck = new FoodTruck();
    private EditContext? _editContext;
    private ValidationMessageStore? _messageStore;
    private ValidationMessageStore messageStore => _messageStore!;
    private int counter;
    private bool loaded;

    protected override async Task OnInitializedAsync()
    {
        // emulate gwtting some async data
        await Task.Delay(100);
        FoodTruck _foodTruck = new FoodTruck();
        // assign the mdel data to the Edit Context
        _editContext = new EditContext(_foodTruck);
        // Get the ValidationMessageStore
        _messageStore = new ValidationMessageStore(_editContext);
        loaded = true;
    }

    private void HandleValidSubmit()
    {
        if (_editContext is not null)
        {
            // create a FieldIdentifier for EndDelivery
            var fi = new FieldIdentifier(_foodTruck, "EndDelivery");
            // Clear the specific entry from the message store using the FieldIdentifier
            messageStore.Clear(fi);

            if (_foodTruck.StartDelivery >= _foodTruck.EndDelivery)
            {
                // Add a validation message and raise the validation state change event
                messageStore.Add(fi, "Bad time entered");
                _editContext.NotifyValidationStateChanged();
            }
        }
    }

    protected void ResetValidation(string field)
    {
        counter++;
        if (_editContext is not null)
        {
            // clear the validation message and raise the validation state change event
            messageStore.Clear(new FieldIdentifier(_foodTruck, field));
            _editContext.NotifyValidationStateChanged();
        }
    }

    public class FoodTruck
    {
        public TimeOnly EndDelivery { get; set; }
        public TimeOnly StartDelivery { get; set; }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我刚刚重新阅读了其他答案 - 多么一堆的拼凑和巫毒! (2认同)

Jes*_*tan 2

在事件操作的末尾添加 this.StateHasChanged() ,以便它可以再次渲染 ui 元素并删除验证消息。

EditContext _editContext = new EditContext(_foodTruck);
private async void HandleValidSubmit()
{
  var messageStore = new ValidationMessageStore(_editContext);
  if (_foodTruck.StartDelivery >= _foodTruck.EndDelivery)
  {
    messageStore.Add(_editContext.Field("EndDelivery"), "Bad time entered");
    _editContext.NotifyValidationStateChanged();
     this.StateHasChanged(); //this line
  }
 if (!_editContext.Validate()) return;
}
Run Code Online (Sandbox Code Playgroud)

对于另一个

protected void ResetValidation(string field)
{
        var messageStore = new ValidationMessageStore(_editContext);        
        messageStore.Clear(_editContext.Field(field));
        messageStore.Clear();
        _editContext.NotifyValidationStateChanged();
        this.StateHasChanged(); //this line
}
Run Code Online (Sandbox Code Playgroud)

请告诉我是否有效

  • 遗憾的是没有任何效果 (3认同)