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());
Related
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
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();
}
}
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.
I am experimenting with the relationship between Elmah and MVC's plumbed in exception handling, and am surprised at the outcome of the following code. This is a brand new, straight from project template MVC application, and I have only added Elmah modules and handlers to the web.config. And of the course the 'throw':
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
throw new Exception("Just for you Elmah!");
return View();
}
Break when error is thrown is set to off, yet the debugger still breaks. When I continue I get a YSOD, and an Elmah error log, but it seems HandleError is doing nothing.
JUST IN
I didn't think I had to have custom errors turned on, as I thought that was only for 'my' unhandled errors. I guess MVC is just as much a client of that service as I am.
So to start ASP.net MVC [HandleError] not catching exceptions and then onto the logging How to get ELMAH to work with ASP.NET MVC [HandleError] attribute?
Check HandleErrorAttribute is added to the GlobalFiltersCollection in the Global.asax.cs
public static void RegisterGlobalFilters(GlobalFiltersCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
I am running the 2.0 RTM of NServiceBus and am getting a NullReferenceException when my MessageModule binds the CurrentSessionContext to my NHibernate sessionfactory.
From within my Application_Start, I call the following method:
public static void WithWeb(IUnityContainer container)
{
log4net.Config.XmlConfigurator.Configure();
var childContainer = container.CreateChildContainer();
childContainer.RegisterInstance<ISessionFactory>(NHibernateSession.SessionFactory);
var bus = NServiceBus.Configure.WithWeb()
.UnityBuilder(childContainer)
.Log4Net()
.XmlSerializer()
.MsmqTransport()
.IsTransactional(true)
.PurgeOnStartup(false)
.UnicastBus()
.ImpersonateSender(false)
.LoadMessageHandlers()
.CreateBus();
var activeBus = bus.Start();
container.RegisterInstance(typeof(IBus), activeBus);
}
When the bus is started, my message module starts with the following:
public void HandleBeginMessage()
{
try
{
CurrentSessionContext.Bind(_sessionFactory.OpenSession());
}
catch (Exception e)
{
_log.Error("Error occurred in HandleBeginMessage of NHibernateMessageModule", e);
throw;
}
}
In looking at my log, we are logging the following error when the bind method is called:
System.NullReferenceException: Object reference not set to an instance of an object.
at NHibernate.Context.WebSessionContext.GetMap()
at NHibernate.Context.MapBasedSessionContext.set_Session(ISession value)
at NHibernate.Context.CurrentSessionContext.Bind(ISession session)
Apparently, there is some issue in getting access to the HttpContext. Should this call to configure NServiceBus occur later in the lifecycle than Application_Start? Or is there another workaround that others have used to get handlers working within an Asp.NET Web application?
Thanks,
Steve
I wouldn't use WebSessionContext in this case, precisely because NServiceBus can operate independently of HttpContexts. If you want to use a single session context implementation for both web and NServiceBus message handling, I'd implement NHibernate.Context.ICurrentSessionContext with an hybrid storage, i.e. if HttpContext.Current != null, use the HttpContext as session storage. Otherwise use a thread local storage. This is similar to what Castle ActiveRecord does with its HybridWebThreadScopeInfo.