using global exception handeling messes up DelegatingHandler - c#

When ovveride the IExceptionHandler, the response does not reach the DelegatingHandler when a unexpected exception occurs. How can I fix this?
In webapi 2, I want to implement a audit logger for request and response messages. I also want to add a global exception handler. However, when I replace the IExceptionHandler with my custom implementation. the response never reaches the DelegatingHandler -on exception - And thus the audit for response is lost.
in WebApiConfig
// add custom audittrail logger
config.MessageHandlers.Add(new AuditLogHandler());
// replace global exception handeling
config.Services.Replace(typeof(IExceptionHandler), new WebAPiExceptionHandler());
Custom Exception Handler
public class WebAPiExceptionHandler : ExceptionHandler
{
//A basic DTO to return back to the caller with data about the error
private class ErrorInformation
{
public string Message { get; set; }
public DateTime ErrorDate { get; set; }
}
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new ResponseMessageResult(context.Request.CreateResponse(HttpStatusCode.InternalServerError,
new ErrorInformation { Message = "Iets is misgegaan", ErrorDate = DateTime.UtcNow }));
}
}
Custom Auditlogger
public class AuditLogHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Content != null)
{
var task = await request.Content.ReadAsStringAsync();
// .. code for loggign request
}
var result = await base.SendAsync(request, cancellationToken);
// .. code for logging response
// when I do not replace WebAPiExceptionHandler, code is reachred here
// When I Do use WebAPiExceptionHandler, code is not reached here
return result;
}
}
Code for throwing exception in webapi
public class Values_v2Controller : ApiController
{
public string Get(int id)
{
throw new Exception("haha");
}
}

Dont use ExceptionHandler as base class, implement interface IExceptionHandler
public class WebAPiExceptionHandler : IExceptionHandler
{
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
var fout = new ErrorInformation
{
Message = "Iets is misgegaan"
, ErrorDate = DateTime.UtcNow
};
var httpResponse = context.Request.CreateResponse(HttpStatusCode.InternalServerError, fout);
context.Result = new ResponseMessageResult(httpResponse);
return Task.FromResult(0);
}
private class ErrorInformation
{
public string Message { get; set; }
public DateTime ErrorDate { get; set; }
}
}

The problem is that ExceptionHandler only executes Handle(ExceptionHandlerContext context) method if ShouldHandle(ExceptionHandlerContext context) returns true.
Overriding bool ShouldHandle(ExceptionHandlerContext context) to always return true fix the problem for me.

Related

Exception handler doesn't catch exceptions

I have two projects in one solution. The 1st it's library project and the second it's Asp.Net WebApi project. Both using .Net Framework 4.6.1.
I want to make some class that will handling exceptions globally. I found nice solution using ExceptionHandler, however it doesn't work as I expected. I was following this article https://learn.microsoft.com/pl-pl/aspnet/web-api/overview/error-handling/web-api-global-error-handling.
Here is my class where I handle exceptions.
public class GlobalExceptionHandler : ExceptionHandler
{
public override void HandleCore(ExceptionHandlerContext context)
{
context.Result = new TextPlainErrorResult
{
Request = context.ExceptionContext.Request,
Content = context.Exception.Message
};
}
private class TextPlainErrorResult : IHttpActionResult
{
public HttpRequestMessage Request { get; set; }
public string Content { get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response =
new HttpResponseMessage(HttpStatusCode.InternalServerError);
response.Content = new StringContent(Content);
response.RequestMessage = Request;
return Task.FromResult(response);
}
}
}
For instance I have this code in my library project.
public class ThrowSomething
{
public void ThrowSomeException()
{
throw new Exception("Custom exception");
}
}
And in my controller in Asp.Net I have
public IHttpActionResult SomeAction()
{
var throwSomething = new ThrowSomething();
throwSomething.ThrowSomeException();
return Ok();
}
I would like to catch the exception in my GlobalExceptionHandler class and return some result to the Api. Currently the GlobalExceptionHandler is not handling the exceptions.
I also have config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler()); in Register method in WebApiConfig.

DataException from Dapper disappear when using MediatR

I have a strange problem with DataExceptions from Dapper doesn't properagte correctly.
Here's my setup:
public class CustomerController : Controller
{
private readonly IMediator _mediator;
public CustomerController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async Task<IActionResult> Get(Get.Query query)
{
var result = await _mediator.Send(query);
return Ok(result);
}
}
public class Get
{
public class Query : IRequest<IEnumerable<Result>>
{
}
public class Result
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class QueryHandler : IAsyncRequestHandler<Query, IEnumerable<Result>>
{
private readonly IDbConnection _dapper;
public QueryHandler(IDbConnection dapper)
{
_dapper = dapper;
}
public async Task<IEnumerable<Result>> Handle(Query message)
{
// the below throws because of incorrect type mapping
// (yes, the connection is open)
var customers =
await _dapper.Connection.QueryAsync<Result>("SELECT Id, Name FROM [Customer].[Customers]");
return customers;
}
}
}
The result
Curl
curl -X GET 'http://localhost:5000/api/Customer'
Request URL
http://localhost/api/Customer
Response Body
no content
Response Code
500
Expected
I was expecting 500 with an error description and not no content.
This is the exception thrown:
If I change my Handle method to:
public async Task<IEnumerable<Result>> Handle(Query message)
{
throw new DataException("What is going on?");
}
I get the expected result. A 500 with an error saying "What is going on?"
Because I have app.UseDeveloperExceptionPage(); enabled it looks like this.
An unhandled exception occurred while processing the request.
DataException: What is going on?
...Customer.Get+QueryHandler+<Handle>d__2.MoveNext() in Get.cs, line 42
Stack Query Cookies Headers
DataException: What is going on?
...
But that's expected.
So what is going on? Why doesn't the DataException from Dapper work as expected?
MediatR's preprocessor aggreggates all exception from pipeline in AggregateException.
You have to expose it - for instance with ExceptionFilterAttribute:
public class MyExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
Exception exception = context.Exception;
if (exception is AggregateException
&& exception.InnerException != null)
{
exception = exception.InnerException;
}
// check type and do you stuff ........
context.Response = new HttpResponseMessage
{
Content = this.CreateContent(response),
StatusCode = HttpStatusCode.BadRequest
};
//....

Global Exception Handling Web Api 2

I am trying to figure out how to implement a Global Exception Handler in .NET Web Api 2.
I tried following the example set out by Microsoft here:
https://learn.microsoft.com/en-us/aspnet/web-api/overview/error-handling/web-api-global-error-handling
But when exception occured, it did nothing.
This is my code:
public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
Trace.WriteLine(context.Exception.Message);
context.Result = new TextPlainErrorResult
{
Request = context.ExceptionContext.Request,
Content = "Oops! Sorry! Something went wrong." +
"Please contact support#testme.com so we can try to fix it."
};
}
private class TextPlainErrorResult : IHttpActionResult
{
public HttpRequestMessage Request { private get; set; }
public string Content { private get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response =
new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(Content),
RequestMessage = Request
};
return Task.FromResult(response);
}
}
}
Is there a better way (or more proper way) to implement a global exception handler?
Try adding this to your WebApiConfig
webConfiguration.Services.Replace(typeof(IExceptionHandler), new MyExceptionHandler()); // You have to use Replace() because only one handler is supported
webConfiguration.Services.Add(typeof(IExceptionLogger), new MyExceptionLogger()); // webConfiguration is an instance of System.Web.Http.HttpConfiguration
You missed
class GlobalExceptionHandler : ExceptionHandler
{
public override bool ShouldHandle(ExceptionHandlerContext context)
{
return true;
}
//...
}
See WebApi v2 ExceptionHandler not called

Web API global error handling add custom header in response

I was wondering if it was possible to set some custom header values whenever an internal server error has occurred? I am currently doing:
public class FooExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
// context.Result already contains my custom header values
context.Result = new InternalServerErrorResult(context.Request);
}
}
Here I also want to set some header values but though it appears in the request the response does not contain it.
Is there a way of doing this?
There is a sample code for your reference, my ApiExceptionHandler is your
FooExceptionHandler
public class ApiExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
var response = new Response<string>
{
Code = StatusCode.Exception,
Message = $#"{context.Exception.Message},{context.Exception.StackTrace}"
};
context.Result = new CustomeErrorResult
{
Request = context.ExceptionContext.Request,
Content = JsonConvert.SerializeObject(response),
};
}
}
internal class CustomeErrorResult : IHttpActionResult
{
public HttpRequestMessage Request { get; set; }
public string Content { get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response =
new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(Content),
RequestMessage = Request
};
response.Headers.Add("Access-Control-Allow-Origin", "*");
response.Headers.Add("Access-Control-Allow-Headers", "*");
return Task.FromResult(response);
}
}
It should be possible by creating your own exception filter.
namespace MyApplication.Filters
{
using System;
using System.Net;
using System.Net.Http;
using System.Web.Http.Filters;
public class CustomHeadersFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
context.Response.Content.Headers.Add("X-CustomHeader", "whatever...");
}
}
}
http://www.asp.net/web-api/overview/error-handling/exception-handling

WebApi v2 ExceptionHandler not called

How comes that a custom ExceptionHandler is never called and instead a standard response (not the one I want) is returned?
Registered like this
config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
and implemented like this
public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new ExceptionResponse
{
statusCode = context.Exception is SecurityException ? HttpStatusCode.Unauthorized : HttpStatusCode.InternalServerError,
message = "An internal exception occurred. We'll take care of it.",
request = context.Request
};
}
}
public class ExceptionResponse : IHttpActionResult
{
public HttpStatusCode statusCode { get; set; }
public string message { get; set; }
public HttpRequestMessage request { get; set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(statusCode);
response.RequestMessage = request;
response.Content = new StringContent(message);
return Task.FromResult(response);
}
}
and thrown like this (test)
throw new NullReferenceException("testerror");
in a controller or in a repository.
UPDATE
I do not have another ExceptionFilter.
I found a trigger for this behavior:
Given URL
GET http://localhost:XXXXX/template/lock/someId
sending this header, my ExceptionHandler works
Host: localhost:XXXXX
sending this header, it doesn't work and the built-in handler returns the error instead
Host: localhost:XXXXX
Origin: http://localhost:YYYY
This might be an issue with CORS requests (I use the WebAPI CORS package globally with wildcards) or eventually my ELMAH logger. It also happens when hosted on Azure (Websites), though the built-in error handler is different.
Any idea how to fix this?
Turns out the default only handles outermost exceptions, not exceptions in repository classes. So below has to be overridden as well:
public virtual bool ShouldHandle(ExceptionHandlerContext context)
{
return context.ExceptionContext.IsOutermostCatchBlock;
}
UPDATE 1
WebAPI v2 does not use IsOutermostCatchBlock anymore. Anyway nothing changes in my implementation, since the new code in ShouldHandle still prevents my Error Handler. So I'm using this and my Error Handler gets called once. I catch errors in Controllers and Repositories this way.
public virtual bool ShouldHandle(ExceptionHandlerContext context)
{
return true;
}
UPDATE 2
Since this question got so much attention, please be aware that the current solution is the one linked by #JustAMartin in the comments below.
The real culprit here is CorsMessageHandler inserted by EnableCors method in message processing pipline. The catch block intercept any exception and convert into a response before it can reach the HTTPServer try-catch block and ExceptionHandler logic can be invoked
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
CorsRequestContext corsRequestContext = request.GetCorsRequestContext();
HttpResponseMessage result;
if (corsRequestContext != null)
{
try
{
if (corsRequestContext.IsPreflight)
{
result = await this.HandleCorsPreflightRequestAsync(request, corsRequestContext, cancellationToken);
return result;
}
result = await this.HandleCorsRequestAsync(request, corsRequestContext, cancellationToken);
return result;
}
catch (Exception exception)
{
result = CorsMessageHandler.HandleException(request, exception);
return result;
}
}
result = await this.<>n__FabricatedMethod3(request, cancellationToken);
return result;
}

Categories

Resources