Duplicate Validation Messages with FluentValidation in ASP.NET Core 6 MVC
Validation is an important step when handling data from a request in ASP.NET Core 6. FluentValidation is a popular .NET library which can make validation your models easier. It allows you to define your validation rules in a single place and integrate custom logic for your validation, which would require you to make a new filter if you used data annotations.
However, in ASP.NET Core 6 MVC there was a change in the project template that could result in seeing data annotation produced validation messages, in addition to FluentValidation produced messages.
What is happening?
Let’s take the example of a contact list application which stores names and email addresses. A model for the view when editing a contact might look something like the below.
public class ContactViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string EmailAddress { get; set; }
}
And in addition, the FluentValidation rules which go with that class might look like this.
public class ContactValidator : AbstractValidator<ContactViewModel>
{
public ContactValidator()
{
RuleFor(contact => contact.Id)
.NotEmpty()
.WithMessage("An ID must be specified.");
RuleFor(contact => contact.Name)
.NotEmpty()
.WithMessage("You must enter a name.");
RuleFor(contact => contact.EmailAddress)
.NotEmpty()
.WithMessage("You must enter an email address.");
RuleFor(contact => contact.EmailAddress)
.EmailAddress()
.WithMessage("Please enter a valid email address.");
}
}
If you used this in an ASP.NET Core 5 MVC project and forgot to complete the Name
, you would see the following validation error messages.
You must enter a name.
However, in ASP.NET Core 6 MVC you would end up seeing the following messages instead.
The Name field is required.
This is due to the .NET 6 MVC templates coming with the Nullable Reference Types feature for C# enabled. This means that if you have a model with a property with a type of string, it is expected that the property should have a value. As a result, ASP.NET Core 6 MVC automatically applies a Required data annotation to the property, resulting in the different error message.
The solutions
There are two main options to deal with this behaviour, each with their own individual set of advantages and disadvantages.
Mark the property as nullable
The simplest solution is to mark the property as being nullable by adding a question mark ?
at the end of the type declaration.
public class ContactViewModel
{
public int Id { get; set; }
public string? Name { get; set; }
public string? EmailAddress { get; set; }
}
The required data annotation will no longer be automatically added since this makes the property optional, however it will continue to be validated by your FluentValidation rules as normal.
This would be my preferred option since it is the most likely one to reflect the actual possible values for your view models. An individual using the app may not complete a field, and it should be clear this is a possibility. After the model for the view has passed your validation rules, it is likely you would then map this to a domain model which would then not need to consider this and use non-nullable property types.
Disable the automatic required data annotation for Nullable Reference Types
You could also disable the automatic adding of the required data annotation. This might be appropriate if you have a lot of properties and you do not want to mark each one as nullable.
You can disable this behaviour by adding the following code to your application startup.
builder.Services.AddControllers(options => {
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
});
If you take this approach, you should ensure that you are validating the fields properly since you will no longer have the safety net of the automatic validation. It is also important to check that no areas of the application are relying upon this new behaviour for model validation.
Summary
The introduction of the nullabe reference types. Is a definite improvement in allowing the compiler to detect potential causes of null reference exceptions. And the addition of automatically applying the required data annotation to non-nullable properties on view models is a bonus, however it can cause some initial confusion since they aren’t immediately apparent.
Thankfully this can easily be resolved by determining if a null value is a valid state for the property and marking it appropriately, or by disabling the feature of automatically applying the required data annotation.
You can find more information about the change in the model validation section for ASP.NET Core 6.