ASP.NET Core SignalR Clients объект избавился от исключения при вызове из обратного вызова

У меня есть следующая реализация концентратора в новом и чистом веб-приложении ASP.NET Core 2.1. Я только что обновил до последней версии ASP.NET Core 2.1 и до последней версии Visual Studio 2017.

Класс работал и при запуске из отладчика подключается один клиент. Я могу видеть это с помощью отладчика, и я могу видеть это в клиенте, потому что я регистрирую "userobject", который я посылаю после того, как он подключился. Клиент остается на связи.

На втором этапе я внедрил IPbxConnection в концентратор, который также работает (я вижу действительный объект с помощью отладчика). Реализация IPbxConnection вызовет обработчик OnUserUpdated через 5 секунд (я просто делаю это с обратным вызовом по таймеру, который теперь находится в реализации IPbxConnection для тестирования). Это всегда приводит к исключению удаления объекта, выданному объекту Clients. Как я могу отправить уведомление всем клиентам в этом обратном вызове? Кажется, объект Clients не сохраняет свое состояние и действителен только во время обработки сообщений... однако я хочу постоянно передавать информацию клиенту.

public class PresenceHub : Hub
{
    //Members
    private IPbxConnection _connection; 

    /// <summary>
    /// Constructor, pbx connection must be provided by dependency injection
    /// </summary>        
    public PresenceHub(IPbxConnection connection)
    {
        _connection = connection;
        _connection.OnUserUpdated((e) =>
        {                
            Clients.All.SendAsync("UpdateUser", "updateuserobject");
        });
        _connection.Connect();
    }

    /// <summary>
    /// Called whenever a user is connected to the hub. We will send him all the user information
    /// </summary>
    public override async Task OnConnectedAsync()
    {            
        await base.OnConnectedAsync();
        await Clients.Caller.SendAsync("AddUser", "userobject");
    }
}

4 ответа

Как описано в этом выпуске на Github, https://github.com/aspnet/SignalR/issues/2424 Концентраторы являются недолговечными по своей конструкции и располагаются после каждого вызова.

Единственный способ, с помощью которого я нашел доступ к вашим Клиентам за пределами текущего объема запросов на вашем концентраторе, - это внедрение HubContext в отдельный класс.

В вашем случае трансляция для всех клиентов будет выглядеть примерно так

public class HubEventEmitter
{
    private IHubContext<PresenceHub> _hubContext;

    public HubEventEmitter(IPbxConnection connection, IHubContext<PresenceHub> hubContext)
    {
        _hubContext = hubContext;
        _connection.OnUserUpdated((e) =>
        {
            _hubContext.Clients.All.SendAsync("UpdateUser", "updateuserobject");
        });
    }
}

если вы хотите уведомить только определенных Клиентов, вам нужно будет получить идентификатор соединения из контекста и использовать его следующим образом

_hubContext.Clients.Client(connectionId).SendAsync("UpdateUser", "updateuserobject");

Это простое решение работает для меня. Я не добавил ничего лишнего к стартовому классу.

РЕДАКТИРОВАТЬ После некоторого размышления я решил, что, хотя этот код работает, он не является хорошим шаблоном, не в последнюю очередь потому, что статический код в конечном итоге пытается использовать поля внутри удаленного объекта. Концентратор - это легкий, недолговечный контейнер, который следует использовать как таковой. Поэтому я перемещаю свой длительный процесс из статических элементов концентратора в шаблон IHostedService

Мой хаб содержит длительный асинхронный процесс, определенный в статическом члене. Поскольку концентраторы являются временными, в некоторых случаях концентратор удаляется, когда асинхронный процесс пытается отправить сообщения. Я добавил хаб-контекст для Инъекции в Хаб-конструктор

public class IisLogFileHub : Hub
{
    IHubContext<IisLogFileHub> _hubContext = null;

    public IisLogFileHub(IHubContext<IisLogFileHub> hubContext)
    {
        _hubContext = hubContext;
    }
}

В любой момент длительного процесса сообщения можно отправлять следующим образом.

await _hubContext.Clients.All.SendAsync("ReceiveMessage", msg);

Я столкнулся с той же проблемой некоторое время назад. Я тоже думал, что хаб был местом, куда нужно звонить, когда бэкэнд C# должен отправить сообщение, но на самом деле это не так. Концентратор больше похож на точку крепления. Ваш бэкэнд C# должен иметь свой собственный клиент, который также подключается к концентратору. Итак, ваш хаб выглядит так:

public class PresenceHub : Hub
{
    public async Task Send(Userobject userobject)
    {
        await Clients.Others.SendAsync("AddUser", userobject);
    }


    public override async Task OnConnectedAsync()
    {            
        await base.OnConnectedAsync();
        await Clients.Caller.SendAsync("AddUser", "userobject");
    }
}

и чем пользовательский класс / кусок кода, который вызывает концентратор, как это

public class MyCustomCode{
    public PresenceHub(IPbxConnection connection)
    {
        hubConnection = new HubConnectionBuilder().WithUrl("localhost\mysignalrhub").Build();
        hubConnection.StartAsync().Wait();

        _connection = connection;
        _connection.OnUserUpdated((e) =>
        {                
            hubConnection.InvokeAsync("Send", e).Wait();
        });
        _connection.Connect();
    }

}

Некоторая документация: https://docs.microsoft.com/en-us/aspnet/core/signalr/dotnet-client?view=aspnetcore-2.1

Эта проблема косвенно дается на github https://github.com/aspnet/Docs/issues/6888. Это была "проблема" в документации, поэтому то, что я пытался сделать, наверняка невозможно (однако там не упоминается, как это сделать).

Кажется, что экземпляр класса-концентратора в целом живет только тогда, когда был сделан вызов, и поэтому вы не можете использовать обратные вызовы.

Другие вопросы по тегам