Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do we no longer need to manually validate models in higher versions of ASP.NET Core?

All my models are automatically validated before hitting the endpoint, and return appropriate errors if some form of validation has failed.

I remember back in ASP.NET Core 2.2 we needed to manually call ModelState.IsValid to make sure an object has passed validation checks, but with the latest ASP.NET Core 3.0, this doesn't seem to be the case, and nowhere am I including/configuring any services explicitly for this behavior to exist.

Could someone shine some light on the matter, and perhaps link a relevant source where they mention this change?

EDIT: Is it due to the [ApiController] attribute? See: https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#automatic-http-400-responses

Thank you!

like image 960
SpiritBob Avatar asked Dec 06 '19 09:12

SpiritBob


2 Answers

When using the [ApiController] attribute, you do not need to check ModelState.IsValid in each method since a 400 status code is automatically returned with the name of the fields where the validation failed, see https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-3.1

You can even modify the way the 400 status code should look like. This blog article should get you started: https://coderethinked.com/customizing-automatic-http-400-error-response-in-asp-net-core-web-apis/

Add this dependency:

services.AddMvc.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.ConfigureApiBehaviorOptions(options =>
{
    options.InvalidModelStateResponseFactory = context =>
    {
        var problems = new CustomBadRequest(context);
        return new BadRequestObjectResult(problems);
    };
});

Your custom bad request class can look like this. Create a YourErrorClass class if you want to include additional information regarding the errors, e.g., error severity, warnings etc.

public class CustomBadRequest
{
    [JsonProperty("httpstatuscode")]
    public string HttpStatusCode { get; set; }

    [JsonProperty("errors")]
    public List<YourErrorClass> Errors { get; set; } = new List<YourErrorClass>();

    public CustomBadRequest(ActionContext context)
    {
        this.HttpStatusCode = "400";
        this.Errors = new List<YourErrorClass>();
        this.ConstructErrorMessages(context);
    }

    private void ConstructErrorMessages(ActionContext context)
    {
        foreach (var keyModelStatePair in context.ModelState)
        {
            var key = keyModelStatePair.Key;
            var errors = keyModelStatePair.Value.Errors;
            if (errors != null && errors.Count > 0)
            {
                foreach (var error in errors)
                {
                    Errors.Add(new YourErrorClass()
                    {
                        ErrorMessage = error.ErrorMessage
                        // add additional information, if you like
                    });
                }
            }
        }
}
like image 199
citronas Avatar answered Oct 21 '22 17:10

citronas


Yes it is because of the ApiController attribute.

However this would happen even in Core 2.2, however there was an options to set the compatibility to 2.0, this however no longer works since 3.0, which might explain the behavior you see.

To turn it off in 3.0 and up, you need to set it in the options when doing services.AddControllers() or services.AddMvc() as in the following code:

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

Also note that there was a difference between core 2.1 and later versions, as described in this MSDN article, as in the following examples:

Core 2.1 sample response:

{
   "": [
      "A non-empty request body is required."
   ]
}

Core 2.2 and up sample response:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "|7fb5e16a-4c8f23bbfc974667.",
  "errors": {
    "": [
      "A non-empty request body is required."
    ]
  }
}

To get back the 2.1 behavior, you could set the compatibilty back to 2.1, but this no longer works in since 3.0 and up, instead you need the following code in the option configuration when doing services.AddControllers() or services.AddMvc() as in the following code:

services.AddControllers().ConfigureApiBehaviorOptions(options =>
{
    options.InvalidModelStateResponseFactory = context => 
         new BadRequestObjectResult(new SerializableError(context.ModelState));
});
like image 23
yoel halb Avatar answered Oct 21 '22 19:10

yoel halb