【问题标题】:How to set ValidationMessage<TValue>.For Property dynamically in Blazor?如何在 Blazor 中动态设置 ValidationMessage<TValue>.For 属性?
【发布时间】:2021-03-31 06:53:34
【问题描述】:

Docs 的这一部分描述了如何显示验证消息。

<ValidationMessage For="() => Parameters.PropertyA"></ValidationMessage>      

如何动态设置ValidationMessage.For属性?

由于ForExpression&lt;Func&lt;TValue&gt;&gt; 类型,我想传递一个Func,但这不能编译:

[Parameter]
public Func<string> PropertyLocator { get; set; }

<ValidationMessage For="PropertyLocator"></ValidationMessage>

这会编译,但不会正确解析验证消息

<ValidationMessage For="() => PropertyLocator"></ValidationMessage>

我还尝试使 Component 具有通用性,以便它知道 Parameters 类型:

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

public partial class MyComponent<TParam>
{
    [Parameter]
    public TParam Parameters { get; set; }

    [Parameter]
    public Func<TReportParam, string> PropertyLocator { get; set; }
}


@using System.Linq.Expressions
@typeparam TParam
<ValidationMessage For="@((Expression<Func<string>>)(() => PropertyLocator(this.Parameters)))"></ValidationMessage>


<MyComponent TParam="MyParameters" Parameters="BindToSomeValue" PropertyLocator="(parameters) => parameters.PropertyA" />

但这会导致以下运行时异常:

Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] 未处理的异常呈现组件:提供的表达式包含不受支持的 InvocationExpression1。 FieldIdentifier 仅支持简单的成员访问器(字段、 对象的属性)。 System.ArgumentException:提供的 表达式包含不受支持的 InvocationExpression1。 FieldIdentifier 仅支持简单的成员访问器(字段、 对象的属性)。在 Microsoft.AspNetCore.Components.Forms.FieldIdentifier.ParseAccessor[String](Expression`1 访问器、对象和模型、字符串和字段名)在 Microsoft.AspNetCore.Components.Forms.FieldIdentifier.Create[String](Expression`1 存取器)在 Microsoft.AspNetCore.Components.Forms.ValidationMessage`1[[System.String, System.Private.CoreLib,版本=5.0.0.0,文化=中性, PublicKeyToken=7cec85d7bea7798e]].OnParametersSet() 在 Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync() 在 Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()

【问题讨论】:

  • 如果属性是Expression&lt;Func&lt;TValue&gt;&gt; 类型,你为什么要尝试将Func&lt;TValue&gt; 传递给它?不应该只是public Expressiona&lt;Func&lt;string&gt;&gt; PropertyLocator { get; set; }吗?
  • 请注意public partial class MyComponent&lt;TParam&gt; { [Parameter] public TParam Parameters { get; set; } [Parameter] public Func&lt;TReportParam, string&gt; PropertyLocator { get; set; } } 不能编译。 TReportParam 来自哪里?

标签: c# validation blazor blazor-webassembly


【解决方案1】:

我创建了一个小示例页面。

模型使用DataAnnotations作为验证机制。


    public class DemoInputModel
    {
        [Required]
        public String PropertyOne { get; set; }

        [MinLength(2)]
        public String PropertyTwo { get; set; }

        [MaxLength(5)]
        public String PropertyThree { get; set; }
    }

在页面上,模型被初始化并设置为编辑上下文。我们有三个文本输入和一个选择框。选择框可用于切换验证消息。如果选择框的值发生变化,则将新的表达式分配给ValidationMessage


@using System.ComponentModel.DataAnnotations;
@using System.Linq.Expressions;

@page "/test"


<h1>ValidationMessageTest</h1>

<EditForm Model="_model">
    <DataAnnotationsValidator />

    <ValidationMessage For="ValidationResolver"></ValidationMessage>

    <InputText @bind-Value="_model.PropertyOne" />
    <InputText @bind-Value="_model.PropertyTwo" />
    <InputText @bind-Value="_model.PropertyThree" />

    <InputSelect @bind-Value="SelectedValidationProperty">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
    </InputSelect>

    @*<ValidationSummary />*@

</EditForm>


@code {

    private DemoInputModel _model = new DemoInputModel
    {
        PropertyOne = "Test",
        PropertyTwo = "42",
        PropertyThree = "Math.PI",
    };

    private String _selectedValidationProperty;

    public String SelectedValidationProperty
    {
        get => _selectedValidationProperty;
        set
        {
            _selectedValidationProperty = value;
            ChangeValidator(value);
        }
    }

    public Expression<Func<String>> ValidationResolver { get; set; }

    protected override void OnInitialized()
    {
        SelectedValidationProperty = "1";
        base.OnInitialized();
    }

    public void ChangeValidator(String value)
    {
        switch (value)
        {
            case "1":
                ValidationResolver = () => _model.PropertyOne;
                break;
            case "2":
                ValidationResolver = () => _model.PropertyTwo;
                break;
            case "3":
                ValidationResolver = () => _model.PropertyThree;
                break;
            default:
                break;
        }
    }
}

你的意思是这样的吗?如果你的模型不只有字符串,它会稍微复杂一些,就像例子中一样。一个“快速”的解决方法可能是为每种可能的类型设置一个Expression

在底层,表达式用于创建FieldIdentifier。然后使用FieldIdentifierEditContext 获取相应的属性/字段以检查验证状态。因此,您在为表达式选择什么方面受到限制。错误消息FieldIdentifier 只支持对象的简单成员访问器(字段、属性)很好地表明了这种限制。

【讨论】:

  • 是的,我正在考虑自己创建 FieldIdentifiers。感谢您的示例。它非常类似于我自己编写的代码;)我会接受这个答案,因为我认为它也可以帮助其他人实现这种行为。
  • 谢谢。构建一个CustomFieldIIdentifier 类听起来很有趣。也许,您可以在完成后分享您的解决方案?
  • 既然你问了,我就这个话题做了更多的研究。似乎我已经找到了问题的解决方案,它可能是 blazor 团队的意图。 check it out :)
【解决方案2】:

我想传递一个 Func 来代替

为什么?如果没有特定原因应该传递Func&lt;TValue&gt; 而不是Expression&lt;Func&lt;TValue&gt;&gt;,则只需使用参数

[Parameter]
public Expression<Func<string>> PropertyLocator { get; set; }

如果您只想要一个Func&lt;&gt;,因为您要将它重用于ValidationMessageFor 参数以外的其他东西,您可以查看Extracting Func<> from Expression<> 以从Expression&lt;Func&lt;string&gt;&gt; PropertyLocator.

如果你真的想传递一个Func&lt;&gt;,也许你在尝试传递convert a .net Func to a .net Expression<Func>时会遇到一些问题。

【讨论】:

  • 是的。就是这样。没有具体理由不使用Expression&lt;Func&lt;string&gt;&gt;
【解决方案3】:

经过一番研究,我偶然发现了以下 blazor 功能:

blazor 绑定的神圣三位一体

阅读更多相关信息here

简而言之,如果[Parameter] 与以下语法绑定...

<MyComponent @bind-Value="My.Binding.Path" />

...它不仅支持two-way bindings,而且还设置了一个定位器表达式。

[Parameter]
public string Value { get; set; }

[Parameter]
public EventCallback<string> ValueChanged { get; set; }

[Parameter]
public Expression<Func<string>> ValueExpression { get; set; }

你可以使用任何类型,而不是 string

由于ValueExpression 的值是自动设置的,您可以使用此行为来显示绑定属性的验证消息。只需使用表达式将 ValidationMessage 组件添加到您的组件中即可。

<ValidationMessage For="ValueExpression" />

一点额外的

如果您正在构建一个支持验证的组件(此时,我假设您是)。以下内容可能对您来说也很有趣。

您不仅可以使用三位一体来显示验证消息,还可以创建支持验证的组件。有很多文章涵盖了这个主题。

简而言之:

  • 构建您的组件
  • 在需要时通知 EditContext 上的字段更改

要使上面创建的MyComponents Value 属性支持验证,只需按照以下步骤操作即可。

定义一个CascadingParameterEditContext,这会获取当前的EditContext,通常来自EditForm组件。另请注意,如果没有 CascadingValue,则可能不会设置 EditContext。例如,如果组件没有放在 EditForm 内:

[CascadingParameter]
public EditContext? EditContext

定义一个属性来存储FieldIdentifier,并在设置参数时设置它。

public FieldIdentifier? FieldIdentifier { get; private set; }

public override async Task SetParametersAsync(ParameterView parameters)
{
    await base.SetParametersAsync(parameters);

    if (this.EditContext != null && this.DateExpression != null && this.FieldIdentifier?.Model != this.EditContext.Model)
    {
        this.FieldIdentifier = Microsoft.AspNetCore.Components.Forms.FieldIdentifier.Create(this.DateExpression);
    }
}

在需要时触发字段验证(通常在调用ValueChanged 之后):

this.Value = value;
this.ValueChanged.InvokeAsync(this.Value);
if (this.FieldIdentifier?.FieldName != null)
{
    this.EditContext?.NotifyFieldChanged(this.FieldIdentifier!.Value);
}

【讨论】:

  • 为了可读性和与帖子其余部分的一致性,我认为您想将 Set Parameters Async 的最后一行更改为:this.FieldIdentifier = Microsoft.AspNetCore.Components.Forms.FieldIdentifier.Create( this.ValueExpression);
  • if 子句也一样
猜你喜欢
  • 2022-01-13
  • 2017-08-16
  • 2011-09-21
  • 1970-01-01
  • 2023-01-08
  • 1970-01-01
  • 2020-12-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多