• Stars
    star
    267
  • Rank 152,928 (Top 4 %)
  • Language
    C#
  • License
    MIT License
  • Created about 7 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Exception handling as a convention in the ASP.NET Core request pipeline

Global Exception Handling for ASP.NET Core

Build status

GlobalExceptionHandler.NET allows you to configure application level exception handling as a convention within your ASP.NET Core application, opposed to explicitly handling exceptions within each controller action.

Configuring your error handling this way reaps the following benefits:

  • Centralised location for handling errors
  • Reduce boilerplate try-catch logic in your controllers
  • Catch and appropriately handle exceptions outside of the ASP.NET Core framework
  • You don't want error codes being visible by consuming APIs (for instance, you want to return 500 for every exception)

This middleware targets the ASP.NET Core pipeline with an optional dependency on the MVC framework for content negotiation if so desired.

Note: GlobalExceptionHandler.NET builds on top of the app.UseExceptionHandler() middleware so they cannot be used in tandem. GlobalExceptionHandler.NET turns your exception configuration provided by this library into an ExceptionHandler used within the UseExceptionHandler middleware.

Installation

GlobalExceptionHandler is available on NuGet and can be installed via the below commands depending on your platform:

$ Install-Package GlobalExceptionHandler

or via the .NET Core CLI:

$ dotnet add package GlobalExceptionHandler

Bare Bones Setup

// Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // app.UseExceptionHandler(); You no longer need this.
    app.UseGlobalExceptionHandler(x => {
        x.ContentType = "application/json";
        x.ResponseBody(s => JsonConvert.SerializeObject(new
        {
            Message = "An error occurred whilst processing your request"
        }));
    });
    
    app.Map("/error", x => x.Run(y => throw new Exception()));
}

Any exception thrown by your application will result in the follow response:

HTTP/1.1 500 Internal Server Error
Date: Fri, 24 Nov 2017 09:17:05 GMT
Content-Type: application/json
Server: Kestrel
Cache-Control: no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Expires: -1

{
  "Message": "An error occurred whilst processing your request"
}

Handling specific exceptions

You can explicitly handle exceptions like so:

app.UseGlobalExceptionHandler(x => {
    x.ContentType = "application/json";
    x.ResponseBody(s => JsonConvert.SerializeObject(new
    {
        Message = "An error occurred whilst processing your request"
    }));

    x.Map<RecordNotFoundException>().ToStatusCode(StatusCodes.Status404NotFound);
});
HTTP/1.1 404 Not Found
Date: Sat, 25 Nov 2017 01:47:51 GMT
Content-Type: application/json
Server: Kestrel
Cache-Control: no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Expires: -1

{
  "Message": "An error occurred whilst processing your request"
}

Runtime Status Code

If talking to a remote service, you could optionally choose to forward the status code on, or propagate it via the exception using the following ToStatusCode(..) overload:

app.UseGlobalExceptionHandler(x =>
{
    x.ContentType = "application/json";
    x.Map<HttpNotFoundException>().ToStatusCode(ex => ex.StatusCode).WithBody((e, c) => "Resource could not be found");
    ...
});

Per exception responses

Or provide a custom error response for the exception type thrown:

app.UseGlobalExceptionHandler(x => {
    x.ContentType = "application/json";
    x.ResponseBody(s => JsonConvert.SerializeObject(new
    {
        Message = "An error occurred whilst processing your request"
    }));

    x.Map<RecordNotFoundException>().ToStatusCode(StatusCodes.Status404NotFound)
        .WithBody((ex, context) => JsonConvert.SerializeObject(new {
            Message = "Resource could not be found"
        }));
});

Response:

HTTP/1.1 404 Not Found
...
{
  "Message": "Resource could not be found"
}

Alternatively you could output the exception content if you prefer:

app.UseGlobalExceptionHandler(x => {
    x.ContentType = "application/json";
    x.ResponseBody(s => JsonConvert.SerializeObject(new
    {
        Message = "An error occurred whilst processing your request"
    }));

    x.Map<RecordNotFoundException>().ToStatusCode(StatusCodes.Status404NotFound)
        .WithBody((ex, context) => JsonConvert.SerializeObject(new {
            Message = ex.Message
        }));
});

Content Negotiation

GlobalExceptionHandlerDotNet plugs into the .NET Core pipeline, meaning you can also take advantage of content negotiation provided by the ASP.NET Core MVC framework, enabling the clients to dictate the preferred content type.

To enable content negotiation against ASP.NET Core MVC you will need to include the GlobalExceptionHandler.ContentNegotiation.Mvc package.

Note: Content negotiation is handled by ASP.NET Core MVC so this takes a dependency on MVC.

//Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcCore().AddXmlSerializerFormatters();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseGlobalExceptionHandler(x =>
    {
        x.Map<RecordNotFoundException>().ToStatusCode(StatusCodes.Status404NotFound)
            .WithBody(e => new ErrorResponse
            {
                Message = e.Message
            });
    });

    app.Map("/error", x => x.Run(y => throw new RecordNotFoundException("Resource could not be found")));
}

Now when an exception is thrown and the consumer has provided the Accept header:

GET /api/demo HTTP/1.1
Host: localhost:5000
Accept: text/xml

The response will be formatted according to the Accept header value:

HTTP/1.1 404 Not Found
Date: Tue, 05 Dec 2017 08:49:07 GMT
Content-Type: text/xml; charset=utf-8
Server: Kestrel
Cache-Control: no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Expires: -1

<ErrorResponse 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Message>Resource could not be found</Message>
</ErrorResponse>

Logging

Under most circumstances you'll want to keep a log of any exceptions thrown in your log aggregator of choice. You can do this via the OnError endpoint:

x.OnError((exception, httpContext) =>
{
    _logger.Error(exception.Message);
    return Task.CompletedTask;
});

Configuration Options:

  • ContentType
    Specify the returned content type (default is application/json).

  • ResponseBody(...)
    Set a default response body that any unhandled exception will trigger.

x.ResponseBody((ex, context) => {
    return "Oops, something went wrong! Check the logs for more information.";
});
  • DebugMode Enabling debug mode will cause GlobalExceptionHandlerDotNet to return the full exception thrown. This is disabled by default and should not be set in production.

More Repositories

1

graphiql-dotnet

GraphiQL middleware for ASP.NET Core
CSS
142
star
2

Serilog-Sinks-Loki

A Serilog Sink for Loki, Grafana's new Prometheus inspired log aggregator
C#
126
star
3

VSCodeILViewer

A Visual Studio Code C# IL (Intermediate Language) Viewer
C#
93
star
4

Angular2PianoNoteTrainingGame

Angular 2 based piano note training game
TypeScript
83
star
5

DotNetInstallSdkGlobalTool

Global tool to make installing .NET version that little bit easier
C#
16
star
6

gin-errorhandling

Go
10
star
7

SlugityDotNet

Simple class library to sanitize text and turn it into a search engine friendly URL
C#
9
star
8

RouteUrlRedirector

ASP.NET Core MVC Middleware for redirecting old paths to new paths using either 301 or 302 redirects
C#
9
star
9

CKFinderJcrop

CKFinder plugin of a much needed image cropping feature using the JQuery based JCrop
JavaScript
5
star
10

tftp-server

A TFTP Server written in Go
Go
4
star
11

httpclient-interception-go

Go
3
star
12

chaosproxy

A simple means of introducing chaos into your infrastructure for Windows, Mac and Linux.
Go
2
star
13

git-hotspot

Go
2
star
14

TrafficCop

Simple rules based traffic manager for ASP.NET MVC
C#
2
star
15

SeleniumGridDotNetCore

C#
1
star
16

OpenApiApprovalTesting

C#
1
star
17

otelnats

1
star
18

NServiceBusPlayground

Heard lots of interesting things about NServiceBus so want to have a play
C#
1
star
19

BlazorDemo

C#
1
star
20

RazorPlayground

C#
1
star
21

WorldsSmallestViolin

Lunchtime Android project perfect for winding friends up.
Java
1
star
22

ExceptionRerouter

A simple exception rerouting library for ASP.NET MVC.
C#
1
star
23

AzureAdTemp

C#
1
star
24

HttpLogger

Go
1
star
25

NetworkProgrammingInGo

Go
1
star
26

TypeScriptSpaceShooter

A really simple Javascript based game I've been working on during lunchtime using TypeScript.
JavaScript
1
star