【问题标题】:Notify EditContext that field has changed for Blazor validation通知 EditContext 该字段已更改以进行 Blazor 验证
【发布时间】:2021-08-09 21:09:08
【问题描述】:

我正在尝试了解 Blazor 的内部工作原理(并最终编写一些中间件)。我有一个fiddle,它以三种不同的方式绑定三个不同的字段:

  • FirstName 字段绑定到 InputText 按预期工作,并在清除框和焦点更改时显示验证消息。
  • MiddleName 字段绑定到常规输入,不能“按预期”工作,并且在清除该字段和焦点更改时不显示任何验证消息。通过 Blazor 源,我发现 EditContext.NotifyFieldChanged 如果控件本身(InputBase)是一个问题。
    • 值得注意的是,如果您提交我想了解所涉及的生命周期的表单,则会显示该消息。
  • LastName 字段也绑定到常规输入,但需要做一些小技巧来引发 EditContext.OnFieldChanged 并触发验证工作。 有没有更好的方法来为 onchange 做到这一点?提交如何设法引发验证消息? 有很多引用捕获、表达式、反射和使用“仅供内部使用”的 CreateBinder 方法。

下面是小提琴的代码,方便参考:

@page "/"
@using System.ComponentModel.DataAnnotations
@using System.Linq.Expressions;
@using System.Reflection;
@implements IHasEditContext;

<h1>Hello, world!</h1>

<EditForm EditContext="this.EditContextRef">
    <DataAnnotationsValidator></DataAnnotationsValidator>
    <div class="form-group">
        <InputText @bind-Value="@this.FirstName" class="form-control" />
        <ValidationMessage For="() => this.FirstName"></ValidationMessage>
    </div>
    <div class="form-group">
        <input @bind-value="@this.MiddleName" class="form-control" />
        <ValidationMessage For="() => this.MiddleName"></ValidationMessage>
    </div>
    <div class="form-group">
        <input value="@this.LastName" class="form-control" @onchange="(CreateBinder2(this, () => this.LastName,  this.LastName))" />
        <ValidationMessage For="() => this.LastName"></ValidationMessage>
    </div>


    <input type="submit" value="Go" />
</EditForm>

@code {
    protected override void OnInitialized()
    {
        base.OnInitialized();
        EditContextRef = new EditContext(this);
    }

    //BasicFormValidator Form1Validator = new BasicFormValidator();
    [Required]
    public String FirstName { get; set; } = "delete me and change focus to cause validation";
    [Required]
    public String MiddleName { get; set; } = "delete me and change focus - no validation";
    [Required]
    public String LastName { get; set; } = "delete me and change focus - validation but manually calling NotifyFieldChanged";

    public EditContext EditContextRef { get; set; }

    public static EventCallback<ChangeEventArgs> CreateBinder2(
        IHasEditContext receiver,
        Expression<Func<string?>> propExpression,
        string existingValue,
        System.Globalization.CultureInfo? culture = null)
    {
        var fieldIdentifier = FieldIdentifier.Create(propExpression);
        Action<String> valueSetter = (string v) =>
        {
            PropertyInfo prop = fieldIdentifier.Model.GetType().GetProperty(fieldIdentifier.FieldName, BindingFlags.Public | BindingFlags.Instance);
            prop.SetValue(fieldIdentifier.Model, v);
            receiver.EditContextRef.NotifyFieldChanged(fieldIdentifier);
        };

        return EventCallback.Factory.CreateBinder<string>(receiver, valueSetter, existingValue, culture);
    }


}

【问题讨论】:

  • 这可能会有所帮助...chrissainty.com/…
  • @NeilW 这肯定有助于创建示例。但是,我希望不创建控件而只控制绑定。我仍然希望能够使用现有的控件库,但要操纵它们从模型中获取数据的方式。在 MVC 中,这可以通过 ModelBinderProviders、MetadataProviders 和 ValidatorProviders 来完成,但这里似乎不存在该层。我对我创建的 CreateBinder2 方法没问题,但我正在努力更好地理解提交案例,看看是否可以进行任何改进。
  • 在不知道“现有控件”示例的情况下,我认为您的问题没有很好的答案。一般来说,您需要某种形式的包装器组件来将数据连接到您的控件并与 Blazor EditForm/EditContext 基础架构进行接口。入侵它几乎肯定会产生您尚未发现的副作用。
  • @MrCakaShaunCurtis 我将为我在示例中使用的通用 提供答案。 NielW 的示例或多或少至少让我觉得我的解决方案没有我想象的那么老套(或多或少地对editcontext、表达式和通知做同样的事情)。因此,虽然我希望对我的 CreateBinder2 有更直接或更直接的方法,但我可以接受它。但我仍然不明白提交是如何设法引发 NotifyFieldChanged 的​​。
  • @b_levitt。我在下面添加了一个答案。评论空间不足。

标签: blazor blazor-server-side blazor-webassembly


【解决方案1】:

但我仍然不明白提交是如何设法引发 NotifyFieldChanged

它没有。当您提交 EditForm 时,会调用 EditContext.Validate() 并验证模型中的 所有 字段(属性)。

我不知道 Validate() 的确切内部工作原理,但您可以将其添加到小提琴中:

 <input type="button" value="Check" @onclick="ValidateThis" />

void ValidateThis()
{
    Console.WriteLine("Before " + string.Join(",", EditContextRef.GetValidationMessages()));
    EditContextRef.Validate();
    Console.WriteLine("After  " + string.Join(",", EditContextRef.GetValidationMessages()));
}

如果为空,“After”写入行将包含 MiddleName。

【讨论】:

  • 谢谢你,这至少回答了那部分。
【解决方案2】:

您的EditForm 包含一个提交按钮,因此当您单击该按钮时,EditForm 会识别提交操作并将其称为内部HandleSubmitAsync。即使您没有附加到EditForm 的处理程序,此方法也会在EditContext 上调用Validate。这将验证您的 model 中的所有验证属性属性,在您的情况下是实际组件(页面)。

只要您对InputBaseEditFormEditContext 的工作原理和相互作用有很好的了解,您的手册OnChange 的处理和设置就没有问题。您只是在复制 InputBase 中包含的内容。调用NotifyFieldChanged 维护EditContext 上的编辑状态。但是,请注意,这并不是编辑状态的真实表示,它仅针对上次编辑而不是针对原始模型维护状态。

由于这是一个演示,使用组件作为模型是可以的,但你应该有一个适当的模型数据类(你几乎肯定已经知道这一点,但其他人可能稍后会阅读这个答案,...) .

【讨论】:

  • 谢谢你。是的,从技术上讲,使用“this”作为视图模型只是为了简单起见。我也很感谢您指出简单性与“应该”为其他读者做什么。就是说……
  • ....也就是说,值得在别处辩论这是否是不好的做法。我坚信模型或业务类之间的区别可能会完整地保留在数据库中,而“视图模型”通常代表 UI 的总体状态(包括对它尝试的正式模型的引用)维持)。在具有代码隐藏的模式中,代码隐藏是视图模型并且不需要在两个类之间拆分是合理的。就像我说的值得商榷,但你可以回到 webforms 并将代码隐藏视为一个视图模型。
【解决方案3】:

使用&lt;InputText&gt; 组件时,您可以超级轻松地使用更复杂的绑定。

例如:

<InputText Value="@this.MiddleName"
           ValueChanged="(string middleName) => MiddleNameChanged(middleName)"
           ValueExpression="() => this.MiddleName" />

@code 
{
    private void MiddleNameChanged(string middleName)
    {
        this.MiddleName = middleName;
        // Do whatever you want here
    }
}
    

这适用于所有其他内置输入组件,适用于它们支持的每种数据类型。它也可以与 async 和 await 结合使用。

如果您想为其他数据类型创建自己的输入,我建议从 InputBase 继承并在那里实现您的 BindConverter。这是一个示例,它向您展示如何使用 InputDate 组件绑定到日期时间值。 https://github.com/dotnet/aspnetcore/blob/main/src/Components/Web/src/Forms/InputDate.cs

【讨论】:

    猜你喜欢
    • 2022-08-02
    • 1970-01-01
    • 1970-01-01
    • 2011-04-09
    • 2022-07-01
    • 1970-01-01
    • 2020-07-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多