Sdílet prostřednictvím


Práce se skupinami v knihovně SignalR 1.x

Patrick Fletcher, Tom FitzMacken

Upozornění

Tato dokumentace není určená pro nejnovější verzi 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.

Přehled

Skupiny v 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 vytvoří automaticky při prvním zadání jejího názvu ve volání skupiny.Přidat a odstraní se, když odeberete poslední připojení z členství v ní. Úvod k používání skupin najdete v tématu Správa členství ve skupinách z třídy Hub v příručce k rozhraní Hubs API – Server.

Neexistuje žádné rozhraní API pro získání seznamu členství ve skupině nebo 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 do nového uzlu rozšířit jakýkoli stav, který SignalR udržuje.

Když přidáte uživatele do skupiny pomocí Groups.Add metody, uživatel obdrží zprávy směrované do této skupiny po dobu trvání aktuálního připojení, ale členství uživatele v této skupině není zachováno nad rámec aktuálního připojení. Pokud chcete trvale uchovávat 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 uživatel připojí k vaší aplikaci, pak z úložiště načtete, do kterých skupin uživatel patří, a ručně ho přidáte 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í pouze při opětovném připojení, ne při navazování nového připojení. Z klienta se předá 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, zavoláte metody Add nebo Remove a předáte id připojení uživatele a název skupiny jako parametry. Po ukončení připojení nemusíte uživatele ze skupiny ručně odebírat.

Následující příklad ukazuje Groups.Add metody a Groups.Remove používané 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 Groups.Add a Groups.Remove se 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, jeden pomocí kódu, který funguje v .NET 4.5, a druhý pomocí kódu, který funguje v .NET 4.

Asynchronní .NET 4.5 – příklad

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

Příklad asynchronního rozhraní .NET 4

public void JoinRoom(string roomName)
{
    (Groups.Add(Context.ConnectionId, roomName) as Task).ContinueWith(antecedent =>
      Clients.Group(roomName).addChatMessage(Context.User.Identity.Name + " joined."));
}

Obecně platí, že při volání Groups.Remove metody byste neměli zahrnoutawait, 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 ze skupiny odebrán, můžete ji přidat await před Skupiny.Odebrat a 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 zadaný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ů identifikovaných 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 ale 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 zahrnuje 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í nezbytně nutná pro sledování členství ve skupině, 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 můžete v centru 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í uživatelských připojení. 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, abyste měli jistotu, že se asynchronní operace dokončila.

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 se podobá 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í

Ve výchozím nastavení SignalR 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ž je připojení ukončeno a znovu navázané před vypršením časového limitu připojení. Informace o skupině uživatele se předají v tokenu při opětovném připojení a tento token se ověří na serveru. Informace o procesu ověření opětovného připojení uživatelů ke skupinám najdete v tématu Opětovné připojení ke skupinám při opětovném připojení.

Obecně byste měli použít výchozí chování automatického opětovného připojení ke skupinám při opětovném připojení. Skupiny SignalR nejsou určeny 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řipojení pečlivě zkontrolovat č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ěžovat, protože členství uživatele ve skupinách se musí načíst pro každé opětovné připojení, a ne jenom při připojení uživatele.

Pokud musíte ověřit členství ve skupině při opětovném připojení, 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 přidejte tento modul do kanálu centra, jak je zvýrazněno níže.

public class Global : HttpApplication
{
    void Application_Start(object sender, EventArgs e)
    {
        // Code that runs on application startup
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        AuthConfig.RegisterOpenAuth();
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        RouteTable.Routes.MapHubs();
        GlobalHost.HubPipeline.AddModule(new RejoingGroupPipelineModule());
    }
}