Handle errors with your convention in ASP.NET Core
Tags: csharp, dotnet, exceptions, logging
Published: 2020-08-15 22:44:58

This post is a natural continuation of some of my previous ones:
- [Make SOAP requests using IHttpClientFactory in .NET Core](https://dev.to/nikolicbojan/make-soap-requests-using-ihttpclientfactory-in-net-core-46hk) and
- [Log HttpClient request and response based on custom conditions in .NET Core](https://dev.to/nikolicbojan/log-httpclient-request-and-response-based-on-custom-conditions-in-net-core-412f)

All work is done in the same GitHub repository https://github.com/nikolic-bojan/soap-client so you can see the code.

Here, we are dealing with handling errors in API project and also with logging some additional information that are valuable both for error analysis and determining how your service behaves.

# Error handling

There is a good explanation on how to do error handling in Microsoft documentation - https://docs.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-3.1#exception-handler-lambda.

What I would like to point out is that if you want to have a good API(s), you should make their responses clear, especially error responses. Those responses are often not read by humans, but by someone else's code and they should be clear on what to do when error occurs.

There is a well established [standard for error responses](https://tools.ietf.org/html/rfc7807) implemented also in .NET as `ProblemDetails` and `ValidationProblemDetails` classes. It is also built into the `ControllerBase` since Core 2.1.

It is not a problem to use these, it is more of an issue what to put into their properties and based on what. In this sample, I tried to make things simple, but you might want to make them more complex for your case. My advice - do not go too complex as someone needs to implement their code based on them.

In order to handle errors, I added the following line in Startup.cs

```csharp
app.UseExceptionHandler(errorApp => errorApp.Run(ExceptionHandler.CreateExceptionHandler(env)));
```

Important stuff is in that method, which will handle exceptions with a few steps:

1. We need to get the exception via `IExceptionHandlerFeature` and create some starting `ProblemDetails`.
2. We then `switch` by exception type and change some `ProblemDetails` properties and at the same time add some log scope data.
3. Finally, we write `ProblemDetails` directly to Response Body stream.

In order not to c/p entire method and bloat the post, please check the code here https://github.com/nikolic-bojan/soap-client/blob/master/Api/Handlers/ExceptionHandler.cs

What you will notice that I have only 3 outcomes, all based on exception type:
1. Validation exception - we return `ValidationProblemDetails` and log errors (maybe it could be LogWarning)
2. Service exception - any exception that happens in Service class (adapter to 3rd party)
3. Any other not handled exception

## Some advises on property values

- Create few (fewer is better) exception types that will define some standard exceptional situations.
- Return 400 for Validation errors (thanks Mr. Obvious!)
- Return 404 from your controller (NotFound) if you want to inform there is no resource.
- Use `app.UseStatusCodePages()`, so e.g. wrong URL will not produce same error as when resource is not found.
- All exceptions that bubble up in your code should be returned as 500.
  - Do not try to be "smart" and correlate exceptions to status codes (we made that mistake and status codes became misleading).
  - Correlate exception types to `type` property in response either by exception type name or (better) by having URL to your documentation that will explain what that error type is all about.

This way the consumer of your service will know if they should retry the call, was that a temporary issue, something to alarm on immediately (you should also do that by logging Critical error, for example); in general - how to behave.

## Sample responses

Here is how `BadRequest` looks like
```json
{
    "type": "ValidationException",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "92d14dddb407d945909d970efdc03988",
    "errors": {
        "FirstName": [
            "First name must not be 400"
        ]
    }
}
```

This is some error calling 3rd party system (SoapUI mock was down)
```json
{
    "type": "ServiceException",
    "title": "The content type text/html; charset=iso-8859-1 of the response message does not match the content type of the binding (text/xml; charset=utf-8). If using a custom encoder, be sure that the IsContentTypeSupported method is implemented properly. The first 95 bytes of the response were: '<html><body><p>There are currently 0 running SoapUI MockServices</p><ul></ul></p></body></html>'.",
    "status": 500,
    "traceId": "145be362dd1e7c42b84c4a566ae0ed24"
}
```

# Log more information for diagnostics and alarming

This is kind of separate subject, but in my head exceptional situations and logging additional information that can help identifying the pattern of the issue or can be used for diagnostics and alarming, goes in the same package.

This logging is per request and contains all kind of request related data. You can write this code by yourself or use some add-on from your logging library, like Serilog has `RequestLoggingMiddleware`. In the sample I use clean Microsoft Logger, so I had to write it by myself. Code is available here https://github.com/nikolic-bojan/soap-client/blob/master/Api/Handlers/TraceContextMiddleware.cs

On each request we `LogInformation` on that request. Base on those data we can create some dashboard widgets that would show us the statistics on HTTP status code per Controller and Action. We could also setup some alarms based on certain thresholds, e.g. "if there are more than X error logs during Y minutes then..."

# Feedback

This post feels a bit "rough" and most likely opinionated (isn't everything?)
 
I would really like to get a feedback on this subject as I do not think this is something you can call best-practice and all will agree with the approach.

What are your thoughts? How do you handle error handling?


BR,
Bojan