SignalR, Owin и обработка исключений

Я разработал образец приложения SignalR на основе ASP.NET 4.5 и Owin и разместил это приложение на IIS 7.5.

Все работает нормально, но как я могу обрабатывать исключения в Оуине?

Рассмотрим следующий код:

[HubName("SampleHub")]
public class SampleHub : Hub
{
    public SampleHub()
    {
        throw new InvalidOperationException("?!");
    }
}

Это исключение не вызовет Application_Error (и это моя проблема).

Где я могу получить все исключения от Owin для ведения журнала и отладки, аналогично Application_Error?

Мне не интересно что-то вроде этого:

app.UseErrorPage(new ErrorPageOptions()
{
    ShowCookies = true,
    ShowEnvironment = true,
    ShowExceptionDetails = true,
    ShowHeaders = true,
    ShowQuery = true,
    ShowSourceCode = true
});

Это совершенно бесполезно для сложных сценариев, таких как ASP.NET Web API и ASP.NET MVC.

Фильтры действий с методом OnException для целей переопределения намного лучше.

Ответ 1

Если вам нужна обработка исключений специально для концентраторов SignalR, промежуточное ПО OWIN не подходит.

Чтобы проиллюстрировать только одну причину, предположим, что SignalR использует свой транспорт WebSocket, когда исключение выбрасывается из метода Hub. В этом случае SignalR не закрывает соединение WebSocket. Вместо этого SignalR будет записывать JSON-кодированное сообщение непосредственно в сокет, чтобы указать клиенту, что было создано исключение. Нет простого способа использования промежуточного программного обеспечения OWIN для запуска какого-либо события, когда это происходит за пределами возможной упаковки всего OWIN WebSocket Extension, который я бы сильно посоветуйте.

К счастью, SignalR предоставляет свой собственный Hub Pipeline, который идеально подходит для вашего сценария.

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);
    }
}

Вы можете использовать ExceptionContext для более чем простого ведения журнала. Например, вы можете установить ExceptionContext.Error в другое исключение, которое изменит исключение, которое получает клиент.

Вы даже можете исключить исключение, установив для параметра ExceptionContext.Error значение null или установив ExceptonContext.Result. Если вы сделаете это, клиенту появится, что метод Hub вернул значение, найденное в ExceptonContext.Result, вместо того, чтобы метать.

A, а назад написал другой ответ SO о том, как вы можете вызвать обратный вызов одного клиента для каждого исключения, созданного методом Hub: Регистрация журнала SignalR?

Существует также документация MSDN для HubPipelineModules: http://msdn.microsoft.com/en-us/library/microsoft.aspnet.signalr.hubs.hubpipelinemodule(v=vs.118).aspx

Ответ 2

Ответ @halter73 отлично подходит для ошибок, генерируемых внутри хабов, но он не улавливает ошибки, возникающие при их создании.

Я получил исключение:

System.InvalidOperationException: концентратор 'foobarhub' не может быть разрешен.

Сервер возвращал HTML-страницу для этого исключения, но мне нужна была она в формате JSON для лучшей интеграции с моим приложением Angular, поэтому на основе этого ответа я реализовал OwinMiddleware для перехвата исключений и изменения формата вывода. Вы можете использовать это для регистрации ошибок вместо этого.

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));
        }
    }

}

Добавьте регистрацию в OwinStartup.cs, просто не забудьте разместить ее перед MapSignalR метода MapSignalR:

public class OwinStartup
{
    public void Configuration(IAppBuilder app)
    {
        app.Use<GlobalExceptionMiddleware>(); // must come before MapSignalR()

        app.MapSignalR();
    }
}