.Net Core SignalR Hub Sınıfı Özelleştirip Dependency Injection ile Kullanma

Selamlar,

Bu makalede signalR hub sınıfına ait bir methodu, başka bir sınıftan dependency Injection yardımı ile çağıracağız. Gelin kısaca konu ile alakalı bir senaryo oluşturalım. Ekanda bir küp şekil olsun. Klavyenin ok tuşlarına basıldığı zaman, küp şekli istenen yönde hareket etsin ve bu hareketin, tüm clientlara bildirilmesi aynı zamanda da loglanması için bir webservisine istekde bulunulsun. Bu durumda, webservisi üzerinden bir signalR Hub sınıfına bağlanılır ve hareket methodu trigger edilerek, bu sayfayı dinleyen tüm clientlardaki küplerin de aynı hareketi yapması sağlanır. Hub sınıfına bağlanan örnek kod aşağıdaki gibi olabilir:

İşte tam olarak yukarıdaki kodu yazmak istemiyoruz. Neden ?

Önemli Not: Öncelikle eğer bir microservices yazmıyor isek, ya da yazacağımız webservices’i, ilgili signalR Hub sınıfı ile aynı sunucu üzerinde ise, yukarıdaki yöntemden başka bir yola gitmek daha doğru olacaktır. Aynı sunucu üzerinde olan signalR Hub sınıfına bağlanmak, hem performans anlamında hem de kod kalitesi açısından pek de sağlıklı değildir. Bu durumda signalR Hub sınıfının, Dependency Injection ile ilgili webapi servisinde kullanılması, performans ve kod kalitesi anlamında çok daha doğru bir yoldur.

Öncelikle aşağıdaki komut ile bir webapi projesi oluşturulur.

Oluşan proje içinde, öncelikle signalR AnimeHub sınıfı aşağıdaki gibi oluşturulur: Herhangi bir client’ın connect olması durumunda, connect olan client’a connectionId’si gönderilir. ConnectionId’ye neden ihtiyacımız olduğuna, makalenin devamında değinilecektir.

Tam bu sırada, signalR Hub sınıfı ile WebApi servisi arasında ara bir katmanın oluşturulması, genişletilebilir bir yapının tüm olanaklarını kullanabilmemizi bize sağlar . İlgili katmanda, signalR Hub sınıfında olması istenen tüm methodlar özelleştirilerek yazılır.

IHubAnimeDispatcher: Dependency Injection için, ilgili “HubAnimeDispatcher” class’ı, “IHubAnimeDispatcher” interface’inden türetilir. Bu interface’de, kullanılması zorunlu kılınan method “Move()” methodudur. Parametre olarak “MoveType” enumu ve “connectionId” beklemektedir. Yani block’un hareket ettirildiği yön ve 2. bir parametre olarak client’ın Hub sınıfından aldığı connectionId beklenmektedir. ConnectionId’nin alınmasında amaç, hareket’in yapıldığı client’a, “Move()” methodunun tekrardan triger edilmemesidir. Yani ilgili clientda, zaten hareket eden block’a, tekrardan hareket et komutunun çalıştırılmaması amaçlanmaktadır.

MoveType: Klavyede Keydown ile basılan yön-ok tuşlarının ascii kodları, enum olarak MoveType’da tanımlanır.

HubAnimeDispatcher: İlgili sınıfda, signalR “IHubContext<AnimeHub>” tanımlanır ve Dependency Injection ile Constructor’ında atanır. Böylece ilgili HubContext’e erişilmiş olunur. Bundan sonra signalR Hub class’ında kullanılmak istenen methodlar, bu özelleştirilmiş sınıf altında tanımlanabilir. Mesela bu örnekte, ilgili bloğu hareket ettirmek için Move() methodu tanımlanmıştır.

İlgili HubAnimeDispatcher sınıfının, Dependency Injection ile kullanılabilmesi için Startup.cs’de aşağıdaki gibi tanımlanması gerekmektedir. Ayrıca projede signalR kullanılacağı için, AddSignalR() methodu çağrılır. Son olarak Cross Domain hatasına düşmemek için, ilgili izinlerin aşağıdaki gibi verilmesi gerekmektedir :

Startup.cs:

Startup.cs / Configure(): Ayrıca SignalR Hub sınıfı, aşağıdaki gibi Configure methodu içinde routing amaçlı tanımlanmalıdır. Cross Domain’e düşmemek için, ilgili izinlerin verilmesi gerekmektedir. ConfigureServices’de tanımlanan Policy, burada UseCors() ile beraber kullanılır.

Projenin çalışacağı portun özelleştirilmesi için, program.cs altında CreateHostBuilder() methoduna “webBuilder.UseUrls()” tanımlaması gerekmektedir.

Program.cs:

Log : Her yapılan hareketin Move(), yani yönlendirme işinin Sql bir DB’ye yazılarak Loglanması sağlanır.

Model/MoveLog: Öncelikle, aşağıda görüldüğü gibi MoveLog adında yeni bir model oluşturulur.

SqlDB tarafında aşağıdaki gibi bir tablo oluşturulur.

.Net Core Projesine, aşağıdaki komut ile EntityFramework kütüphanesi indirilir.

Dal/CustomerDataContext : DbContext aşağıdaki gibi oluşturulur.

Startup.cs/ConfigureServices() : Yukarıda oluşturulan “CustomerDataContext”‘in Startup.cs’de, Dependency Injection ile kullanılabilmesi için aşağıdaki gibi tanımlanır.

appsettings.json: DefaultConnection aşağıdaki gibi tanımlanır.

AnimeController.cs : Sıra geldi, esas webapi servisinin oluşturulmasına.

  • “private readonly IHubAnimeDispatcher _dispatcher” : Erişilecek Hub interface’ı, burada tanımlanmıştır
  • ÖNEMLİ : “public AnimeController(IHubAnimeDispatcher dispatcher)” : Makalenin esas amacı olan, Constructor‘da “IHubAnimeDispatcher” sınıfı parametre olarak alınır. Ve “_dispatcher“‘a atanır. Böylece ilgili Hub sınıfına connect yapılmadan erişilmiş olunur. Ayrıca Loglama için “CustomerDataContext” dependency Injection ile _context nesnesine atanmıştır.
  • “public ActionResult<IEnumerable<string>> Get()” : Test amaçlı webApi servisi ayaktamı diye kontrol amaçlı yazılmış bir methoddur.
  • “public async Task<ActionResult> Get(MoveType moveType, string connectionId)”: ClientSide taraftan hareket ettirilen block’un hareket yönü, diğer clientlara webapi servisi çağrılıp signalR ile haber verilmektedir. Bu işlem sırasında yukarıda tanımlanan “HubAnimeDispatcher” sınıfının “Move()” methodu çağrılmıştır. Parametre olarak, hareketin yönü MoveType ve hareketin yapıldığı client’ın signalR ConnectionId’si(connectionId) kullanılmaktadır.
  • Yapılan hareketin MoveLog tablosuna loglanması için, “_context.MoveLog” tablosuna, yeni bir kayıt atılır ve _context.SaveChanges() methodu ile kaydedilir.

Örnek MoveLog tablosundan kayıtlar:

Web Client:

Web uygulaması için Angular 8.1.3 projesi aşağıdaki gibi oluşturulur:

Oluşturulan projeye, signalR kütüphanesi aşağıdaki komut ile eklenir:

app.component.ts(1): Aşağıda görülen signalR kütüphaneleri import edilir.

app.component.ts(2):

  • Aşağıda görüldüğü gibi signalR Hub sınıfınına connect olunması durumunda console’a “Hub Connection Start” mesajı yazılmıştır.
  • GetConnectionId() client side function’ında connect olan client’ı ekrana basılmıştır.
  • MoveBlock() makalenin devamında yazılacaktır. Şimdilik esas amcı ekrandaki bloğun hareket ettirilmesidir. Makalenin devamında, içi doldurulacaktır.

app.component.html: Html sayfası, yukarıda görüldüğü gibi kırmızı bir başlık ve hareket ettirilecek mor bir kareden oluşmaktadır.

app.component.scss: Html sayfa üzerindeki karenin ve hareket yapılacağı div’in style’ı aşağıdaki gibi tanımlanmıştır.

  • SQUARE_SIZE: Karenin boyutu bir değişkende saklanır.
  • MOVEMENT_STEP: Karenin her bir hareketin için ilerleyeceği pixel miktarıdır.
  • requestAnimationFrame : Bloğun animatif olarak hareket ettirilmesi için atanan değişkendir.
  • direction : Bloğun hareket edeceği yöndür. Default 12 atanmıştır. Hiçbir yönü ifade etmediği için, en başta blok durmaktadır.

moveSqure() : Karenin hareket ettirilmesi, aşağıdaki method ile yapılmaktadır.

  • left,top,right,bottom : Öncelikle karenin bulunduğu noktanın kordinatları, karenin height ve weight’i de göz önüne alınarak bulunur.
  • switch (this.direction) { } : Direction ile emir verilen hareket yönü belirlenir.
  • 37,38,39,40 : İstenen yöne, belli bir aralığa (MOVEMENT_STEP) göre hareket ettirilir.
    • if (right < document.documentElement.clientWidth) : 39 (Sağ) için ekranın genişliğine kadar izin verilir.
    • if (bottom < document.documentElement.clientHeight-80) : 40 (aşağı) için ekranın yüksekliği kadar izin verilir. Ordaki “-80”, karenin en son sayfanın tabanında durması için yapılan küçük bir rutuşdur :)
  • requestAnimationFrame(): Bloğun, animatif olarak hareket etmesini sağlayan function’dır.
  • @ViewChild : Html taraftaki #squre olarak tanımlı “div”‘i ifade etmektedir. Böylece component tarafında ilgili kareye erişilip, pozisyonu değiştirilebilecektir.

  • Constructor’da, blok hareket ettirildiğinde ilgili webapi servisinin(AnimeController) çağrılması için angular tarafında yazılan services, Dependency Injection ile alınmıştır.
  • “ngAfterViewInit” : Sayfa üzerindeki tüm elementler yüklendikten sonra, ilgil method çağrılır. Amaç bloğun koordinatlarının default olarak atanmasıdır.

Blok hareket ettirildikten sonra, SignalR Hub sınıfından tüm clientların tetikleneceği MoveBlock() function’ı aşağıdaki gibidir.

  • “if (connectionId != _this._connectionId) {” : Buradaki kontrol, bloğu hareke ettirilen client kendisi ise moveSquare() methodu tekrardan tetiklenmez. Bu gelen connectionId’nin kendisininki ile aynı olup olmamasından anlaşılır.
  • “_this.requestAnimationFrame” kendisine bağlanan methodu, animatif olarak çağırmaktadır. Burada çağrılan method, demin yukarıda tanımladığımız “moveSquare()” methodudur.

Sayfa üzerindeki bloğun, yön tuşları ile hareket ettirildiği makalenin başında anlatılmıştı. Bu işlem sayfa üzerinde herhangi bir tuşa basıldığında tetiklenen “onkeydown()” function’ı ile yapılmaktadır. Basılan tuşun keyCode değeri, >37 ve <40 arasında ise yö ntuşlarında birine basılmış demektir. Diğer basılan tuşlar için herhangi bir işlem yapılmamaktadır.

  • direction : Bloğun hareket ettirileceği yön atanır.
  • _this.requestAnimationFrame(_this.moveSquare.bind(_this)) : Blok gelen komut yönünde animatif olarak hareket ettirilir.
  • _this.service.moveBlock(): Bir sonraki adımda tanımlanacak service’den moveBlock() methodu, yöne ve connectionId parametreleri ile çağrılmaktadır.

service.ts: İlgili servicesde anime webapi servisine bağlanıp, signalR Hub sınıfına bağlı moveBlock() methodu çağrılmakta, ve böylece bloğa yapılan hareket tüm clientlara bildirmekte ve SqldB’ye loglamaktadır.

Angular tarafında ilgili servisin kullanılabilmesi için, module’de aşağıdaki gibi tanımlanması gerekmektedir.

Bu makalede, signalR Hub sınıfına ara bir katman oluşturularak istenen methodlar burada tanımlanmıştır. Bu şekilde ilgili hubcontext özelleştirilmiştir. Daha sonra .NetCore Webapi bir servisde, dependency injection ile ilgili SignalR hubcontext’e erişilip, Move() methodu tetiklenmiş ve yapılan hareket Sql bir DB’ye kaydedilmiştir.  Ayrıca HubContext, bir sınıfa connection işlemi yapılmadan constructor’dan alınarak erişilmesi, performansda büyük bir artışa sebebiyet vermiştir. Son olarak ara bir katman kullanılarak, ilgili HubContext’in özelleştirilmesi, diğer yapılarda aynı hubcontex’in farklı methodlar ile kullanılabilmesi imkanını sağlamıştır. Bu da genişletilebilir, değiştirilmesi hızlı ve kolay olan, okunaklığı arttırılmış bir koda ulaşılmasına imkan sağlamıştır.

Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.

Source: https://docs.microsoft.com/tr-tr/aspnet/core/signalr/hubcontext?view=aspnetcore-2.2, https://stackoverflow.com/questions/5605588/how-to-use-requestanimationframe

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

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

9 Cevaplar

  1. Veli dedi ki:

    .net core version’nun 3.0+ olması gerekiyor (startup daki ayarlar için) onu update etmeyi unutmayın arkadaşlar. Yazı için teşekkürler ???
    https://dotnet.microsoft.com/download/thank-you/dotnet-sdk-3.0.100-preview7-macos-x64-installer

  2. mahmut dedi ki:

    Merhabalar Bora abi,
    Bu yazıda geçen

    “Önemli Not: Öncelikle eğer bir microservices yazmıyor isek, ya da yazacağımız webservices’i, ilgili signalR Hub sınıfı ile aynı sunucu üzerinde ise, yukarıdaki yöntemden başka bir yola gitmek daha doğru olacaktır. Aynı sunucu üzerinde olan signalR Hub sınıfına bağlanmak, hem performans anlamında hem de kod kalitesi açısından pek de sağlıklı değildir. Bu durumda signalR Hub sınıfının, Dependency Injection ile ilgili webapi servisinde kullanılması, performans ve kod kalitesi anlamında çok daha doğru bir yoldur.”

    kısmı ile ilgili olarak yazıdaki yaklaşımın yerine IHostedService kullanımının eksi bir yönü var mıdır?

Umit Yücesoy için bir cevap yazın Cevabı iptal et

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