SignalR 1.x Sürümünde Gruplarla Çalışma

Patrick Fletcher, Tom FitzMacken tarafından

Uyarı

Bu belgeler SignalR'nin en son sürümüne yönelik değildir. ASP.NET Core SignalR'ye göz atın.

Bu konuda, gruplara kullanıcı ekleme ve grup üyeliği bilgilerini kalıcı hale getirmek açıklanmaktadır.

Genel Bakış

SignalR'deki gruplar, bağlı istemcilerin belirtilen alt kümelerine ileti yayınlamak için bir yöntem sağlar. Bir grubun herhangi bir sayıda istemcisi olabilir ve bir istemci herhangi bir sayıda grubun üyesi olabilir. Grupları açıkça oluşturmanız gerekmez. Grup, Groups.Add çağrısında adını ilk kez belirttiğinizde otomatik olarak oluşturulur ve bu grup, üyelikten son bağlantıyı kaldırdığınızda silinir. Grupları kullanmaya giriş için bkz. Hubs API'sindeki Hub sınıfından grup üyeliğini yönetme - Sunucu Kılavuzu.

Grup üyeliği listesi veya grup listesi almak için API yoktur. SignalR, istemcilere ve gruplara pub/sub modelini temel alan iletiler gönderir ve sunucu grup veya grup üyeliği listelerini korumaz. Bu, web grubuna her düğüm eklediğinizde SignalR'nin koruduğu herhangi bir durumun yeni düğüme yayılması gerektiğinden ölçeklenebilirliği en üst düzeye çıkarmaya yardımcı olur.

yöntemini kullanarak Groups.Add bir gruba kullanıcı eklediğinizde, kullanıcı geçerli bağlantı süresi boyunca bu gruba yönlendirilen iletileri alır, ancak kullanıcının bu gruptaki üyeliği geçerli bağlantının ötesinde kalıcı olmaz. Gruplar ve grup üyeliği hakkındaki bilgileri kalıcı olarak saklamak istiyorsanız, bu verileri veritabanı veya Azure tablo depolama alanı gibi bir depoda depolamanız gerekir. Ardından, bir kullanıcı uygulamanıza her bağlandığında, kullanıcının ait olduğu grupları depodan alır ve bu kullanıcıyı bu gruplara el ile eklersiniz.

Geçici bir kesintiden sonra yeniden bağlanırken, kullanıcı önceden atanmış grupları otomatik olarak yeniden birleştirir. Bir gruba otomatik olarak yeniden katılma yalnızca yeniden bağlanırken geçerli olur, yeni bağlantı kurulurken geçerli olmaz. Daha önce atanmış grupların listesini içeren istemciden dijital olarak imzalanan bir belirteç geçirilir. Kullanıcının istenen gruplara ait olup olmadığını doğrulamak istiyorsanız, varsayılan davranışı geçersiz kılabilirsiniz.

Bu konu aşağıdaki bölümleri içermektedir:

Kullanıcıları ekleme ve kaldırma

Bir gruba kullanıcı eklemek veya gruptan kullanıcı kaldırmak için Ekle veya Kaldır yöntemlerini çağırırsınız ve kullanıcının bağlantı kimliğini ve grubunun adını parametre olarak geçirirsiniz. Bağlantı sona erdiğinde bir kullanıcıyı gruptan el ile kaldırmanız gerekmez.

Aşağıdaki örnekte Hub yöntemlerinde kullanılan ve Groups.Remove yöntemleri gösterilmektedirGroups.Add.

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.Add ve Groups.Remove yöntemleri zaman uyumsuz olarak yürütülür.

Gruba bir istemci eklemek ve grubu kullanarak hemen istemciye bir ileti göndermek istiyorsanız, önce Groups.Add yönteminin tamamlandığından emin olmanız gerekir. Aşağıdaki kod örnekleri, bunun nasıl yapılacağını gösterir; biri .NET 4.5'te çalışan kodu, diğeri de .NET 4'te çalışan kodu kullanarak.

Zaman Uyumsuz .NET 4.5 Örneği

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

Zaman Uyumsuz .NET 4 Örneği

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

Genel olarak, kaldırmaya çalıştığınız bağlantı kimliği artık kullanılamayabileceği için yöntemini çağırırken Groups.Remove eklememelisinizawait. Bu durumda istek TaskCanceledException zaman aşımına uğradıktan sonra oluşturulur. Uygulamanızın gruba ileti göndermeden önce kullanıcının gruptan kaldırıldığından emin olması gerekiyorsa, Groups.Remove'tan önce ekleyebilir await ve ardından oluşturulabilecek özel durumu yakalayabilirsiniz TaskCanceledException .

Grubun üyelerini çağırma

Aşağıdaki örneklerde gösterildiği gibi, bir grubun tüm üyelerine veya yalnızca grubun belirtilen üyelerine ileti gönderebilirsiniz.

  • Belirtilen gruptaki tüm bağlı istemciler.

    Clients.Group(groupName).addChatMessage(name, message);
    
  • Bağlantı kimliğiyle tanımlanan belirtilen istemciler dışında, belirtilen gruptaki tüm bağlı istemciler.

    Clients.Group(groupName, connectionId1, connectionId2).addChatMessage(name, message);
    
  • Belirtilen gruptaki çağıran istemci dışındaki tüm bağlı istemciler.

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

Grup üyeliğini veritabanında depolama

Aşağıdaki örneklerde, bir veritabanında grup ve kullanıcı bilgilerinin nasıl tutulacakları gösterilmektedir. Herhangi bir veri erişim teknolojisini kullanabilirsiniz; ancak aşağıdaki örnekte Entity Framework kullanarak modellerin nasıl tanımlanacağı gösterilmektedir. Bu varlık modelleri veritabanı tablolarına ve alanlarına karşılık gelir. Veri yapınız, uygulamanızın gereksinimlerine bağlı olarak önemli ölçüde değişebilir. Bu örnek, kullanıcıların spor veya bahçe gibi farklı konularla ilgili konuşmalara katılmasını sağlayan bir uygulama için benzersiz olabilecek adlı ConversationRoom bir sınıf içerir. Bu örnek, bağlantılar için bir sınıf da içerir. Bağlantı sınıfı, grup üyeliğini izlemek için kesinlikle gerekli değildir, ancak genellikle kullanıcıları izlemeye yönelik güçlü çözümün bir parçasıdır.

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

Ardından, hub'da gruptaki ve kullanıcı bilgilerini veritabanından alabilir ve kullanıcıyı uygun gruplara el ile ekleyebilirsiniz. Örnek, kullanıcı bağlantılarını izlemeye yönelik kod içermez. Bu örnekte, bir ileti grubun üyelerine await hemen gönderilmediğinden anahtar sözcüğü daha önce Groups.Add uygulanmamıştır. Yeni üyeyi ekledikten hemen sonra grubun tüm üyelerine ileti göndermek istiyorsanız, zaman uyumsuz işlemin tamamlandığından emin olmak için anahtar sözcüğünü uygulamak await istersiniz.

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

Grup üyeliğini Azure tablo depolamada depolama

Grup ve kullanıcı bilgilerini depolamak için Azure tablo depolamayı kullanmak, veritabanı kullanmaya benzer. Aşağıdaki örnekte, kullanıcı adını ve grup adını depolayan bir tablo varlığı gösterilmektedir.

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

Kullanıcı bağlandığında hub'da atanan grupları alırsınız.

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

Yeniden bağlanırken grup üyeliğini doğrulama

Varsayılan olarak SignalR, bağlantı zaman aşımına uğramadan önce bağlantının bırakılması ve yeniden kurulması gibi geçici bir kesintiden yeniden bağlanırken kullanıcıyı otomatik olarak uygun gruplara yeniden atar. Kullanıcının grup bilgileri yeniden bağlanırken bir belirteçte geçirilir ve bu belirteç sunucuda doğrulanır. Kullanıcıları gruplara yeniden eklemeye yönelik doğrulama işlemi hakkında bilgi için bkz. Yeniden bağlanırken grupları yeniden katılma.

Genel olarak, yeniden bağlantıda grupları otomatik olarak yeniden eklemenin varsayılan davranışını kullanmanız gerekir. SignalR grupları, hassas verilere erişimi kısıtlamaya yönelik bir güvenlik mekanizması olarak tasarlanmamıştır. Ancak, uygulamanızın yeniden bağlanırken kullanıcının grup üyeliğini bir kez daha denetlemesi gerekiyorsa, varsayılan davranışı geçersiz kılabilirsiniz. Bir kullanıcının grup üyeliğinin yalnızca kullanıcı bağlandığında değil her yeniden bağlantı için alınması gerektiğinden, varsayılan davranışı değiştirmek veritabanınıza yük oluşturabilir.

Yeniden bağlanırken grup üyeliğini doğrulamanız gerekiyorsa, aşağıda gösterildiği gibi atanan grupların listesini döndüren yeni bir hub işlem hattı modülü oluşturun.

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

Ardından, aşağıda vurgulandığı gibi bu modülü hub işlem hattına ekleyin.

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