Single Model (Validates As Expected)
I have created an EditForm
in Blazor where I was able to easily add validation for a single model where I added OnValidSubmit
to the form with the DataAnnotationsValidator
and ValidationSummary
tags at the top:
<EditForm FormName="CriminalConvictions" Model="criminalConvictions" OnValidSubmit="SubmitMany">
<DataAnnotationsValidator />
<ValidationSummary />
This is the model declaration where is instantiates a new instance of the model:
[SupplyParameterFromForm]
public CriminalConvictionModel? criminalConviction { get; set; }
protected override async Task OnInitializedAsync()
{
criminalConviction ??= new();
}
Then I can add in the validation message under the field as well:
<InputTextArea @bind-Value="criminalConviction!.Offence" class="form-control" />
<ValidationMessage For="@(() => criminalConviction!.Offence)" />
And in the model class I can then add a required tag to require a value for this field:
public class CriminalConvictionModel
{
public int CriminalConvictionID { get; set; }
public DateTime? DateOfOffence { get; set; }
[Required(ErrorMessage = "Please record details of the offence")]
public string? Offence { get; set; }
...
}
This all works and validates the field as expected:
List of Model (Won't Validate)
However I now need to allow multiple items to be submitted so changed the model declaration to be a list of model objects like this:
public CriminalConvictionModel? criminalConviction { get; set; }
[SupplyParameterFromForm]
public List<CriminalConvictionModel>? criminalConvictions { get; set; }
protected override async Task OnInitializedAsync()
{
criminalConviction ??= new();
criminalConvictions = new List<CriminalConvictionModel>();
criminalConvictions?.Add(criminalConviction);
}
Then I changed the submit function to save a list of models to the database which works and saves the records to the SQL database but the validation no longer works as this required field can now be left blank and it is not picked up and OnValidSubmit
is being executed regardless of if the models in the list are valid or not.
If the EditForm model is set as the single criminalConviction
then it validates the first set of elements but not subsequent ones but if I set it as the list I declared which is criminalConvictions
then no validation occur and I can't work out what I need to change to make it work.
This is the function I added to add new models to the list of models to add a new set of form fields:
private void AddMoreConvictions() {
formSubmitted = false;
CriminalConvictionModel newConviction = new CriminalConvictionModel();
criminalConvictions = new List<CriminalConvictionModel>();
criminalConvictions?.Add(newConviction);
}
So it all works and saves multiple records to the database but I just can't work out how to get validation working for List<Model>
when it works as expected for Model
and I can't work out what I am missing and also ideally it would be better if the validation summary could be per list of fields so it is clear which set has the missing field.
Other Approaches Attempted
The most obvious approach to this from looking online is to use ObjectGraphDataAnnotationsValidator
, which seems to validate sub-elements and be exactly what I need, however it seems it was deprecated and removed with nothing to replace it.
I then attempted to write custom validation to cycle through the list objects checking each one for being empty but the form just still submitted.
Fluent validation is another option but it wouldn't work and led to an error saying No pending ValidationResult found
. I'm not sure if I perhaps need a parent class that has a subset of the list of classes but then only the main subclass is in the database and is the only one I want to insert into using the form.
Single Model (Validates As Expected)
I have created an EditForm
in Blazor where I was able to easily add validation for a single model where I added OnValidSubmit
to the form with the DataAnnotationsValidator
and ValidationSummary
tags at the top:
<EditForm FormName="CriminalConvictions" Model="criminalConvictions" OnValidSubmit="SubmitMany">
<DataAnnotationsValidator />
<ValidationSummary />
This is the model declaration where is instantiates a new instance of the model:
[SupplyParameterFromForm]
public CriminalConvictionModel? criminalConviction { get; set; }
protected override async Task OnInitializedAsync()
{
criminalConviction ??= new();
}
Then I can add in the validation message under the field as well:
<InputTextArea @bind-Value="criminalConviction!.Offence" class="form-control" />
<ValidationMessage For="@(() => criminalConviction!.Offence)" />
And in the model class I can then add a required tag to require a value for this field:
public class CriminalConvictionModel
{
public int CriminalConvictionID { get; set; }
public DateTime? DateOfOffence { get; set; }
[Required(ErrorMessage = "Please record details of the offence")]
public string? Offence { get; set; }
...
}
This all works and validates the field as expected:
List of Model (Won't Validate)
However I now need to allow multiple items to be submitted so changed the model declaration to be a list of model objects like this:
public CriminalConvictionModel? criminalConviction { get; set; }
[SupplyParameterFromForm]
public List<CriminalConvictionModel>? criminalConvictions { get; set; }
protected override async Task OnInitializedAsync()
{
criminalConviction ??= new();
criminalConvictions = new List<CriminalConvictionModel>();
criminalConvictions?.Add(criminalConviction);
}
Then I changed the submit function to save a list of models to the database which works and saves the records to the SQL database but the validation no longer works as this required field can now be left blank and it is not picked up and OnValidSubmit
is being executed regardless of if the models in the list are valid or not.
If the EditForm model is set as the single criminalConviction
then it validates the first set of elements but not subsequent ones but if I set it as the list I declared which is criminalConvictions
then no validation occur and I can't work out what I need to change to make it work.
This is the function I added to add new models to the list of models to add a new set of form fields:
private void AddMoreConvictions() {
formSubmitted = false;
CriminalConvictionModel newConviction = new CriminalConvictionModel();
criminalConvictions = new List<CriminalConvictionModel>();
criminalConvictions?.Add(newConviction);
}
So it all works and saves multiple records to the database but I just can't work out how to get validation working for List<Model>
when it works as expected for Model
and I can't work out what I am missing and also ideally it would be better if the validation summary could be per list of fields so it is clear which set has the missing field.
Other Approaches Attempted
The most obvious approach to this from looking online is to use ObjectGraphDataAnnotationsValidator
, which seems to validate sub-elements and be exactly what I need, however it seems it was deprecated and removed with nothing to replace it.
I then attempted to write custom validation to cycle through the list objects checking each one for being empty but the form just still submitted.
Fluent validation is another option but it wouldn't work and led to an error saying No pending ValidationResult found
. I'm not sure if I perhaps need a parent class that has a subset of the list of classes but then only the main subclass is in the database and is the only one I want to insert into using the form.
1 Answer
Reset to default 0After spending hours on this I have finally managed to solve it using the Blazored.FluentValidation
package.
I couldn't find any examples online that validate a list of elements or the ones I did find didn't work or used now deprecated or removed features but here is what did work in the end.
First install the Fluent Validation package in Package Manager:
Install-Package Blazored.FluentValidation
Now go to the _Imports.razor
file and add an include for fluent validation to save adding it to every page:
@using Blazored.FluentValidation
In terms of models I just had the one called CriminalConvictionModel
and declared a list of them in the page to be able to add multiple records and loop through these.
However this wouldn't work for validation as I needed to create a parent model which would just be a container for the list of items so as well as CriminalConvictionModel
I had to create CriminalConvictionsModel
as a new parent class:
public class CriminalConvictionsModel
{
public ICollection<CriminalConvictionModel>? Convictions { get; set; }
}
I then had to make sure the model for the form was this parent class and the foreach
loop to cycle through these became:
foreach (var conv in criminalConvictions.Convictions) {
}
Then I needed to add the validator code for a single record:
public class CriminalConvictionValidator : AbstractValidator<CriminalConvictionModel>
{
public CriminalConvictionValidator()
{
RuleFor(c => c.Offence).NotEmpty();
}
}
Then I needed to add a validator for the parent class with a ForEach
command that would then call the singular validator for each record:
public class CriminalConvictionsValidator : AbstractValidator<CriminalConvictionsModel>
{
public CriminalConvictionsValidator()
{
RuleFor(c => c.Convictions).ForEach(x => x.SetValidator(new CriminalConvictionValidator()));
}
}
Finally I needed to change DataAnnotationsValidator />
at the top of the form to <FluentValidationValidator @ref="_fluentValidationValidator" />
and change OnValidSubmit
to OnSubmit
then add this in the @code
area:
private FluentValidationValidator? _fluentValidationValidator;
Then change the submit function so all the code is contained in this if statement:
if (await _fluentValidationValidator!.ValidateAsync())
{
//Submit form
}
Now each set of fields are validated as needed:
[![It Works][1]][1]
And I changed the submit code to submit `criminalConvictions.Convictions` so it is in effect submitting the same format of data to the API and again successfully saving to the database.
Hopefully this helps others as working this out without any examples has taken ages and lots of trial and error but at least I'll know for next time and it saves reverting to JS.
[1]: https://i.sstatic/bm6Xg4OU.png