Catch All Unhandled Exceptions in ASP.NET Web API

catch all unhandled exceptions in ASP.NET Web Api

This is now possible with WebAPI 2.1 (see the What's New):

Create one or more implementations of IExceptionLogger. For example:

public class TraceExceptionLogger : ExceptionLogger
{
public override void Log(ExceptionLoggerContext context)
{
Trace.TraceError(context.ExceptionContext.Exception.ToString());
}
}

Then register with your application's HttpConfiguration, inside a config callback like so:

config.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());

or directly:

GlobalConfiguration.Configuration.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());

Catching unhandled exceptions with asp.net webapi and TPL

Exceptions that are thrown from inside a running task aren't unhandled. They are captured on that task turning it faulted.

The correct way to handle these exceptions would be to await each task, or register continuations using Task.ContinueWith. However, if you want a global handler you can use the TaskScheduler.UnobservedTaskException event.

When there's an exception inside a task it's handled and stored on the Task object for you to observe. If the task gets garbage collected and the exception wasn't yet observed by any code .Net knows that it will never be observed (as you don't have a reference to the task) and raises the TaskScheduler.UnobservedTaskException event (in .Net 4.0 it will also crash the app and that behavior can be replicated in newer versions as well).

So, an event handler for TaskScheduler.UnobservedTaskException will globally handle all exceptions raised from tasks, but only after they have been GCed (which isn't immediately):

TaskScheduler.UnobservedTaskException += (sender, args) => HandleException(args.Exception);

How to catch all exceptions in Web API 2?

Implement IExceptionHandler.

Something like:

 public class APIErrorHandler : IExceptionHandler
{
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
var customObject = new CustomObject
{
Message = new { Message = context.Exception.Message },
Status = ... // whatever,
Data = ... // whatever
};

//Necessary to return Json
var jsonType = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;

var response = context.Request.CreateResponse(HttpStatusCode.InternalServerError, customObject, jsonType);

context.Result = new ResponseMessageResult(response);

return Task.FromResult(0);
}
}

and in the configuration section of WebAPI (public static void Register(HttpConfiguration config)) write:

config.Services.Replace(typeof(IExceptionHandler), new APIErrorHandler());

How to globally handle exceptions in asp.net web api when exception is raised through lambda expression

The ExceptionFilterAttribute only works for exceptions thrown in your action method, see Exception Handling in ASP.NET Web API - Exception Filters. Your code will throw an exception during the materialization of the result, causing a SerializationException.

As explained in Global Error Handling in ASP.NET Web API 2:

Some unhandled exceptions can be processed via exception filters, but there are a number of cases that exception filters can’t handle. For example:

  • Exceptions thrown from controller constructors.
  • Exceptions thrown from message handlers.
  • Exceptions thrown during routing.
  • Exceptions thrown during response content serialization.

Register an exception handler or logger and act appropriately:

We provide two new user-replaceable services, IExceptionLogger and IExceptionHandler, to log and handle unhandled exceptions. The services are very similar, with two main differences:
We support registering multiple exception loggers but only a single exception handler.

  1. Exception loggers always get called, even if we’re about to abort the connection.
  2. Exception handlers only get called when we’re still able to choose which response message to send.

See this answer in How do I log ALL exceptions globally for a C# MVC4 WebAPI app? for an implementation of both.

You can of course also materialize the enumerable in your controller, causing the exception to be thrown there and handled by the exception filter:

var result = list.Select(a => GetData(a)).ToList();

Globally handle all exceptions in WebAPI 2

for handling Owin pipeline exception you can write your own ExceptionHandler Middleware and add it to the begining of your owin pipeline. Your middleware can be like this

public class GlobalExceptionMiddleware : OwinMiddleware
{
public GlobalExceptionMiddleware(OwinMiddleware next) : base(next)
{}

public override async Task Invoke(IOwinContext context)
{
try
{
await Next.Invoke(context);
}
catch(Exception ex)
{
// your handling logic
}
}
}

and also you should write exception handling in Application_Error in case of an exception happened in your handling login in GlobalExceptionMiddleware.

In global.asax in Application_Error you can use Response.Write(JsonConvert.SerializeObject(yourObject) to return your custom error.

And also there's no global.asax on selfhost so the advantage of having GlobalExceptionMiddleware is that if you ever decide to use SelfHost instead of IIS, all your exceptions are handled.

ASP.NET Core Web API exception handling

Quick and Easy Exception Handling

Simply add this middleware before ASP.NET routing into your middleware registrations.

app.UseExceptionHandler(c => c.Run(async context =>
{
var exception = context.Features
.Get<IExceptionHandlerPathFeature>()
.Error;
var response = new { error = exception.Message };
await context.Response.WriteAsJsonAsync(response);
}));
app.UseMvc(); // or .UseRouting() or .UseEndpoints()

Done!



Enable Dependency Injection for logging and other purposes

Step 1. In your startup, register your exception handling route:

// It should be one of your very first registrations
app.UseExceptionHandler("/error"); // Add this
app.UseEndpoints(endpoints => endpoints.MapControllers());

Step 2. Create controller that will handle all exceptions and produce error response:

[AllowAnonymous]
[ApiExplorerSettings(IgnoreApi = true)]
public class ErrorsController : ControllerBase
{
[Route("error")]
public MyErrorResponse Error()
{
var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
var exception = context.Error; // Your exception
var code = 500; // Internal Server Error by default

if (exception is MyNotFoundException) code = 404; // Not Found
else if (exception is MyUnauthException) code = 401; // Unauthorized
else if (exception is MyException) code = 400; // Bad Request

Response.StatusCode = code; // You can use HttpStatusCode enum instead

return new MyErrorResponse(exception); // Your error model
}
}

A few important notes and observations:

  • You can inject your dependencies into the Controller's constructor.
  • [ApiExplorerSettings(IgnoreApi = true)] is needed. Otherwise, it may break your Swashbuckle swagger
  • Again, app.UseExceptionHandler("/error"); has to be one of the very top registrations in your Startup Configure(...) method. It's probably safe to place it at the top of the method.
  • The path in app.UseExceptionHandler("/error") and in controller [Route("error")] should be the same, to allow the controller handle exceptions redirected from exception handler middleware.

Here is the link to official Microsoft documentation.



Response model ideas.

Implement your own response model and exceptions.
This example is just a good starting point. Every service would need to handle exceptions in its own way. With the described approach you have full flexibility and control over handling exceptions and returning the right response from your service.

An example of error response model (just to give you some ideas):

public class MyErrorResponse
{
public string Type { get; set; }
public string Message { get; set; }
public string StackTrace { get; set; }

public MyErrorResponse(Exception ex)
{
Type = ex.GetType().Name;
Message = ex.Message;
StackTrace = ex.ToString();
}
}

For simpler services, you might want to implement http status code exception that would look like this:

public class HttpStatusException : Exception
{
public HttpStatusCode Status { get; private set; }

public HttpStatusException(HttpStatusCode status, string msg) : base(msg)
{
Status = status;
}
}

This can be thrown from anywhere this way:

throw new HttpStatusCodeException(HttpStatusCode.NotFound, "User not found");

Then your handling code could be simplified to just this:

if (exception is HttpStatusException httpException)
{
code = (int) httpException.Status;
}

HttpContext.Features.Get<IExceptionHandlerFeature>() WAT?

ASP.NET Core developers embraced the concept of middlewares where different aspects of functionality such as Auth, MVC, Swagger etc. are separated and executed sequentially in the request processing pipeline. Each middleware has access to request context and can write into the response if needed. Taking exception handling out of MVC makes sense if it's important to handle errors from non-MVC middlewares the same way as MVC exceptions, which I find is very common in real world apps. So because built-in exception handling middleware is not a part of MVC, MVC itself knows nothing about it and vice versa, exception handling middleware doesn't really know where the exception is coming from, besides of course it knows that it happened somewhere down the pipe of request execution. But both may needed to be "connected" with one another. So when exception is not caught anywhere, exception handling middleware catches it and re-runs the pipeline for a route, registered in it. This is how you can "pass" exception handling back to MVC with consistent content negotiation or some other middleware if you wish. The exception itself is extracted from the common middleware context. Looks funny but gets the job done :).



Related Topics



Leave a reply



Submit