Exception handling in OWIN Middlware - c#

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

Related

.NET Core 3 ExceptionHandler with Generic Logger parameter - AmbiguousMatchException

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.

.Net Web API returns occasionally no response at all if Exception is thrown

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

C# Exception Handling: Get elements of extended CustomException

I want to catch custom exceptions and return a custom object containing information about the exception. For this I have created a custom CustomInvalidException that extends Extension:
[Serializable]
public class CustomInvalidException<T> : Exception
where T : Entity
{
public string Title { get; set; }
public HttpStatusCode StatusCode { get; set; }
public CustomInvalidException(string message)
: base($"{typeof(T).Name}: {message}")
{
this.Title = "Custom invalid exception";
this.StatusCode = HttpStatusCode.BadRequest;
}
}
At some point I can throw this exception:
if (something) throw new CustomInvalidException<CustomEntity>(message);
And I can catch this exception and handle it in some middleware I use for this:
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
// Here I want to create a response with error information
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)exception.StatusCode;
var result = new {exception.Title, exception.StatusCode, exception.Message}
return context.Response.WriteAsync(JsonConvert.SerializeObject(result));
}
This gives errors since I try to get exception.Title and exception.StatusCode, and these elements do not exist on the Exception class. In debug mode I can see that these elements are present since I thrown the CustomInvalidException.
I want to keep the HandleExceptionAsync method as generic as possible, but I can only get these values by explicitly casting to CustomInvalidException (which is not what I want). How can I achieve the result as generic as possible?
EDIT
HandleExceptionAsync is called in the ErrorHandlingMiddleware class:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
// Handle exception
}
}
Which in turn is called in the Configure of Startup.cs:
app.UseMiddleware(typeof(ErrorHandlingMiddleware));

Exception handler middleware not catching

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.

Web API OWIN startup exception handling

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);
}
}
}

Categories

Resources