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();
}
}
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
Is there any way in ASP.NET Web API to mark an exception as handled in an ExceptionFilterAttribute?
I want to handle the exception at the method level with an exception filter and stop the propagation to a globally registered exception filter.
Filter used on a controller action:
public class MethodExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is NotImplementedException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(context.Exception.Message)
};
// here in MVC you could set context.ExceptionHandled = true;
}
}
}
The globally registered filter:
public class GlobalExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is SomeOtherException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.SomethingElse)
{
Content = new StringContent(context.Exception.Message)
};
}
}
}
Try throwing an HttpResponseException at the end of your local handling. By design, they are not caught by exception filters.
throw new HttpResponseException(context.Response);
Web API 2 is designed with inversion of control in mind. You consider the possibility for the exception to already be handled, rather than interrupting the filter execution after you handle it.
In this sense, attributes deriving from ExceptionFilterAttribute should check if the exception is already handled, which your code already does since is operator returns false for null values. In addition, after you handle the exception, you set context.Exception to null in order to avoid further handling.
To achieve this in your code, you need to replace your comment from MethodExceptionFilterAttribute with context.Exception = null to clear the exception.
It is important to note that it is not a good idea to register more than one global exception filter, due to ordering issues. For information about the execution order of attribute filters in Web API, see the following thread Order of execution with multiple filters in web api.
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.
The default behavior of a [WebMethod] attributed static method on an aspx page is to return the error to the caller. We are accessing these methods using json, and the only way we have found of capturing exceptions is either a try/catch in every webmethod on the site or using a javascript callback with the error (which has the unacceptable downside of exposing the error to the client).
Is there any way to globally handle these exceptions using the HealthMonitoring setup in ASP.NET?
I don't know about health monitoring, but I normally have a generic wrapper that executes the endpoint code inside. This records the real exception but always throws a generic exception across the boundry.
public static T wrapAjaxRequestsToCatchException<T>(Func<T> wrappedDelegate) where T : JsonBase, new()
{
try
{
return wrappedDelegate();
}
catch (Exception ex)
{
var errResponse = new T()
{
Success = false,
Message = getErrorMessage(ex)
};
// Log the exception
ErrorLog.LogAjaxEvent(string.Format("AJAX EXCEPTION : {0}", ex.ToString()), System.Diagnostics.EventLogEntryType.Error);
return errResponse;
}
}
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.