I have created custom 'ValidationFilter' in order to validate request before it reaches controller, there it is:
ValidationFilter class
using Contracts.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Middleware.Filters
{
public class ValidationFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ModelState.IsValid)
{
#pragma warning disable CS8602 // Dereference of a possibly null reference.
var errorsInModelState = context.ModelState
.Where(o => o.Value.Errors.Count > 0)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Errors.Select(o => o.ErrorMessage)).ToArray();
#pragma warning restore CS8602 // Dereference of a possibly null reference.
var errorResponse = new ErrorResponse();
foreach(var error in errorsInModelState)
{
foreach(var subError in error.Value)
{
var errorModel = new ErrorModel
{
FieldName = error.Key,
Message = subError
};
errorResponse.Errors.Add(errorModel);
}
}
context.Result = new BadRequestObjectResult(errorResponse);
return;
}
await next();
}
}
}
There is simple Validator for request:
using Contracts;
using FluentValidation;
namespace Middleware.Validators
{
public class AddressingDtoValidator : AbstractValidator<AddressingDto>
{
public AddressingDtoValidator()
{
RuleFor(x => x.District)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.Mr)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.Quarter)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.Street)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.Building)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.Corpus)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.Building)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
RuleFor(x => x.InstitutionName)
.NotNull()
.NotEmpty()
.Matches("^[a-zA-Z0-9 ]*$");
}
}
}
I have also "ErrorModel" and "ErrorResponse" classes which you can see below and my goal is to display error using this class but somemwhy it doesn't work:
ErrorModel
namespace Contracts.ViewModels
{
public class ErrorModel
{
public string? FieldName { get; set; }
public string? Message { get; set; }
}
}
ErrorResponse class
namespace Contracts.ViewModels
{
public class ErrorResponse
{
public List<ErrorModel> Errors { get; set; } = new List<ErrorModel>();
}
}
I want the error to be displayed like this:
{
"errors": [
{
"fieldname": "....",
"message": "...."
}
]
}
but instead, i get something like that:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-b9157268dc1f793004182694c1acf1a7-67fd063ebec5cbc3-00",
"errors": {
"Quarter": [
"'Quarter' must not be empty."
]
}
}
I mean this type of error is understandable and okay to read but i am practicing with action filters and something is not working.
There is also Program.cs class where i inject this validator in pipeline:
builder.Services.AddControllers(options =>
{
options.Filters.Add<ValidationFilter>();
})
.AddFluentValidation(configuration => configuration.RegisterValidatorsFromAssemblyContaining<AddressingDtoValidator>());
So what could be the problem, am i missing something?
CodePudding user response:
To disable the default model state handling, you need to add the below setting in Program.cs.
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});