如何在 Blazor 中动态设置 ValidationMessage<TValue>.For 属性?

How to set ValidationMessage<TValue>.For Property dynamically in Blazor?

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

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

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

由于 ForExpression<Func<TValue>> 类型,我想传递一个 Func,但这无法编译:

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

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

可以编译,但无法正确解析验证消息

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

我还尝试使组件通用,这样它就知道 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] Unhandled exception rendering component: The provided expression contains a InvocationExpression1 which is not supported. FieldIdentifier only supports simple member accessors (fields, properties) of an object. System.ArgumentException: The provided expression contains a InvocationExpression1 which is not supported. FieldIdentifier only supports simple member accessors (fields, properties) of an object. at Microsoft.AspNetCore.Components.Forms.FieldIdentifier.ParseAccessor[String](Expression`1 accessor, Object& model, String& fieldName) at Microsoft.AspNetCore.Components.Forms.FieldIdentifier.Create[String](Expression`1 accessor) at Microsoft.AspNetCore.Components.Forms.ValidationMessage`1[[System.String, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnParametersSet() at Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync() at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()

I want to pass a Func instead

为什么?如果没有特定原因说明为什么要传递 Func<TValue> 而不是 Expression<Func<TValue>>,只需使用参数

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

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

得到 Func<>

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

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

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


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

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

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

在页面上,模型被初始化并设置为编辑上下文。我们有三个文本输入框和一个 select 框。 select 框可用于切换验证消息。如果 select 框的值被改变,一个新的表达式被分配给 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 中获取相应的 property/field 以检查验证状态。因此,您在为表达式选择什么方面受到限制。错误消息 FieldIdentifier only supports simple member accessors (fields, properties) of an object 很好地说明了此限制。

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

神圣 blazor 绑定的三位一体

了解更多信息

简而言之,如果 [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,一般是从EditFormComponent中获取。另请注意,如果没有 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);
}