SignalR обычно используется в настройках, где есть несколько клиентов и один сервер, к которому подключаются клиенты. Это делает обычным для клиентов вызов сервера и получение результатов обратно. Поскольку сервер обычно не заботится о том, какие отдельные клиенты подключены, и поскольку сервер обычно осуществляет широковещательную передачу для набора клиентов (например, с использованием группы), направление связи в основном используется для уведомлений или широковещательных сообщений. Сообщения с одной целью возможны, но нет встроенного механизма для шаблона запроса / ответа.
Чтобы сделать эту работу с SignalR, вам потребуется способ для клиента, чтобы перезвонить серверу. Поэтому вам понадобится действие концентратора для отправки ответа.
Это само по себе не затрудняет, но может потребоваться связать вызов клиента с входящим сообщением результата, полученным хаб. Для этого вам нужно будет что-то построить.
Вот пример реализации, которая поможет вам начать. MyRequestClient
- это одноэлементная служба, которая в основном инкапсулирует сообщения и предлагает вам асинхронный метод, который будет вызывать клиента и завершаться только после того, как клиент ответит, вызвав метод обратного вызова в концентраторе:
public class MyRequestClient
{
private readonly IHubContext<MyHub> _hubContext;
private ConcurrentDictionary<Guid, object> _pendingTasks = new ConcurrentDictionary<Guid, object>();
public MyRequestClient(IHubContext<MyHub> hubContext)
{
_hubContext = hubContext;
}
public async Task<int> Square(string connectionId, int number)
{
var requestId = Guid.NewGuid();
var source = new TaskCompletionSource<int>();
_pendingTasks[requestId] = source;
await _hubContext.Clients.Client(connectionId).SendAsync("Square", nameof(MyHub.SquareCallback), requestId, number);
return await source.Task;
}
public void SquareCallback(Guid requestId, int result)
{
if (_pendingTasks.TryRemove(requestId, out var obj) && obj is TaskCompletionSource<int> source)
source.SetResult(result);
}
}
In концентратор, тогда вам нужно действие обратного вызова для вызова клиента запроса для завершения задачи:
public class MyHub : Hub
{
private readonly ILogger<MyHub> _logger;
private readonly MyRequestClient _requestClient;
public MyHub(ILogger<MyHub> logger, MyRequestClient requestClient)
{
_logger = logger;
_requestClient = requestClient;
}
public Task SquareCallback(Guid requestId, int number)
{
_requestClient.SquareCallback(requestId, number);
return Task.CompletedTask;
}
// just for demo purposes
public Task Start()
{
var connectionId = Context.ConnectionId;
_ = Task.Run(async () =>
{
var number = 42;
_logger.LogInformation("Starting Square: {Number}", number);
var result = await _requestClient.Square(connectionId, number);
_logger.LogInformation("Square returned: {Result}", result);
});
return Task.CompletedTask;
}
}
Действие концентратора Start
предназначено только для демонстрационных целей, чтобы иметь возможность запустить его с действительным соединением id.
На клиенте вам необходимо реализовать метод client и заставить его вызвать указанный метод обратного вызова, как только это будет сделано:
connection.on('Square', (callbackMethod, requestId, number) => {
const result = number * number;
connection.invoke(callbackMethod, requestId, result);
});
Наконец, вы можете попробовать это, вызвав Start
действие клиента:
connection.invoke('Start');
Конечно, эта реализация очень проста c и потребует нескольких вещей, таких как правильная обработка ошибок и поддержка тайм-аута задач, если клиент этого не сделал. не отвечает должным образом. Также было бы возможно расширить это для поддержки произвольных вызовов, без необходимости создавать все эти методы вручную (например, с помощью одного метода обратного вызова в концентраторе, который может выполнить любую задачу).