我正在使用 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,基本组件也不会绑定。当参数深入两层时,我应该使用级联参数还是其他参数?或者是否存在不同的错误,因为当我从顶级表单使用基本组件时,它工作正常。
为了确保在父组件的
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);
}
}