Problem Details - The right way to specify errors in Web API responses

Problem Details - The right way to specify errors in Web API responses

The need for Problem Details

Often when you are developing REST APIs, one of the tough conversations to have with the consumers of your API (web client, mobile app, other 3rd parties, etc.) is defining the way your API would specify errors. Now, this is a crucial piece of design as depending on it the client/consumer will design their part of showing errors to the end-users. For example, imagine an API that allows customers to book movie tickets online and this call returns a Bad request (400) response without any other additional info. The user doesn't understand why the Client UI is showing an error and the Client UI doesn't know why the API has failed.

Many people have different designs for specifying the errors in the API response - some just return status code without any response message, while some describe a simple text message in the body and others specify custom JSON schema for specifying the errors.

Due to this, the Internet Engineering Task Force (IETF) proposed the "Problem Details for HTTP APIs" specification in March 2016. As per it,

"Problem Details" is a way to carry machine-readable details of errors in a HTTP response to avoid the need to define new error response formats for HTTP APIs.

A simple example of HTTP response of JSON problem details is :

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "errors": {
        "Id": [
            "The Id field is required."
        ],
        "Fact": [
            "The Fact field is required."
        ]
    }
}

The format of the message is application/problem+json or application/problem+xml media type and the response body must at bare minimum contain the below fields:

  • type : A URI that defines the problem detail type.
  • title : A title that summarizes the problem.
  • status : HTTP status code.

We can extend the problem details with additional properties, for example, the above error message defined "errors" to communicate additional information to the client.

Problem details in ASP.NET Core

In ASP.NET Core 2.1, the team added ProblemDetails class to support problem details model. And in ASP.NET Core 3.0, the ControllerBase.Problem method was added to produce a ProblemDetails response from controller action methods.

In the rest of the article, we will look at handling different error types from ASP.NET Core and how to use the inbuilt functionality already provided to specify the errors in ProblemDetails response format.
We will look at specifying error responses for :

  • 400 Bad Request
  • 404 Not Found
  • 500 Internal Server Error

We will primarily be using the ControllerBase.Problem method to specify errors manually and leaving it to the ASP.NET Core to generate for others.

400 Bad Request

We have 2 scenarios here: Failed model validations using DataAnnotations and Custom bad request

  • Failed model validations using DataAnnotations: In this case, you don't need to do any additional work. ASP.NET Core will take care of it for you, given that you are using the [ApiController] attribute to the API controller and DataAnnotations for validating the model.

    400-BadRequest-DataAnnotations.png In the above example, we have a POST API and the input data model is validated using the Required attribute of the DataAnnotations class. When this API is called without passing in the required parameters of the model, a 400-Bad Request in the ProblemDetails format is generated. 400-BadRequest-DataAnnotations-Postman-Edited.png

  • Custom bad request: Maybe the business rule failed or some other logic check failed and you need to return 400-Bad Request. In this case, you can use this.Problem(detail, statusCode: 400) inside the Controller to return the error in problem details format. 400-Custom Bad Request-Updated.png

    In the above example code, we are sending 400 from the controller using the Problem method when the business logic fails. Hitting the API with an invalid request will result in a 400-Bad Request in the ProblemDetails format. 400-BadRequest-Custom-Updated-Postman-Edited.png

404 Not Found

For the 404 wherein the route didn't match, there isn't much you can do, but for scenarios where you want to return "Not Found" when data for the specified Id is not found, etc.; using the NotFound() method in Controller will return the 404 in Problem Details format.

404-Not Found-Updated.png In the above example code, we are returning NotFound() from the controller when the result is not found for the specified Id. Hitting the API with an invalid Id will result in 404-Not Found in the ProblemDetails format. 404-NotFound-Postman-Edited.png

500 Internal Server Error

In case of 500 Internal server error, you don't want to expose off any exception object details as it will contain crucial code and system-related information; but should still adhere to the Problem Details format by giving a vague "An error occurred while processing your request." error message to the API caller. In this case, you can use this.Problem() inside the Controller to return the error in problem details format with the default 500 error message and status code. In some very exceptional cases wherein you would like to send custom messages for the exception, use the this.Problem(customErrorMessage) inside the Controller.

500-Internal Server Error.png

Hitting the above API which might result in some exceptions will return the response in the right format. 500--Postman-Edited.png

Summary

Problem Details standard is proposed by IETF and has been around for quite some time, but still, its usage or adherence is very low. Often people reinvent the wheel or follow different approaches while specifying errors in the Web API responses. In this article, we looked at the ProblemDetails specification and also using the Problem() method in the ControllerBase of ASP.Net Core application to return errors in this standard format.