Reputation: 1251
I believe I'm doing everything according to the documentation. I've setup fluent validation in the Startup.cs class:
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(swagger =>
{
swagger.SwaggerDoc("v1", new OpenApiInfo { Title = _swaggerApiName });
swagger.DescribeAllParametersInCamelCase();
});
services.AddMvcCore()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.IgnoreNullValues = false;
})
.AddFluentValidation(options =>
{
options.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
options.RegisterValidatorsFromAssemblyContaining(typeof(ContentCoreMarker));
options.ImplicitlyValidateChildProperties = true;
})
.AddApiExplorer()
.AddRazorViewEngine();
}
My simple validator. Note that a breakpoint is hit on RuleFor.
public class AddSectionRequestValidator : AbstractValidator<AddSectionRequest>
{
public AddSectionRequestValidator()
{
RuleFor(m => m.SectionName)
.NotEmpty()
.MinimumLength(1)
.WithMessage("invalid");
RuleFor(m => m.ParentSectionId)
.NotEmpty();
}
}
Shouldn't fluentvalidation automatically return the validation errors without hitting the controller action?
According to the documentation:
If you want to disable this behavior so that FluentValidation is the only validation library that executes, you can set the RunDefaultMvcValidationAfterFluentValidationExecutes to false in your application startup routine
The breakpoint in the Controller is still hit. I've also created a local copy of the validator to test and the result is the model is invalid.
[HttpPost]
public async Task<IActionResult> Post([FromBody]AddSectionRequest request)
{
var validator = new AddSectionRequestValidator();
var isValid = validator.Validate(request); // Not valid
var result = await _addSectionRequestHandler.Handle(request);
return Ok(result.NewSectionId);
}
I'm on ASP.Net Core 3.1
Upvotes: 1
Views: 4739
Reputation: 1251
Finally found the problem. The issue in my case was that I was missing the [ApiController] attribute at the top of my controller:
[ApiController] // <-- Fix is here
[Route("[controller]")]
public class SectionController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Post([FromBody] DtoArticle request)
{
var ms = ModelState;
if (!ms.IsValid)
throw new Exception("Should not be possible");
return Ok();
}
}
My controller is no longer hit and I instead get a tidy response from Postman:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|f5f5b1a2-4dd202e6998784c3.",
"errors": {
"Name": [
"'Name' is required."
],
"Content": [
"'Content' is required."
]
}
}
Upvotes: 4
Reputation: 1057
Your controller is hit because that is where you are supposed to handle validation. The documentation has this example for using the validator in a controller:
Once you’ve configured FluentValidation, ASP.NET will then automatically validate incoming requests using the validator mappings configured in your startup routine.
[HttpPost]
public IActionResult Create(Person person) {
if(!ModelState.IsValid) { // re-render the view when validation failed.
return View("Create", person);
}
Save(person); //Save the person to the database, or some other logic
TempData["notice"] = "Person successfully created";
return RedirectToAction("Index");
}
As you can see they check if the ModelState
is valid and then return the view with the Person
model.
Since you are not returning a view I suggest you try returning the ModelState:
[HttpPost]
public async Task<IActionResult> Post([FromBody]AddSectionRequest request)
{
if(!ModelState.IsValid) return BadRequest(ModelState);
var result = await _addSectionRequestHandler.Handle(request);
return Ok(result.NewSectionId);
}
This would return a json like this:
{
"Name": ["The Name field is required."]
}
I also found this blogpost that goes into more detail and encapsulates model validation so that it is enabled for every controller action.
Upvotes: -1