Customize automatic response on validation error

With asp.net core 2.1 an ApiController will automatically respond with a 400 BadRequest when validation errors occur.

How can I change/modify the response (json-body) that is sent back to the client? Is there some kind of middleware?

I´m using FluentValidation to validate the parameters sent to my controller, but I am not happy with the response that I am get. It looks like

{
    "Url": [
        "'Url' must not be empty.",
        "'Url' should not be empty."
    ]
}

I want to change the response, cause we have some default values that we attach to responses. So it should look like

{
    "code": 400,
    "request_id": "dfdfddf",
    "messages": [
        "'Url' must not be empty.",
        "'Url' should not be empty."
    ]
}

Solution 1:

The ApiBehaviorOptions class allows for the generation of ModelState responses to be customised via its InvalidModelStateResponseFactory property, which is of type Func<ActionContext, IActionResult>.

Here's an example implementation:

apiBehaviorOptions.InvalidModelStateResponseFactory = actionContext => {
    return new BadRequestObjectResult(new {
        Code = 400,
        Request_Id = "dfdfddf",
        Messages = actionContext.ModelState.Values.SelectMany(x => x.Errors)
            .Select(x => x.ErrorMessage)
    });
};

The incoming ActionContext instance provides both ModelState and HttpContext properties for the active request, which contains everything I expect you could need. I'm not sure where your request_id value is coming from, so I've left that as your static example.

To use this implementation, configure the ApiBehaviorOptions instance in ConfigureServices:

serviceCollection.Configure<ApiBehaviorOptions>(apiBehaviorOptions =>
    apiBehaviorOptions.InvalidModelStateResponseFactory = ...
);

Solution 2:

Consider creating of custom action filer, e.g.:

public class CustomValidationResponseActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            var errors = new List<string>();

            foreach (var modelState in context.ModelState.Values)
            {
                foreach (var error in modelState.Errors)
                {
                    errors.Add(error.ErrorMessage);
                }
            }

            var responseObj = new
            {
                code = 400,
                request_id = "dfdfddf",
                messages = errors
            };

            context.Result = new JsonResult(responseObj)
            {
                StatusCode = 400
            };
        }
    }

    public void OnActionExecuted(ActionExecutedContext context)
    { }
}

You can register it in ConfigureServices:

services.AddMvc(options =>
{
    options.Filters.Add(new CustomValidationResponseActionFilter());
});