SignalR ile Gerçek Zamanlı .NET Uygulamaları

12 dakika okuma9 Mart 2026
SignalR .NETReal-time .NETWebSocket .NETSignalR HubSignalR RedisSignalR scalingReal-time dashboardSignalR authentication

# .NET ile SignalR Kullanarak Gercel Zamanli Uygulamalar

Canli panolar, sohbet sistemleri, bildirim akislari, ortak duzenleme araclar -- bunlar klasik istek-yanit modeliyle yapilamaz. Kalici baglanti, tasinma protokolu secimi ve yeniden baglanma mantigi gerektirir. SignalR bu karmasikligi soyutlayarak temiz bir programlama modeli sunar. Gercek zamanli izleme panolari gelistirdigim projelerde, SignalR her zaman fikirden uretime en hizli yol oldu.

Neden Ham WebSocket Yerine SignalR

SignalR sadece bir WebSocket sarmalayicisi degil. Tasinma protokolu geri donusu (WebSockets, Server-Sent Events, Long Polling), otomatik yeniden baglanma, baglanti yasam dongusu yonetimi ve mesaj serializasyonu gibi kritik islevleri icerir. Bunlarin hepsini sifirdan yazmak mumkun ama is uygulamalari icin nadiren deger.

Temel avantajlar:

  • Istemci ve sunucu yeteneklerine gore otomatik protokol secimi
  • Yerlesik grup ve kullanici yonetimi
  • Derleme zamaninda tip guvenligi sunan strongly-typed hub'lar
  • ASP.NET Core kimlik dogrulama ve yetkilendirme ile dogal entegrasyon
  • Redis, Azure SignalR Service veya ozel backplane ile olceklendirme destegi
  • SignalR Hub Kurulumu

    Hub, istemci iletisiminin sunucu tarafindaki giris noktasidir. Hub uzerindeki her public metot, bagli istemciler tarafindan cagrilabilir.

    Temel Hub

    csharp
    public class BildirimHub : Hub
    {
        private readonly ILogger<BildirimHub> _logger;
    
        public BildirimHub(ILogger<BildirimHub> logger)
        {
            _logger = logger;
        }
    
        public async Task BildirimGonder(string kullanici, string mesaj)
        {
            _logger.LogInformation(
                class="code-string">"{ConnectionId} kullanicisinden {Kullanici} kullanicisina bildirim",
                Context.ConnectionId, kullanici);
    
            await Clients.User(kullanici).SendAsync(class="code-string">"BildirimAl", mesaj);
        }
    
        public async Task HerkeseMesajGonder(string mesaj)
        {
            await Clients.All.SendAsync(class="code-string">"MesajAl", Context.User?.Identity?.Name, mesaj);
        }
    
        public override async Task OnConnectedAsync()
        {
            _logger.LogInformation(class="code-string">"Istemci baglandi: {ConnectionId}", Context.ConnectionId);
            await base.OnConnectedAsync();
        }
    
        public override async Task OnDisconnectedAsync(Exception? exception)
        {
            _logger.LogInformation(
                class="code-string">"Istemci ayrildi: {ConnectionId}, Sebep: {Sebep}",
                Context.ConnectionId, exception?.Message ?? class="code-string">"temiz ayrilma");
            await base.OnDisconnectedAsync(exception);
        }
    }

    Program.cs Kaydi

    csharp
    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddSignalR(options =>
    {
        options.EnableDetailedErrors = builder.Environment.IsDevelopment();
        options.KeepAliveInterval = TimeSpan.FromSeconds(class="code-number">15);
        options.ClientTimeoutInterval = TimeSpan.FromSeconds(class="code-number">30);
        options.MaximumReceiveMessageSize = class="code-number">64 * class="code-number">1024; class=class="code-string">"code-comment">// class="code-number">64 KB
    });
    
    var app = builder.Build();
    
    app.MapHub<BildirimHub>(class="code-string">"/hubs/bildirimler");
    app.Run();

    Strongly-Typed Hub'lar

    `SendAsync("MesajAl", ...)` seklindeki string tabanli metot isimleri kirilgandir. Bir yazim hatasi derleme sirasinda yakalanmaz, calisma zamaninda sessizce basarisiz olur. Strongly-typed hub'lar bu sorunu tamamen ortadan kaldirir.

    Istemci Arayuzu Tanimla

    csharp
    public interface IBildirimIstemcisi
    {
        Task BildirimAl(string mesaj);
        Task MesajAl(string kullanici, string mesaj);
        Task KullaniciGrubaKatildi(string kullanici, string grup);
        Task KullaniciGruptenAyrildi(string kullanici, string grup);
        Task PanelGuncellemesiAl(PanelVerisi veri);
    }

    Strongly-Typed Hub Uygulamasi

    csharp
    public class BildirimHub : Hub<IBildirimIstemcisi>
    {
        public async Task BildirimGonder(string kullanici, string mesaj)
        {
            class=class="code-string">"code-comment">// Derleme zamaninda kontrol edilir -- artik sihirli string yok
            await Clients.User(kullanici).BildirimAl(mesaj);
        }
    
        public async Task HerkeseMesajGonder(string mesaj)
        {
            var gonderen = Context.User?.Identity?.Name ?? class="code-string">"Anonim";
            await Clients.All.MesajAl(gonderen, mesaj);
        }
    
        public async Task PanelGuncellemesiGonder(PanelVerisi veri)
        {
            await Clients.Group(class="code-string">"panel-izleyicileri").PanelGuncellemesiAl(veri);
        }
    }

    Gercek zamanli panolar gelistirirken, strongly-typed hub'lar derleme sirasinda birden fazla hatanin yakalanmasini sagladi. Arayuz tanimlamanin ilk maliyeti kucuk ama geri donusu aninda.

    Istemci-Sunucu Iletisim Kaliplari

    SignalR farkli senaryolara uygun cesitli iletisim kaliplarini destekler.

    Sunucudan Istemciye

    En yaygin kalip. Sunucu, bagli istemcilere veriyi istemciler talep etmeden iter.

    csharp
    class=class="code-string">"code-comment">// Hub metodu icinden
    await Clients.All.MesajAl(kullanici, mesaj);              class=class="code-string">"code-comment">// Tum istemciler
    await Clients.Caller.BildirimAl(class="code-string">"Onaylandi");              class=class="code-string">"code-comment">// Sadece arayan
    await Clients.Others.MesajAl(kullanici, mesaj);            class=class="code-string">"code-comment">// Arayan haric herkes
    await Clients.User(userId).BildirimAl(msg);                class=class="code-string">"code-comment">// Belirli kullanici
    await Clients.Group(class="code-string">"yoneticiler").BildirimAl(msg);        class=class="code-string">"code-comment">// Grup uyeleri

    Istemciden Sunucuya

    Istemciler hub metotlarini dogrudan cagirabilir. JavaScript istemci ornegi:

    javascript
    const connection = new signalR.HubConnectionBuilder()
        .withUrl(class="code-string">"/hubs/bildirimler", {
            accessTokenFactory: () => getAccessToken()
        })
        .withAutomaticReconnect([class="code-number">0, class="code-number">2000, class="code-number">5000, class="code-number">10000, class="code-number">30000])
        .configureLogging(signalR.LogLevel.Information)
        .build();
    
    connection.on(class="code-string">"MesajAl", (kullanici, mesaj) => {
        console.log(`${kullanici}: ${mesaj}`);
        mesajiArayuzeEkle(kullanici, mesaj);
    });
    
    connection.on(class="code-string">"PanelGuncellemesiAl", (veri) => {
        paneliGuncelle(veri);
    });
    
    async function mesajGonder(mesaj) {
        await connection.invoke(class="code-string">"HerkeseMesajGonder", mesaj);
    }
    
    await connection.start();

    Hub Disinda Hub Metotlarini Cagirma

    Uretim sistemlerinde mesajlari genellikle arka plan servisleri, API controller'lar veya olay isleyicilerinden gondermeniz gerekir.

    csharp
    public class SiparisIslemServisi : BackgroundService
    {
        private readonly IHubContext<BildirimHub, IBildirimIstemcisi> _hubContext;
    
        public SiparisIslemServisi(
            IHubContext<BildirimHub, IBildirimIstemcisi> hubContext)
        {
            _hubContext = hubContext;
        }
    
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                var guncellemeler = await BekleyenGuncellemeleriGetir();
    
                foreach (var guncelleme in guncellemeler)
                {
                    await _hubContext.Clients.User(guncelleme.KullaniciId)
                        .BildirimAl($class="code-string">"Siparis {guncelleme.SiparisId} durumu: {guncelleme.Durum}");
                }
    
                await Task.Delay(TimeSpan.FromSeconds(class="code-number">5), stoppingToken);
            }
        }
    }

    Grup ve Kullanici Yonetimi

    Gruplar, hangi istemcilerin hangi mesajlari alacagini organize etmenin temel mekanizmasidir. Hafif, dinamik ve on kayit gerektirmez.

    Grup Islemleri

    csharp
    public class IsbirligiHub : Hub<IIsbirligiIstemcisi>
    {
        private readonly IGrupTakipci _grupTakipci;
    
        public IsbirligiHub(IGrupTakipci grupTakipci)
        {
            _grupTakipci = grupTakipci;
        }
    
        public async Task ProyeKatil(string projeId)
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, $class="code-string">"proje-{projeId}");
    
            var kullaniciAdi = Context.User?.Identity?.Name ?? class="code-string">"Anonim";
            await _grupTakipci.KullaniciGrubaEkle(projeId, kullaniciAdi, Context.ConnectionId);
    
            await Clients.Group($class="code-string">"proje-{projeId}")
                .KullaniciGrubaKatildi(kullaniciAdi, projeId);
        }
    
        public async Task ProjeyiTerkEt(string projeId)
        {
            await Groups.RemoveFromGroupAsync(Context.ConnectionId, $class="code-string">"proje-{projeId}");
    
            var kullaniciAdi = Context.User?.Identity?.Name ?? class="code-string">"Anonim";
            await _grupTakipci.KullaniciGruptanCikar(projeId, Context.ConnectionId);
    
            await Clients.Group($class="code-string">"proje-{projeId}")
                .KullaniciGruptenAyrildi(kullaniciAdi, projeId);
        }
    
        public override async Task OnDisconnectedAsync(Exception? exception)
        {
            var gruplar = await _grupTakipci.BaglantiIcinGruplariGetir(Context.ConnectionId);
            foreach (var grup in gruplar)
            {
                await _grupTakipci.KullaniciGruptanCikar(grup, Context.ConnectionId);
                await Clients.Group($class="code-string">"proje-{grup}")
                    .KullaniciGruptenAyrildi(Context.User?.Identity?.Name ?? class="code-string">"Anonim", grup);
            }
    
            await base.OnDisconnectedAsync(exception);
        }
    }

    Kritik detay: SignalR gruplari kalici degildir. Sunucu yeniden basladiginda tum grup uyelikleri kaybolur. Uygulamaniz kalici grup uyeligi gerektiriyorsa, bunu kendi veri deponuzda tutun ve yeniden baglanma sirasinda gruplara tekrar katilin.

    SignalR ile Kimlik Dogrulama

    SignalR, ASP.NET Core kimlik dogrulamasiyla dogrudan entegre olur. API endpoint'lerinizi koruyan ayni JWT veya cerez kimlik dogrulamasi hub'larinizi da korur.

    SignalR icin JWT Kimlik Dogrulama

    WebSocket baglantilari ilk el sikismadan sonra ozel HTTP basliklar gonderemez. SignalR bu sorunu, muzakere asamasinda token'i query string parametresi olarak kabul ederek cozer.

    csharp
    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = builder.Configuration[class="code-string">"Jwt:Issuer"],
                ValidAudience = builder.Configuration[class="code-string">"Jwt:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(builder.Configuration[class="code-string">"Jwt:Key"]!))
            };
    
            options.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    var accessToken = context.Request.Query[class="code-string">"access_token"];
                    var path = context.HttpContext.Request.Path;
    
                    if (!string.IsNullOrEmpty(accessToken) &&
                        path.StartsWithSegments(class="code-string">"/hubs"))
                    {
                        context.Token = accessToken;
                    }
    
                    return Task.CompletedTask;
                }
            };
        });

    Hub Metotlarini Guvenli Hale Getirme

    csharp
    [Authorize]
    public class GuvenliHub : Hub<IBildirimIstemcisi>
    {
        [Authorize(Roles = class="code-string">"Admin")]
        public async Task YoneticiDuyurusu(string mesaj)
        {
            await Clients.All.BildirimAl(mesaj);
        }
    
        [Authorize(Policy = class="code-string">"PremiumKullanici")]
        public async Task PremiumOzellik(string veri)
        {
            await Clients.Caller.BildirimAl($class="code-string">"Premium sonuc: {veri}");
        }
    }

    Redis Backplane ile SignalR Olceklendirme

    Tek bir sunucu tum baglantilari bellekte yonetir. Bir yuk dengeleyici arkasinda birden fazla sunucuya olceklendiginde, Sunucu A'ya bagli bir istemci Sunucu B'den gonderilen mesajlari alamaz. Redis backplane bu sorunu tum sunucular arasinda mesaj yayinlayarak cozer.

    Yapilandirma

    csharp
    builder.Services.AddSignalR()
        .AddStackExchangeRedis(builder.Configuration.GetConnectionString(class="code-string">"Redis")!, options =>
        {
            options.Configuration.ChannelPrefix = RedisChannel.Literal(class="code-string">"SignalR");
        });

    Nasil Calisir

  • Istemci Sunucu A'ya baglanir ve "panel" grubuna katilir
  • Sunucu B'deki bir arka plan servisi "panel" grubuna mesaj gonderir
  • Sunucu B mesaji Redis'e yayinlar
  • Sunucu A Redis kanalina abone olur, mesaji alir ve istemciye iletir
  • Ne Zaman Azure SignalR Service Kullanilmali

    Birkac bin esanlamli baglantinin uzerindeki uretim yuklerinde Azure SignalR Service'i dusunun. Baglanti yonetimini tamamen devreder, yapiskan oturum, Redis altyapisi ve baglanti sayisi kapasite planlamasi ihtiyacini ortadan kaldirir.

    csharp
    builder.Services.AddSignalR()
        .AddAzureSignalR(builder.Configuration[class="code-string">"Azure:SignalR:ConnectionString"]);

    Hata Yonetimi ve Yeniden Baglanma Stratejisi

    Gercek zamanli baglantiler dogasi geregi kirilgandir. Aglar kesilir, sunucular yeniden baslar, istemciler uyku moduna gecer. Saglikli bir yeniden baglanma stratejisi uretim sistemleri icin tartisilamaz.

    Sunucu Tarafinda Hata Yonetimi

    csharp
    public class DayanikliHub : Hub<IBildirimIstemcisi>
    {
        private readonly ILogger<DayanikliHub> _logger;
    
        public DayanikliHub(ILogger<DayanikliHub> logger)
        {
            _logger = logger;
        }
    
        public async Task VeriIsle(VeriIstegi istek)
        {
            try
            {
                var sonuc = await DogrulaVeIsle(istek);
                await Clients.Caller.BildirimAl($class="code-string">"Basarili: {sonuc}");
            }
            catch (ValidationException ex)
            {
                _logger.LogWarning(ex, class="code-string">"{ConnectionId} icin dogrulama basarisiz", Context.ConnectionId);
                throw new HubException($class="code-string">"Dogrulama hatasi: {ex.Message}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, class="code-string">"{ConnectionId} icin beklenmeyen hata", Context.ConnectionId);
                throw new HubException(class="code-string">"Beklenmeyen bir hata olustu. Lutfen tekrar deneyin.");
            }
        }
    }

    `HubException` firmak mesaji istemciye gonderir. Diger exception turleri `EnableDetailedErrors` acik degilse genel bir hata mesajiyla sonuclanir. Uretimde asla detayli hatalari etkinlestirmeyin -- yigin izlerini ve dahili durumu sizdirir.

    Istemci Tarafinda Yeniden Baglanma

    javascript
    const connection = new signalR.HubConnectionBuilder()
        .withUrl(class="code-string">"/hubs/bildirimler")
        .withAutomaticReconnect({
            nextRetryDelayInMilliseconds: (retryContext) => {
                if (retryContext.elapsedMilliseconds < class="code-number">60000) {
                    return Math.random() * class="code-number">10000;
                }
                return null; class=class="code-string">"code-comment">// class="code-number">60 saniye sonra denemeyi birak
            }
        })
        .build();
    
    connection.onreconnecting((error) => {
        baglantiDurumunuGoster(class="code-string">"Yeniden baglaniyor...");
    });
    
    connection.onreconnected((connectionId) => {
        baglantiDurumunuGoster(class="code-string">"Bagli");
        class=class="code-string">"code-comment">// Yeniden baglanma sonrasi gruplara tekrar katil
        gruplaraYenidenKatil();
    });
    
    connection.onclose((error) => {
        baglantiDurumunuGoster(class="code-string">"Baglanti kesildi");
        setTimeout(() => baglantiBaslat(), class="code-number">5000);
    });

    Bircogu takimin kacirdigi kritik detay: yeniden baglanma sonrasinda istemci yeni bir ConnectionId alir. Onceki baglantidaki grup uyelikleri kaybolur. Gruplara acikca tekrar katilmaniz gerekir. Gercek zamanli panolar gelistirirken, istemci tarafinda aktif aboneliklerin listesini tutar ve her yeniden baglanmada bunlari tekrar oynatirdim.

    SignalR vs WebSockets vs Server-Sent Events

    Her teknoloji farkli kullanim senaryolarina hizmet eder. Yanlis secim gereksiz karmasikliga veya eksik yeteneklere yol acar.

    | Ozellik | SignalR | Ham WebSockets | Server-Sent Events (SSE) |

    |---|---|---|---|

    | Yon | Cift yonlu | Cift yonlu | Sadece sunucudan istemciye |

    | Tasinma | Otomatik | Sadece WebSocket | HTTP akisi |

    | Yeniden baglanma | Yerlesik | Manuel | Yerlesik (EventSource) |

    | Tarayici destegi | Evrensel (geri donuslu) | Modern tarayicilar | Modern tarayicilar |

    | Mesaj formati | JSON/MessagePack | Ham byte/metin | Sadece metin |

    | Grup/Kullanici | Yerlesik | Manuel | Manuel |

    | Olceklendirme | Redis/Azure backplane | Ozel cozum | Ozel cozum |

    | Kimlik dogrulama | ASP.NET Core yerlesik | Manuel | Standart HTTP |

    | Karmasiklik | Dusuk | Yuksek | Dusuk |

    | En uygun | .NET tam yigin uygulamalar | Ozel protokoller, oyunlar | Basit bildirimler, akislar |

    SignalR'i secin -- .NET ekosistemindeyseniz ve minimum sablonla cift yonlu iletisim gerekiyorsa. Ham WebSockets'i secin -- ozel ikili protokol veya cok oyunculu oyun motoru yapiyorsaniz. SSE'yi secin -- sadece sunucudan istemciye akis gerekiyorsa ve en basit uygulamayi istiyorsaniz.

    Yaygin SignalR Hatalari

    1. Hub Orneklerinde Durum Saklamak

    Hub'lar gecicidir. Her metot cagrisinda yeni bir ornek olusturulur. Hub sinifinda alan olarak saklanan her durum cagrilar arasinda kaybolur.

    csharp
    class=class="code-string">"code-comment">// YANLIS -- bu sayac her cagride sifirlanir
    public class KotuHub : Hub
    {
        private int _mesajSayisi = class="code-number">0;
    
        public async Task MesajGonder(string msg)
        {
            _mesajSayisi++; class=class="code-string">"code-comment">// Her zaman class="code-number">1
            await Clients.All.SendAsync(class="code-string">"Sayac", _mesajSayisi);
        }
    }
    
    class=class="code-string">"code-comment">// DOGRU -- enjekte edilmis singleton servis kullanin
    public class IyiHub : Hub
    {
        private readonly IMesajSayaci _sayac;
    
        public IyiHub(IMesajSayaci sayac) => _sayac = sayac;
    
        public async Task MesajGonder(string msg)
        {
            var sayi = _sayac.Artir();
            await Clients.All.SendAsync(class="code-string">"Sayac", sayi);
        }
    }

    2. Hub'i Uzun Sureli Islemlerle Bloklamak

    Hub metotlari SignalR baglantisinin thread'inde calisir. Bloklama, istemcinin diger mesajlari almasini engeller.

    3. Yeniden Baglanma Sonrasi Gruplara Tekrar Katilmayi Unutmak

    Yeniden baglanma yeni bir baglanti olusturur. Gruplar otomatik olarak geri yuklenmez. Bu, "gelistirmede calisiyor ama uretimde calismiyor" hatalarinin bir numarali kaynagindir.

    4. CORS'u Dogru Yapilandirmamak

    SignalR hem HTTP (muzakere icin) hem WebSocket kullanir. CORS her ikisine de izin vermelidir.

    csharp
    builder.Services.AddCors(options =>
    {
        options.AddPolicy(class="code-string">"SignalRPolitikasi", policy =>
        {
            policy.WithOrigins(class="code-string">"https:class="code-commentclass="code-string">">//uygulama.ornek.com")
                  .AllowAnyHeader()
                  .AllowAnyMethod()
                  .AllowCredentials(); class=class="code-string">"code-comment">// SignalR icin gerekli
        });
    });

    5. Mesaj Boyutu Sinirlarini Gormezden Gelmek

    Varsayilan maksimum mesaj boyutu 32 KB'dir. Bu siniri ayarlamadan buyuk yukler gondermek sessiz baglantiyi koparmaya neden olur.

    6. Yuksek Is Hacmi Senaryolarinda MessagePack Kullanmamak

    JSON varsayilan protokoldur, ama yuksek frekansl guncellemeler icin MessagePack ikili serializasyonu yuk boyutunu %30-50 azaltir.

    csharp
    builder.Services.AddSignalR()
        .AddMessagePackProtocol();

    Sonuc

    SignalR, gercek zamanli iletisimi sistem duzeyinde bir zorluktan basit bir uygulama duzeyinde ozellige donusturur. Strongly-typed hub kaliplari, yerlesik grup yonetimi ve kesintisiz kimlik dogrulama entegrasyonu, canli guncellemelere ihtiyac duyan .NET uygulamalari icin dogal tercih yapar. Kritik kararlar olceklendirme stratejisi (Redis vs Azure SignalR Service), yeniden baglanma yonetimi ve hub'larin gecici oldugunu anlamak etrafinda doner. Bunlari dogru yapin ve SignalR arka plana karisir -- iyi altyapinin yapmasi gereken tam da budur.

    Gercek zamanli ozelliklerinizi tasarlamanizda ve SignalR olceklendirme stratejinizi uretim icin gozden gecirmemde yardimci olabilirim.

    İlgili Makaleler

    Flutter Projeniz mi Var?

    iOS, Android ve web için yüksek performanslı Flutter uygulamaları geliştiriyorum.

    İletişime Geç