WebApi v2 ExceptionHandler not called - c#

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

Related

.Net Core: Return IActionResult from a custom Exception Middleware

I have created a new Exception middleware in my .Net Core application. All the exceptions throughout the application are captured and logged here. What I want is to return a IActionResult type like InternalServerError() or NotFound() from the Exception Middleware and not do response.WriteAsync as below.
Controller Method:
public async Task<IActionResult> Post()
{
//Do Something
return Ok();
}
Middleware:
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
public ExceptionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next.Invoke(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var response = context.Response;
var statusCode = (int)HttpStatusCode.InternalServerError;
var message = exception.Message;
var description = exception.Message;
response.ContentType = "application/json";
response.StatusCode = statusCode;
await response.WriteAsync(JsonConvert.SerializeObject(new ErrorResponse
{
Message = message,
Description = description
}));
}
}
IActionResult is a thing from MVC, so it is only available within the MVC pipeline (including Razor Pages). Just before the MVC middleware terminates, it will execute those action results using ExecuteResultAsync. That method is then responsible of writing that response to HttpContext.Response.
So in custom middleware, you cannot just set an action result and have it executed, since you are not running within the MVC pipeline. However, with that knowledge, you can simply execute the result yourself.
Let’s say you want to execute a NotFoundResult which is what Controller.NotFound() creates. So you create that result and call ExecuteResultAsync with an . That executor will be able to execute that result object and write to the response:
var result = new NotFoundResult();
await result.ExecuteResultAsync(new ActionContext
{
HttpContext = context
});
That's not really possible due to where IActionResult and middleware sit in relation to one another in the architecture. Middleware sits much lower, and so it can't reach further up the stack to IActionResult. Here's an answer that talks more about it: https://stackoverflow.com/a/43111292/12431728
What you're trying to do can be done by simply adding this line:
app.UseStatusCodePagesWithReExecute("/Public/Error", "?statusCode={0}");
to the Configure method in the Startup.cs. Then you can create your Public Controller with Error method that does the following:
[AllowAnonymous]
public IActionResult Error(int? statusCode = null)
{
// Retrieve error information in case of internal errors.
var error = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error;
var path = HttpContext.Features.Get<IExceptionHandlerPathFeature>()?.Path;
// TODO: Redirect here based on your status code or perhaps just render different views for different status codes.
}
There is also another middleware that allows you to do a similar thing:
app.UseStatusCodePages(async context =>
{
if (context.HttpContext.Response.StatusCode == 401)
{
context.HttpContext.Response.Redirect("Errors/Unauthorized/");
}
else if (context.HttpContext.Response.StatusCode == 500)
{
// TODO: Redirect for 500 and so on...
}
});

MediatR CQRS - How to deal with unexisting resources (asp.net core web api)

So I've recently started to learn about using the MediatR library with ASP.NET Core Web API and I'm unsure how to go about returning a NotFound() when a DELETE/PUT/PATCH request has been made for an unexisting resource.
If we take DELETE for example, here is my controller action:
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
await Mediator.Send(new DeleteCourseCommand {Id = id});
return NoContent();
}
The Command:
public class DeleteCourseCommand : IRequest
{
public int Id { get; set; }
}
The Command Handler:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand>
{
private readonly UniversityDbContext _context;
public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}
return Unit.Value;
}
}
As you can see in the Handle method, if there is an error when saving, an exception is thrown which results in a 500 internal server error (which is correct I believe). But if the Course is not found, how can I feed this back to the Action on the Controller? Is it simply a case of invoking a Query to GET the course in the Controller Action, then return NotFound() if it doesn't exist or then invoke the Command to DELETE the Course? This would work of course but of all the examples I've been through, I haven't come across an Action which uses two Mediator calls.
MediatR supports a Request/Response pattern, which allows you to return a response from your handler class. To use this approach, you can use the generic version of IRequest, like this:
public class DeleteCourseCommand : IRequest<bool>
...
In this case, we're stating that bool will be the response type. I'm using bool here for simplicity: I'd suggest using something more descriptive for your final implementation but bool suffices for explanation purposes.
Next, you can update your DeleteCourseCommandHandler to use this new response type, like this:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, bool>
{
...
public async Task<bool> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = ...
if (course == null)
return false; // Simple example, where false means it wasn't found.
...
return true;
}
}
The IRequestHandler being implemented now has two generic types, the command and the response. This requires updating the signature of Handle to return a bool instead of Unit (in your question, Unit isn't being used).
Finally, you'll need to update your Delete action to use the new response type, like this:
public async Task<IActionResult> Delete(int id)
{
var courseWasFound = await Mediator.Send(new DeleteCourseCommand {Id = id});
if (!courseWasFound)
return NotFound();
return NoContent();
}
I like returning events from my commands. The command is telling your application what the client wants it to do. The response is what it actually did.
BTW—it's said that command handlers should return anything. That's really only true in a fully async environment where the command won't be completed until sometime after the response to the client that it's accepted. In that case, you would return Task<Unit> and publish these events. The client would get them via some other channel, like a SignalR hub once they were raised. Either way, events are the best way to tell a client what's going on in your application.
Start by defining an interface for your events
public interface IEvent
{
}
Then, create events for each of the things that can happen in a command. You can include information in them if you'd want to do something with that information or just leave them empty if the class itself is enough.
public class CourseNotFoundEvent : IEvent
{
}
public class CourseDeletedEvent : IEvent
{
}
Now, have your command return an event interface.
public class DeleteCourseCommand : IRequest<IEvent>
{
}
Your handler would look something like this:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, IEvent>
{
private readonly UniversityDbContext _context;
public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}
public async Task<IEvent> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
if (course is null)
return new CourseNotFoundEvent();
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
return new CourseDeletedEvent();
}
}
Finally, you can use pattern matching on your web API to do things based on the event that gets returned.
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var #event = await Mediator.Send(new DeleteCourseCommand {Id = id});
if(#event is CourseNotFoundEvent)
return NotFound();
return NoContent();
}
I managed to solve my problem through some more examples I found. The solution is to define custom Exceptions such as NotFoundException and then throw this in the Handle method of the Query/Command Handler. Then in order for MVC to handle this appropriately, an implementation of ExceptionFilterAttribute is needed to decide how each Exception is handled:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
if (context.Exception is ValidationException)
{
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.Result = new JsonResult(
((ValidationException)context.Exception).Failures);
return;
}
var code = HttpStatusCode.InternalServerError;
if (context.Exception is NotFoundException)
{
code = HttpStatusCode.NotFound;
}
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.StatusCode = (int)code;
context.Result = new JsonResult(new
{
error = new[] { context.Exception.Message }
});
}
}
Startup Class:
services.AddMvc(options => options.Filters.Add(typeof(CustomExceptionFilterAttribute)));
Custom Exception:
public class NotFoundException : Exception
{
public NotFoundException(string entityName, int key)
: base($"Entity {entityName} with primary key {key} was not found.")
{
}
}
Then in the Handle method:
if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}
else
{
throw new NotFoundException(nameof(Course), request.Id);
}
return Unit.Value;
This seems to do the trick, if anyone can see any potential issues with this please let me know!

using global exception handeling messes up DelegatingHandler

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.

Web Api always returns http status code 200 when an exception occurs

public class GlobalExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new NiceInternalServerExceptionResponse("The current operation could not be completed sucessfully.);
}
}
When a call this Get action:
[HttpGet]
public async Task<IHttpActionResult> Get()
{
Convert.ToInt16("this causes an exception state");
var data = await service.Get();
return Ok(data);
}
An exception is raised... and my global exc handler is triggered.
When my custom response is returned to the client my fiddler always says:
Result: 200
I could also change the return Ok(data); to return NotFound();
That will not change anything in the result status code.
How can I overwrite/intercept the http status creation and return my own status code 500 instead?
On my web client I need to show a nice error dialog with a logging id + error message ONLY when status code 500 is returned.
You need to set the status code on the IHttpActionResult:
public class NiceInternalServerExceptionResponse : IHttpActionResult
{
public string Message { get; private set; }
public HttpStatusCode StatusCode { get; private set; }
public NiceInternalServerExceptionResponse(
string message,
HttpStatusCode code)
{
Message = message;
StatusCode = code;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(StatusCode);
response.Content = new StringContent(Message);
return Task.FromResult(response);
}
}
And in your GlobalExceptionHandler pass HttpStatusCode.InternalServerError (500):
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new NiceInternalServerExceptionResponse(
"The current operation could not be completed sucessfully.",
HttpStatusCode.InternalServerError);
}
I do it like this...
[HttpPost]
public HttpResponseMessage Post()
{
try
{
// Do stuff
}
catch (Exception ex)
{
// Something went wrong - Return Status Internal Server Error
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
Works same for a Get.
You could use next code for custom error:
return Content(HttpStatusCode.NotFound, "Foo does not exist.");

Uniform, consistent error responses from ASP.Net Web API 2

I'm developing a Web API 2 application and I'm currently trying to format error resposnes in a uniform way (so that the consumer will also know what data object/structure they can inspect to get more info about the errors). This is what I've got so far:
{
"Errors":
[
{
"ErrorType":5003,
"Message":"Error summary here",
"DeveloperAction":"Some more detail for API consumers (in some cases)",
"HelpUrl":"link to the docs etc."
}
]
}
This works fine for exceptions thrown by the application itself (i.e inside controllers). However, if the user requests a bad URI (and gets a 404) or uses the wrong verb (and gets a 405) etc, Web Api 2 spits out a default error message e.g.
{
Message: "No HTTP resource was found that matches the request URI 'http://localhost/abc'."
}
Is there any way of trapping these kinds of errors (404, 405 etc.) and formatting them out into the error response in the first example above?
So far I've tried:
Custom ExceptionAttribute inherting ExceptionFilterAttribute
Custom ControllerActionInvoker inherting ApiControllerActionInvoker
IExceptionHandler (new Global Error Handling feature from Web API 2.1)
However, none of these approaches are able to catch these kinds of errors (404, 405 etc). Any ideas on how/if this can be achieved?
...or, am I going about this the wrong way? Should I only format error responses in my particular style for application/user level errors and rely on the default error responses for things like 404?
You can override the DelegatingHandler abstract class and intercept the response to the client. This will give you the ability to return what you want.
Here's some info on it.
http://msdn.microsoft.com/en-us/library/system.net.http.delegatinghandler(v=vs.118).aspx
Here's a poster of the Web Api pipeline that shows what can be overriden.
http://www.asp.net/posters/web-api/asp.net-web-api-poster.pdf
Create a Handler class like this to override the response
public class MessageHandler1 : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
Debug.WriteLine("Process request");
// Call the inner handler.
var response = base.SendAsync(request, cancellationToken);
Debug.WriteLine("Process response");
if (response.Result.StatusCode == HttpStatusCode.NotFound)
{
//Create new HttpResponseMessage message
}
;
return response;
}
}
In your WebApiConfig.cs class add the handler.
config.MessageHandlers.Add(new MessageHandler1());
UPDATE
As Kiran mentions in the comments you can use the OwinMiddleware to intercept the response going back to the client. This would work for MVC and Web Api running on any host.
Here's an example of how to get the response and change it as it goes to the client.
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(typeof(MyMiddleware));
}
}
public class MyMiddleware : OwinMiddleware
{
public MyMiddleware(OwinMiddleware next) : base(next) { }
public override async Task Invoke(IOwinContext context)
{
await Next.Invoke(context);
if(context.Response.StatusCode== 404)
{
context.Response.StatusCode = 403;
context.Response.ReasonPhrase = "Blah";
}
}
}
I have done in same way as #Dan H mentioned
public class ApiGatewayHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.NotFound)
{
var objectContent = response.Content as ObjectContent;
return await Task.FromResult(new ApiResult(HttpStatusCode.NotFound, VmsStatusCodes.RouteNotFound, "", objectContent == null ? null : objectContent.Value).Response());
}
return response;
}
catch (System.Exception ex)
{
return await Task.FromResult(new ApiResult(HttpStatusCode.BadRequest, VmsStatusCodes.UnHandledError, ex.Message, "").Response());
}
}
}
Added routing like below and now it hits the try catch for invalid url
config.Routes.MapHttpRoute(name: "DefaultApi",routeTemplate: "api/{controller}/{id}",defaults: new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute(name: "NotFound", routeTemplate: "api/{*paths}", defaults: new { controller = "ApiError", action = "NotFound" });

Categories

Resources