问题
Please note that although this is closely related to this question, the answer there is specific to one known model property, whereas I want this to work for any property, so the component is reusable.
I want to use the Blazor <ValidationMessage>
tag within a component. However, when I do this, the validation message isn't shown.
For example, the following component (FormRowText.razor
) creates a row (in the Bootstrap grid sense) containing an <input type="text />
for a named property on a model...
<div class="form-group row">
<label for="@PropertyName" class="col-lg-2 col-form-label">@(Caption ?? PropertyName)</label>
<div class="col-lg-10 input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="far fa-@Icon"></i>
</span>
</div>
<input class="form-control" Value="@Value" type="text"
id="@PropertyName" name="@PropertyName" @onchange="@OnChanged" />
</div>
</div>
@code {
[Parameter]
public string PropertyName { get; set; }
[Parameter]
public string Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
[Parameter]
public string Caption { get; set; }
[Parameter]
public string Icon { get; set; }
private async Task OnChanged(ChangeEventArgs cea) =>
await ValueChanged.InvokeAsync(cea.Value?.ToString() ?? "");
}
It is used like this...
<EditForm ...>
<FormRowText PropertyName="@nameof(Customer.Email)" @bind-Value="_customer.Email" Icon="at" />
</EditForm>
If I add a <ValidationMessage>
after this markup, it works fine, but if I add it inside the component (which is where I want it to be), it doesn't work.
I saw this answer from @enet, which shows how to do this for a specific, known model property. However, my component is designed to work with any model property, and I'm not sure how to modify it.
I tried adding a parameter for the model (with @typeparam T
at the top of the file)...
[Parameter]
public T Model { get; set; }
...and adding the following inside the HTML...
<ValidationMessage For="() => PropertyName" />
However this doesn't work.
Any ideas?
回答1:
You can cascade the EditContext to your component and use it to notify that the current field has changed
FormRowText.razor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
public partial class FormRowText
{
[CascadingParameter] EditContext CascadedEditContext { get; set; }
[Parameter]
public string PropertyName { get; set; }
[Parameter]
public string Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
[Parameter]
public string Caption { get; set; }
[Parameter]
public string Icon { get; set; }
[Parameter] public Expression<Func<string>> ValueExpression { get; set; }
private async Task OnChanged(ChangeEventArgs cea)
{
await ValueChanged.InvokeAsync(cea.Value?.ToString() ?? "");
var identifier = CascadedEditContext.Field(PropertyName);
CascadedEditContext.NotifyFieldChanged(identifier);
}
}
FormRowText.razor
<div class="form-group row">
<label for="@PropertyName" class="col-lg-2 col-form-label">@(Caption ?? PropertyName)</label>
<div class="col-lg-10 input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="far fa-@Icon"></i>
</span>
</div>
<input class="form-control" value="@Value" type="text" id="@PropertyName" name="@PropertyName"
@onchange="@OnChanged" />
<ValidationMessage For="@ValueExpression" />
</div>
</div>
You'll also need
public class Customer
{
[Required]
public string Name { get; set; }
public Country Country { get; set; } = Country.Israel;
[Required]
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
}
Usage
@page "/"
@using System.ComponentModel.DataAnnotations;
@using Microsoft.AspNetCore.Components.Forms;
<EditForm EditContext="EditContext" OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<div class="form-group">
<label for="name">Name: </label>
<InputText @bind-Value="@customer.Name"></InputText>
<ValidationMessage For="@(() => customer.Name)" />
</div>
<div class="form-group">
<FormRowDropdownEnum @bind-Value="customer.Country" Caption="Country" T="Country"></FormRowDropdownEnum>
<ValidationMessage For="@(() => customer.Country)" />
<FormRowText @bind-Value="customer.Email" Caption="Email"
PropertyName="@nameof(Customer.Email)" Icon="at"></FormRowText>
</div>
<p>
<button type="submit" class="btn btn-primary">Save</button>
</p>
</EditForm>
@code{
private Customer customer = new Customer();
private EditContext EditContext;
protected override void OnInitialized()
{
EditContext = new EditContext(customer);
}
public async Task HandleValidSubmit()
{
await Task.Delay(1);
Console.WriteLine("Saving...");
}
}
Another version
As you're using the EditForm component, it'd be a good idea to use the Forms components, such as the InputText component and such like.
The following code snippet describes how to implement the FormRowText component by sub-classing the InputText components, and adding new properties. Note that the PropertyName is not required for the functioning of the component
FormRowText2.razor
@inherits InputText
<div class="form-group row">
<label for="@PropertyName" class="col-lg-2 col-form-label">@(Caption ?? PropertyName)</label>
<div class="col-lg-10 input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="far fa-@Icon"></i>
</span>
</div>
<input class="form-control" @bind="CurrentValue" type="text" id="@PropertyName" name="@PropertyName" />
<ValidationMessage For="@ValueExpression" />
</div>
</div>
@code{
[Parameter]
public string Caption { get; set; }
[Parameter]
public string Icon { get; set; }
[Parameter]
public string PropertyName { get; set; }
}
来源:https://stackoverflow.com/questions/65224446/how-do-i-use-validationmessage-within-a-component