11

I am developing an API with ASP.NET Core and I am struggling with the exception handling.

When any exception occurs, or in any controller where I want to return custom errors with different status codes, I want to return JSON-formatted exception reports. I do not need an HTML in the error responses.

I'm not sure if I should use middleware for this, or something else. How should I return JSON exceptions in an ASP.NET Core API?

Hinrich
  • 12,818
  • 4
  • 39
  • 61
  • 1
    Sorry but "do not work" is a nonsense. Check the [How to create a Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve), please. – David Ferenczy Rogožan Jul 29 '16 at 17:24
  • Sorry, wrote the question while trainriding. Will elaborate more on it when I am back at office. – Hinrich Jul 29 '16 at 17:35
  • 1
    @Hinrich I thought the question had potential for a good canonical answer, so I took the liberty of editing a little and proposing a solution. Feel free to roll back or edit further if you feel I changed the meaning of your question too much. – Nate Barbettini Jul 29 '16 at 17:52
  • Possible duplicate of [Error handling in ASP.NET Core 1.0 Web API (Sending ex.Message to the client)](http://stackoverflow.com/questions/38014379/error-handling-in-asp-net-core-1-0-web-api-sending-ex-message-to-the-client) – Set Aug 01 '16 at 10:35

2 Answers2

12

An exception filter (either as an attribute, or a global filter) is what you are looking for. From the docs:

Exception filters handle unhandled exceptions, including those that occur during controller creation and model binding. They are only called when an exception occurs in the pipeline. They can provide a single location to implement common error handling policies within an app.

If you want any unhandled exception to be returned as JSON, this is the simplest method:

public class JsonExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        var result = new ObjectResult(new
        {
            code = 500,
            message = "A server error occurred.",
            detailedMessage = context.Exception.Message
        });

        result.StatusCode = 500;
        context.Result = result;
    }
}

You can customize the response to add as much detail as you want. The ObjectResult will be serialized to JSON.

Add the filter as a global filter for MVC in Startup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(JsonExceptionFilter));
    });
}
Nate Barbettini
  • 47,220
  • 23
  • 132
  • 141
  • This handles all my uncatched failures, which is a good start. Thx. But what if I wanted to return a `BadRequest` or any other status code from a controller programmatically? – Hinrich Jul 31 '16 at 11:47
  • 3
    note, that Exception filters handle only MVC exceptions, more generic approach in ASP.NET Core is to use ExceptionHandler middleware: http://stackoverflow.com/documentation/asp.net-core/1479/middleware/4815/using-the-exceptionhandler-middleware-to-send-custom-json-error-to-client#t=201608011043382097082 – Set Aug 01 '16 at 10:44
6

Ok, I got a working solution, that I am pretty happy with.

  1. Add middleware: In the Configure Method, register the middleware (comes with ASP.NET Core).

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        // logging stuff, etc.
    
        app.UseStatusCodePagesWithReExecute("/error/{0}");
        app.UseExceptionHandler("/error");
    
        app.UseMvc(); // if you are using Mvc
    
        // probably other middleware stuff 
    }
    
  2. Create a Class for Messages Write a simple class that represents instances of JSON Error Messages you want to send as a request in any error case:

    public class ExceptionMessageContent
    {
    
        public string Error { get; set; }
        public string Message { get; set; }
    
    }
    
  3. Create Error Controller add the Error Controller that handles all expected and unexpected errors. Note, that these routes correspond to the middleware configuration.

    [Route("[controller]")]
    public class ErrorController : Controller
    {
    
        [HttpGet]
        [Route("")]
        public IActionResult ServerError()
        {
    
            var feature = this.HttpContext.Features.Get<IExceptionHandlerFeature>();
            var content = new ExceptionMessageContent()
            {
                Error = "Unexpected Server Error",
                Message = feature?.Error.Message
            };
            return Content( JsonConvert.SerializeObject( content ), "application/json" );
    
        }
    
    
        [HttpGet]
        [Route("{statusCode}")]
        public IActionResult StatusCodeError(int statusCode)
        {
    
            var feature = this.HttpContext.Features.Get<IExceptionHandlerFeature>();
            var content = new ExceptionMessageContent() { Error = "Server Error", Message = $"The Server responded with status code {statusCode}" };
            return Content( JsonConvert.SerializeObject( content ), "application/json" );
    
        }
    }
    

Now, when I want to throw an error anywhere, I can just do that. The request gets redirected to the error handler and sends a 500 with a nice formatted error message. Also, 404 and other codes are handled gracefully. Any custom status codes I want to send, I can also return them with an instance of my ExceptionMessageContent, for example:

// inside controller, returning IActionResult

var content = new ExceptionMessageContent() { 
    Error = "Bad Request", 
    Message = "Details of why this request is bad." 
};

return BadRequest( content );
Hinrich
  • 12,818
  • 4
  • 39
  • 61