14

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.

Marcos Dimitrio
  • 6,651
  • 5
  • 38
  • 62
Yaser Moradi
  • 3,267
  • 3
  • 24
  • 50

2 Answers2

24

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

Community
  • 1
  • 1
halter73
  • 15,059
  • 3
  • 49
  • 60
  • 2
    Thanks a lot for your good solution. But this module can not log exceptions such as 'Exceptions of hub descriptor provider' & 'hub activator' parts of SignalR The signalR module will not log constructor exceptions of hubs. Your module is useful, and I'm going to use that as a SignalR logger. But creation and startup process of SignalR is performed by 'Owin' itself. Where can I get those exceptions. Temporarily , I've implemented all extensibility points of SignalR, such as IHubDescriptorProvider, IHubActivator and so on to make logging possible, which is clearly wrong in my opinion. Any Idea? – Yaser Moradi Feb 16 '14 at 05:07
  • @halter73 it doesn't work for exceptions which are thrown in other HubPipeLineModules methods or other methods of current module. – Mahmoud Moravej Oct 21 '14 at 08:03
  • 1
    Thanks for the answer. Still I can not get `OnIncomingError` called when I throw exception in my hub method called by a client. It is registered via `GlobalHost.HubPipeline.AddModule()`. Why my `HubPipelineModule` is not get called? – Artyom Sep 23 '16 at 10:06
0

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();
    }
}
Marcos Dimitrio
  • 6,651
  • 5
  • 38
  • 62