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. Это была "проблема" в документации, поэтому то, что я пытался сделать, наверняка невозможно (однако там не упоминается, как это сделать).
Кажется, что экземпляр класса-концентратора в целом живет только тогда, когда был сделан вызов, и поэтому вы не можете использовать обратные вызовы.