Giter Site home page Giter Site logo

josephwoodward / globalexceptionhandlerdotnet Goto Github PK

View Code? Open in Web Editor NEW
271.0 22.0 32.0 1.63 MB

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

License: MIT License

C# 98.95% PowerShell 0.89% Shell 0.16%
webapi net-core

globalexceptionhandlerdotnet's Introduction

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.

globalexceptionhandlerdotnet's People

Contributors

alek-mcgarry avatar btastic avatar josephwoodward avatar lsgroi avatar slang25 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

globalexceptionhandlerdotnet's Issues

Logging

Ensure exceptions are logged accordingly in whatever logging tool the consumer is using.

ASP.Net Core 2.1 Issue

Just upgraded to ASP.Net Core 2.1 and got this error:

InvalidOperationException: An error occurred when configuring the exception handler middleware. Either the 'ExceptionHandlingPath' or the 'ExceptionHandler' option must be set in 'UseExceptionHandler()'.

My setup before was this:

 app.UseExceptionHandler().WithConventions(x => {
                x.ContentType = "application/json";
                x.MessageFormatter(s => JsonConvert.SerializeObject(new
                {
                    Message = "Internal Server Error"
                }));
                x.OnError((exception, httpContext) =>
                {
                    Log.Error(exception, "WebAPI Global Exception Handler");
                    return Task.CompletedTask;
                });
            });

Adding app.UseExceptionHandler("/error") as below seems to fix it.

app.UseExceptionHandler("/error").WithConventions(x => {
                x.ContentType = "application/json";
                x.MessageFormatter(s => JsonConvert.SerializeObject(new
                {
                    Message = "Internal Server Error"
                }));
                x.OnError((exception, httpContext) =>
                {
                    Log.Error(exception, "WebAPI Global Exception Handler");
                    return Task.CompletedTask;
                });
            });

Just wanted to give you a heads up and check the workaround makes sense. Thanks for a great repo.

How to handle the response in unit test cases?

Hello
How can I handle the exceptions deliberately thrown from the controller using MediatR in UT cases

Ex: This code snippet expects an ArgumentException from the controller side

command.CustomerProfileId = id; await _mediator.Send(command); return Ok();

The line await _mediator.Send(command); throws NullReference Exception.

Doesn't handle custom exceptions in net.core 3.1

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

                    x.Map<EntityNotFoundException>().ToStatusCode(HttpStatusCode.NotFound);
                });

Having this configuration and throwing EntityNotFoundException, i'm getting HTTP 500

can not handle api constructor exception

dotnet version : .net 5
os : windows

when i raise new custome exception in my ctor of webapi in my project ,app.UseGlobalExceptionHandler can not handle exception
here is my ctor code :
CurrentLoggedUser = _unitOfWork.GetRepository<AuthXUser>().SingleOrDefault(a => a.UUID == _userUUID) ?? throw new CanNotFoundUserWithCurrentUUID(_userUUID);

here is my error handling code :

app.UseGlobalExceptionHandler(x => { x.ContentType = "application/json"; x.ResponseBody(s => JsonConvert.SerializeObject(new { Description = "An error occured whilst processing your request and has not developet in global exception", ErrorMessage = s.Message })); x.Map<CanNotFoundUserWithCurrentUUID>().ToStatusCode(StatusCodes.Status404NotFound) .WithBody((ex, context) => JsonConvert.SerializeObject(new ContainerErrorModel { Message = $"can not found user by uuid {ex.UUID}", ContainerName = "Profile", Code = 0 })); });

HTTP status code within exception

I am using this library, works great for mapping argument exceptions etc.

I have another exception type where I am calling another API. The HTTP status code is within a property in that exception. Is there or can there be a way to handle for this?

Access to mapped status code in OnError handler

In order to categorize my logging entry I would like to have access to the mapped status code. For example:

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

        x.Map<ArgumentNullException>().ToStatusCode(StatusCodes.Status400BadRequest);
        x.OnError((exception, httpContext) =>
        {
            if (<mappedStatusCode> >= 500) 
            {
               log.error(...);
            }
            else
            {
               log.warning(...);
            }
       }
);

User configured global message formatter is never used

In the example below, I would expect the message "Something went wrong!" to be returned whenever any exception other than a DivideByZeroException is encountered.

           app.UseWebApiGlobalExceptionHandler(x =>
            {
                x.MessageFormatter(exception => JsonConvert.SerializeObject(new
                {
                    error = new
                    {
                        message = "Something went wrong!"
                    }
                }));

                x.ForException<DivideByZeroException>().ReturnStatusCode(HttpStatusCode.BadRequest).UsingMessageFormatter(
                    exception => JsonConvert.SerializeObject(new
                    {
                        error = new
                        {
                            exception = exception.GetType().Name,
                            message = exception.Message
                        }
                    }));
            });

This however doesn't happen, because the HandleExceptionAsync method creates a new instance of DefaultExceptionConfig, which provides its own Formatter.

Removing the Formatter from the DefaultExceptionConfig seems to solve my issue, but may have result in issues for other scenarios.

public class DefaultExceptionConfig : IExceptionConfig
 {
     public HttpStatusCode StatusCode { get; set; }
     public Func<Exception, string> Formatter { get; set; }

     public DefaultExceptionConfig()
     {
         //Formatter = exception => JsonConvert.SerializeObject(new
         //{
         //    error = new
         //    {
         //        exception = exception.GetType().Name,
         //        message = exception.Message
         //    }
         //});
     }
 }

Unhandled exception log

As far as I understand, the library is built on top of ExceptionHandlerMiddleware by setting its exception handler.

By default the exception is logged as "unhandled exception" after which is handled by the library via ExceptionHandlerConfiguration.

This is causing a message "An unhandled exception has occurred: ..." sent to the logger. What would you suggest to avoid that behaviour?

Is This Repo Alive?

We are evaluating using this in our new project. Just wondering is it still under active development? And if so is it compatible with dotnet core 2.1?

Is it possible to add a non-generic method of Map<T>() ?

I would like to scan the Implement classes of the interface IException that I defined to custom StatusCode in GlobalExceptionHandler, but with generic version of Map<T>(), I had to use reflection to achieve that, it's a bit of annoying... is it possible to add a non-generic version of Map<T>(), such as Map(Type t)?
or add a property of StatusCode to ExceptionHandlerConfiguration?

var exceptionList = Assembly.GetExecutingAssembly().GetTypes().Where(t => t != typeof(IException) && typeof(IException).IsAssignableFrom(t)).ToList();

 foreach (var item in exceptionList)
{
/// can not use o.Map<T>() here directly.
}

Handling a specific exception in MVC Core 2.1

I'm hoping you can point me in the right direction... There's a certain exception that occurs in my project, and when it happens, I want to show a special error message to advise the user. My research has led me here.

Couple quick questions:

  • Is this a reasonable use of GlobalExceptionHandlerDotNet? It seems a little more focused on WebAPI. I don't want to do anything that interferes with AppInsights/New Relic exception logging, etc.
  • If I have come to the right place, could you point me towards some documentation showing how to do this? Most of what I see seems to be around generating text responses in WebAPI. Granted, there is a lot I don't know about this part of the framework .

In case you want more background... In my project, I'm using a 3rd party auth provider, Auth0, and I'm using their hosted login page. If a user ever goes to a stale login page (> 15m old), we end up with a Correlation Failed error, and one way to handle it may be with global exception handling. But I'm concerned this is all wild overkill, of course.

Thanks for your time!

OnError endpoint and exception map

I'm using the OnError endpoint to write exceptions to serilog. Additionally I have mapped ValidationException to status code 400.
In order to test my GlobalExeptionHandler configuration I have distributed some new ApplicationException("...") in my code. But these never get logged. Only the ValidationExceptions are. If I add a map for ApplicationException, these get logged, too. Is this the intended behavior?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.