Artem A
Artem A

Reputation: 2448

Why ModelState returns different result and how to fix it?

Asp.net core 3.1 WebApi.

I have a model with required properties.

1.If model is not valid, then the response contains data like :

{
    "errors": {
        "Name": [
            "Update model can't have all properties as null."
        ],
        "Color": [
            "Update model can't have all properties as null."
        ]
    },
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|f032f1c9-4c36d1e62aa60ead."
}

And this looks good for me.

But if I add some custom validation to modelState.AddModelError("statusId", "Invalid order status id.") then it returns different structure:

[
    {
        "childNodes": null,
        "children": null,
        "key": "statusId",
        "subKey": {
            "buffer": "statusId",
            "offset": 0,
            "length": 8,
            "value": "statusId",
            "hasValue": true
        },
        "isContainerNode": false,
        "rawValue": "11202",
        "attemptedValue": "11202",
        "errors": [
            {
                "exception": null,
                "errorMessage": "Invalid order status id."
            }
        ],
        "validationState": 1
    }
]

Also looks like ModelState.IsValid is actual no more for controller, because the bad request is returned before it even enters the controller with invalid mode. Or is there some flag with global validation via ModelSate?

Why the structure is different? How to make it the same? How to force to get to ModelState.IsValid method inside of api controller as it worked in MVC?

Update:



    [Route("....")]
    [Authorize]
    [ApiController]
    public class StatusesController : ApiControllerBase
    {


        [HttpPut, Route("{statusId}")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        [ProducesResponseType(StatusCodes.Status409Conflict)]
        [Produces("application/json")]
        public async Task<ObjectResult> UpdateStatusAsync(int statusId, [FromBody] StatusUpdateDto orderStatusUpdateDto)
        {

            int companyId = User.Identity.GetClaimValue<int>(ClaimTypes.CompanyId);

            const string errorNotFound  = "There is not order status with this id for such company";
            if (statusId <= 0)
            {
                Logger.LogError(errorNotFound);
                ModelState.AddErrorModel(nameof(statusId), "Invalid order status id")
                throw new NotFound(ModelState);
            }
            
            if (orderStatusUpdateDto == null)
            {
                const string error = "Invalid (null) order status can't be added";
                Logger.LogError(error);
                throw new ArgumentNullException(error);
            }

            if (ModelState.IsValid == false) // also this code is always true or returns 400 before this line
            {
                return BadRequest(ModelState); 
            }

           ....
            return result;
        }

}

Upvotes: 3

Views: 2726

Answers (1)

treze
treze

Reputation: 3289

The ApiController attribute adds some specific opinionated behaviors to a controller. One of them is to return a 400 error if the model is not valid. This behavior can be disabled, but only on a global level.

services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressModelStateInvalidFilter = true;
    });

I think you have the following options:

  • Disable this behavior and check ModelState.IsValid yourself. Use ValidationProblem method to produce the same response
  • Add this check to the validator of the model
  • Keep everything as it is. But use ValidationProblem inside the controller method to return the validation errors.

See https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#automatic-http-400-responses for information

Upvotes: 8

Related Questions