Noobie3001
Noobie3001

Reputation: 1251

Why is my controller hit when the fluentvalidation for the model is not valid in ASP.Net Core?

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

Answers (2)

Noobie3001
Noobie3001

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

Jay Fridge
Jay Fridge

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

Related Questions