Arbeiten mit Gruppen in SignalR
von Patrick Fletcher, Tom FitzMacken
Warnung
Diese Dokumentation gilt nicht für die neueste Version von SignalR. Sehen Sie sich ASP.NET Core SignalR an.
In diesem Thema wird beschrieben, wie Benutzer zu Gruppen hinzugefügt und Informationen zur Gruppenmitgliedschaft beibehalten werden.
In diesem Thema verwendete Softwareversionen
- Visual Studio 2013
- .NET 4.5
- SignalR, Version 2
Frühere Versionen dieses Themas
Informationen zu früheren Versionen von SignalR finden Sie unter Ältere Versionen von SignalR.
Fragen und Kommentare
Bitte hinterlassen Sie Feedback dazu, wie Ihnen dieses Tutorial gefallen hat und was wir in den Kommentaren unten auf der Seite verbessern könnten. Wenn Sie Fragen haben, die nicht direkt mit dem Tutorial zusammenhängen, können Sie diese im ASP.NET SignalR-Forum oder StackOverflow.com posten.
Überblick
Gruppen in SignalR bieten eine Methode zum Senden von Nachrichten an angegebene Teilmengen verbundener Clients. Eine Gruppe kann eine beliebige Anzahl von Clients aufweisen, und ein Client kann Mitglied einer beliebigen Anzahl von Gruppen sein. Gruppen müssen nicht explizit erstellt werden. Tatsächlich wird eine Gruppe automatisch erstellt, wenn Sie ihren Namen zum ersten Mal in einem Aufruf von Groups.Add angeben, und sie wird gelöscht, wenn Sie die letzte Verbindung aus der Mitgliedschaft entfernen. Eine Einführung in die Verwendung von Gruppen finden Sie unter Verwalten der Gruppenmitgliedschaft über die Hub-Klasse in der Hubs-API – Serverhandbuch.
Es gibt keine API zum Abrufen einer Gruppenmitgliedschaftsliste oder einer Liste von Gruppen. SignalR sendet Nachrichten an Clients und Gruppen basierend auf einem Pub-/Untermodell, und der Server verwaltet keine Listen mit Gruppen oder Gruppenmitgliedschaften. Dies trägt zur Maximierung der Skalierbarkeit bei, denn wenn Sie einer Webfarm einen Knoten hinzufügen, muss jeder Zustand, den SignalR verwaltet, an den neuen Knoten weitergegeben werden.
Wenn Sie einer Gruppe mithilfe der Groups.Add
-Methode einen Benutzer hinzufügen, empfängt der Benutzer nachrichten, die für die Dauer der aktuellen Verbindung an diese Gruppe gerichtet sind, aber die Mitgliedschaft des Benutzers in dieser Gruppe wird nicht über die aktuelle Verbindung hinaus beibehalten. Wenn Sie Informationen zu Gruppen und Gruppenmitgliedschaften dauerhaft speichern möchten, müssen Sie diese Daten in einem Repository wie einer Datenbank oder einem Azure-Tabellenspeicher speichern. Jedes Mal, wenn ein Benutzer eine Verbindung mit Ihrer Anwendung herstellt, rufen Sie dann aus dem Repository ab, zu dem der Benutzer gehört, und fügen diesen Benutzer manuell zu diesen Gruppen hinzu.
Wenn die Verbindung nach einer vorübergehenden Unterbrechung wiederhergestellt wird, verknüpft der Benutzer die zuvor zugewiesenen Gruppen automatisch erneut. Das automatische Erneute Beitreten einer Gruppe gilt nur, wenn die Verbindung wiederhergestellt wird, nicht beim Herstellen einer neuen Verbindung. Ein digital signiertes Token wird vom Client übergeben, der die Liste der zuvor zugewiesenen Gruppen enthält. Wenn Sie überprüfen möchten, ob der Benutzer zu den angeforderten Gruppen gehört, können Sie das Standardverhalten überschreiben.
Dieses Thema enthält die folgenden Abschnitte:
- Hinzufügen und Entfernen von Benutzern
- Aufrufen von Mitgliedern einer Gruppe
- Speichern der Gruppenmitgliedschaft in einer Datenbank
- Speichern der Gruppenmitgliedschaft im Azure-Tabellenspeicher
- Überprüfen der Gruppenmitgliedschaft beim Erneuten Herstellen der Verbindung
Hinzufügen und Entfernen von Benutzern
Zum Hinzufügen oder Entfernen von Benutzern aus einer Gruppe rufen Sie die Add- oder Remove-Methoden auf und übergeben die Verbindungs-ID des Benutzers und den Namen der Gruppe als Parameter. Sie müssen einen Benutzer nicht manuell aus einer Gruppe entfernen, wenn die Verbindung beendet wird.
Das folgende Beispiel zeigt die methoden undGroups.Remove
, die Groups.Add
in Hub-Methoden verwendet werden.
public class ContosoChatHub : Hub
{
public Task JoinRoom(string roomName)
{
return Groups.Add(Context.ConnectionId, roomName);
}
public Task LeaveRoom(string roomName)
{
return Groups.Remove(Context.ConnectionId, roomName);
}
}
Die Groups.Add
Methoden und Groups.Remove
werden asynchron ausgeführt.
Wenn Sie einer Gruppe einen Client hinzufügen und mithilfe der Gruppe sofort eine Nachricht an den Client senden möchten, müssen Sie sicherstellen, dass die Groups.Add-Methode zuerst abgeschlossen wird. Die folgenden Codebeispiele zeigen, wie dies zu tun ist.
public async Task JoinRoom(string roomName)
{
await Groups.Add(Context.ConnectionId, roomName);
Clients.Group(roomName).addChatMessage(Context.User.Identity.Name + " joined.");
}
Im Allgemeinen sollten Sie die Groups.Remove
Methode nicht einschließenawait
, da die Verbindungs-ID, die Sie entfernen möchten, möglicherweise nicht mehr verfügbar ist. In diesem Fall wird ausgelöst, TaskCanceledException
nachdem das Anforderungs-Zeitüberschreitungsout aufgetreten ist. Wenn Ihre Anwendung sicherstellen muss, dass der Benutzer aus der Gruppe entfernt wurde, bevor sie eine Nachricht an die Gruppe sendet, können Sie vor Groups.Remove
hinzufügen await
und dann die Ausnahme abfangen, die TaskCanceledException
möglicherweise ausgelöst wird.
Aufrufen von Mitgliedern einer Gruppe
Sie können Nachrichten an alle Mitglieder einer Gruppe oder nur an bestimmte Mitglieder der Gruppe senden, wie in den folgenden Beispielen gezeigt.
Alle verbundenen Clients in einer angegebenen Gruppe.
Clients.Group(groupName).addChatMessage(name, message);
Alle verbundenen Clients in einer angegebenen Gruppe mit Ausnahme der angegebenen Clients, die durch die Verbindungs-ID identifiziert werden.
Clients.Group(groupName, connectionId1, connectionId2).addChatMessage(name, message);
Alle verbundenen Clients in einer angegebenen Gruppe mit Ausnahme des aufrufenden Clients.
Clients.OthersInGroup(groupName).addChatMessage(name, message);
Speichern der Gruppenmitgliedschaft in einer Datenbank
Die folgenden Beispiele zeigen, wie Gruppen- und Benutzerinformationen in einer Datenbank beibehalten werden. Sie können jede Datenzugriffstechnologie verwenden. Das folgende Beispiel zeigt jedoch, wie Modelle mithilfe von Entity Framework definiert werden. Diese Entitätsmodelle entsprechen Datenbanktabellen und -feldern. Ihre Datenstruktur kann je nach Den Anforderungen Ihrer Anwendung erheblich variieren. Dieses Beispiel enthält eine Klasse namens ConversationRoom
, die für eine Anwendung eindeutig ist, die es Benutzern ermöglicht, An Unterhaltungen zu verschiedenen Themen wie Sport oder Gartenarbeit teilzunehmen. Dieses Beispiel enthält auch eine Klasse für die Verbindungen. Die Verbindungsklasse ist für die Nachverfolgung der Gruppenmitgliedschaft nicht unbedingt erforderlich, ist aber häufig Teil einer robusten Lösung zum Nachverfolgen von Benutzern.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
namespace GroupsExample
{
public class UserContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Connection> Connections { get; set; }
public DbSet<ConversationRoom> Rooms { get; set; }
}
public class User
{
[Key]
public string UserName { get; set; }
public ICollection<Connection> Connections { get; set; }
public virtual ICollection<ConversationRoom> Rooms { get; set; }
}
public class Connection
{
public string ConnectionID { get; set; }
public string UserAgent { get; set; }
public bool Connected { get; set; }
}
public class ConversationRoom
{
[Key]
public string RoomName { get; set; }
public virtual ICollection<User> Users { get; set; }
}
}
Anschließend können Sie im Hub die Gruppen- und Benutzerinformationen aus der Datenbank abrufen und den Benutzer manuell den entsprechenden Gruppen hinzufügen. Das Beispiel enthält keinen Code zum Nachverfolgen der Benutzerverbindungen. In diesem Beispiel wird die await
Schlüsselwort (keyword) nicht zuvor Groups.Add
angewendet, da eine Nachricht nicht sofort an Mitglieder der Gruppe gesendet wird. Wenn Sie unmittelbar nach dem Hinzufügen des neuen Elements eine Nachricht an alle Mitglieder der Gruppe senden möchten, sollten Sie die await
Schlüsselwort (keyword) anwenden, um sicherzustellen, dass der asynchrone Vorgang abgeschlossen ist.
using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
namespace GroupsExample
{
[Authorize]
public class ChatHub : Hub
{
public override Task OnConnected()
{
using (var db = new UserContext())
{
// Retrieve user.
var user = db.Users
.Include(u => u.Rooms)
.SingleOrDefault(u => u.UserName == Context.User.Identity.Name);
// If user does not exist in database, must add.
if (user == null)
{
user = new User()
{
UserName = Context.User.Identity.Name
};
db.Users.Add(user);
db.SaveChanges();
}
else
{
// Add to each assigned group.
foreach (var item in user.Rooms)
{
Groups.Add(Context.ConnectionId, item.RoomName);
}
}
}
return base.OnConnected();
}
public void AddToRoom(string roomName)
{
using (var db = new UserContext())
{
// Retrieve room.
var room = db.Rooms.Find(roomName);
if (room != null)
{
var user = new User() { UserName = Context.User.Identity.Name};
db.Users.Attach(user);
room.Users.Add(user);
db.SaveChanges();
Groups.Add(Context.ConnectionId, roomName);
}
}
}
public void RemoveFromRoom(string roomName)
{
using (var db = new UserContext())
{
// Retrieve room.
var room = db.Rooms.Find(roomName);
if (room != null)
{
var user = new User() { UserName = Context.User.Identity.Name };
db.Users.Attach(user);
room.Users.Remove(user);
db.SaveChanges();
Groups.Remove(Context.ConnectionId, roomName);
}
}
}
}
}
Speichern der Gruppenmitgliedschaft im Azure-Tabellenspeicher
Die Verwendung von Azure-Tabellenspeicher zum Speichern von Gruppen- und Benutzerinformationen ähnelt der Verwendung einer Datenbank. Das folgende Beispiel zeigt eine Tabellenentität, in der der Benutzername und der Gruppenname gespeichert werden.
using Microsoft.WindowsAzure.Storage.Table;
using System;
namespace GroupsExample
{
public class UserGroupEntity : TableEntity
{
public UserGroupEntity() { }
public UserGroupEntity(string userName, string groupName)
{
this.PartitionKey = userName;
this.RowKey = groupName;
}
}
}
Im Hub rufen Sie die zugewiesenen Gruppen ab, wenn der Benutzer eine Verbindung herstellt.
using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.Storage.Table;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure;
namespace GroupsExample
{
[Authorize]
public class ChatHub : Hub
{
public override Task OnConnected()
{
string userName = Context.User.Identity.Name;
var table = GetRoomTable();
table.CreateIfNotExists();
var query = new TableQuery<UserGroupEntity>()
.Where(TableQuery.GenerateFilterCondition(
"PartitionKey", QueryComparisons.Equal, userName));
foreach (var entity in table.ExecuteQuery(query))
{
Groups.Add(Context.ConnectionId, entity.RowKey);
}
return base.OnConnected();
}
public Task AddToRoom(string roomName)
{
string userName = Context.User.Identity.Name;
var table = GetRoomTable();
var insertOperation = TableOperation.InsertOrReplace(
new UserGroupEntity(userName, roomName));
table.Execute(insertOperation);
return Groups.Add(Context.ConnectionId, roomName);
}
public Task RemoveFromRoom(string roomName)
{
string userName = Context.User.Identity.Name;
var table = GetRoomTable();
var retrieveOperation = TableOperation.Retrieve<UserGroupEntity>(
userName, roomName);
var retrievedResult = table.Execute(retrieveOperation);
var deleteEntity = (UserGroupEntity)retrievedResult.Result;
if (deleteEntity != null)
{
var deleteOperation = TableOperation.Delete(deleteEntity);
table.Execute(deleteOperation);
}
return Groups.Remove(Context.ConnectionId, roomName);
}
private CloudTable GetRoomTable()
{
var storageAccount =
CloudStorageAccount.Parse(
CloudConfigurationManager.GetSetting("StorageConnectionString"));
var tableClient = storageAccount.CreateCloudTableClient();
return tableClient.GetTableReference("room");
}
}
}
Überprüfen der Gruppenmitgliedschaft beim Erneuten Herstellen der Verbindung
Standardmäßig weist SignalR einen Benutzer automatisch den entsprechenden Gruppen zu, wenn die Verbindung von einer vorübergehenden Unterbrechung wiederhergestellt wird, z. B. wenn eine Verbindung abgebrochen und wieder hergestellt wird, bevor die Verbindung zu einem Timeout führt. Die Gruppeninformationen des Benutzers werden in einem Token übergeben, wenn die Verbindung wiederhergestellt wird, und dieses Token wird auf dem Server überprüft. Informationen zum Überprüfungsprozess für die Erneute Einbindung von Benutzern in Gruppen finden Sie unter Erneutes Beitreten von Gruppen bei erneuter Verbindung.
Im Allgemeinen sollten Sie das Standardverhalten des automatischen Erneuten Beitretens von Gruppen verwenden, wenn die Verbindung wiederhergestellt wird. SignalR-Gruppen sind nicht als Sicherheitsmechanismus zum Einschränken des Zugriffs auf vertrauliche Daten vorgesehen. Wenn Ihre Anwendung jedoch die Gruppenmitgliedschaft eines Benutzers beim erneuten Herstellen der Verbindung überprüfen muss, können Sie das Standardverhalten überschreiben. Das Ändern des Standardverhaltens kann ihre Datenbank belasten, da die Gruppenmitgliedschaft eines Benutzers für jede erneute Verbindung abgerufen werden muss und nicht nur, wenn der Benutzer eine Verbindung herstellt.
Wenn Sie die Gruppenmitgliedschaft bei der erneuten Verbindung überprüfen müssen, erstellen Sie ein neues Hubpipelinemodul, das eine Liste der zugewiesenen Gruppen zurückgibt, wie unten gezeigt.
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace GroupsExample
{
public class RejoingGroupPipelineModule : HubPipelineModule
{
public override Func<HubDescriptor, IRequest, IList<string>, IList<string>>
BuildRejoiningGroups(Func<HubDescriptor, IRequest, IList<string>, IList<string>>
rejoiningGroups)
{
rejoiningGroups = (hb, r, l) =>
{
List<string> assignedRooms = new List<string>();
using (var db = new UserContext())
{
var user = db.Users.Include(u => u.Rooms)
.Single(u => u.UserName == r.User.Identity.Name);
foreach (var item in user.Rooms)
{
assignedRooms.Add(item.RoomName);
}
}
return assignedRooms;
};
return rejoiningGroups;
}
}
}
Fügen Sie dann dieses Modul der Hubpipeline hinzu, wie unten hervorgehoben.
public partial class Startup {
public void Configuration(IAppBuilder app) {
app.MapSignalR();
GlobalHost.HubPipeline.AddModule(new RejoingGroupPipelineModule());
}
}