Отправка сообщений для конкретного клиента, с помощью SignalR

  • Михаил
  • 8 мин. на прочтение
  • 74
  • 27 Jun 2023
  • 27 Jun 2023

SignalR позволяет отправлять сообщения определенному клиентскому соединению, всем соединениям, связанным с конкретным пользователем, а также именованным группам соединений. Каждый клиент, подключающийся к концентратору SignalR, имеет уникальный идентификатор подключения. Мы можем получить это, используя Context.ConnectionId свойство контекста концентратора. Используя это, мы можем отправлять сообщения только этому конкретному клиенту:

public async Task BroadcastToConnection(string data, string connectionId)    
   => await Clients.Client(connectionId).SendAsync("broadcasttoclient", data);

По умолчанию SignalR использует объект, ClaimTypes.NameIdentifier связанный ClaimsPrincipal с соединением, в качестве идентификатора пользователя. Мы можем отправлять сообщения конкретному пользователю, используя это значение:

public async Task BroadcastToUser(string data, string userId)     
   => await Clients.User(userId).SendAsync("broadcasttouser", data);

Помните, что когда мы отправляем сообщения пользователю, они будут отправлены всем соединениям, связанным с этим пользователем, а не только любому конкретному соединению. Однако для отправки сообщений отдельным пользователям наше приложение должно выполнить аутентификацию пользователей и установить NameIdentifier утверждение в формате ClaimsPrincipal. Только тогда соединения можно будет сопоставить конкретным пользователям.

Группа SignalR — это коллекция подключений, связанных с именем. Мы можем отправлять сообщения всем соединениям в группе, используя имя группы. Группы — это рекомендуемый способ отправки сообщений нескольким соединениям, поскольку в нашем приложении группами легко управлять на основе логики приложения. Соединение может стать членом нескольких групп. Соединения можно добавлять в группы или удалять из них с помощью методов AddToGroupAsync() и RemoveFromGroupAsync() соответственно:

public async Task AddToGroup(string groupName)     
   => await Groups.AddToGroupAsync(Context.ConnectionId, groupName); 
       
public async Task RemoveFromGroup(string groupName)     
   => await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);

Затем мы можем отправлять сообщения группе, используя имя группы:

public async Task BroadcastToGroup(string groupName) => await Clients.Group(groupName)
       .SendAsync("broadcasttogroup", $"{Context.ConnectionId} has joined the group {groupName}.");

Группы — хороший выбор, когда мы хотим реализовать в нашем приложении уведомления, специфичные для определенных групп пользователей, ролей и т. д. 

Реализация сообщений, специфичных для клиента, с помощью SignalR
В предыдущем разделе мы обсудили, как мы можем отправлять сообщения отдельным соединениям, пользователям и группам. Теперь давайте сразу реализуем сообщения, специфичные для клиента, в нашем приложении SignalR. В этой статье мы реализовали сервер ASP.NET Core SignalR, который использует таймер для отправки данных в реальном времени всем подключенным клиентам. Затем мы реализовали диаграмму Angular в клиентском приложении, которая использует эти данные. При нажатии на диаграмму мы отправляем сообщение от клиента на сервер, который, в свою очередь, отправляет сообщение всем подключенным клиентам. Здесь мы собираемся изменить последний шаг таким образом, чтобы, как только мы щелкнем диаграмму Angular в клиентском приложении, она дополнительно отправит connectionIdна сервер, что поможет серверу идентифицировать этого клиента. Затем концентратор SignalR может отправить сообщение обратно только этому клиенту.

Чтобы реализовать это, сначала давайте изменим ChartHub класс в нашем серверном проекте:

public class ChartHub : Hub
{
   public async Task BroadcastChartData(List<ChartModel> data, string connectionId) => 
       await Clients.Client(connectionId).SendAsync("broadcastchartdata", data);
   public string GetConnectionId() => Context.ConnectionId;
}

Мы изменяем BroadcastChartData() метод, чтобы он принимался connectionIdв качестве дополнительного параметра. Таким образом, мы можем найти клиента с помощью connectionIdи отправить сообщение только этому клиенту. 

Дополнительно мы добавляем новый GetConnectionId() метод, который возвращает данные connectionId клиента.

Далее давайте изменим наше приложение Angular. Нам нужно изменить, чтобы SignalRService получить connectionId и передать его при отправке сообщения на сервер:

export class SignalrService {
 public data: ChartModel[];
 public connectionId: string;
 public bradcastedData: ChartModel[];
 private hubConnection: signalR.HubConnection
   public startConnection = () => {
     this.hubConnection = new signalR.HubConnectionBuilder()
                             .withUrl('https://localhost:5001/chart')
                             .build();
     this.hubConnection
       .start()
       .then(() => console.log('Connection started'))
       .then(() => this.getConnectionId())
       .catch(err => console.log('Error while starting connection: ' + err))
   }
   ...
   private getConnectionId = () => {
     this.hubConnection.invoke('getconnectionid')
     .then((data) => {
       console.log(data);
       this.connectionId = data;
     });
   }
   public broadcastChartData = () => {
     const data = this.data.map(m => {
       const temp = {
         data: m.data,
         label: m.label
       }
       return temp;
     });
     this.hubConnection.invoke('broadcastchartdata', data, this.connectionId)
     .catch(err => console.error(err));
   }
   ...
}

В startConnection() методе мы вызываем  getConnectionId() метод, который вызывает наш метод концентратора для возврата файла connectionId. Как только мы получим это значение, мы можем установить его как свойство класса. Позже, когда мы вызываем broadcastchartdata метод концентратора, мы передаем его connectionId, чтобы наш концентратор SignalR мог идентифицировать клиента, использующего его.

Вот и все. Мы реализовали отправку сообщений для конкретного клиента в SignalR.