Im trying to learn .net 3.1 by building a small test webapi and currently my objective is to validate dtos with fluentvalidation and in case it fails, present a custom json to the caller. The problems i have found and cant get over are two;
My code is as follows:
1. The Controller
[ApiController]
[Route("[controller]")]
public class AdminController : ControllerBase
{
[HttpPost]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status202Accepted)]
public async Task<IActionResult> RegisterAccount(NewAccountInput dto)
{
return Ok();
}
}
2. The Dto and the custom validator
public class NewAccountInput
{
public string Username { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public AccountType Type { get; set; }
}
public class NewAccountInputValidator : AbstractValidator<NewAccountInput>
{
public NewAccountInputValidator()
{
RuleFor(o => o.Email).NotNull().NotEmpty().WithMessage("Email vazio");
RuleFor(o => o.Username).NotNull().NotEmpty().WithMessage("Username vazio");
}
}
3. The Filter im using for validation
public class ApiValidationFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
//the only output i want are the error descriptions, nothing else
var data = context.ModelState
.Values
.SelectMany(v => v.Errors.Select(b => b.ErrorMessage))
.ToList();
context.Result = new JsonResult(data) { StatusCode = 400 };
}
//base.OnActionExecuting(context);
}
}
finally, my configureservices
public void ConfigureServices(IServiceCollection services)
{
services
//tried both lines and it doesnt seem to work either way
.AddScoped<ApiValidationFilter>()
.AddControllers(//config=>
//config.Filters.Add(new ApiValidationFilter())
)
.AddFluentValidation(fv => {
fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;//i was hoping this did the trick
fv.RegisterValidatorsFromAssemblyContaining<NewAccountInputValidator>();
});
}
Now, trying this with postman i get the result
which highlights both issues im having atm
This was done with asp.net core 3.15 and visualstudio 16.6.3
The message you are seeing is in fact coming from FluentValidation - see the source.
The reason you aren't seeing the custom message you are providing is that FluentValidation will show the validation message from the first validator that fails in the chain, in this case NotNull
.
This question gives some options for specifying a single custom validation message for an entire chain of validators.
In this case the Action Filter you describe is never being hit, as the validation is failing first. To prevent this you can use:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
which will stop the automatic return of BadRequest for an invalid model. This question provides some alternative solutions, including configuring an InvalidModelStateResponseFactory
to do what you require.