Bagikan melalui


Bekerja dengan Grup di SignalR

oleh Patrick Fletcher, Tom FitzMacken

Peringatan

Dokumentasi ini bukan untuk versi terbaru SignalR. Lihatlah ASP.NET Core SignalR.

Topik ini menjelaskan cara menambahkan pengguna ke grup dan mempertahankan informasi keanggotaan grup.

Versi perangkat lunak yang digunakan dalam topik ini

Versi sebelumnya dari topik ini

Untuk informasi tentang versi SignalR yang lebih lama, lihat Versi Lama SignalR.

Pertanyaan dan komentar

Silakan tinggalkan umpan balik tentang bagaimana Anda menyukai tutorial ini dan apa yang dapat kami tingkatkan di komentar di bagian bawah halaman. Jika Anda memiliki pertanyaan yang tidak terkait langsung dengan tutorial, Anda dapat mempostingnya ke forum ASP.NET SignalR atau StackOverflow.com.

Gambaran Umum

Grup di SignalR menyediakan metode untuk menyiarkan pesan ke subset tertentu dari klien yang terhubung. Grup dapat memiliki sejumlah klien, dan klien dapat menjadi anggota dari sejumlah grup. Anda tidak perlu membuat grup secara eksplisit. Akibatnya, grup secara otomatis dibuat saat pertama kali Anda menentukan namanya dalam panggilan ke Grup.Tambahkan, dan grup dihapus saat Anda menghapus koneksi terakhir dari keanggotaan di dalamnya. Untuk pengenalan menggunakan grup, lihat Cara mengelola keanggotaan grup dari kelas Hub di Hubs API - Panduan Server.

Tidak ada API untuk mendapatkan daftar keanggotaan grup atau daftar grup. SignalR mengirim pesan ke klien dan grup berdasarkan model pub/sub, dan server tidak mempertahankan daftar grup atau keanggotaan grup. Ini membantu memaksimalkan skalabilitas, karena setiap kali Anda menambahkan simpul ke farm web, status apa pun yang dipertahankan SignalR harus disebarluaskan ke simpul baru.

Saat Anda menambahkan pengguna ke grup menggunakan metode , Groups.Add pengguna menerima pesan yang diarahkan ke grup tersebut selama durasi koneksi saat ini, tetapi keanggotaan pengguna dalam grup tersebut tidak bertahan di luar koneksi saat ini. Jika Anda ingin menyimpan informasi secara permanen tentang grup dan keanggotaan grup, Anda harus menyimpan data tersebut di repositori seperti database atau penyimpanan tabel Azure. Kemudian, setiap kali pengguna terhubung ke aplikasi Anda, Anda mengambil dari repositori tempat pengguna berada, dan menambahkan pengguna tersebut secara manual ke grup tersebut.

Saat menyambungkan kembali setelah gangguan sementara, pengguna secara otomatis bergabung kembali dengan grup yang ditetapkan sebelumnya. Secara otomatis bergabung kembali dengan grup hanya berlaku saat menyambungkan kembali, bukan saat membuat koneksi baru. Token yang ditandatangani secara digital diteruskan dari klien yang berisi daftar grup yang ditetapkan sebelumnya. Jika Anda ingin memverifikasi apakah pengguna termasuk dalam grup yang diminta, Anda dapat mengambil alih perilaku default.

Topik ini mencakup bagian berikut:

Menambahkan dan menghapus pengguna

Untuk menambahkan atau menghapus pengguna dari grup, Anda memanggil metode Tambahkan atau Hapus , dan meneruskan id koneksi pengguna dan nama grup sebagai parameter. Anda tidak perlu menghapus pengguna secara manual dari grup saat koneksi berakhir.

Contoh berikut menunjukkan Groups.Add metode dan Groups.Remove yang digunakan dalam metode Hub.

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

Metode Groups.Add dan Groups.Remove dijalankan secara asinkron.

Jika Anda ingin menambahkan klien ke grup dan segera mengirim pesan ke klien dengan menggunakan grup, Anda harus memastikan bahwa metode Groups.Add selesai terlebih dahulu. Contoh kode berikut menunjukkan cara melakukannya.

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

Secara umum, Anda tidak boleh menyertakan await saat memanggil Groups.Remove metode karena id koneksi yang Anda coba hapus mungkin tidak lagi tersedia. Dalam hal ini, TaskCanceledException dilemparkan setelah waktu permintaan habis. Jika aplikasi Anda harus memastikan bahwa pengguna telah dihapus dari grup sebelum mengirim pesan ke grup, Anda dapat menambahkan await sebelum Groups.Remove, lalu menangkap TaskCanceledException pengecualian yang mungkin dilemparkan.

Memanggil anggota grup

Anda bisa mengirim pesan ke semua anggota grup atau hanya anggota grup tertentu, seperti yang diperlihatkan dalam contoh berikut.

  • Semua klien yang terhubung dalam grup tertentu.

    Clients.Group(groupName).addChatMessage(name, message);
    
  • Semua klien yang terhubung dalam grup tertentu kecuali klien yang ditentukan, yang diidentifikasi oleh ID koneksi.

    Clients.Group(groupName, connectionId1, connectionId2).addChatMessage(name, message);
    
  • Semua klien yang terhubung dalam grup tertentu kecuali klien panggilan.

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

Menyimpan keanggotaan grup dalam database

Contoh berikut menunjukkan cara mempertahankan informasi grup dan pengguna dalam database. Anda dapat menggunakan teknologi akses data apa pun; namun, contoh di bawah ini menunjukkan cara menentukan model menggunakan Kerangka Kerja Entitas. Model entitas ini sesuai dengan tabel dan bidang database. Struktur data Anda dapat sangat bervariasi tergantung pada persyaratan aplikasi Anda. Contoh ini mencakup kelas bernama ConversationRoom yang akan unik untuk aplikasi yang memungkinkan pengguna untuk bergabung dalam percakapan tentang subjek yang berbeda, seperti olahraga atau berkebun. Contoh ini juga mencakup kelas untuk koneksi. Kelas koneksi tidak benar-benar diperlukan untuk melacak keanggotaan grup tetapi sering menjadi bagian dari solusi yang kuat untuk melacak pengguna.

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

Kemudian, di hub, Anda dapat mengambil informasi grup dan pengguna dari database dan menambahkan pengguna secara manual ke grup yang sesuai. Contoh tidak menyertakan kode untuk melacak koneksi pengguna. Dalam contoh ini, await kata kunci tidak diterapkan sebelumnya Groups.Add karena pesan tidak segera dikirim ke anggota grup. Jika Anda ingin mengirim pesan ke semua anggota grup segera setelah menambahkan anggota baru, Anda ingin menerapkan await kata kunci untuk memastikan operasi asinkron telah selesai.

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

Menyimpan keanggotaan grup di penyimpanan tabel Azure

Menggunakan penyimpanan tabel Azure untuk menyimpan informasi grup dan pengguna mirip dengan menggunakan database. Contoh berikut menunjukkan entitas tabel yang menyimpan nama pengguna dan nama grup.

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

Di hub, Anda mengambil grup yang ditetapkan saat pengguna tersambung.

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

Memverifikasi keanggotaan grup saat menyambungkan kembali

Secara default, SignalR secara otomatis menetapkan ulang pengguna ke grup yang sesuai saat menyambungkan kembali dari gangguan sementara, seperti ketika koneksi terputus dan dibuat kembali sebelum waktu koneksi habis. Informasi grup pengguna diteruskan dalam token saat menyambungkan kembali, dan token tersebut diverifikasi di server. Untuk informasi tentang proses verifikasi untuk bergabung kembali dengan pengguna ke grup, lihat Bergabung kembali dengan grup saat menyambungkan kembali.

Secara umum, Anda harus menggunakan perilaku default untuk secara otomatis bergabung kembali dengan grup saat menyambungkan kembali. Grup SignalR tidak dimaksudkan sebagai mekanisme keamanan untuk membatasi akses ke data sensitif. Namun, jika aplikasi Anda harus memeriksa kembali keanggotaan grup pengguna saat menyambungkan kembali, Anda dapat mengambil alih perilaku default. Mengubah perilaku default dapat menambahkan beban ke database Anda karena keanggotaan grup pengguna harus diambil untuk setiap koneksi ulang daripada hanya saat pengguna terhubung.

Jika Anda harus memverifikasi keanggotaan grup saat menyambungkan kembali, buat modul alur hub baru yang mengembalikan daftar grup yang ditetapkan, seperti yang ditunjukkan di bawah ini.

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

Kemudian, tambahkan modul tersebut ke alur hub, seperti yang disorot di bawah ini.

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