SignalR 1.x でグループを使用するWorking with Groups in SignalR 1.x

によってPatrick FletcherTom FitzMackenby Patrick Fletcher, Tom FitzMacken

Note

この記事では、ASP.NET SignalR を指します。This article refers to ASP.NET SignalR. SignalR を使用して、Java、Node.js、またはサーバーレス シナリオでは、リアルタイムのシナリオを有効にする方法と思う場合を見てASP.NET Core SignalRします。If you're thinking about using SignalR to enable real-time scenarios with Java, Node.js, or in a serverless scenario, take a look at ASP.NET Core SignalR. 既に ASP.NET SignalR を使用した場合を見て、のバージョンの違いバージョンの違いと ASP.NET Core SignalR での機能強化を理解するページ。If you've already used ASP.NET SignalR, take a look at the version differences page to understand the differences in the versions and the improvements in ASP.NET Core SignalR. 最後に、Microsoft Azure でリアルタイム アプリを実行することがわかっている場合を見て、 Azure SignalR サービスなど、アプリを必要とすると、クラウド ベースのスケール アウトを提供します。Finally, if you know you'll be running your real-time apps in Microsoft Azure, take a look at the Azure SignalR Service, as it provides cloud-based scale-out once your apps need it.

このトピックでは、ユーザーをグループに追加し、グループのメンバーシップ情報を保持する方法について説明します。This topic describes how to add users to groups and persist group membership information.

概要Overview

SignalR でグループは、接続されているクライアントのサブセットを指定するメッセージをブロードキャストする方法を提供します。Groups in SignalR provide a method for broadcasting messages to specified subsets of connected clients. グループは、クライアントの任意の数を持つことができ、クライアントは任意の数のグループのメンバーであることができます。A group can have any number of clients, and a client can be a member of any number of groups. グループを明示的に作成する必要はありません。You don't have to explicitly create groups. 実際には、初めて Groups.Add への呼び出しでその名前を指定するグループを自動的に作成し、そのメンバーシップから最後の接続を削除する場合は削除します。In effect, a group is automatically created the first time you specify its name in a call to Groups.Add, and it is deleted when you remove the last connection from membership in it. グループの使用の概要については、次を参照してください。ハブ クラスからグループ メンバーシップを管理する方法Hubs API - サーバー ガイドにします。For an introduction to using groups, see How to manage group membership from the Hub class in the Hubs API - Server Guide.

グループ メンバーシップの一覧またはグループの一覧を取得するための API はありません。There is no API for getting a group membership list or a list of groups. SignalR クライアントおよび、パブリッシュ/サブスクライブ モデルに基づいてグループにメッセージを送信して、サーバーは、グループまたはグループ メンバーシップの一覧を保持しません。SignalR sends messages to clients and groups based on a pub/sub model, and the server does not maintain lists of groups or group memberships. こうため、SignalR を保持する任意の状態が新しいノードに適用するのには web ファームにノードを追加するたびに、スケーラビリティを最大化します。This helps maximize scalability, because whenever you add a node to a web farm, any state that SignalR maintains has to be propagated to the new node.

使用してグループにユーザーを追加すると、Groups.Addメソッドでは、ユーザーは、現在の接続の期間中、そのグループに送信されるメッセージを受け取りますが、そのグループ内のユーザーのメンバーシップは、現在の接続を超えて保持されません。When you add a user to a group using the Groups.Add method, the user receives messages directed to that group for the duration of the current connection, but the user's membership in that group is not persisted beyond the current connection. グループとグループ メンバーシップに関する情報を完全に保持する場合は、データベースまたは Azure テーブル ストレージなどのリポジトリにデータを格納する必要があります。If you want to permanently retain information about groups and group membership, you must store that data in a repository such as a database or Azure table storage. ユーザーがアプリケーションに接続するたびにするリポジトリから、ユーザーが属するグループを取得し、それらのグループにそのユーザーを手動で追加します。Then, each time a user connects to your application, you retrieve from the repository which groups the user belongs to, and manually add that user to those groups.

一時中断の後再接続時に、ときに、ユーザーが自動的に再結合以前に割り当てられているグループ。When reconnecting after a temporary disruption, the user automatically re-joins the previously-assigned groups. グループを自動的に再参加と、新しい接続を確立するときではなく、再接続時にのみ適用します。Automatically rejoining a group only applies when reconnecting, not when establishing a new connection. デジタル署名されたトークンは、以前に割り当てられているグループの一覧を含む、クライアントから渡されます。A digitally-signed token is passed from the client that contains the list of previously-assigned groups. ユーザーが要求されたグループに属しているかどうかを確認する場合は、既定の動作をオーバーライドできます。If you want to verify whether the user belongs to the requested groups, you can override the default behavior.

このトピックには、次のセクションがあります。This topic includes the following sections:

追加して、ユーザーを削除します。Adding and removing users

追加またはグループからユーザーを削除を呼び出す、追加または削除メソッド、およびユーザーの接続の id とグループの名前のパラメーターとして渡します。To add or remove users from a group, you call the Add or Remove methods, and pass in the user's connection id and group's name as parameters. 接続の終了時に、グループからユーザーを手動で削除する必要はありません。You do not need to manually remove a user from a group when the connection ends.

次の例は、Groups.AddGroups.Removeハブ メソッドで使用されるメソッド。The following example shows the Groups.Add and Groups.Remove methods used in Hub methods.

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

Groups.AddGroups.Removeメソッドが非同期的に実行します。The Groups.Add and Groups.Remove methods execute asynchronously.

クライアント グループを追加して、すぐに、グループを使用して、クライアントにメッセージを送信する場合は、Groups.Add メソッドが最初に終了するかどうかを確認する必要があります。If you want to add a client to a group and immediately send a message to the client by using the group, you have to make sure that the Groups.Add method finishes first. 次のコード例では、その .NET 4.5、および .NET 4 で動作するコードを使用していずれかで動作するコードを使用して 1 つの方法を示します。The following code examples show how to do that, one by using code that works in .NET 4.5, and one by using code that works in .NET 4.

.NET 4.5 の非同期の例Asynchronous .NET 4.5 Example

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

非同期 .NET 4 の例Asynchronous .NET 4 Example

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

一般に、含める必要はありませんawaitを呼び出すときに、Groups.Removeメソッドを削除しようとしている接続の id が使用可能な不要になった可能性があるためです。In general, you should not include await when calling the Groups.Remove method because the connection id that you are trying to remove might no longer be available. その場合は、TaskCanceledExceptionが、要求がタイムアウトした後にスローされます。追加できるかどうか、アプリケーションが、ユーザーがグループにメッセージを送信する前に、グループから削除されたことを確認する必要があります、 await Groups.Remove、および、キャッチする前に、TaskCanceledExceptionがスローされる例外。In that case, TaskCanceledException is thrown after the request times out. If your application must ensure that the user has been removed from the group before sending a message to the group, you can add await before Groups.Remove, and then catch the TaskCanceledException exception that might be thrown.

グループのメンバーの呼び出しCalling members of a group

次の例に示すように、すべてのグループのメンバーまたはグループの唯一の指定したメンバーにメッセージを送信できます。You can send messages to all of the members of a group or only specified members of the group, as shown in the following examples.

  • すべて指定したグループ内のクライアントを接続します。All connected clients in a specified group.

    Clients.Group(groupName).addChatMessage(name, message);
    
  • 指定したグループ内のクライアントが接続されているすべて 、指定されたクライアントを除く接続 ID によって識別されます。All connected clients in a specified group except the specified clients, identified by connection ID.

    Clients.Group(groupName, connectionId1, connectionId2).addChatMessage(name, message);
    
  • 指定したグループ内のクライアントが接続されているすべて呼び出し元のクライアントを除くします。All connected clients in a specified group except the calling client.

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

グループのメンバーシップをデータベースに保存します。Storing group membership in a database

次の例では、データベース内のグループとユーザーの情報を保持する方法を示します。The following examples show how to retain group and user information in a database. 任意のデータ アクセス テクノロジを使用することができます。ただし、次の例では、Entity Framework を使用してモデルを定義する方法を示します。You can use any data access technology; however, the example below shows how to define models using Entity Framework. これらのエンティティ モデルは、データベース テーブルとフィールドに対応します。These entity models correspond to database tables and fields. データ構造は、アプリケーションの要件に応じて大きく異なる可能性があります。Your data structure could vary considerably depending on the requirements of your application. この例には、という名前のクラスが含まれています。ConversationRoomスポーツやガーデンなどの異なるサブジェクトについての会話に参加できるアプリケーション固有であります。This example includes a class named ConversationRoom which would be unique to an application that enables users to join conversations about different subjects, such as sports or gardening. この例では、接続するためのクラスも含まれています。This example also includes a class for the connections. 接続クラスは、グループ メンバーシップを追跡するために必須ではありませんが、ユーザーを追跡する堅牢なソリューションの一部では頻繁に。The connection class is not absolutely required for tracking group membership but is frequently part of robust solution to tracking users.

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

次に、ハブでは、データベースから、グループとユーザー情報を取得し、適切なグループにユーザーを手動で追加できます。Then, in the hub, you can retrieve the group and user information from the database and manually add the user to the appropriate groups. この例では、ユーザー接続を追跡するためのコードは含まれません。The example does not include code for tracking the user connections. この例で、awaitキーワードは、前に適用されませんGroups.Addグループのメンバーにメッセージがすぐに送信されないためです。In this example, the await keyword is not applied before Groups.Add because a message is not immediately sent to members of the group. 適用するグループのすべてのメンバーに、新しいメンバーを追加した後すぐにメッセージを送信する場合、awaitキーワードを非同期操作が完了したかどうかを確認します。If you want to send a message to all members of the group immediately after adding the new member, you would want to apply the await keyword to make sure the asynchronous operation has completed.

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

Azure table storage に格納するグループのメンバーシップStoring group membership in Azure table storage

Azure テーブル ストレージを使用して、グループとユーザーの情報を格納するは、データベースを使用してに似ています。Using Azure table storage to store group and user information is similar to using a database. 次の例では、ユーザー名とグループ名を格納するテーブル エンティティを示します。The following example shows a table entity that stores the user name and group name.

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

ハブで、ユーザーが接続するときに割り当てられているグループを取得します。In the hub, you retrieve the assigned groups when the user connects.

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

再接続時にグループ メンバーシップの確認Verifying group membership when reconnecting

既定では、SignalR 自動的に再割り当てをユーザー適切なグループに接続が削除され、接続がタイムアウトする前に再確立するなどの一時中断から再接続時にします。再接続時に、時に、ユーザーのグループの情報がトークンで渡され、サーバーでそのトークンを確認します。By default, SignalR automatically re-assigns a user to the appropriate groups when reconnecting from a temporary disruption, such as when a connection is dropped and re-established before the connection times out. The user's group information is passed in a token when reconnecting, and that token is verified on the server. グループへのユーザーの再参加の検証プロセスについては、次を参照してください。再接続時にグループの再参加します。For information about the verification process for rejoining users to groups, see Rejoining groups when reconnecting.

一般に、グループに再接続を自動的に再参加の既定の動作を使用する必要があります。In general, you should use the default behavior of automatically rejoining groups on reconnect. SignalR のグループは、機密データへのアクセスを制限するためのセキュリティ メカニズムとして意図されていません。SignalR groups are not intended as a security mechanism for restricting access to sensitive data. ただし、アプリケーションは、再接続時に、ユーザーのグループ メンバーシップを再確認する必要がある場合、は、既定の動作を上書きできます。However, if your application must double-check a user's group membership when reconnecting, you can override the default behavior. 既定の動作を変更することができます、負担、データベースを追加ため各再接続はなく、ユーザーが接続するときに、ユーザーのグループ メンバーシップを取得する必要があります。Changing the default behavior can add a burden to your database because a user's group membership must be retrieved for each reconnection rather than just when the user connects.

グループのメンバーシップを確認する必要がある場合は、再接続、次に示すように、割り当てられたグループの一覧を返す新しいハブ パイプライン モジュールを作成します。If you must verify group membership on reconnect, create a new hub pipeline module that returns a list of assigned groups, as shown below.

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

次に、以下の強調表示されている、ハブ パイプラインにそのモジュールを追加します。Then, add that module to the hub pipeline, as highlighted below.

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