【问题标题】:Validating Blazor sub-components?验证 Blazor 子组件?
【发布时间】:2021-09-11 18:40:36
【问题描述】:

我有一个 Blazor 组件,其中包含一个人的姓名和他们的地址。我已经拆分了地址,以便我可以重复使用它。我在人员和地址之间使用 2 路数据绑定,以确保将数据传递到地址并且人员可以接收地址更改。

我无法让验证工作。人员全名和地址行 1 不能为空。当我使用 VaidationSummary 时,它会正确报告两个字段都不能为空。但是当我使用 ValidationMessage 时,只有人的全名会报告验证消息。我正在使用 Fluent 验证,但我认为问题是 ValidationMessage 在复杂类型中不报告。

我认为这是因为地址行 1 ValidationMessage 的 For() 属性与主表单(Person)数据模型中的字段名称不匹配。主数据模型将地址类作为地址,但地址组件将其作为值。但是,如果我要重用组件,那么很可能会发生这种情况!

分离像地址这样的组件似乎是一件合理的事情,并且您可能在一个表单上拥有多个地址对象(例如交付和计费),所以我只需要知道如何去做。

有人做过吗?是否需要自定义 ValidationMessage 或不同的 For() 实现?

感谢您对此的帮助。这是源代码。

表格:

<EditForm Model=@FormData>
    <FluentValidator/>
    <ValidationSummary/>
    <InputText @bind-Value=FormData.FullName />
    <ValidationMessage For="@(() => FormData.FullName)"/>
    <ComponentAddress @bind-Value=FormData.Address />
    <input type="submit" value="Submit" class="btn btn-primary" />
</EditForm>

@code{
    PersonDataModel FormData = new PersonDataModel();
}

地址组件:

<InputText @bind-Value=Value.Address1 @onchange="UpdateValue" />
<ValidationMessage For="@(() => Value.Address1)" />
@code{
    [Parameter] public AddressDataModel Value { get; set; }
    [Parameter] public EventCallback<AddressDataModel> ValueChanged { get; set; }

    protected async Task UpdateValue()
    {
        await ValueChanged.InvokeAsync(Value);
    }
}

人物模型:

   public class PersonDataModel
    {
        [Required]
        public string FullName { get; set; }
        public AddressDataModel Address { get; set; }

        public PersonDataModel()
        {
            Address = new AddressDataModel();
        }
    }

地址模型:

public class AddressDataModel
{
    [Required]
    public string Address1 { get; set; }
}

Person Fluent 验证器:

public class PersonValidator : AbstractValidator<PersonDataModel>
{
    public PersonValidator()
    {
        RuleFor(r => r.FullName).NotEmpty().WithMessage("You must enter a name");
        RuleFor(r => r.Address.Address1).NotEmpty().WithMessage("You must enter Address line 1");
    }
}

【问题讨论】:

  • 你可以看看this
  • 所以我发现了一个关于这个的新问题。问题似乎是 FieldIdentifier 是由字段名称定义的。在子组件中,这是本地名称,但对于父窗体,它将是 ChildComponentName.FieldName。针对本地字段正确执行验证。 ValidationSummary 有效,因为它包含所有验证错误。 ValidationMessage 不起作用,因为它会查找 EditContext 的字段名称。由于 EditContext 是针对父级设置的,因此名称不同且未找到任何消息。

标签: asp.net-core blazor-server-side


【解决方案1】:

问题在于用于验证组件的ValidationContext 是组件的Value 属性——而不是父页面使用的模型。

我一直在努力寻找一些东西来让组件验证工作,直到我发现使用另一个验证属性(我将其应用于组件的Value 属性)有点技巧。在验证Value 时,我使用组件的EditContext,这是一个通过级联参数设置的属性。我可以通过反射获取属性的名称,这意味着我可以获取正确的FieldIdentifier,通知该字段已更改,然后从父级的EditContext 获取ValidationResults。然后我可以返回相同的错误详细信息。

验证属性

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using ValueBinding.Shared;

namespace ValueBinding.Data.Annotations
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public class MyValidationContextCheckAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            EditContext ec = null;
            string propName = "NOT SET";

            // My StringWrapper is basic component with manual Value binding
            if (validationContext.ObjectInstance is MyStringWrapper)
            {
                var strComp = (MyStringWrapper)validationContext.ObjectInstance;
                ec = strComp.ParentEditContext; // Uses Cascading Value/Property
                propName = strComp.GetPropertyName();
            }

            if (ec != null)
            {
                FieldIdentifier fld = ec.Field(propName);
                ec.NotifyFieldChanged(in fld);

                // Validation handled in Validation Context of the correct field not the "Value" Property on component
                var errors = ec.GetValidationMessages(fld);

                if (errors.Any())
                {
                    string errorMessage = errors.First();
                    return new ValidationResult(errorMessage, new List<string> { propName });
                }
                else
                {
                    return null;
                }
            }
            else if (typeof(ComponentBase).IsAssignableFrom(validationContext.ObjectType))
            {
                return new ValidationResult($"{validationContext.MemberName} - Validation Context is Component and not data class", new List<string> { validationContext.MemberName });
            }
            else
            {
                return null;
            }
        }
    }
}

组件

@using System.Linq.Expressions
@using System.Reflection
@using Data
@using Data.Annotations

<div class="fld" style="border-color: blue;">
    <h3>@GetPropertyName()</h3>
    <InputText @bind-Value=@Value />
    <ValidationMessage For=@ValidationProperty />
    <div class="fld-info">@HelpText</div>
</div>

@code {
    [Parameter]
    public string Label { get; set; } = "NOT SET";

    [Parameter]
    public string HelpText { get; set; } = "NOT SET";

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

    private string stringValue = "NOT SET";
    [MyValidationContextCheck]
    [Parameter]
    public string Value
    {
        get => stringValue;
        set
        {
            if (!stringValue.Equals(value))
            {
                stringValue = value;
                _ = ValueChanged.InvokeAsync(stringValue);
            }
        }
    }

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

    [CascadingParameter]
    public EditContext ParentEditContext { get; set; }

    public string GetPropertyName()
    {
        Expression body = ValidationProperty.Body;
        MemberExpression memberExpression = body as MemberExpression;
        if (memberExpression == null)
        {
            memberExpression = (MemberExpression)((UnaryExpression)body).Operand;
        }
        PropertyInfo propInfo = memberExpression.Member as PropertyInfo;
        return propInfo.Name;
    }
} 

【讨论】:

  • 此结构应用于我们的基本表单输入组件,以便所有继承的组件以相同的方式运行,因此您不必在所有表单输入组件上应用属性。我们实际上只有一个属性表达式参数,因此所有值和所有显示值都通过反射和 Linq 在基本组件内处理,并且页面标记代码非常干净。
猜你喜欢
  • 2021-05-26
  • 2022-01-09
  • 2021-06-10
  • 1970-01-01
  • 2020-06-16
  • 2020-10-26
  • 2023-02-22
  • 2021-08-07
  • 1970-01-01
相关资源
最近更新 更多