Mvc, SignalR ve Redis ile Microservice Mantığında Kullanıcı Bilgilendirme

Selamlar,

Bu makalede Visual Studio 2017 ile bir Mvc proje yaratılıp, Session Timeout süresi 30sn’nin altında kalan tüm clientlar bir popup penceresi ile uyarılacaklardır. Bu işlem için, SignalR Hub sınıfına connect olan her bir client’ın, connectionID’si, key ve session başlangıç zamanı value olarak, Redis’e kaydedilecektir. (Redis hakkında detaylı bilgiyi, buradaki makalemden ulaşabilirsiniz.) Ayrıca signalR hakkında detaylı bilgiye de bu makalemden erişebilirsiniz.

Daha sonra bu sistemden bağımsız, arkada çalışan bir microservice uygulama redis içerisindeki tüm elemanları gezerek, SessionTimeOut süresinin bitmesine 30 sn kalan tüm clientları, signalR Hub methodunu tetikleyerek websocket ile uyaracak ve işi biten herbir elemanı redisden çıkaracaktır.

Projenin Genel Tablosu:

Örnek Çalışma Ekranı:

 

Öncelikle aşağıdaki paketler Nuget’den indirilir:

Bu makalede Redis için ServiceStack kütüphanesi kullanılmıştır.

Socket teknolojisi için de SignalR kullanılmıştır.

Ayrıca Local Windows 10 bir makinada redis server ve client uygulamaların çalıştırılması için aşağıdaki dosyaların indirilmesi gerekmektedir. Ben aşağıdaki dosyaları bu adresten ,  şu anki son sürüm olan (3.2.100)’i indirdim.

Asp.Net’de Session’ın TimeOut süresi default olarak 20 dakikadır. Aşağıdaki webconfig ayarı ile bu süre test amaçlı 1 dakikaya düşürülmüştür.

Not: “İlk aşamada aşağıda yazılan kodların çoğu ihtiyaca göre tekrardan değiştirilecektir. Tüm code’un bir seferde yazılmamasının nedeni, sizin hangi ihtiyaçlardan ne yazılması gerektiğini daha iyi anlamanızı sağlamaktır.”

HomeController.cs(Index): Aşağıda görüldüğü gibi client sayfaya ilk geldiği zaman test amaçlı Session‘a “UserName” atanmıştır.

Index.cshtml: Aşağıda görüldüğü gibi ilgili “<Head>” section’ında scriptler=> signalR ve jquery yolları tanımlanmıştır. Ayrıca magic script denilen “signalR/hubs” tanımlaması unutulmamalıdır. “$.connection.session” ile Session:Hub sınıfına connect olunur. Ayrıca “hubProxy.client.notifySession()” function’ı ile server sidedaki “Session:Hub” sınıfından gönderilen=> client’ın connectionID‘si ve yine aynı client’a ait Session TimeOut zamanı mesaj olarak console’a basılır.

Class Session: Hub: Aşağıda görüldüğü gibi Client Sayfaya geldiği zaman signalR tarafında Session:Hub sınıfına connect olunur. İşte tam da bu sırada “OnConnected” methodu çağrılır.

  • Öncelikle “RedisEndpoint” yani redis sunucuya bağlanmak için gerekli olan bağlantı ayarlar local makina olarak ayarlanır.
  • section” değişkenine yukarıda webconfig’de tanımlanan session TimeOut süresi “Configuration” ve “SessionStateSection” sınıfları ile çekilerek atanır. Burada amaç session’ın time out olucağı zamanı bir config dosyadan almaktır. Böylece bu süre uygulama çalışırken de kolayca config dosyasından değiştirilebilir.
  • RedisClient’a bağlanıldıktan sonra connect olacak Client’ın connectionID’si “Context.ConnectionId” ile çekilir. Redis’e key olarak eklenecek bu ID’nin, daha önceden var olup olmadığı “ContainsKey()” methodu ile kontrol edilerek, işlem adımlarına devam edilir.
  • client.set()” methodu ile Redis’e bağlanan Client’ın connectionID‘si key olarak ve Session’ın timeout olucağı zaman da value olarak atanır.
  • Son olarak connect olan client, “Clients.Caller” şeklinde yakalanarak client side tarafdaki “notifySession()” function’ı, ilgili client’ın connectionID’si ve Session’ın TimeOut olacağı zaman ile birlikte parametre olarak gönderilir.

Şimdi sıra geldi client’ın session timeout olmasına 30 sn kala gösterilecek olan uyarı mesajının html’ine ve gösterilmesi için tetiklenecek olan javascript’in kodlanmasına:

Öncelikle aşağıdaki gibi bir css oluşturulur.

alert.css:

Örnek Alert Görünümü:

Index.html: Aşağıda görüldüğü gibi  görünümü “display:none” ile gizlenmiş uyarı mesajının verildiği bir “div“‘e konulmuştur. İçerisinde “<strong>” html tagleri ile yazılmış uyarı mesajı bulunmaktadır.

Index.html/Script: Script tagları arasına aşağıdaki “hubclient.notifyUser()” function’ı eklenir. Amacı microservice’te süresi dolan bir client bulunduğunda signalR Hub sınıfı olan Session’da, yeni yazacağımız bir methodun, bu yazdığımız client tarafındaki function’ı tetikleyip, süresi dolan cilent’a ilgili uyarının gösterilmesidir.

Şimdi sıra geldi redis’i her 30sn’de bir kontrol eden Microservice yazmaya. Yukarıda görüldüğü gibi “CheckSession” adında yeni bir Console Application yaratılır.

CheckSession/Program.cs (Full): Bu proje için de yine Service.Stack.Redis kütüphanesi indirilmiştir. Ayrıca session süresi 30’sn kalan clientları uyarmak için aşağıda görüldüğü gibi SignalR.Client indirilmiştir.

  • Öncelikle static olarak connect olunacak remote’daki Redis sunucusunun erişim bilgileri ip ve portu ile birlikte tanımlanır. “new RedisEndpoint() { Host = “127.0.0.1”, Port = 6379 }
  • Redis’deki kayıtların önceden belirlenen 10sn de  bir zaman aralığında taranabilmesi için, yeniden static bir Timer nesnesi yaratılır. “static Timer _Timer = new Timer(10000)
  • Main() methodunda “_Timer” nesnesinin her 10sn bir tetiklenmesinde, çağrılacak “CheckRedis()” methodu bağlanır. Ve _Timer nesnesinin sayma işlemi “Start()” methodu ile başlanır.

CheckRedis():

  • En başta yapılacak işlemin uzun sürebilmesinden dolayı ilgili “_Timer” nesnesi “Stop()” methodu ile durdurulur.
  • using(){} ile ilgili “IRedisClient” create edilir.
  • Redisdeki tüm keyler “client.SearchKeys(“*”)” methodu ile çekilir. Yani tüm clientlara ait “ConnectionID“‘ler.
  • Tüm keyler “foreach()” ile gezilerek, value değer olan session’ın bitiş zamanı, şimdiki zamandan çıkarılarak “seconds” değişkenine kaç sn kaldığı atanır. “var seconds = sessionTime.Subtract(DateTime.Now).TotalSeconds” Böylece kalan zaman saniye cinsinden bulunmuş olunur.
  • Eğer kalan süre 30sn’den az ise yukarıda yazılan Session Hub class’ına bağlanılır. Burada bir de parametre olarak, bağlanılan kaynağın console olduğunu gösteren “console=1” değişkeni atanmıştır. “var connection = new HubConnection(“http://localhost:2533/”, “console=1”)

Önemli Not: “console=1” şeklinde url’de paramtere gönderilme nedeni, SignalR sınıfının “OnConnected()” methodunda gelen kaynağa göre redise yeni bir connectionID’li datanın, atılmasının engellenmesidir. Makelenin devamında “Session : Hub” sınıfının codeları değişecektir.

  • İlgili “Session:Hub” sınıfına Connection işlemi yapılıp beklenildikten sonra, “connection.Start().Wait()” notifyClient() methodu gönderilecek client’ın connectionID’si (key) ve gönderilecek  text mesaj ile birlikte parametre geçilerek tetiklenir(Invoke). “myHub.Invoke(“notifyClient”, new object[]{ key, “Session Süreniz 30sn Sonra Dolucak!.”}).Wait()
  • Uyarı gönderilen client’a ait kayıt (connectionID) keyine göre, redisden çıkarılır. “client.Remove(key)
  • Garbage Collector’un temizleme işlemine zorlanmasından sonra, “_Timer” tekrardan başlatılır. “_Timer.Start()

Program.cs(Full):

Şimdi gelin önce SignalR Session: Hub sınıfına ait yukarıda Invoke edilen notifyClient() methodunun yazımına.

Clientları uyaran örnek ekran çıktıları.

notifyClient(): Aşağıda görüldüğü gibi “notifyClient()” methodunda, session dolma süresi 30sn’nin altında olan clientın “notifyUser()” methodu, connectionID ve  gösterilecek message text parametreleri ile birlikte tetiklenir. Yukarıdaki resimde görüldüğü gibi 2 farklı client connect durumundadır. Soldaki client, microservisde de görüldüğü gibi “27.sn”‘de olduğundan, kendisine uyarı mesajı gösterilmiştir. Sağdaki client henüz “47.9.sn”‘de olduğundan kendisine hiç bir uyarı mesajı çıkarılmamıştır. Her ikisinin de ConnectionID’si browser console’da gözükmektedir. Herbir client, ilgili ConnectionID’lerine göre redis tarafında saklanmaktadır.

Şimdi sıra geldi  SignalR Session:Hub sınıfına ait OnConnected() methodunun değiştirilmesine:

OnConnected(): Aşağıda görüldüğü gibi değişikliğin esas sebebi, “OnConnected()” methodunun, yukarıda yazılan microservice tarafından da çağrılmasından dolayı kayanağın belirlenmesi ihtiyacıdır. Çünkü console yani microservice tarafından yapılan connection kale alınmayacaktır. Bu ayrılıma gidilebilmesi için, Url bazında “console” parametresi gönderilmiştir. Yukarıda anlatılan maddelerin dışında :

  • isFormConsole” değişkeni isteğin Microservice’den mi yoksa Web sayfasından mı geldiğini belirler. Ayrıca “sessionID” her client’a özel unique bir ID’dir.
  • Eğer console’dan gelinmiyor ise tüm işlere devam edilir ==> “if(isFromConsole == “0”)” koşulu eklenmiştir.
  • Not: PersonList aşağıda detaylıca gösterilen ayrı bir sınıftır. Kullanım Amacı: Web sayfasında refresh yapılması durumunda, client’ın var olan connectionID’si değişmektedir. Bu durumda eğer önceden redis tarafında ilgili session’a ait tanımlanmış connectionID var ise, bu listeden(PersonList) ilgili sessionID’ye ait eski connectionID’nin kaldırılıp, yeni connectionID’li kayıdın konarak, redis tarafında güncelleme yapılması gerekmektedir. Böylece, süre dolduğu zaman microservice, ilgili güncellenmiş coonectionID ile doğru client’a erişip notify yapabilecektir. 
  • PersonList sınıfının “Add()” methodu ile  sessionID ve ona ait signalR connectionID değerleri, eğer ilgili Dictionary Listede yok ise atanır. “if (!PersonList.HasSession(sessionID))
  • Session’ın timeout olacağı zaman hesaplanarak “sessionExpireDate” değerine atanır. “DateTime sessionExpireDate = DateTime.Now.AddMinutes(section.Timeout.TotalMinutes)
  • PersonList dictionary listesine “sessionID” key olarak, “ClientData()” sınıfı value olarak atanır. ClientID sınıfı, connect olan client’a ait “ConnectionID” ve “SessionExpireDate” değerlerini alır. “var _clientData = new ClientData() { ClientConnectionID = Context.ConnectionId, ClientSessionTime = sessionExpireDate }
  • * Eğer PersonList’de ilgili sessionID’ye ait bir kayıt var ise, bu durumda client ya ilgili sayfayı yeni bir pencerede açmıştır ya da sayfayı refresh yapmıştır. “if(seconds>30)” session time out süresi 30sn fazla ise, ilgili kayıt Redis’e eklenir. Değil ise kullanıcıya kalan süreye göre uyarı mesajı, signalR socket ile direk gönderilir. “await Clients.Caller.notifyUser(message)
  • “PersonList.ClearExpiredPersonList()” methodu ile session süresi tamamıyla dolmuş kayıtlar, PersonList Dictionary listesinden temizlenir.

*PersonList: PersonList sınıfı, dictionary nesnesinde herbir client’ın sessionID’sini key, bu key’e ait value değerini de ClientData sınıfı şeklinde tutmaktır. ClientData sınıfında client’ın signalR connectionID’si ve session’ın expire olacağı zaman datetime cinsinden saklanmaktadır. En önemli durum olarak, client bulunduğu sayfayı yeniler ise, signalR connectionID’si değişecektir. Bu durumda öncelikle client, disconnect olacağı için ilgili sessionID’ye ait kayıt(connectionID) Redis’den çıkartılır. Daha sonra client tekrardan connect olur ve PersonList dictionary nesnesinden unique sessionID key’i ile ilgili client’a ait sayfaya ilk geldiği session timeout süresi bulunur. Daha sonra Redis’e yeni connectionID’si ve en baştaki session timeout süresi ile tekrardan kaydedilir. Böylece eski session timeout süresi yeni connectionID ile redis tarafında atanmış olunur.

SignalR ‘da Connection’ın Yaşam Döngüsü:

ClientData: PersonList dictionary’de tutulan value tipidir.

HomeController/Index(): Sayafa ilk gelinildiğinde Session değeri “ViewBag.SessionID” ile index.cshtml sayfasına aktarılır.

Index.cshtml(Full):$.connection.hub.qs = “console=0&sessionID=@ViewBag.SessionID”” ile “Session:Hub” sınıfının OnConnected() methoduna client’ın sessionID değeri, parametre olarak gönderilir. Böylece hangi platformdan gelindiği anlaşılır. Böylece gelen client’a ait kayıtlar SessionList ve Redis’e atılır.

OnDisconnected(): Client disconnect olunca:

  • Disconnection işleminin Web tarafından olması url parameter’dan “console“‘a bakılarak araştırılır.
  • Disconnet işlemi webden yapılmış ise ilgili connectionID’ye ait kayıt redis’den kaldırılır.

Basit Yol:  Şimdi bu makaleyi okuyan bazı arkadaşların aklına, aşağıdaki gibi daha kısa bir yol gelebilir. Görüldüğü gibi client taraflı bir timer “Session.Timeout” değerini server side tarafından alıp toplam kalan “sn” değeri “popupTime” değişkenine atanıp geri sayılabilir. Süre dolunca da ilgili uyarı div’i gösterilebilir. Tabi client’ın ilk girdiği zaman yine bir yerde saklanıp kalan süre “sn” cinsinden Session’a atılmalıdır. Videoda da açıkladığım gibi bir sorunun bir çok çözümü vardır. Bu makalede amaç vizyonunuzu açmak ve karşınıza çıkabilecek çok daha komplike durumlar için size fikir vermekdir. Örneğin herkes için bitme zamanının aynı olduğu geri sayan bir online sınavda, herbir client’ın sınav sırasındaki durumunu redis’de tutup, sürenin bitmesine 20 dakika kala hala soruların yarısını tamamlamayanların acele etmeleri için push notification atmak istenir ise, bu sistem gayet başarılı çalışacaktır. İstenildiği kadar farklı bussines rulelar microservis olarak adlandırdığımız console applicationlara dağtılabilir.Herbir bussines logic’e yeni bir microservis yazılabilir. Kişilerin sınav sırasındaki anlık durumları redis’de tutulabilir. Son olarak signalR kullanılarak belirlenen kurallar içerisinde kalan clientlara, yine belirlenen rulelara göre özel mesajlar atılabilir. Böyle bir seneryoda kısa bir yol söz konusu değildir:)

Benim bütün amacım sizin hayal dünyanızı, modern teknolojiler ile süsleyip neler yapabileceğinizi bir nebze olsun göstermektir. Unutmayın yazılımda hiçbirşey imkansız değil, sadece halen çözülmeyi bekleyen bir bulmacadır. İstenen codelar yazılsa dahi, her zaman daha iyi ve daha performanslı bir yolu vardır. Bu da sonsuz bir yolculuktur.

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

 Source Code: https://github.com/borakasmer/SignalR-RedisUserNotify

HomeController.cs(Full):

 

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir