TaskCanceledException Results in Controller Action Being Called Again - asp.net

I've got an ASP.NET Web API 2 controller action that uses HttpClient.PostAsync() to make an upstream HTTP request. If the upstream request, which is being awaited in the controller action, times out then a TaskCanceledException is thrown from the controller action. At this point I'm seeing a few things I can't explain and could use some help:
Global exception handling doesn't catch this exception If I surround the awaited PostAsync() call with try/catch I can catch the TaskCanceledException. However, without try/catch, this exception doesn't appear in:
Global.asax.cs Application_Error method
IExceptionLogger or IExceptionHandlers add to the HttpConfiguration.Services in WebApiConfig.cs
Controller Action is Invoked Again - As I watch in Fiddler, there is only one request to the service made but a breakpoint set at the beginning of the controller action is hit multiple times, following each TaskCanceledException until Fiddler stops awaiting a response and returns a 504 gateway error. It seems that something in the ASP.NET or Web API pipeline is handling the TaskCanceledException and until the client (Fiddler, browser, etc) disconnects is re-attempting to call my controller action.
At this point I know I can prevent the action from being re-tried by using try/catch and throwing an HttpResponseException or something but I'm particularly interested in why I can't catch this exception using normal means and why I'm seeing this apparent re-try behavior. Any input is welcome!
Minimal Repro
Add this Web API Controller to a project:
namespace TaskRetryRepro.Controllers
{
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
public class ValuesController : ApiController
{
public async Task<IHttpActionResult> Get()
{
// I see two lines output in my debug console per single request to this API action
Debug.WriteLine(
"Get called with HttpRequestMessage: {0} and HttpContext.Current.Request: {1}",
this.Request.GetHashCode(),
HttpContext.Current.Request.GetHashCode());
var httpClient = new HttpClient(new TimeoutHandler());
// NOTE: No try/catch here
await httpClient.GetAsync("http://www.google.com");
return this.Ok(new[] { "value1", "value2" });
}
}
/// <summary>The timeout handler.</summary>
public class TimeoutHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// in my scenario calling another service it's an actual upstream service timeout but
// it throws the TaskCanceledException just the same
Thread.Sleep(TimeSpan.FromSeconds(5));
var tcs = new TaskCompletionSource<HttpResponseMessage>();
tcs.SetCanceled();
return tcs.Task;
}
}
}
Clear your debug console
Issue a SINGLE request to the api action in a browser or via Fiddler e.g. GET http://localhost:xxxxx/api/values
Verify that output from the Debug.WriteLine() statement appears twice. In my test I see something like this:
Get called with HttpRequestMessage: 30376100 and HttpContext.Current.Request: 37460558
A first chance exception of type 'System.Threading.Tasks.TaskCanceledException' occurred in mscorlib.dll
Get called with HttpRequestMessage: 12036987 and HttpContext.Current.Request: 42715336
A first chance exception of type 'System.Threading.Tasks.TaskCanceledException' occurred in mscorlib.dll

Related

Should I log errors in a controller or in a service?

I have a Symfony controller which basically checks if requested parameters are in the request, then passes these parameters to a service. The service use Guzzle to call an API, does some things with the result and then passes it back to the controller in order to display a Json with the response.
I have a noob question about the handling of errors, if the Api I call with Guzzle return an error, what is the best solution ?
Solution 1: Should I log the error using the Logger service injected in my own service and return an error to my controller in order to display it.
Solution 2: Should I throw an Exception in the service, catch it in my controller and use the $this->get("Logger") in the controller in order to log the error in log files
It would be nice if your core logic is itself in a service and not in your controller.
That way, you could use try-catch block inside the service where you call another service and your controller stays clean and neat - you just call the service without catching any exception.
// AppBundle/src/Controller/MainController.php
public function mainAction()
{
// ...
$result = $this->get('my_service')->getResult($parameters);
if (!$result) {
// show an error message, pass it to another service, ignore it or whatever you like
}
}
// AppBundle/src/Service/MyService.php
public function getResult($parameters)
{
try {
$apiResult = $this->apiService->get($parameters);
} catch (ApiException $e)
$this->logger->error('My error message');
$apiResult = null;
}
return $apiResult;
}
Consider also a Solution 3: Throw an exception in a service and catch it in a custom exception listener, where you can log it and take further action (like replacing the Response object, etc.).

SignalR, Owin and exception handling

I've developed a sample SignalR application based on ASP.NET 4.5 & Owin, and I've hosted that app on IIS 7.5.
Everything is working fine, but how can I handle exceptions in Owin?
Consider the following code:
[HubName("SampleHub")]
public class SampleHub : Hub
{
public SampleHub()
{
throw new InvalidOperationException("?!");
}
}
This exception won't call Application_Error (and this is my problem).
Where can I get all exceptions from Owin for logging and debugging purposes similarly to Application_Error?
I'm not interested in something like this:
app.UseErrorPage(new ErrorPageOptions()
{
ShowCookies = true,
ShowEnvironment = true,
ShowExceptionDetails = true,
ShowHeaders = true,
ShowQuery = true,
ShowSourceCode = true
});
This is totally useless for advanced scenarios, something like ASP.NET Web API and ASP.NET MVC.
Action filters with OnException method for override purposes is much better.
If you want exception handling specifically for SignalR Hubs, OWIN middleware is not the way to go.
To illustrate just one reason why, suppose that SignalR is using its WebSocket transport when an exception is thrown from inside a Hub method. In this case, SignalR will not close the WebSocket connection. Instead SignalR will write a JSON encoded message directly to the socket to indicate to the client that an exception was thrown. There is no easy way using OWIN middleware to trigger any sort of event when this happens outside of possibly wrapping the entire OWIN WebSocket Extension which I would strongly advise against.
Fortunately SignalR provides its own Hub Pipeline which is perfectly suited for your scenario.
using System;
using System.Diagnostics;
using Microsoft.AspNet.SignalR.Hubs;
public class MyErrorModule : HubPipelineModule
{
protected override void OnIncomingError(ExceptionContext exceptionContext, IHubIncomingInvokerContext invokerContext)
{
MethodDescriptor method = invokerContext.MethodDescriptor;
Debug.WriteLine("{0}.{1}({2}) threw the following uncaught exception: {3}",
method.Hub.Name,
method.Name,
String.Join(", ", invokerContext.Args),
exceptionContext.Error);
}
}
You can use the ExceptionContext for more than just logging. For example you can set ExceptionContext.Error to a different exception which will change the exception the client receives.
You can even suppress the exception by setting ExceptionContext.Error to null or by setting ExceptonContext.Result. If you do this, It will appear to the client that the Hub method returned the value you found in ExceptonContext.Result instead of throwing.
A while back a wrote another SO answer about how you can call a single client callback for every exception thrown by a Hub method: SignalR exception logging?
There is also MSDN documentation for HubPipelineModules: http://msdn.microsoft.com/en-us/library/microsoft.aspnet.signalr.hubs.hubpipelinemodule(v=vs.118).aspx
The answer by #halter73 is great for errors thrown inside hubs, but it doesn't catch errors thrown during their creation.
I was getting the exception:
System.InvalidOperationException: 'foobarhub' Hub could not be resolved.
The server was returning an HTML page for this exception, but I needed it in JSON format for better integration with my Angular app, so based on this answer I implemented an OwinMiddleware to catch exceptions and change the output format. You could use this for logging errors instead.
public class GlobalExceptionMiddleware : OwinMiddleware
{
public GlobalExceptionMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
try
{
await Next.Invoke(context);
}
catch (Exception ex)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = 500;
await context.Response.WriteAsync(JsonConvert.SerializeObject(ex));
}
}
}
Add the registration in OwinStartup.cs, just remember to place it before the MapSignalR method call:
public class OwinStartup
{
public void Configuration(IAppBuilder app)
{
app.Use<GlobalExceptionMiddleware>(); // must come before MapSignalR()
app.MapSignalR();
}
}

Application_Error isn't triggered in asp.net web api

How do I get the Application_Error triggered in an ASP.NET WebAPI application? The error I'm having now is that when we resolve a controller through NInject and fails it won't got into Application_Error and we can't log the error since we are doing the global logging in Application_Error.
We also have a global filter for handling error, but that is only triggered if we have found a controller but since we are failing we instantiating the controller we won't go through any filters.
So how do I catch an Exception raised why trying to create the controller handling the response?
ASP.Net Web API has its own Exception Handler, you can override it and re-throw exception from there in order to handle it in Application_Error
public class MyExceptionHandler : IExceptionHandler
{
public virtual Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
ExceptionDispatchInfo.Capture(context.Exception).Throw();
return Task.CompletedTask;
}
}
you also need to add MyExceptionHandler class to Web API services.
httpConfiguration.Services.Replace(typeof(IExceptionHandler), new MyExceptionHandler());

Exception handling beyond Exception Filters?

Using Asp.net WebApi (RC), how can I catch errors that are not caught by Exception Filters or Application_Error() in global.asax?
With both of these in place it seems that there is a class of exceptions still not covered. For example: ApiControllerActionSelector_AmbiguousMatch error (Multiple actions were found that match the request: {0}).
I'm not specifically concerned about the above error, this error just pointed out that there is a class of errors that aren't being caught by either my Exception Filter or Application_Error method.
So how can I cover all my bases?
You're right, there are several classes of exception not trapped by either Application_Error or ExceptionFilter. The Web API request pipeline is processed separately from the ASP.NET MVC pipeline (at least through MVC 4) so the MVC Application_Error doesn't kick-in. Also, if your application throws HttpResponseException type exceptions, they will not be caught by an ExceptionFilter by design (see the ExceptionFilter paragraph). To access all exceptions thrown by your code, you'll need to create a DelegatingHandler along the lines of this code:
public class ResponseExceptionTrapper : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
return base
.SendAsync(request, cancellationToken)
.ContinueWith(response =>
{
var result = response.Result;
if (!result.IsSuccessStatusCode)
{
var exceptionResult = string.Format(
"Response exception: Path({0}) Status({1}) ",
request.RequestUri,
result.StatusCode);
if (result.Content != null)
{
var exceptionReadTask =
result.Content.ReadAsStringAsync();
exceptionReadTask.Wait();
exceptionResult += "Message:\n\r" +
exceptionReadTask.Result;
}
// Do something appropriate with exceptionResult
}
return result;
}, cancellationToken);
}
}
You can wire up the handler with this line in your global config logic:
GlobalConfiguration.Configuration.MessageHandlers.Add(
new ResponseExceptionTrapper());
I believe that Exception Filters only get called once the action is invoked (in which case there is a try/catch around it). The Ambiguous match error would pop up before that in the pipeline and there could be other errors that pop up after that (e.g. a formatter error) as you mention.
I'm not sure you can have one solution to address all of the aspects (since the hosting implementation can vary), but you could try overriding the HttpControllerDispatcher. This class is one of the "root" classes used in the pipeline. Specifically, you could override SendAsync to do your try/catch and handle accordingly.

Returning an error and message from a Spring controller or service

I'm migrating some servlets over to the Spring framework, using Spring MVC. Currently in each servlet we authenticate the user and if the authentication fails we do this:
if (authfailed)
{
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"You are not authorized.");
return;
}
On the front end is a YUI-based application, and when an error status is returned the "failure" callback displays a dialog with the error message given above.
I know in my controller I can get the response object and call sendError, but is that the best way to handle this? sendError also throws an IOException so I'd have to catch that - a bit of annoying code to insert in every method of every controller.
I have the same problem handling exceptions - the servlets have try-catch blocks that call sendError in the catch method. I know I can mark my exception handlers with
#ExceptionHandler
#ResponseStatus(value = HttpStatus.NOT_FOUND)
but doesn't the exception handling class need to be in each controller class?
Finally, if the exception happens in a service called from a controller, does the exception bubble up to the controller or should I handle the exception in the service (thus pushing these exception handling issues into the service layer)?
This seems more difficult than it should be, but as with many things in Spring it's likely I don't understand what's going on. All I want to do is to send an error status and message back in the response!
Thanks,
Paul
It looks like you have the most of the answers in your question itself :)
To reiterate,
Have the controller like this
#RequestMapping("/test")
public String verifyAuth(HttpServletRequest request) throws NotFoundException {
String id = request.getParameter("id");
if (id == null)
throw new NotFoundException("Id not found in the request");
return "success";
}
Declare the exception class in NotFoundException.java,
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Id Not Found")
public class NotFoundException extends Exception {
public NotFoundException(String msg) {
super(msg);
}
}
This exception class need not be every controller class. Declare it as public class and import it in every required controller.
This is one way of doing it. If you like the non-spring style, declare HttpServletResponse in every controller arguments and do
#RequestMapping("/test")
public String verifyAuth(HttpServletRequest request, HttpServletResponse response) {
...
try {
response.sendError(..)
catch(..) {}
}
Or you can use views to show error message,
#RequestMapping("/test")
public String verifyAuth(HttpServletRequest request, Map<String, Object> map){
String id = request.getParameter("id");
if (id == null) {
map.put("status", HttpStatus.NOTFOUND);
map.put("reason", "Id Not Found");
return "error"
}
return "success";
}
Make sure your viewResolver is configured correctly and in the error.jsp to get the error string, you could say.
<body>
${status} ${reason}
</body>
Define error.jsp with nice css for all kind of errors you would expect.
These are not the only ways. With spring you have freedom to do anything. I have seen few ppl rendering json object for error message.
To answer your another question of if the error happens in the service called by the controller is depend on your scenario. For example you are trying to read the user store, if the user store not available error happens, I would handle there itself to read from another replica user store if one available and If I found user does not exist I would leave the exception to the controller to throw.

Resources