.Net Core’da SignalR ile Client’a Özel Background Değiştirme

Selamlar Arkadaşlar,

Bu makalede bir sayafaya connect olan tüm userlar arasından istediğimiz bir tanesinin arka rengini, yine istediğimiz bir resim ile signalR websocket kullanarak .Net Core ortamında nasıl değiştireceğimizi beraberce inceleyeceğiz. .Net Core 2.0 ‘da SignalR üzerinde daha detaylı bilgiye buradaki makalemden erişebilirsiniz. Özellikle bu makalede anlatılmayan, signalR’ın bir .Net Core projesine eklenme adımlarını  incelemenizi tavsiye ederim.

Yapılacak İşler:

  • Öncelikle, .Net Core ile bir Mvc projesi oluşturacağız.
  • Daha sonra sayfaya gelen her bir kişiyi ayırt etmek için Login sayfası yapacağız. Login için gelen kullanıcıları Session State’den konturol edeceğiz. Bunun için gene .Net Core’da bir takım Configurasyon işlemlerinin yapılması gerekmektedir. Eğer ilgili kullanıcı session’da yok ise, login sayfasına var ise Index sayfasına yönlendireceğiz.
  • Daha sonra signalR bir “Background:Hub” sınıfı yaratıp, ilgili client’ın bağlandığı an, yeni oluşan connectionID’sini kendisine gönderip, alınan bu Nick’i ve yeni connectionID’sini static bir listeye yok ise ekleyeceğiz var ise güncelleyeceğiz ki aynı kullanıcı bir daha geldiğinde aynı Nick’i alabilsin. Ya da 2. bir kullanıcı bir başkasının Nick’ini alamasın.
  • Bu işlemin sonunda değişen static listeyi tüm clientlara göndereceğiz. Bu bizim yeni Online Listemiz olacak.
  • Son olarak seçilen kişiye yine istenen background resmi realtime olarak signalR ile atanacaktır.

Hata ve İncelenmesi Gereken Durumları:

  • Aynı Nick ile 2.bir kişi sisteme dahil edilmemelidir.
  • Connect olan user sayfayı refresh eder ise, connectionID’si değişecektir. Tüm clientların listesinden ve static listeden, aynı Nick’e ait yeni connectionID’nin değiştirilmesi gerekmektedir.
  • Bir user logout olur ise, tüm clientlardan ve statick listeden çıkarılması gerekmektedir.
  • Statick Listenin, Mvc “HomeController” sınıfı ve “Background:Hub” sınıfı için ortak ve tek olması gerekmektedir.
  • Diyelim ki bir client 2 tab açtı ve 1 sini kapattı. Session’dan dolayı 2 sinde de aynı Nick’e ait farklı connectionIDler vardır. SignalR sınıfı, ilk tab kapanınca ilgili connectionID’ye ait user Nick’ini Static Listeden çıkarır. Diğer clientlarda bu user’ın aslında halen durması gerekmektedir. Çünkü gerçekte client çıkmamış, halen açık tab’i vardır. Ayrıca client’ın en son açık kalan tab’ı, geçerli olan yeni bir connectionID almalı ve bu durum da tüm clientlara bildirilmelidir. Yoksa ilgili Nick’e tıklanıp, istenen resimin listeden seçilen user’a gösterilmesi denendiğinde bu client’a erişilemiyecektir.

.NET Core Mvc Proje oluşturlması: Aşağıdaki komut ile ilgili proje oluşturulur.

Home / Index(): Sayfaya ilk gelinildiğinde “Index()” action’ına gelinir. Session’da tanımlı bir “UserName” yok ise “Login” sayfasına yönlenilir.

.Net Core’da Session Tanımlama: Öncelikle session için ilgili package aşağıda görüldüğü gibi indirilir.

Startup.cs’de : Aşağıda görülen configurasyon ile projede Session kullanılacağı tanımlanır.

Aşağıda görüldüğü gibi “ConfigurationServices()” methodunda, “services.AddSession()” methodu ile timeout süre 30 dakika olucak şekilde cookie’ye izin verilecek şekilde set edilmiştir.

Home/Login: Login sayfasına gelinir.

Login.cshtml: Bu sayfada Jquery kullanılmıştır. Sayfada bir giriş buttonu, bir de Nick yazılacak text alanı bulunmaktadır. Amaç sisteme gelen user’a daha önce alınmamış bir Nick’i verip, ilgili ismi statick bir Dictionary listeye eklemektir.

  • Sayfa “$(document).ready(function () {” function’ı ile yüklendikten sonra, text alana focus olunur. ==>”$(“#userName”).focus()“.
  • Button tıklanma durumunda “Home/Login” urline Jquery ile post işlemi yapılır. Parametre olarak name gönderilir ==>”name: $(“#userName”).val()“. Ayrıca aynı işlem, “pushEnter()” function’ı ile Enter tuşuna basılarak da yapılabilir.
  • Ayrıca geri dönüş değerinde ==>”if (result != “Error”)“, “Error” text’i var ise, bu böyle bir Nick’in daha önceden alınmış olduğu anlamına gelmektedir. Yok ise “Index” sayfasına yönlenilir.==>”location.href = “/Home/Index”

LogIn(Post): Aşağıda görüldüğü gibi ilgili nickin “UserList” static sınıfına ait bir Dictionary’de olup olmadığına “HasNick()” methodu ile bakılır. Eğer var ise, yani başka biri ilgili nick’i önceden almış ise geriye “Error” text content’i dönülür. Yok ise “Index” sayfasına yönlenilr.

Not: Burada static bir list kullanılmasının sebebi, ilgili nick listesine sadece Mvc “HomeController” sınıfından değil, aynı zamanda ilerde yazacağımız “Background : Hub” sınıfından da erişilecek olmasıdır.

UserList: Ortak kullanılan clientların Nicklerinin bir static Dictionary’de yani “_userlist”‘de saklandığı ve gerektiğinde aranıp bakıldığı bir yardımcı sınıftır. Tüm clientlar için tekil ve sabit bir liste olduğu için static olarak ayrılmıştır. Bu şekilde ayırmakta amaç, farklı yerlerde kullanılan ortak bir listeye erişmektir.

  • Add() : İlgili “_UserList” Dictionary’ye yeni bir Nick eklemek için kullanılır.
  • Remove() : İlgili “_UserList” Dictionary’den, ilerde bahsediceğimiz client’ın signalR connectionID’sinden Nick’i “GetNickByConnectionID()” methodu ile bulunup çıkarılmasıdır.
  • GetNickByConnectionID() : signalR sınıfından disconnect (Logout) olan clientların Nicklerinin, ilgili statick listeden çıkarılması gerekmektedir. İşte bu disconnect durumda, hub sınıfına client’ın sadece connectionID’si gelmektedir. İlgili statick dictionary listeden eleman çıkarılma işlemi sadece key ile olduğu için ilgili connectionID’ye ait nick çekilir. Bunun için, ilgili static listeden connectionID yani value ye karşılık gelen key yani Nick bulunur.
  • HasNick() : Sorgulanacak nick’in listede olup olmadığına bakılır. Bu işlem sayfaya connect olan clientların yeni bir Nick alması durumunda kontrol amaçlı yapılır.

Index Sayfasına gelmeden önce gelin hep beraber “Background : Hub” sınıfını hep beraber inceleyelim.

Background : Hub:
  • OnConnectedAsync()” : Client’ın SignalR sınıfına connect olduğu an tetiklenen methoddur. Burada connect olan client’a ait signalR connectionID’si alınıp, ilgili user’ın client sidedaki “SetConnectionId()” function’ına parametre olarak gönderilmiştir. Böylece her client’ın imsazısı yani erişin adresi kendi üzerinde saklanır hale getirilmiştir.
  • OnDisconnectedAsync()” : Client’ın browser’ını kapattığı zaman ya da başka bir sebepten dolayı signalR sınıfı ile olan connection’ı kopunca ilgili method devreye girer. Burda ayrılan client’ın connectionID’si alınır. Tüm kayıtlı userların bulunduğu static Dictionary UserList’den çıkartılır. Ve listenin son (“UserList._UserList”) hali, tüm clientların “SetListDisconnect()” functionına gönderilir. Böylece comboya dolacak olan isimlerden ayrılan client çıkarılmış olunur. Kısacası Server Side’dan Client Side’a bir push notify atılmış olunur.
    • NOT: Gerçek hayatta tüm liste değil, sadece çıkan client’ın bilgileri gönderilmelidir. Ayrıca client side tarafda da, ilgili combo listesinden sadece bu gönderilen client bilgisi çıkarılarak listelenmelidir. Makalenin amacından uzaklaşmaması ve kodları daha da fazla uzamaması için bu basit ama performans katledici :) yöntem ile yapılmıştır. Bu yöntem size ödev olsun. Geri dönüşlerinizi bekliyeceğim.
  • “AddUserList()“:  Bu method client side taraftan çağrılarak, connect olan client’ın static Dictionary UserList’e eklenmesini sağlamaktadır. Daha sonrasında, güncel liste gene tüm clientların “SetList()” function’ına parametre olarak gönderilmektedir. İlgili method parametre olarak, yeni gelen client’ın username’ini ve connectionID’sini almaktadır.
    • Sayfanın çalışma döngüsü : sayfanın ilerisinde bu konu ile ilgili detaylı bir abak bulunmaktadır.
      1. Login olan client SignalR sınıfının “1-OnConnectedAsync()” methoduna gelir. Burdan ==>
      2. Connect olan user’ın Client Side tarafdaki “2-SetConnectionId()” function’ına gelinir. Burdan ==>
      3. Tekrardan server side tarafdaki “3-AddUserList()” methoduna gelinir. Burdan ==>
      4. Tüm clientlar’ın “4-SetList()” function’ına gelinir.
  • ChangeBackground()“: İlgili client’a gösterilecek resim bilgisinin gönderildiği methoddur. Parametre olarak gönderilecek client’ın connectionID’sini ve gösterilecek resmin adını almaktadır. Gösterilecek resmin adı, ilgili client’ın “ChangeImage()” function’ına paramete olarak gönderilir.

Geldik tüm işin yürüdüğü “Index” sayfasına:

Index.cshtm: Bu sayfada signalR ve jquery kütüphaneleri kullanılmıştır. Bir daha hatırlatmak isterim. Artık signalR jquery kütüphanesine ihtiyaç duymamaktadır. Zaten bu projede de jquery özellikle signalR kütüphanesinden sonra tanımlanmıştır. Jquery bu projede tamamen benim kendi inisiyatifimde kullanılmıştır.

  • @using Microsoft.AspNetCore.Http” : Session’dan Username bilgisinin alınabilmesi için eklenmiştir.
  • “<script src=”~/scripts/signalr-client-1.0.0-alpha2-final.min.js”></script>” : İlgili signalR ve Jquery kütüphaneleri eklenir.
  • let connection = new signalR.HubConnection(‘background’)” : Server side taraftaki “Background: Hub” sınıfına bağlanılır.
  • connection.on(‘SetConnectionId’, data =>” : Client’ın Server Side tarafta, ilgili Background sınıfına connect olması durumunda “OnConnectedAsync()” methodu tarafından çağrılan functiondır. Burada kendisine parametre olarak gönderilen “ConnectionID” degerini ve Session’dan (“@Context.Session.GetString(“UserName”)“) aldığı UserName’i hidden fieldlara atar. Sonra da bu Nick’in, server side taraftaki statick dictionary bir List’de saklanması için “AddUserList()” methoduna gönderir.

  • connection.on(‘ChangeImage’, data => {” : Diğer bir client’dan kendisine atanan resmin, “pictureDiv” id’li html element’in arka rengi olarak atandığı yerdir.

  • connection.on(‘SetListDisconnect’, data => {” : Herhangi bir client’ın disconnect olduğu zaman server side tarafında “OnDisconnectedAsync()” methodu tarafından tetiklenen, yeni UserList’in parametre olarak gönderildiği functiondır. Burda başlıca 2 durum sözkonusudur:
    1. Gönderilen liste boş ise: Diyelimki Online tek bir client var ve 2 tab açmış. 1 tanesini kapatılır ise kendisi de listeden çıkarılacağı için geriye dönen liste boş olur. İşte bu neden ile ana sayfa yönlendirilmiş ve tekrar son client’ın connect olması sağlanmıştır. Böylece 2. bir tab açılması ile pasif hale gelen ilk tab, 2. tab’in kapatılması ile tekrardan yeni bir connectionID alınarak aktif hale getirilmiştir.
    2. Bu durumda geriye içi dolu bir UserList dönülmüştür. Bu koşul da kendi içinde 2 duruma ayrılmaktadır.
      1. Gönderilen UserList içinde client’ın kendi UserName’i var ise, ilgili liste gezilerek “selectUser“‘id li (<select>) html elementi altına, her bir item(Nick, connectionID) tek tek eklenir.
      2. Eğer ilgili liste içerisinde client’ın UserName’i yok ise ==> Aynı user için 2 tab açılması ve 1’inin kapatılması durumu sözkonusudur. Bu durumda yukarıda bahsedildiği gibi, eski pasif tab’in aktif hale getirlimesi ve tekrardan yeni bir connectionID alması için ==>”window.location.href =”/Home/Index”” ana sayfaya yönlenme işlemi yapılır.

  • connection.on(‘SetList’, data => {” : Yeni bir kullanıcı signalR “background:Hub” sınıfına connect olduğu anda server side taraftaki “OnConnectedAsync()” methodu tetiklenir. Burada connect olan client’a ait connectionID çekilir. Daha sonra bu method da çekilen connectionID ile beraber==> Client Side taraftaki “SetConnectionID()” function’ını tetikler. Bu function’da, alınan Nick ve connectionID client side tarafdaki hidden alanlara saklanır. Bu function’ın sonunda ==> server side taraftaki “AddUserList()” methodu çağrılır. Burda da en son alınan Nick ve connectionID, static bir listeye kaydedilir. Burdan da en sonunda client site tarafındaki ==> “Setlist()” function’ını çağrır. Bu function’da, yeni gelen client’ın bilgilerinin de eklendiği statict Dictionary UserList’i gezilerek, “selectUser” dropdown list’i doldurulur.

  • $(“.img-check”).click(function(){” : Sıralanan resimlerden biri tıklanıldığı zaman, Server Side tarafdaki “ChangeBackground()” function’ı tıklanan resmin adı ile birlikte tetiklenir. Amaç ilgili resmin seçilen client’da gösterilmesidir.

Index.cshtml: Sayfanın tam kodu.

Projenin Genel Profili:

Geldik bir makalenin daha sonuna. Bu makalede bir .Net Core Mvc projesinde signalR ile iki client arasında nasıl private iletişeme geçildiğini hep beraber inceledik. Private iletişimde en önemli sorun Connection’ın kaybedilmesidir. İşte bu ve bunun gibi sorunları da neler yapabileceğimizi detaylıca inceledik. Bunun yanında bakmadığımız en önemli konu güvenlik. Bununla ilgili de ilerde bizi güzel şeyler bekliyor. Yukarıda gördüğünüz örümcek ağına benzeyen geçişler :) Client Side ve Server Side tarafdaki bağımlılığı ve herbirine düşen görevleri işleme sırası ile birlikte  göstermektedir.

Yeni bir makalede görüşmek üzere hoşçakalın.

Source Code : https://github.com/borakasmer/.NetCoreSignalRPrivateBackgroundChange

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

3 Cevaplar

  1. Ahmet dedi ki:

    Bora abi selamlar, bu mükemmel makale için teşekkürler. Projeyi win 2012 server iis 8.5 a deploy ettiğimde. background SignalR Hub için 404 (Not Found) hatası alıyorum ama localhost da çalışıyor. Hatanın nedeni ne olabilir ?

    • borsoft dedi ki:

      Büyük ihtimalle SignalR server’a IP’si ile gitmiyorsun. Ya da SignalR’da Cross Domain problemi yaşıyorsun. Şöyle bir ayar yapman lazım:

      public class Startup
      {
      public void Configuration(IAppBuilder app)
      {
      app.Map(“/signalr”, map =>
      {
      map.UseCors(CorsOptions.AllowAll);
      var hubConfiguration = new HubConfiguration { };
      map.RunSignalR(hubConfiguration);
      });
      }
      }

      • ahmet dedi ki:

        Abi cross origin problemini dediğin gibi çözdüm fakat ip adresi ile erişmeme rağmen hub bulunamıyor.
        Error: Failed to start the connection. Error: Not Found şeklinde hata alıyorum

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.