Blazor Mudblazor 表单验证未使用多级子组件触发

问题描述 投票:0回答:1

我正在使用 Blazor Mudblazor v6.17.0 并且我有一个名为 CustomerComponent

的组件
<div class="row" style="padding-top:10px;">
    <div class="col-4 mb-3">
        <MudAutocomplete id="name" For="@(() => Customer.Name)" T="string" Label="Name:" @bind-Value="Customer.Name" SearchFunc="@SearchCustomer" Margin="Margin.Dense" Dense="true" Variant="Variant.Outlined" />
    </div>
</div>


@code {
    [Parameter] public Customer Customer { get; set; }
    [Parameter] public IEnumerable<string> CustomerList { get; set; }
   
    private Task<IEnumerable<string>> SearchCustomer(string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            Customer.Name = "";
            return Task.FromResult(CustomerList);
        }
        var filteredList = CustomerList.Where(x => x.Contains(value, StringComparison.InvariantCultureIgnoreCase));
        return Task.FromResult(filteredList);
    }

以及使用该组件的组件,称为 CustomerTemplateComponent

 <EditForm Model="@Template" OnValidSubmit="SaveEvent">
       <DataAnnotationsValidator/>
       <br />
       <div class="row" style="padding-top: 6px;">
           <div class="col-12">
               <MudTextField id="TemplateName" For="@(() => Template.TemplateName)" @bind-Value="Template.TemplateName" 
                             Label="Template Name:" Variant="Variant.Outlined" Margin="Margin.Dense"></MudTextField>
           </div>
       </div>
       <CustomerComponent  Currencies="@Currencies" CustomerList="@CustomerList" 
                            Customer="@Template.Customer"
                            "/>
       
       <div class="row" style="padding-top:8px;">
           <div class="col-12 text-end mb-3">
               <button class="btn btn-primary" disabled="@Processing" id="Save-Button">Save</button>
               <div type="button" class="btn btn-secondary" @onclick="CancelEvent" id="Cancel-Button">Cancel</div>
           </div>
       </div>
   </EditForm>
}
@code {
    [Parameter] public bool Processing { get; set; }
    [Parameter] public Template Template { get; set; }
    [Parameter] public EventCallback CancelEvent { get; set; }
    [Parameter] public EventCallback SaveEvent { get; set; }
    [Parameter] public IEnumerable<string> CustomerList { get; set; }

}

该组件的使用方式如下

这里使用的是这个

<MudPaper Elevation="10">
    <MudGrid Class="d-flex align-content-center justify-center flex-grow-1 gap-4" Style="padding:10px;">
        <CustomerTemplateComponent CancelEvent="@Cancel" CustomerList="@Customers" 
                             SaveEvent="@Save" Processing="@Processing" Customer="@_customer" />
    </MudGrid>
</MudPaper> 



private async Task Save()
    {
        Processing = true;

        try
        {
            await _templateService.SaveAsync(_template);
            ToastService.ShowSuccess("Template Updated.");
            NavigationManager.NavigateTo("/tradetemplates");
        }
        catch (Exception ex)
        {
            Logger.Error(ex, "Error saving templates.");
            ToastService.ShowError(ex.Message);
        }
        finally
        {
            Processing = false;
        }
    }

here are the classes


public class Template 
    {
        public string TemplateName { get; set; }
        public Customer Customer { get; set; }
    }
    
    public class Customer 
    {
        public string Name { get; set; }
    }

我遇到的问题是,当单击按钮时,表单未经过验证,并且使用无效模式调用 api,基本组件也不会绑定。当参数深入两层时,我应该使用级联参数还是其他参数?或者是否存在不同的错误,因为当我从顶级表单使用基本组件时,它工作正常。

可以找到工作示例https://try.mudblazor.com/snippet/GEGSkxGrGBqyVXZR

blazor mudblazor
1个回答
0
投票

为了确保在父组件的

EditForm
中捕获子组件验证,您可以使用
EditContext.OnValidationRequested
,将
EditContext
作为
CascadingParameter
从父组件传递到子组件。

注意:我在这里使用了

@bind-Customer

// CustomerTemplateComponent.razor
<EditForm Model="@Template" OnValidSubmit="SaveEvent" Context="EditContext">
    <DataAnnotationsValidator/>
    <br />
    <div class="row" style="padding-top: 6px;">
        <div class="col-12">
            <MudTextField id="TemplateName" For="@(() => Template.TemplateName)" @bind-Value="Template.TemplateName" 
                            Label="Template Name:" Variant="Variant.Outlined" Margin="Margin.Dense">
                </MudTextField>
        </div>
    </div>
    <CascadingValue Value="EditContext">
        <CustomerComponent CustomerList="@CustomerList" @bind-Customer="@Template.Customer"/>
    </CascadingValue>
    
    <div class="row" style="padding-top:8px;">
        <div class="col-12 text-end mb-3">
            <button class="btn btn-primary" disabled="@Processing" id="Save-Button">Save</button>
            <div type="button" class="btn btn-secondary" @onclick="CancelEvent" id="Cancel-Button">Cancel</div>
        </div>
    </div>
</EditForm>

@code {
    [Parameter] public bool Processing { get; set; }
    [Parameter] public Template Template { get; set; }
    [Parameter] public EventCallback CancelEvent { get; set; }
    [Parameter] public EventCallback SaveEvent { get; set; }
    [Parameter] public IEnumerable<string> CustomerList { get; set; }

    private EditContext EditContext;
    protected override void OnInitialized()
    {
        EditContext = new EditContext(Template);
    }
}

然后,在子组件中,即

CustomerComponent
。我们订阅
EditContext.OnValidationRequested
事件,当表单请求验证时,即单击提交按钮时,该事件将被触发。在这里,我们可以使用此事件来验证属性,然后
EditContext.NotifyValidationStateChanged
将验证传播回父组件
EditContext

其他一些更改:

  • @bind-Value
    更改为使用
    Value
    ValueChanged
    ,因为我们需要在
    MudAutoComplete
    的值发生变化时实现自定义逻辑,这允许我们在父组件上使用
    @bind-Customer

  • ValidationMessageStore
    - 用于保存验证消息,并在实例化时绑定到 EditContext,即
    new ValidationMessageStore(ParentEditContext);

// CustomerComponent.razor
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Components.Forms
@implements IDisposable

<div class="row" style="padding-top:10px;">
    <div class="col-4 mb-3">
        <MudAutocomplete id="name" T="string" Label="Name:"
            Value="@Customer.Name" ResetValueOnEmptyText="true"
            ValueChanged="@HandleNameChanged" SearchFunc="@SearchCustomer" 
            For="@(() => Customer.Name)"
            Margin="Margin.Dense" Dense="true" Variant="Variant.Outlined" />
    </div>
</div>

@code {
    [CascadingParameter] public EditContext ParentEditContext { get; set; }
    private ValidationMessageStore _messageStore;

    [Parameter] public Customer Customer { get; set; }
    [Parameter] public EventCallback<Customer> CustomerChanged { get; set; }
    [Parameter] public IEnumerable<string> CustomerList { get; set; }

    protected override void OnInitialized()
    {
        if (ParentEditContext != null)
        {
            _messageStore = new ValidationMessageStore(ParentEditContext);

            ParentEditContext.OnValidationRequested += (sender, eventArgs) =>
            {
                _messageStore.Clear();
                Validate();
                ParentEditContext.NotifyValidationStateChanged();
            };
        }
    }

    async Task HandleNameChanged(string newName)
    {
        Customer.Name = newName;
        await CustomerChanged.InvokeAsync(Customer);
    }

    void Validate()
    {
        var validationResults = new List<ValidationResult>();
        var context = new ValidationContext(Customer)
        {
            MemberName = nameof(Customer.Name)
        };

        Validator.TryValidateProperty(Customer.Name, context, validationResults);

        foreach (var validationResult in validationResults)
        {
            _messageStore.Add(new FieldIdentifier(Customer, nameof(Customer.Name)), validationResult.ErrorMessage);
        }
    }

    public void Dispose()
    {
        if (ParentEditContext != null)
        {
            ParentEditContext.OnValidationRequested -= (sender, eventArgs) =>
            {
                _messageStore.Clear();
                Validate();
                ParentEditContext.NotifyValidationStateChanged();
            };
        }
    }

    private Task<IEnumerable<string>> SearchCustomer(string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            Customer.Name = "";
            return Task.FromResult(CustomerList.AsEnumerable());
        }
        var filteredList = CustomerList.Where(x => x.Contains(value, StringComparison.InvariantCultureIgnoreCase));
        return Task.FromResult(filteredList.AsEnumerable());
    }
}

演示👉MudBlazor 片段

如果您想重新验证整个对象而不仅仅是一个属性,那么您可以使用

Validator.TryValidateObject
。我已将该片段包含在演示中。

void ValidateEntireObject()
{
    var validationResults = new List<ValidationResult>();
    Validator.TryValidateObject(Customer, new ValidationContext(Customer), validationResults, true);

    foreach (var validationResult in validationResults)
    {
        _messageStore.Add(new FieldIdentifier(Customer, validationResult.MemberNames.First()), validationResult.ErrorMessage);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.