I'm developping a web API with ASP.NET Core and I'm trying to implement a custom error handling middleware so I can throw standard exceptions that can be converted into a JSON response with the appropriate HTTP Status code.
For example if I do:
throw new NotFoundApiException("The object was not found");
I need it to be converted into:
StatusCode: 404
ContentType: application/json
ResponseBody: {"error": "The object was not found"}
Here is my middleware:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try {
await next(context);
} catch (ApiException ex) {
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, ApiException exception)
{
var result = JsonConvert.SerializeObject(new { error = exception.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = exception.httpStatusCode;
return context.Response.WriteAsync(result);
}
}
Exceptions
public class ApiException : System.Exception
{
private int _httpStatusCode = (int)HttpStatusCode.InternalServerError;
public ApiException() { }
public ApiException(string message): base(message) { }
public int httpStatusCode {
get { return this._httpStatusCode; }
}
}
public class NotFoundApiException : ApiException
{
private int _httpStatusCode = (int)HttpStatusCode.BadRequest;
public NotFoundApiException() { }
public NotFoundApiException(string message): base(message) { }
}
Startup
public void Configure(/*...*/)
{
loggerFactory.AddConsole();
app.UseMiddleware<ErrorHandlingMiddleware>();
app.UseMvc();
}
Controller action
[HttpGet("object/{guid}")]
public WebMessage Get(Guid guid)
{
throw new NotFoundApiException(string.Format("The object {0} was not found", guid));
//...
I can see the request entering my registered middleware but the exception is not catched and simply thrown as usual.
I'm suspecting a race condition or something similar, I don't know very much about them async functions actually.
Has someone got an idea why my exception is not catched ?
edit By continuing the execution with VisualStudio I can see the expected behavior: I'm finally getting my response.
Seems like the Exception is not really catched by the middleware but somehow processed afterwards.
My solution to this problem was to remove app.UseDeveloperExceptionPage(); in Startup.cs
In my case, I found that app.UseMiddleware<ExceptionHandlingMiddleware>(); should be at the top of Configure() method.
You can try also Exception filters.
(of course, filters are not so flexible like as error handling middleware, which is better in general case, but - at least for me - filters are working fine without any issues)
That's what I'm using:
public class ExceptionGlobalFilter : ExceptionFilterAttribute
{
private readonly ILogger logger;
public ExceptionGlobalFilter(ILoggerFactory lf)
{
logger = lf.CreateLogger("ExceptionGlobalFilter");
}
public override void OnException(ExceptionContext context)
{
var customObject = new CustomObject(context.Exception);
//TODO: Add logs
if (context.Exception is BadRequestException)
{
context.Result = new BadRequestObjectResult(customObject);
}
else if (context.Exception is NotFoundException)
{
context.Result = new NotFoundObjectResult(customObject);
}
else
{
context.Result = new OkObjectResult(customObject);
}
base.OnException(context);
}
public override async Task OnExceptionAsync(ExceptionContext context)
{
await base.OnExceptionAsync(context);
return;
}
}
Startup.cs:
services.AddMvc(config =>
{
config.Filters.Add(typeof(ExceptionGlobalFilter));
});
More info:
Introduction to Error Handling in ASP.NET Core
Exception filters
Filters
MVC Issue #5594
ExceptionHandlerMiddleware.cs
In my case app.UseDeveloperExceptionPage(); was written in the Startup after the exception handler middleware. The fix was simply by moving the exception handler middleware to be after it.
#Pierre, I have met the same issue here when using Middleware as the global exception handler. The issue was caused by my mistake to wrote an "async void" method, I have throwed an exception in the method named "NewException":
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public async Task<IActionResult> Get()
{
NewException();
return Ok("<h1>Hi, Welcome!</h1>");
}
private async void NewException()
{
throw new InvalidOperationException("WTF");
}
The exception [InvalidOperationException("WTF")] will not be catching by the Middleware, if I change the code snippet to :
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public async Task<IActionResult> Get()
{
await NewException();
return Ok("<h1>Hi, Welcome!</h1>");
}
private async Task NewException()
{
throw new InvalidOperationException("WTF");
}
The exception Middleware will catch it. Hope this help.
Related
I am trying to implement .NET Core 3 app.UseExceptionHandler for an API project, but I have a generic logger per API controller that I would like to pass into the error method. If, for instance, the error happens in my WorkingController, I would like to have ILogger<WorkingController> be the logging entity. I have found that using the built-in ExceptionHandler, I lose some of the context of the request, and I would like to capture this context if possible.
Here's what all of my API methods used to look like before:
[Route("api/working")]
[ApiController]
public class WorkingController
{
private readonly ILogger<WorkingController> _logger;
public WorkingController(ILogger<WorkingController> logger)
{
_logger = logger;
}
[Route("workingRoute")]
public IActionResult SomeMethod()
{
_logger.LogInformation("Starting SomeMethod");
try
{
// doing some stuff here
}
catch(Exception ex)
{
_logger.LogError(ex, "Something happened");
return Problem();
}
return Ok();
}
}
I tried setting up a BaseErrorController from which other Controllers could inherit:
[ApiController]
public abstract class BaseErrorController<T> : ControllerBase
{
protected readonly ILogger<T> Logger;
public BaseErrorController(ILogger<T> logger)
{
Logger = logger;
}
[AllowAnonymous]
[Route("/error")]
public IActionResult Error()
{
var context = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
if (context != null)
{
var ex = context.Error;
Logger.LogError(ex, $"{context.Path} failed - {ex.Message}");
return Problem(
detail: context.Error.StackTrace,
title: context.Error.Message);
}
return Problem();
}
}
And now my former WorkingController looks like this, which is arguably a lot cleaner (and less code):
[Route("api/working")]
[ApiController]
public class WorkingController : BaseErrorController<WorkingController>
{
public WorkingController(ILogger<WorkingController> logger) : base(logger) { }
[Route("workingRoute")]
public IActionResult SomeMethod()
{
Logger.LogInformation("Starting SomeMethod");
// doing some stuff here
return Ok();
}
}
In Startup.cs, I'm registering this all with app.UseExceptionHandler("/error"), and it seems to work OK . . . except now in my logs, I see the following error (because I have more than one controller implementing this base controller):
An exception was thrown attempting to execute the error handler.
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.
Matches: Namespace.Controllers.WorkingController.Error (Namespace)
Namespace.Controllers.AnotherController.Error (Namespace)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ReportAmbiguity(CandidateState[] candidateState)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ProcessFinalCandidates(HttpContext httpContext, CandidateState[] candidateState)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.Select(HttpContext httpContext, CandidateState[] candidateState)
at Microsoft.AspNetCore.Routing.Matching.DfaMatcher.MatchAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Routing.Matching.DataSourceDependentMatcher.MatchAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.HandleException(HttpContext context, ExceptionDispatchInfo edi)
Does anybody have an idea what might be the solution here? Is there an overload of the ExceptionHandler that might be what I'm looking for? Is this solution too boutique, and I should go back to what I was doing before? Help me, Stack Overflow. Thank you!
Unfortunately, there is simply no built-in way to do this cleanly. You could find a package to help here (I haven't looked), or you could want to re-write some parts of ASP.NET Core, but I wouldn't really want to do that.
There is another way of doing this, which depending on which version you like more, is recommended/recommended against, but I'm pro of the former. Instead of throwing/catching exceptions at the Controller level, I treat Controllers as the dumbest thing possible, so they just call some service and that's it.
If you want to know where an exception was thrown, or you specifically want the exception to go uncaught, a strategy my team follows is to create custom exceptions. You could then leave these uncaught (and the HTTP500 will be returned to the caller) or you could have a custom Middleware and define there what should happen.
The following is an example, written entirely here so there may be some changes needed, and it's solely to demonstrate a possible approach, not a working demo.
Given some exceptions valid to your domain:
public class UserNotFoundException : Exception { public Guid UserId { get; set; } }
public class InvalidModelException : Exception { }
And an exception handler:
public class MyCustomExceptionHandlerMiddleware
{
private readonly ILogger<MyCustomExceptionHandlerMiddleware> _logger;
public MyCustomExceptionHandlerMiddleware(ILogger<MyCustomExceptionHandlerMiddleware> logger)
{
_logger = logger;
}
public async Task Invoke(RequestDelegate next)
{
try
{
await next(); // without this, the request doesn't execute any further
}
catch (UserNotFoundException userNotFound)
{
_logger.LogError(userNotFound, "The user was not found");
// manipulate the response here, possibly return HTTP404
}
catch (Exception ex)
{
_logger.LogError(ex, "Something really bad happened");
// manipulate the response here
}
}
}
You could have something like this:
public class UsersService : IUsersService
{
private readonly ILogger<UsersService> _logger;
private readonly UsersContext _context;
// assume UsersContext is an EntityFramework context or some database service
public UsersService(ILogger<UsersService> logger, UsersContext context)
{
_logger = logger;
_context = context;
}
public async Task<User> GetUserAsync(Guid userId)
{
try
{
if (userId == Guid.Empty)
{
throw new InvalidModelException();
}
var user = await _context.FindUserAsync(userId);
if (user == null)
{
throw new UserNotFoundException(userId);
}
return user;
}
catch (InvalidModelException invalidModel)
{
_logger.LogWarning("The received user id is empty");
return null;
}
}
}
And its corresponding controller:
public class UsersController : ControllerBase
{
private readonly IUsersService _usersService;
public UsersController(IUsersService usersService)
{
_usersService = usersService;
}
[HttpGet("userId:guid")]
public async Task<IActionResult> GetUser(Guid userId)
{
var user = await _usersService.GetUserAsync(userId);
if (user == null)
{
return BadRequest();
}
return Ok(user);
}
}
Again, this is just an example to demonstrate how you could approach this, normally you'd do input validation in a more consistent way.
First of all a bit of background.
I am using .Net Framework 4.6.1, Microsoft.AspNet.WebApi 5.2.4 in Visual Studio 2017 Community.
My ApiController's implement endpoints which throw intended Exceptions for example if certain requirements are not met. I added global ExceptionFilterAttribute and ExceptionHandler to handle those Exceptions and to return a proper response.
The Exceptions are of a type which are inherited of System.Exception.
This is only working occasionally as intended. Every second or third or sometimes fifth (no real pattern) request the api server returns no response at all e.g. for example Postman says: "Could not get any response".
To test this I used the same endpoint with the same input.
Here are a few things I did to get a better idea of the problem:
I added exception logging to Global.asax (to catch First Chance Exceptions)
I subscribed to Global.asax Application_Error Event
I looked at the IIS logs
None of those got my closer to the issue. The exception was caught and logged in Global.asax like expected but no additional error or exception which could give me more info to my problem.
Here is my code:
I simplified the ApiController's function and removed the business logic.
[Route("test")]
[HttpGet]
public IHttpActionResult GetTest()
{
throw new ObjectAlreadyExistsException("test");
return ResponseFactory.CreateOkResponse(null);
}
public class ExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is ObjectAlreadyExistsException)
{
context.Response = ResponseFactory.CreateErrorResponseMessage(context.Exception.Message, new Error("OBJECT_ALREADY_EXISTS_ERROR", context.Exception.Message));
}
else if (context.Exception is ObjectNotFoundException)
{
context.Response = ResponseFactory.CreateErrorResponseMessage(context.Exception.Message, new Error("OBJECT_NOT_FOUND_ERROR", context.Exception.Message));
}
base.OnException(context);
}
}
public class GlobalExceptionHandler : ExceptionHandler
{
private static readonly ILogger Log = new LoggerConfiguration()
.WriteTo.File(new CompactJsonFormatter(), Path.Combine(#Properties.Settings.Default.LOG_DIRECTORY, #"error.json"), rollOnFileSizeLimit: true, retainedFileCountLimit: 5, shared: true)
.Enrich.WithWebApiControllerName()
.Enrich.WithWebApiActionName()
.Enrich.WithWebApiRouteTemplate()
.Enrich.WithWebApiRouteData()
.Enrich.With(new AuthTokenEnricher())
.CreateLogger();
public override void Handle(ExceptionHandlerContext context)
{
if (context != null && context.Exception != null)
{
Log.Error("Unexpected Internal Server Error {Exception}", context.Exception);
}
context.Result = ResponseFactory.CreateErrorResponse(HttpStatusCode.InternalServerError, "Unexpected Internal Server Error", new Error("INTERNAL_SERVER_ERROR", "This request failed because of an unexpected server error."));
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//Exception filters
config.Filters.Add(new ExceptionFilter());
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
// Web API routes
config.MapHttpAttributeRoutes();
}
}
public class ObjectAlreadyExistsException : Exception
{
public ObjectAlreadyExistsException(string message) : base(message)
{
}
public ObjectAlreadyExistsException(string message, Exception inner) : base(message, inner)
{
}
}
For now I put a workaround in place which looks like this:
[Route("test")]
[HttpGet]
public IHttpActionResult GetTest()
{
try
{
throw new ObjectAlreadyExistsException("test");
}
catch (Exception ex)
{
return CustomExceptionHandler.Handle(ex);
}
}
public class CustomExceptionHandler
{
private static readonly ILogger Log = new LoggerConfiguration()
.WriteTo.File(new CompactJsonFormatter(), Path.Combine(#Properties.Settings.Default.LOG_DIRECTORY, #"error.json"), rollOnFileSizeLimit: true, retainedFileCountLimit: 5, shared: true)
.Enrich.WithWebApiControllerName()
.Enrich.WithWebApiActionName()
.Enrich.WithWebApiRouteTemplate()
.Enrich.WithWebApiRouteData()
.Enrich.With(new AuthTokenEnricher())
.CreateLogger();
public static IHttpActionResult Handle(Exception ex)
{
IHttpActionResult result = null;
if (ex != null)
{
if (ex is ObjectAlreadyExistsException)
{
result = ResponseFactory.CreateErrorResponse(ex.Message, new Error("OBJECT_ALREADY_EXISTS_ERROR", ex.Message));
}
else if (ex is ObjectNotFoundException)
{
result = ResponseFactory.CreateErrorResponse(ex.Message, new Error("OBJECT_NOT_FOUND_ERROR", ex.Message));
}
}
if (result == null)
{
if (ex != null)
{
Log.Error("Unexpected Internal Server Error {Exception}", ex);
}
result = ResponseFactory.CreateErrorResponse(HttpStatusCode.InternalServerError, "Unexpected Internal Server Error", new Error("INTERNAL_SERVER_ERROR", "This request failed because of an unexpected server error."));
}
return result;
}
}
I would appreciate any ideas how to debug this or any suggestions to fix it.
Can you try inheriting from IHttpActionResult and use it as returning the exception from your GlobalExceptionHandler
private class ErrorMessageResult : IHttpActionResult
{
private readonly HttpResponseMessage _httpResponseMessage;
private HttpRequestMessage _request;
public ErrorMessageResult(HttpRequestMessage request, HttpResponseMessage httpResponseMessage)
{
_request = request;
_httpResponseMessage = httpResponseMessage;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_httpResponseMessage);
}
}
and call it like,
public override void Handle(ExceptionHandlerContext context)
{
var result = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("Internal Server Error Occurred"),
ReasonPhrase = "Exception"
};
context.Result = new ErrorMessageResult(context.Request, result);
}
From GlobalExceptionHandler : ExceptionHandler
I am trying to add some middleware so that any unhandled exceptions I catch and log it but experiencing some difficulties in doing so. Not been able to find a lot on this and for some odd reason my code doesn't seem to be entering the catch block. Seems like it is gracefully handling this and even interrogating the dictionary I can't see the exception.
What I want to happen is, enter the catch block grab the exception and log the stack trace.
The code:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
app.Use(typeof(FooHandler));
app.UseWebApi(config);
}
}
public class FooHandler : OwinMiddleware
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(FooHandler));
public FooHandler(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
if (Logger.IsErrorEnabled)
{
try
{
await Next.Invoke(context);
}
catch (Exception ex)
{ // DOESN'T FALL INTO HERE!
Logger.Error(message, ex);
}
}
}
}
public class FooController : ApiController
{
public Task<IHttpActionResult> Get()
{
throw new Exception("Foo Bar");
}
}
This is because WebApi is handling the exception. You will need to handle exceptions thrown by Controllers in an ExceptionFilterAttribute
In ASP.NET MVC 5 you could throw a HttpException with a HTTP code and this would set the response like so:
throw new HttpException((int)HttpStatusCode.BadRequest, "Bad Request.");
HttpException does not exist in ASP.NET Core. What is the equivalent code?
I implemented my own HttpException and supporting middleware which catches all HttpException's and turns them into the corresponding error response. A short extract can be seen below. You can also use the Boxed.AspNetCore Nuget package.
Usage Example in Startup.cs
public void Configure(IApplicationBuilder application)
{
application.UseIISPlatformHandler();
application.UseStatusCodePagesWithReExecute("/error/{0}");
application.UseHttpException();
application.UseMvc();
}
Extension Method
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseHttpException(this IApplicationBuilder application)
{
return application.UseMiddleware<HttpExceptionMiddleware>();
}
}
Middleware
internal class HttpExceptionMiddleware
{
private readonly RequestDelegate next;
public HttpExceptionMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await this.next.Invoke(context);
}
catch (HttpException httpException)
{
context.Response.StatusCode = httpException.StatusCode;
var responseFeature = context.Features.Get<IHttpResponseFeature>();
responseFeature.ReasonPhrase = httpException.Message;
}
}
}
HttpException
public class HttpException : Exception
{
private readonly int httpStatusCode;
public HttpException(int httpStatusCode)
{
this.httpStatusCode = httpStatusCode;
}
public HttpException(HttpStatusCode httpStatusCode)
{
this.httpStatusCode = (int)httpStatusCode;
}
public HttpException(int httpStatusCode, string message) : base(message)
{
this.httpStatusCode = httpStatusCode;
}
public HttpException(HttpStatusCode httpStatusCode, string message) : base(message)
{
this.httpStatusCode = (int)httpStatusCode;
}
public HttpException(int httpStatusCode, string message, Exception inner) : base(message, inner)
{
this.httpStatusCode = httpStatusCode;
}
public HttpException(HttpStatusCode httpStatusCode, string message, Exception inner) : base(message, inner)
{
this.httpStatusCode = (int)httpStatusCode;
}
public int StatusCode { get { return this.httpStatusCode; } }
}
In the long term, I would advise against using exceptions for returning errors. Exceptions are slower than just returning an error from a method.
After a brief chat with #davidfowl, it seems that ASP.NET 5 has no such notion of HttpException or HttpResponseException that "magically" turn to response messages.
What you can do, is hook into the ASP.NET 5 pipeline via MiddleWare, and create one that handles the exceptions for you.
Here is an example from the source code of their error handler middleware which will set the response status code to 500 in case of an exception further up the pipeline:
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ErrorHandlerOptions _options;
private readonly ILogger _logger;
public ErrorHandlerMiddleware(RequestDelegate next,
ILoggerFactory loggerFactory,
ErrorHandlerOptions options)
{
_next = next;
_options = options;
_logger = loggerFactory.CreateLogger<ErrorHandlerMiddleware>();
if (_options.ErrorHandler == null)
{
_options.ErrorHandler = _next;
}
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError("An unhandled exception has occurred: " + ex.Message, ex);
if (context.Response.HasStarted)
{
_logger.LogWarning("The response has already started,
the error handler will not be executed.");
throw;
}
PathString originalPath = context.Request.Path;
if (_options.ErrorHandlingPath.HasValue)
{
context.Request.Path = _options.ErrorHandlingPath;
}
try
{
var errorHandlerFeature = new ErrorHandlerFeature()
{
Error = ex,
};
context.SetFeature<IErrorHandlerFeature>(errorHandlerFeature);
context.Response.StatusCode = 500;
context.Response.Headers.Clear();
await _options.ErrorHandler(context);
return;
}
catch (Exception ex2)
{
_logger.LogError("An exception was thrown attempting
to execute the error handler.", ex2);
}
finally
{
context.Request.Path = originalPath;
}
throw; // Re-throw the original if we couldn't handle it
}
}
}
And you need to register it with StartUp.cs:
public class Startup
{
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerfactory)
{
app.UseMiddleWare<ExceptionHandlerMiddleware>();
}
}
Alternatively, if you just want to return an arbitrary status code and aren't concerned with the Exception-based approach, you can use
return new HttpStatusCodeResult(400);
Update: as of .NET Core RC 2, the Http prefix is dropped. It is now:
return new StatusCodeResult(400);
The Microsoft.AspNet.Mvc.Controller base class exposes a HttpBadRequest(string) overload which takes an error message to return to the client. So from within a controller action, you could call:
return HttpBadRequest("Bad Request.");
Ultimately my nose says any private methods called from within a controller action should either be fully http-context-aware and return an IActionResult, or perform some other small task completely isolated from the fact that it's inside of an http pipeline. Granted this is my personal opinion, but a class that performs some piece of business logic should not be returning HTTP status codes, and instead should be throwing its own exceptions which can be caught and translated at the controller/action level.
There is no equivalent in ASP.NET Core itself. As others have said, the way to implement this is with a middleware and your own exceptions.
The Opw.HttpExceptions.AspNetCore NuGet package does exactly this.
Middleware and extensions for returning exceptions over HTTP, e.g. as ASP.NET Core Problem Details. Problem Details are a machine-readable format for specifying errors in HTTP API responses based on https://www.rfc-editor.org/rfc/rfc7807. But you are not limited to returning exception results as Problem Details, but you can create your own mappers for your own custom formats.
It is configurable and well documented.
Here is the list of provided exceptions out of the box:
4xx
400 BadRequestException
400 InvalidModelException
400 ValidationErrorException<T>
400 InvalidFileException
401 UnauthorizedException
403 ForbiddenException
404 NotFoundException
404 NotFoundException<T>
409 ConflictException
409 ProtectedException
415 UnsupportedMediaTypeException
5xx
500 InternalServerErrorException
500 DbErrorException
500 SerializationErrorException
503 ServiceUnavailableException
Here is an extended version of #muhammad-rehan-saeed answer.
It logs exceptions conditionaly and disables http cache.
If you use this and UseDeveloperExceptionPage, you should call UseDeveloperExceptionPage before this.
Startup.cs:
app.UseMiddleware<HttpExceptionMiddleware>();
HttpExceptionMiddleware.cs
/**
* Error handling: throw HTTPException(s) in business logic, generate correct response with correct httpStatusCode + short error messages.
* If the exception is a server error (status 5XX), this exception is logged.
*/
internal class HttpExceptionMiddleware
{
private readonly RequestDelegate next;
public HttpExceptionMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await this.next.Invoke(context);
}
catch (HttpException e)
{
var response = context.Response;
if (response.HasStarted)
{
throw;
}
int statusCode = (int) e.StatusCode;
if (statusCode >= 500 && statusCode <= 599)
{
logger.LogError(e, "Server exception");
}
response.Clear();
response.StatusCode = statusCode;
response.ContentType = "application/json; charset=utf-8";
response.Headers[HeaderNames.CacheControl] = "no-cache";
response.Headers[HeaderNames.Pragma] = "no-cache";
response.Headers[HeaderNames.Expires] = "-1";
response.Headers.Remove(HeaderNames.ETag);
var bodyObj = new {
Message = e.BaseMessage,
Status = e.StatusCode.ToString()
};
var body = JsonSerializer.Serialize(bodyObj);
await context.Response.WriteAsync(body);
}
}
}
HTTPException.cs
public class HttpException : Exception
{
public HttpStatusCode StatusCode { get; }
public HttpException(HttpStatusCode statusCode)
{
this.StatusCode = statusCode;
}
public HttpException(int httpStatusCode)
: this((HttpStatusCode) httpStatusCode)
{
}
public HttpException(HttpStatusCode statusCode, string message)
: base(message)
{
this.StatusCode = statusCode;
}
public HttpException(int httpStatusCode, string message)
: this((HttpStatusCode) httpStatusCode, message)
{
}
public HttpException(HttpStatusCode statusCode, string message, Exception inner)
: base(message, inner)
{
}
public HttpException(int httpStatusCode, string message, Exception inner)
: this((HttpStatusCode) httpStatusCode, message, inner)
{
}
}
I had better results with this code than with :
UseExceptionHandler:
automatically logs every "normal" exceptions (ex 404).
disabled in dev mode (when app.UseDeveloperExceptionPage is called)
cannot catch only specific exceptions
Opw.HttpExceptions.AspNetCore: logs exception when everything works fine
See also ASP.NET Core Web API exception handling
Starting from ASP.NET Core 3 you can use ActionResult to return HTTP status code:
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ITEMS_TYPE> GetByItemId(int id)
{
...
if (result == null)
{
return NotFound();
}
return Ok(result);
}
More details are here: https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1
In my C# Web API, I'm trying to add a global exception handler. I've been using a custom global ExceptionFilterAttribute to handle the exception and return a HttpResponseMessage:
public override void OnException(HttpActionExecutedContext context)
{
...
const string message = "An unhandled exception was raised by the Web API.";
var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(message),
ReasonPhrase = message
};
context.Response = httpResponseMessage;
}
This has worked fine for handling exceptions thrown at the controller level.
However, during development we had an error thrown from our OWIN startup file due to a database connection issue, however, a standard IIS exception was returned, instead of going through the global exception handler, and the full HTML was returned to our API consumer.
I've tried a few different approaches to catch exceptions thrown in my OWIN startup:
Custom ApiControllerActionInvoker:
public class CustomActionInvoker : ApiControllerActionInvoker
{
public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
var result = base.InvokeActionAsync(actionContext, cancellationToken);
if (result.Exception != null && result.Exception.GetBaseException() != null)
{
...
}
return result;
}
}
Custom ExceptionHandler:
public class CustomExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
...
base.Handle(context);
}
public override bool ShouldHandle(ExceptionHandlerContext context)
{
return true;
}
}
Custom OwinMiddleware component:
public class CustomExceptionMiddleware : OwinMiddleware
{
public CustomExceptionMiddleware(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
try
{
await Next.Invoke(context);
}
catch (Exception ex)
{
...
}
}
}
And finally just using Application_Error:
protected void Application_Error(object sender, EventArgs e)
{
...
}
But nothing seems to catch the exception.
Does anyone know of a way to catch the exception and return a HttpResponseMessage? Or if any of the approaches I've already tried should have worked?
Any help much appreciated.
I have an application that does this correctly. In my case I wrote a middleware class that always returns a message telling the caller that the service is unavailable because there was an error during startup. This class is called FailedSetupMiddleware in my solution. The outline of it looks like this:
public class FailedSetupMiddleware
{
private readonly Exception _exception;
public FailedSetupMiddleware(Exception exception)
{
_exception = exception;
}
public Task Invoke(IOwinContext context, Func<Task> next)
{
var message = ""; // construct your message here
return context.Response.WriteAsync(message);
}
}
In my Configuration class I have a try...catch block that configures the OWIN pipeline with only the FailedSetupMiddleware in the case where an exception was thrown during configuration.
My OWIN startup class looks like this:
[assembly: OwinStartup(typeof(Startup))]
public class Startup
{
public void Configuration(IAppBuilder app)
{
try
{
//
// various app.Use() statements here to configure
// OWIN middleware
//
}
catch (Exception ex)
{
app.Use(new FailedSetupMiddleware(ex).Invoke);
}
}
}