如何在 Blazor 中动态设置 ValidationMessage<TValue>.For 属性?
How to set ValidationMessage<TValue>.For Property dynamically in Blazor?
Docs 的这一部分描述了如何显示验证消息。
<ValidationMessage For="() => Parameters.PropertyA"></ValidationMessage>
如何动态设置ValidationMessage.For属性?
由于 For
是 Expression<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<>
因为你要将它重用于 ValidationMessage
的 For
参数之外的其他东西,你可以看看 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
。然后使用 FieldIdentifier
从 EditContext
中获取相应的 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
上的字段更改
要使上面创建的 MyComponent
s Value
属性 支持验证,只需按照以下步骤操作即可。
定义一个CascadingParameter
EditContext,这个获取当前的EditContext,一般是从EditForm
Component中获取。另请注意,如果没有 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);
}
Docs 的这一部分描述了如何显示验证消息。
<ValidationMessage For="() => Parameters.PropertyA"></ValidationMessage>
如何动态设置ValidationMessage.For属性?
由于 For
是 Expression<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<>
因为你要将它重用于 ValidationMessage
的 For
参数之外的其他东西,你可以看看 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
。然后使用 FieldIdentifier
从 EditContext
中获取相应的 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
上的字段更改
要使上面创建的 MyComponent
s Value
属性 支持验证,只需按照以下步骤操作即可。
定义一个CascadingParameter
EditContext,这个获取当前的EditContext,一般是从EditForm
Component中获取。另请注意,如果没有 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);
}