I thought it would be a good idea to use CancellationToken in my controller like so:
[HttpGet("things", Name = "GetSomething")]
public async Task<ActionResult> GetSomethingAsync(CancellationToken ct) {
var result = await someSvc.LoadSomethingAsync(ct);
return Ok(result);
}
The issue is that now Azure Application Insights shows a bunch of exceptions of type System.Threading.Tasks.TaskCancelledException: A Task was canceled. as well as the occasional Npgsql.PostgresException: 57014: canceling statement due to user request. This is noise that I don't need.
Application Insights is registered as a service using the standard method - services.AddApplicationInsightsTelemetry(Configuration);.
Attempted Solution
I thought I could jack into the application pipeline and catch the above Exceptions, converting them to normal Responses. I put the code below in various places. It catches any exceptions and if IsCancellationRequested, it returns a dummy response. Otherwise it rethrows the caught exception.
app.Use(async (ctx, next) =>
{
try { await next(); }
catch (Exception ex)
{
if (ctx.RequestAborted.IsCancellationRequested)
{
ctx.Response.StatusCode = StatusCodes.Status418ImATeapot;
}
else { throw; }
}
});
This code works in that it changes the response. However exceptions are still getting sent to Application Insights.
Requirements
I would like to have a solution that uses RequestAborted.IsCancellationRequested over trying to catch specific exceptions. The reason being that if I've already discovered one implementation that throws an exception not derived from OperationCanceledException the possibility exists there are others that will do the same.
It doesn't matter if dependency failures still get logged, just that the exceptions thrown as a result of the request getting canceled don't.
I don't want to have a try/catch in every controller method. It needs to be put in place in one spot.
Conclusion
I wish I understood Application Insights mechanism of reporting exceptions. After experimenting I feel like trying to catch errors in the Application pipeline isn't the correct approach. I just don't know what is.
I was able to solve this issue with a TelemetryProcessor.
In order to get access to RequestAborted.IsCancellationRequested in the processor, the HttpContext service needed to be made available by calling services.AddHttpContextAccessor() in Startup.ConfigureServices.
In the processor I prevent any exception telemetry from being processed if IsCancellationRequested is true. Below is a simplified example of my final solution, as eventually I realized I needed to do some more nuanced work with Dependencies and Requests that are out of scope of my original request.
internal class AbortedRequestTelemetryProcessor : ITelemetryProcessor
{
private readonly IHttpContextAccessor httpContextAccessor;
private readonly ITelemetryProcessor next;
public AbortedRequestTelemetryProcessor(
ITelemetryProcessor next, IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
this.next = next;
}
public void Process(ITelemetry item)
{
if (httpContextAccessor.HttpContext?.RequestAborted.IsCancellationRequested == true
&& item is ExceptionTelemetry)
{
// skip exception telemetry if cancellation was requested
return;
}
// Send everything else:
next.Process(item);
}
}
Have you tried by creating an ExceptionFilter?
public class MyExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (context?.Exception.Message == "Did it catch this?")
{
context.Result = new BadRequestObjectResult("You have no access.");
}
}
}
Inside ConfigureServices method:
services.AddMvc(config =>
{
config.Filters.Add(typeof(MyExceptionFilter));
})
The logic inside the filter is just to give you an example of how you'd want to filter something and propagate a result. In your case, I'm guessing you'll need to somehow swallow the exception tied to the context, so that no exception afterwards is spotted.
Perhaps simply assigning it to null will do the trick?
Related
Consider a controller endpoint like this:
[HttpGet]
public IActionResult Get()
{
var resource = _myService.GetSomeResource();
return Ok(resource);
}
The GetSomeResource() method performs validation on the identity of the caller, like this:
public SomeResourceModel GetSomeResource()
{
_securityVerifier.VerifyCallerHasAccess();
// do stuff to get resource
return resource;
}
Within the VerifyCallerHasAccess() method, let's pretend that the caller does NOT have access to the resource they requested. So, we throw an exception. Something like:
throw new SecurityException($"User does not have access to this resource");
With all that said, what I want is for the ASP.NET controller to automatically return a 403 when an exception of that type is encountered. By default, it's just returning a 500. Is it possible to register http response codes to specific types of exceptions? If so, how?
(Note, I know I can wrap my endpoint code in a try/catch, but I don't want to have to manually do this)
Edit for some clarity: My endpoint is already using .NET's authorization system. The security verification in question deals with getting the user's identity and checking against an external security service that they have the appropriate access rights to a given security resource.
You could use some middleware in the request pipeline:
public class ErrorHandlingMiddleware
{
readonly RequestDelegate _next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (SecurityException ex)
{
context.Response.StatusCode = 403;
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
await context.Response.WriteAsync(ex.Message);
}
}
}
and then register that in Program.cs:
app.UseMiddleware<ErrorHandlingMiddleware>();
If you could modify VerifyCallerHasAccess() method,you could return (or set some property) true or false instead of throw an exception
in controller,you could try as below:
if (result)
{
return Ok();
}
else
{
return Problem(detail: "User does not have access to the resource", statusCode: 403);
// you could also try return Forbind() to return a 403 with out the detail
//check the controllerbase class and you would find more methods related
}
if you insist on throw exception, then put
var resource = _myService.GetSomeResource();
return Ok(resource)
into try catch block ,return 403 with Forbind() method or other methods you would find in controllerbase class
You can write an exception filter (see https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-7.0) or if you want to standardize your responses as well, take a look at Hellang.Middleware.ProblemDetails
I created an exception handling middleware according to this example:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseExceptionHandler("/error");
// ...
}
[AllowAnonymous]
[ApiExplorerSettings(IgnoreApi = true)]
[ApiController]
public class ErrorsController : ControllerBase
{
[Route("error")]
public IActionResult Error()
{
return Problem();
}
}
Now let's say that I threw the following exception: throw new Exception("BAX");
My exception handling middleware catches this exception and works great. But the problem is that in console I see the following log:
|ERROR|Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware|An unhandled exception has occurred while executing the request.|System.Exception: BAX (stack trace goes here)
Note: I removed stack trace to make it a little bit shorter.
Maybe I should also say that I use NLog for logging. Here is the configuration of it:
<target xsi:
type = "ColoredConsole" name = "colored_console"
layout = "|${level:uppercase=true}|${logger}|${message}|${exception:format=tostring}">
</target >
Question
My exception was caught by Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.
It looks like my exception handling middleware didn't handle the exception. Am I right?
My question is why it works so and how can I fix it?
The duty of app.UseExceptionHandler("errors") is log errors then redirect users to a page in order to show them a proper message.
First of all, when you have app.UseExceptionHandler("errors") it means, ASP.NET Core redirects to a controller named errors and according to your code, it won't work because your Conroller name is Errors and in your code you defined error as its path.
if you don't want Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware to be called, then you have to write your own middleware and log errors there. here is an example of custom middleware to catch all exceptions.
public class CustomMiddlewareMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddlewareMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (System.Exception)
{
/// log it here and then redirect to spesific path
if (httpContext.Response.StatusCode == 404 && !httpContext.Response.HasStarted)
{
httpContext.Request.Path = "/error/404/";
await _next(httpContext);
}
}
}
}
I've added a custom middleware to my Startup class (following the example here).
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Use((context, next) =>
{
try { return next(); }
catch (Exception exception) { return new Task(() => { }); }
});
app.UseCors("Welcome All");
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(_ => _.SwaggerEndpoint("/swagger/v0.1/swagger.json", "Version 0.1"));
}
It was my understanding that throwing an exception in my method in a controller should be caught in try-catch of the custom middleware. But .NET Core seems to diagree with my expectations. By the breakpoints I can see that return next() is invoked but after the controller's method throws an exception, the exception isn't caught and the yellow marker jumps past the middleware alltoghether and lands at the end curly brace of it.
What am I missing?
If it's of any significance or interest, my aim is to move out the exception handling from my methods to the cross-cut middy in orde to simplify the code (and I want not to apply filters, since I'm targetting pure WebApi without MVC). So the controller and the method look something like this.
[Route("api/test")]
public class TestController : Controller
{
public TestController(...) { ... }
...
[HttpPost]
public IActionResult CrashBoom([FromBody] Thing input)
{
if (input.isCrashworthy)
throw new Exception("Boom")
else
return Ok();
}
}
The problem here is that request delegates, the executable parts of a middleware, run asynchronously. If you look at the type of next you can see that it is actually a Func<Task>, so it’s a function that returns a task. Or in other words: It’s an asynchronous function without a return value.
That means that in order to be able to catch exceptions, you need to keep the asynchronous context. So your custom middleware should execute asynchronously as well. If you do that, you will notice that you can catch exceptions and then respond to them accordingly, for example by writing out a plain text response (as an example):
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (Exception ex)
{
// let’s log the exception
var logger = context.RequestServices.GetService<ILogger<Startup>>();
logger.LogWarning(ex, "Encountered an exception");
// write a response
context.Response.StatusCode = 500;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync("Sorry, something went wrong!");
}
});
Now, if an exception is raised further down the middleware pipeline, then your custom pipeline will be able to catch it and respond to it.
When certain exceptions are thrown in controllers, I want to catch those exceptions and do some extra logic.
I was able to achieve this with a custom IExceptionFilter that is added to the global filters list.
However, I preffer to handle these exception within a custom Owin middleware.
My middleware looks like this:
try
{
await Next.Invoke(context);
}
catch (AdalSilentTokenAcquisitionException e)
{
//custom logic
}
This piece of code does not work, it looks like the exception is already catched and handled in MVC.
Is there a way to skip the exception processing from MVC and let the middleware catch the exception?
Update: I've found a cleaner approach, see my updated code below.
With this approach, you don't need a custom Exception Filter and best of all, you don't need the HttpContext ambient service locator pattern in your Owin middleware.
I have a working approach in MVC, however, somehow it doesn't feel very comfortable, so I would appreciate other people's opinion.
First of all, make sure there are no exception handlers added in the GlobalFilters of MVC.
Add this method to the global asax:
protected void Application_Error(object sender, EventArgs e)
{
var lastException = Server.GetLastError();
if (lastException != null)
{
HttpContext.Current.GetOwinContext().Set("lastException", lastException);
}
}
The middleware that rethrows the exception
public class RethrowExceptionsMiddleware : OwinMiddleware
{
public RethrowExceptionsMiddleware(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
await Next.Invoke(context);
var exception = context.Get<Exception>("lastException");
if (exception != null)
{
var info = ExceptionDispatchInfo.Capture(exception);
info.Throw();
}
}
}
There's no perfect way to do this (that I know of), but you can replace the default IExceptionHandler with one that just passes the error through to the rest of the stack.
I did some extensive digging about this, and there really doesn't seem to be a better way for now.
How do I catch all unhandled exceptions that occur in ASP.NET Web Api so that I can log them?
So far I have tried:
Create and register an ExceptionHandlingAttribute
Implement an Application_Error method in Global.asax.cs
Subscribe to AppDomain.CurrentDomain.UnhandledException
Subscribe to TaskScheduler.UnobservedTaskException
The ExceptionHandlingAttribute successfully handles exceptions that are thrown within controller action methods and action filters, but other exceptions are not handled, for example:
Exceptions thrown when an IQueryable returned by an action method fails to execute
Exceptions thrown by a message handler (i.e. HttpConfiguration.MessageHandlers)
Exceptions thrown when creating a controller instance
Basically, if an exception is going to cause a 500 Internal Server Error to be returned to the client, I want it logged. Implementing Application_Error did this job well in Web Forms and MVC - what can I use in Web Api?
This is now possible with WebAPI 2.1 (see the What's New):
Create one or more implementations of IExceptionLogger. For example:
public class TraceExceptionLogger : ExceptionLogger
{
public override void Log(ExceptionLoggerContext context)
{
Trace.TraceError(context.ExceptionContext.Exception.ToString());
}
}
Then register with your application's HttpConfiguration, inside a config callback like so:
config.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());
or directly:
GlobalConfiguration.Configuration.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());
To answer my own question, this isn't possible!
Handling all exceptions that cause internal server errors seems like a basic capability Web API should have, so I have put in a request with Microsoft for a Global error handler for Web API:
https://aspnetwebstack.codeplex.com/workitem/1001
If you agree, go to that link and vote for it!
In the meantime, the excellent article ASP.NET Web API Exception Handling shows a few different ways to catch a few different categories of error. It's more complicated than it should be, and it doesn't catch all interal server errors, but it's the best approach available today.
Update: Global error handling is now implemented and available in the nightly builds! It will be released in ASP.NET MVC v5.1. Here's how it will work: https://aspnetwebstack.codeplex.com/wikipage?title=Global%20Error%20Handling
The Yuval's answer is for customizing responses to unhandled exceptions caught by Web API, not for logging, as noted on the linked page. Refer to the When to Use section on the page for details. The logger is always called but the handler is called only when a response can be sent. In short, use the logger to log and the handler to customize the response.
By the way, I am using assembly v5.2.3 and the ExceptionHandler class does not have the HandleCore method. The equivalent, I think, is Handle. However, simply subclassing ExceptionHandler (as in Yuval's answer) does not work. In my case, I have to implement IExceptionHandler as follows.
internal class OopsExceptionHandler : IExceptionHandler
{
private readonly IExceptionHandler _innerHandler;
public OopsExceptionHandler (IExceptionHandler innerHandler)
{
if (innerHandler == null)
throw new ArgumentNullException(nameof(innerHandler));
_innerHandler = innerHandler;
}
public IExceptionHandler InnerHandler
{
get { return _innerHandler; }
}
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
Handle(context);
return Task.FromResult<object>(null);
}
public void Handle(ExceptionHandlerContext context)
{
// Create your own custom result here...
// In dev, you might want to null out the result
// to display the YSOD.
// context.Result = null;
context.Result = new InternalServerErrorResult(context.Request);
}
}
Note that, unlike the logger, you register your handler by replacing the default handler, not adding.
config.Services.Replace(typeof(IExceptionHandler),
new OopsExceptionHandler(config.Services.GetExceptionHandler()));
You can also create a global exception handler by implementing the IExceptionHandler interface (or inherit the ExceptionHandler base class). It will be the last to be called in the execution chain, after all registered IExceptionLogger:
The IExceptionHandler handles all unhandled exceptions from all
controllers. This is the last in the list. If an exception occurs, the
IExceptionLogger will be called first, then the controller
ExceptionFilters and if still unhandled, the IExceptionHandler
implementation.
public class OopsExceptionHandler : ExceptionHandler
{
public override void HandleCore(ExceptionHandlerContext context)
{
context.Result = new TextPlainErrorResult
{
Request = context.ExceptionContext.Request,
Content = "Oops! Sorry! Something went wrong."
};
}
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);
}
}
}
More on that here.
You may have existing try-catch blocks that you're not aware of.
I thought my new global.asax.Application_Error method wasn't being consistently called for unhandled exceptions in our legacy code.
Then I found a few try-catch blocks in the middle of the call stack that called Response.Write on the Exception text. That was it. Dumped the text on the screen then killed the exception stone dead.
So the exceptions were being handled, but the handling was doing nothing useful. Once I removed those try-catch blocks the exceptions propagated to the Application_Error method as expected.