I'm having some trouble displaying a custom validation error message inline in a Blazor edit form.
On the model, we have a custom ValidationAttribute (OptionFieldsNotEmpty
) which checks that a list is populated.
class Field {
[OptionFieldsNotEmpty]
public List<OptionFieldViewModel> Options { get; set; }
}
In the razor page I have a tried to use the standard ValidationMessage but nothing shows for the markup below (OptionEditor
is a custom component rendering a list of inputs)
<OptionEditor OptionFields="@Field.Options"/>
<ValidationMessage For="() => Field.Options"/>
The custom validation definitely works but but the ValidationMessage doesn't show inline where I'd like it to. The message does appear in the ValidationSummary.
It seems like because Field.Options isn't actually bound to a form input, the framework doesn't know where to render it.
Does anyone know if this is possible or should I fall back to a ValidationSummary?
CodePudding user response:
The Blazor "EditContext" is a bit of a contradition: simplistic in it's concept and complex in it's implementation. ValidationSummary
is a case in point. For
defines an expression that it then needs to jump through hoops backwards to decode. Lots of CPU cycles wasted doing something that doesn't really need doing.
Here's an alternative ValidationMessage
that simply accepts the Model
and FieldName
as parameters. No For
: it constructs the FieldIdentifier
based on the Model
and FieldName
provided. It's a lift of the ValidationSummary
code without the expression untaggling and refection.
In your case just put in a break at a convenient place and find the Model
and FieldName
for your error message to enter as parameters.
@implements IDisposable
@foreach (var message in CurrentEditContext.GetValidationMessages(_fieldIdentifier))
{
<div >
@message
</div>
}
@code {
[CascadingParameter] EditContext CurrentEditContext { get; set; } = default!;
[Parameter, EditorRequired] public object Model { get; set; } = default!;
[Parameter, EditorRequired] public string FieldName { get; set; } = default!;
private readonly EventHandler<ValidationStateChangedEventArgs>? _validationStateChangedHandler;
private FieldIdentifier _fieldIdentifier => new FieldIdentifier(this.Model, this.FieldName);
private EditContext? _previousEditContext;
public CustomValidationMessage()
=> _validationStateChangedHandler = (sender, eventArgs) => StateHasChanged();
protected override void OnParametersSet()
{
if (CurrentEditContext == null)
throw new InvalidOperationException($"{GetType()} requires a cascading parameter "
$"of type {nameof(EditContext)}. For example, you can use {GetType()} inside "
$"an {nameof(EditForm)}.");
if (CurrentEditContext != _previousEditContext)
{
DetachValidationStateChangedListener();
CurrentEditContext.OnValidationStateChanged = _validationStateChangedHandler;
_previousEditContext = CurrentEditContext;
}
}
protected virtual void Dispose(bool disposing) {}
void IDisposable.Dispose()
{
DetachValidationStateChangedListener();
Dispose(disposing: true);
}
private void DetachValidationStateChangedListener()
{
if (_previousEditContext != null)
_previousEditContext.OnValidationStateChanged -= _validationStateChangedHandler;
}
}
To demo I've modified WeatherForecast
:
public class WeatherForecast
{
public DateOnly Date { get; set; }
[Range(-40, 60, ErrorMessage = "Temperature must be between -40 and 60.")]
public int TemperatureC { get; set; }
public int TemperatureF => 32 (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
And then a demo page:
@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<EditForm Model=this.model>
<DataAnnotationsValidator />
<InputNumber @bind-Value=this.model.TemperatureC />
<CustomValidationMessage Model="model" FieldName="TemperatureC" />
<div >
<ValidationSummary />
</div>
</EditForm>
@code {
private WeatherForecast model = new();
}
For reference the ValidationMessage
code is
CodePudding user response:
After looking into the dotnet core code I saw that instead of using ValidationMessage
you can use the ValidationSummary
with a Model
parameter set to show "inline" errors at a model level:
<ValidationSummary Model="Field"/>