Práce se skupinami v knihovně SignalR

Patrick Fletcher, Tom FitzMacken

Upozornění

Tato dokumentace není určená pro nejnovější verzi služby SignalR. Podívejte se na ASP.NET Core SignalR.

Toto téma popisuje, jak přidat uživatele do skupin a zachovat informace o členství ve skupinách.

Verze softwaru použité v tomto tématu

Předchozí verze tohoto tématu

Informace o starších verzích služby SignalR najdete v tématu Starší verze služby SignalR.

Dotazy a komentáře

V komentářích v dolní části stránky nám napište, jak se vám tento kurz líbil a co bychom mohli zlepšit. Pokud máte dotazy, které nesouvisejí přímo s kurzem, můžete je publikovat na fóru ASP.NET SignalR nebo StackOverflow.com.

Přehled

Skupiny ve službě SignalR poskytují metodu pro vysílání zpráv určeným podmnožinám připojených klientů. Skupina může mít libovolný počet klientů a klient může být členem libovolného počtu skupin. Skupiny nemusíte explicitně vytvářet. V důsledku toho se skupina automaticky vytvoří při prvním zadání jejího názvu ve volání skupiny.Add a odstraní se, když odeberete poslední připojení z členství v této skupině. Úvod do používání skupin najdete v tématu Správa členství ve skupinách z třídy Hub v příručce k rozhraní API služby Hubs – server.

Neexistuje žádné rozhraní API pro získání seznamu členství ve skupinách ani seznamu skupin. SignalR odesílá zprávy klientům a skupinám na základě modelu pub/sub a server neudržuje seznamy skupin ani členství ve skupinách. To pomáhá maximalizovat škálovatelnost, protože pokaždé, když přidáte uzel do webové farmy, musí se jakýkoli stav, který SignalR udržuje, rozšířit do nového uzlu.

Když přidáte uživatele do skupiny pomocí Groups.Add metody , uživatel obdrží zprávy směrované na tuto skupinu po dobu trvání aktuálního připojení, ale členství uživatele v této skupině není trvalé mimo aktuální připojení. Pokud chcete trvale zachovat informace o skupinách a členství ve skupinách, musíte tato data uložit v úložišti, jako je databáze nebo úložiště tabulek Azure. Pokaždé, když se pak uživatel připojí k vaší aplikaci, načtete z úložiště, do kterých skupin uživatel patří, a ručně přidáte tohoto uživatele do těchto skupin.

Při opětovném připojení po dočasném přerušení se uživatel automaticky znovu připojí k dříve přiřazeným skupinám. Automatické opětovné připojení ke skupině platí jenom při opětovném připojení, ne při navazování nového připojení. Z klienta se předává digitálně podepsaný token, který obsahuje seznam dříve přiřazených skupin. Pokud chcete ověřit, jestli uživatel patří do požadovaných skupin, můžete výchozí chování přepsat.

Toto téma zahrnuje následující části:

Přidávání a odebírání uživatelů

Pokud chcete přidat nebo odebrat uživatele ze skupiny, zavolejte metody Add nebo Remove a jako parametry předáte ID připojení uživatele a název skupiny. Po ukončení připojení není nutné uživatele ze skupiny ručně odebírat.

Následující příklad ukazuje Groups.Add metody a Groups.Remove použité v metodách centra.

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);
    }
}

Metody a Groups.Remove se Groups.Add provádějí asynchronně.

Pokud chcete přidat klienta do skupiny a okamžitě odeslat zprávu klientovi pomocí skupiny, musíte se ujistit, že Groups.Add metoda dokončí nejprve. Následující příklady kódu ukazují, jak to udělat.

public async Task JoinRoom(string roomName)
{
    await Groups.Add(Context.ConnectionId, roomName);
    Clients.Group(roomName).addChatMessage(Context.User.Identity.Name + " joined.");
}

Obecně platí, že byste při volání Groups.Remove metody neměli zahrnovatawait, protože ID připojení, které se pokoušíte odebrat, už nemusí být k dispozici. V takovém případě TaskCanceledException se vyvolá po vypršení časového limitu požadavku. Pokud vaše aplikace musí před odesláním zprávy skupině zajistit, aby byl uživatel odebrán ze skupiny, můžete přidat await před Groups.Removea pak zachytit TaskCanceledException výjimku, která může být vyvolána.

Volání členů skupiny

Zprávy můžete posílat všem členům skupiny nebo jenom určeným členům skupiny, jak je znázorněno v následujících příkladech.

  • Všichni připojení klienti v zadané skupině.

    Clients.Group(groupName).addChatMessage(name, message);
    
  • Všichni připojení klienti v zadané skupině s výjimkou zadaných klientů, kteří jsou identifikovaní ID připojení.

    Clients.Group(groupName, connectionId1, connectionId2).addChatMessage(name, message);
    
  • Všichni připojení klienti v zadané skupině s výjimkou volajícího klienta.

    Clients.OthersInGroup(groupName).addChatMessage(name, message);
    

Ukládání členství ve skupinách v databázi

Následující příklady ukazují, jak zachovat informace o skupinách a uživatelích v databázi. Můžete použít jakoukoli technologii přístupu k datům; Následující příklad však ukazuje, jak definovat modely pomocí Entity Frameworku. Tyto modely entit odpovídají databázovým tabulkám a polím. Vaše datová struktura se může výrazně lišit v závislosti na požadavcích vaší aplikace. Tento příklad obsahuje třídu s názvem ConversationRoom , která by byla jedinečná pro aplikaci, která umožňuje uživatelům připojit se ke konverzacím o různých tématech, jako je sport nebo zahradaření. Tento příklad obsahuje také třídu pro připojení. Třída připojení není pro sledování členství ve skupině nezbytně nutná, ale je často součástí robustního řešení pro sledování uživatelů.

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; }
    }
}

Pak v centru můžete načíst informace o skupině a uživateli z databáze a ručně přidat uživatele do příslušných skupin. Příklad neobsahuje kód pro sledování připojení uživatelů. V tomto příkladu await není klíčové slovo použito dříve Groups.Add , protože zpráva není okamžitě odeslána členům skupiny. Pokud chcete odeslat zprávu všem členům skupiny ihned po přidání nového člena, měli byste použít await klíčové slovo k zajištění dokončení asynchronní operace.

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);
                }
            }
        }
    }
}

Ukládání členství ve skupinách ve službě Azure Table Storage

Použití služby Azure Table Storage k ukládání informací o skupinách a uživatelech je podobné jako použití databáze. Následující příklad ukazuje entitu tabulky, která ukládá uživatelské jméno a název skupiny.

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;
        }
    }
}

V centru načtete přiřazené skupiny, když se uživatel připojí.

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");
        }
    }
}

Ověření členství ve skupině při opětovném připojení

Služba SignalR ve výchozím nastavení automaticky znovu přiřadí uživatele k příslušným skupinám při opětovném připojení z dočasného přerušení, například když dojde k ukončení a opětovnému navázání připojení před vypršením časového limitu připojení. Informace o skupině uživatele se předávají v tokenu při opětovném připojení a tento token se ověří na serveru. Informace o procesu ověření pro opětovné připojení uživatelů ke skupinám najdete v tématu Opětovné připojení ke skupinám při opětovném připojování.

Obecně platí, že byste při opětovném připojení měli použít výchozí chování automatického opětovného připojení ke skupinám. Skupiny SignalR nejsou zamýšleny jako bezpečnostní mechanismus pro omezení přístupu k citlivým datům. Pokud ale vaše aplikace musí při opětovném připojování překontrolovat členství uživatele ve skupině, můžete výchozí chování přepsat. Změna výchozího chování může databázi zatížit, protože členství uživatele ve skupině se musí načíst při každém opětovném připojení, a ne jenom při připojení uživatele.

Pokud při opětovném připojení musíte ověřit členství ve skupině, vytvořte nový modul kanálu centra, který vrátí seznam přiřazených skupin, jak je znázorněno níže.

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;
        }
    }
}

Pak tento modul přidejte do kanálu centra, jak je zvýrazněno níže.

public partial class Startup {
    public void Configuration(IAppBuilder app) {
        app.MapSignalR();
        GlobalHost.HubPipeline.AddModule(new RejoingGroupPipelineModule());
    }
}