Asp .NET Core Mvc Bir Projede Session Bilgilerini Redis’de Tutma
Selamlar,
Bugün Asp.Net Core ile Web Sayfarı üzerinde yoğun yüklerin olduğu durumlarda, distributed cache ile var olan yükleri nasıl azaltırız hep beraber inceleyeceğiz. Burada diğer durumlardan farklı olarak, eski alışkanlıklarımız olan ya da var olan projede bulunan “Session“‘larımızı hiç değiştirmeden kullanacağız.
Bildiğiniz gibi Session ile çalışmak yoğun trafikli portallarda hiç de tavsiye edilmiyen bir yöntemdir. Çünkü ilgili Session kaybolana kadar server side tarafda “In Memory” olarak tutulmaktadır. Bu da yoğun yükde, sunucunun şişmesine neden olmaktadır. Peki ya biz sessionlarımızı Redis üzerinde tutar isek :)
Yani diyelim ki projenin ortasındasınız ve var olan session kodlarını değiştirmek istemiyorsunuz, ya da session ile çalışmak istiyor ama bunun negatif durumları ile karşılaşmak istemiyorsunuz.
İşte bu durumda Asp.Net Core Mvc prjesine Redis ve Session servisleri tanıtılıp, session’a kaydedilen key ve valuelerin “In Memory” yerine Redis’de saklanması sağlanabilir. Redis ile alakalı yazıma buradan erişebilirsiniz. Yukarıda Windows ortamı için kurulmuş olan 6379 portunda çalışan bir Redis Server gözükmektedir. İstenir ise redis client’da açılıp “Ping” yazılarak, eğer herşey yolunda ise “Pong” cevabının alınması beklenir.
Şimdi gelin Visual Studio 2015 Update 3 ve.NET Core 1.0.1 versiyonlarında Mvc uygulamamızı geliştirelim.
Aşağıda görüldüğü gibi Proje oluşturulur.
Şimdi sıra geldi üç paketin NuGet Pacage Manager’dan kurulmasına:
View => Other Windows => Package Manager Console seçilir.
1-) Asp.Net Core’a Redis kütüphanesi aşağıdaki komut ile en son versiyonu kurulur.
1 |
Install-Package Microsoft.Extensions.Caching.Redis.Core |
2-) Session Asp.Net Core ile Default gelmemektedir. Aşağıdaki komutun çalıştırılması ile kurulur.
1 |
Install-Package Microsoft.AspNetCore.Session |
3-) Görselde Css olarak ben bootstrap kullandım. İlgili paket nugetden aşağıdaki gibi indirilir.
Startup.cs: ConfigureService() methodu önce redis cache’in sonra session’ın configure edilmesi ile sistem ayağa aşağıdaki gibi kaldırılır.
- Redis’e atanacak key (InstanceName) “Blog” ismi ile başlayacaktır.
- Redis uzakta bir sunucu olarak ayarlanır ise ona ait IP adresi girilmelidir. Bu örnekde localhost yani 127.0.0.1 girilmiştir. Her iki kullanım da doğrudur.
- Services’e Session eklenmesi durumunda “services.AddSession()”, redis’e atılan keyler’e, belli bir expire süresi verilmesi gerekmektedir. Aksi takdirde redis’e atanan keyler, Session Time Out süresinde kaybolmıyacak ve belli bir zaman sonra milyonlara ulaşabilecektir. İşte bu neden ile “options.IdleTimeout = TimeSpan.FromMinutes(10);” configuration’ı ile ilgili expire süresi, 10 dakka olarak ayarlanmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddApplicationInsightsTelemetry(Configuration); services.AddDistributedRedisCache(options => { options.InstanceName = "Blog"; options.Configuration = "127.0.0.1"; //options.Configuration = "localhost"; }); services.AddSession(options => { // 10 dakikalı Redis Timeout Süresi. options.IdleTimeout = TimeSpan.FromMinutes(10); options.CookieHttpOnly = true; }); services.AddMvc(); } |
Ayrıca Configure() methoduna Session’ın kullanımı için app.UseSession() ‘ın aşağıdaki gibi eklenmesi unutulmamalıdır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseApplicationInsightsRequestTelemetry(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseApplicationInsightsExceptionTelemetry(); app.UseStaticFiles(); app.UseSession(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } |
Şimdi sıra geldi projemizi oluşturmaya. Projede bir login sayfası yapılacaktır. İlgili girilen bilgiler session’da yani bu sistem de Redis’de saklanacaktır. Daha sonra About sayfasında ilgili bilgiler redisden çekilip karşılama yazısı olarak ekrana basılacaktır.
HomeController.cs/Index(): Aşağıda görüldüğü gibi hiçbir işlem yapılmdan, doğrudan Index.cshtml sayfasına gidilmiştir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using System.Text; namespace Asp.NetCore_Redis.Controllers { public class HomeController : Controller { public IActionResult Index() { return View(); } } } |
Index.cshtml: Yukarıda görüldüğü gibi clinet’ın Kullancı Adı ve TC nosu girilmektedir. Form Post durumunda “Home/Index” [Post] action’ına gidilmektedir. Görsellik için bootstrap kullanılmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
@{ ViewData["Title"] = "Home Page"; } <head> <link href="~/lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet" /> <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script> </head> <div class="container"> <div class="jumbotron"> <h2>ASP.NET Core Mvc'de</h2> <h2>Session bilgisini Redis'de Saklama</h2> </div> <div> <form id="myform" action="Home/Index" method="post"> <table class="table table-responsive"> <tr> <td> <b>Kullanıcı Adı:</b> </td> <td><input type="text" name="UserName" /></td> </tr> <tr> <td> <b>TCKN:</b> </td> <td><input type="text" name="UserID" /> <input type="submit" value="Gönder"/></td> </tr> </table> </form> </div> </div> |
Login işlemi olmadan önce Redis-Client’da tüm keyler listelenmek istendiğinde “keys *” aşağıda görüldüğü hiçbir sonuç dönmemektedir. Çünkü daha henüz bir atama işlemi yapılmamıştır.
Index-Post(): Aşağıda görüldüğü gibi gelen “UserName” ve” UserID” byte’a çevrilip “Session”‘a atandığında, ilgili data Session yerine Redis’de saklanmaktadır. Sonra da “About” action’ına yönlenmeketedir. Atanma işleminden sonra Session kontrol edilir ise boş olduğu görülebilir.
1 2 3 4 5 6 7 8 9 10 11 |
[HttpPost] public IActionResult Index(string UserName, string UserID) { var bytes = Encoding.UTF8.GetBytes(UserName); HttpContext.Session.Set("UserName", bytes); var bytes2 = Encoding.UTF8.GetBytes(UserID); HttpContext.Session.Set("UserID", bytes2); return RedirectToAction("About"); } |
Login işleminden sonra Redis-Client’a gidilip, tüm keyler listelenir ise aşağıda görüldüğü gibi artık tek bir key’in döndüğü görülür. Dönen key’in başlangıç kelimesi “Blog” ==> “AddDistributedRedisCache()” methodunda tanımladığımız “InstanceName“‘inden gelmektedir. Devamı da client’ın _sessionKey‘idir.
About(): Bu actionda Session’dan, yani gerçekte Redis’den çekilen data byte’dan ==> string context’e çevrilir. Bu işlem hem “UserName” hem de “UserID” için yapılır. Ve son olarak çekilen datalar ViewData[“Message”]’a konarak Action view’a dönülür.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public IActionResult About() { var bytes = default(byte[]); HttpContext.Session.TryGetValue("UserName", out bytes); var content = Encoding.UTF8.GetString(bytes); var bytes2 = default(byte[]); HttpContext.Session.TryGetValue("UserID", out bytes2); var content2 = Encoding.UTF8.GetString(bytes2); ViewData["Message"] = content +" User ID:"+content2; return View(); } |
About.cshtml: İlgili sayfa yukarıda görüldüğü gibidir. Login ekranında girilen datalar Redis’den çekilerek About ekranına basılmıştır.
1 2 3 4 5 6 7 |
@{ ViewData["Title"] = "About"; } <h2>@ViewData["Title"].</h2> <h3>@ViewData["Message"]</h3> <p>Use this area to provide additional information.</p> |
Aşağıda görüldüğü gibi Redis’e yazılan key başı “Blog” ile başlamakta ve devamında _sessionKey gelmektedir. Value değerleri olarak “UserName” ve “UserID” değerleri Redis’de Hashes data tipi olarak byte[] olarak tutulmaktadır. Value değerine redis consoleda “hgetall key” komutu ile erişilebilir.
Bu makalede klasik kullanımlarda karşımıza çıkan geleneksel sorunlara, yenilikçi çözümler bulduk. Session, kullanım kolaylığı nedeni ile çokça kullanılan bir yapıdır. Ama yoğun trafikde size büyük sorunlar çıkarabilir. Zaten uygulamanızı bu makalede olduğu gibi Asp.Net Core üzerinde koşturuyorsanız, 2 amacınız vardır. Biri performans, diğeri de işletme maliyetlerini kısıp Linux bir işletim sistemi üzerinde yayımlamak. Bu durumda session çok da mantıklı bir çözüm olmayabilir. Ama ya önceden yazılmış ve içinde bolca Session kullanılmış bir projeyi Asp.Net Core’a çevirmek ve optimizasyon yapmak isterseniz. Ya da Redis kütüphaneleri ile uğraşmadan her zamanki kolay kullanımı ile Session’a devam etmek isterseniz, işte o zaman bu makalede esas anlatılmak istenen, Session’a kaydedilmek istenen tüm verinin Redis’e kaydedilmesi konusu hem performans hem de Session’ın negatif durumlarına karşı karanlıkta parlayan bir ışık gibi karşımıza çıkmaktadır.
Not: Hepsinden önemlisi normal şartlarda farklı makinalarda tutulan ve tek bir yerden erişilemiyen Session bilgisi, bu makalede anlatıldığı gibi redisde saklanması durumunda, ilgili distributed cache “Redis Sentinel” kullanılarak master ve slave şeklinde scale edilerek çoğaltılabilir ve tek bir yerden tüm session bilgisine “hashes” data tipinde erişilebilir.
Madalyonun Öbür Yüzü: Session TimeOut olduğu zaman Redis tarafında atanan keyler silinmez. Eğer makale içinde kullanılan 10 dakikalık redis expire süresi atanır ise, clientın çalışma anındaki bilgileri 10 dakika sonunda sayfa üzerinden kaybolabilir. Bunun önüne geçmek için aşağıda görüldüğü gibi ilgili redis key’e 1 günlük Expire süresi verilebilir. Böylece gelen client’ın session bilgileri 24 saat boyunca korunur. Tek sorun, yüksek trafikli bir sitede herbir session’a ait key’in kaybolmamak üzere 24 saat boyunca redisde tutulması olacaktır. Tek bir sunucuda Redis’in 232 – 1 yani 4 milyardan fazla hash tipinde key tutabildiği düşünülür ise korkulacak pek de birşey yoktur:)
1 2 3 4 5 6 |
services.AddSession(options => { //İlgili keyler Redis'den 24 saat sonra silinir. options.IdleTimeout = TimeSpan.FromHours(24); options.CookieHttpOnly = true; }); |
Geldik bir makelnin daha sonuna:) Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Source: https://redis.io/documentation, https://stackoverflow.com/questions/50228068/not-able-to-increase-session-timeout-in-net-core-2-0, https://www.mikesdotnetting.com/article/270/sessions-in-asp-net-core
Bora hocam öncelikle elinize emeğinize sağlık. Sizden bir ricam olacak. Ben de kendi blogumu yaziyorum ayesilyurt.com uzantili adresim. Bloguma göz atıp bana fikirlerinizi ve eleştirilerinizi iletir misiniz?
Selam Ahmet,
Öncelikle başka Url’leri bloğuna kaynak olarak verirken çok dikkat et. Mümkün ise verme. İlerde o siteler kapanırsa bloğun Google Amcadan düşük score alır. Bunu unutma. Kapanmayacağı kesin siteleri bloğuna referans vermeye çalış. Site tasarımı hoşuma gitti. Bazı imajlar gelmiyor. Ama sade olduğı için hızlı. Seo amaçlı google analistic codeları da koysan fena olmaz. og:titleları koyman facebook adına ii olmuş :)
Bence başarılı. Eline sağlık.
Kolay gelsin.
Site kapanmış hocam :)
:)
Sessionlar in-memory yerine Redis’te saklanabilir demişsiniz. Redis’in kendisi de in-memory değil mi? Ayrımı kavrayamadım.
Selamlar,
Sanırım bir anlam karmaşası yaratmışım:)
Redis harici bir sunucu olabilir ve scalable olabilir. Kısaca Sunucu makinanın memorisi şişirilmeden başka makinanın memorisi üzerinden çalışılabilir. Ayrıca 5 sunuculu bir portalın tüm session bilgilerine tek bir yerden listelenebilir. Uzun lafın kısası tabiki 2si de memoride tutulur. Ama risk dağıtımı açısından başka makinanın kaynakları kullanılır hale getirilebilir. bir de Redis’in memory yönetimi ve istene key’e ait value değerini getirme hızı kat ve kat üstündür.
İyi çalışmalar.
İyi çalışmalar.
Session bilgisini saklayabileceğimiz diğer alternatifler ile ( stateserver veya sqlserver) ile ilgili yorumlarınızı öğrenmek isterim.
Selamlar,
Onların da eksi ve artılarını başka bir makalede paylaşmak isterim. SqlServer ile yavaş ama sağlamcı olabilirsiniz. Redis makinası kapanırsa In Memory data uçar. Tabii istersek redis’in fiziksel diske de yazmasını aynı zamanda sağlayabiliriz. Tabii hızdan feragat ederek.
İyi çalışmalar.
hocam selamlar, asp.net webform projesinde bu yapıyı kullanmak bize hız kazandırırmı, 50 civarı session datası var, aktif kullanıcı sayısı anlık olarak 250-500 arası,öneriniz olabilirmi
Anlık kullanıcı sayııs çok değil. Ama illaki performans derseniz çok fark eder. Bu yapı baya sağlıklı. Tavsiye olunur.
Bora hocam selamlar, Sessionlarda bazen sorunlar yaşıyoruz. timeout sürelerini uzatmamıza rağmen yine sessionlar bazen boşalıyor. tek bir server ımız var. redis bize çare olur mu. Yoksa ikisi de memory de tutulduğundan aynı sorunu yaşamaya devam edermiyiz. Teşekkürler
Selamlar Sevgiler :)
IIS üzrinde mi yayımlıyorsunuz yoksa Kestrel mi? IIS ise session timeout süresini sadece webconfigden değil bir de IIS’den ayarlamalısınız. Eğer Redis kullanacaksanız :Redis Candır. Siz timeout vermediğiniz sürece kalır:)
İyi çalışmalar.
Selamlar ve Sevgiler hocam:)
IIS üzerinde tutuyoruz. Timeout süresini 2 tarafta da artırdık. Merak ettiğim Redis kullanırsak tek sunucu kullandığımızdan sessionları memory de tutarsak sorun yaşama durumumuz var mı
Selamlar,
Hayır yok. Performans için Redislerinizi sentinel ile master slave olarak ayırabilir; hatta read ile write yapacak sunucularınızı da önceden belirleyip ayırabilirsiniz.
İyi çalışmalar.
bora hocam ben asp.neti biliyorum mvc de de yeni yeni birşeyler yapıyorum videolarınız sayesinde. asp.net i tamamen bırakacak mıyız bütün projeleri mvc ile mi yapacağız. birde mvc core yeni çıktı deniyor. mvc core şimdiden geçmelimiyiz. yoksa mvc ile mi devam etmeliyiz
Ben şahsen 2011’den beri Asp.Net kullanmıyorum. Aslında malesef artık Mvc de pek tercih edilmiyor. Angular, React, Vue gibi javascript frameworkleri ile cross platform yani mesela (.Net Core) ile çalışmak artık son 1-2 yılın modası.
İyi çalışmalar.
bora hocam bir de mvc türkçe kitap tavsiye edebilirmisiniz.
Selam Ferdi,
Gerçekten türkçe kitap Mvc tavsiye edebileceğim yok.
İyi çalışmalar.
Merhaba ben HttpContext üzerinden methodlara ulaşmak istiyorum ancak gelmiyor sebebi ne olabilir?
Selamlar Sinem,
HttpContext’e erişemiyorsundur. Eğer uygulaman Console App ya da WebApplication değil ise bu çok normal.
Bu şekilde denermisin “System.Web.HttpContext.Current”. Methodlar :)
İyi çalışmalar.
Makale için teşekkürler. Çok faydalı ve sayenizde kullanmaya başladım. Fakat benim iki ihtiyacım var ki belkide bu konuda birçok kişi müzdarip.
1. Oturum açmış kişi, başka bir bilgisayardan, yada başka bir tarayıcıdan oturum açmışsa, önceki oturumunu düşürmek istiyorum.
2. Bir kullanıcı oturum açmışken, eğer onu pasif duruma getirirsem veya silersem, kullanıcı sürekli sistemde gezmeye devam edecek, taa ki oturumu süresi dolana kadar.
Bu oturumlara bir şekilde ulaşıp, o kişinin login olanlar arasında var olup olmadığını kontrol etmek gerekiyor. Buraya kadar sorun olmuyor fakat o kişinin oturumunu kaldırmak gerektiğinde iş biraz karmaşık hale geliyor. Bu konuda bilginiz var ise en acilinden bir makale bekliyoruz.
Sayın Bora hocam henuz oluşturulmamış bir sessionu HttpContext.Session.GetString(“Id”) şeklinde okumya çalıştığımda NullReferenceException hatası alıyorum bunu nasıl aşabilirim?
Selam Erol,
Sorun ilginç geldi. Öncelikle tabi ki kodu görmem lazım.
Ama şunu diyim önce Session’ın oluşmasını beklesek :)
Kodu github’dan falan paylaşırsan daha mantıklı bir yardımda bulunabilirim.
İyi çalışmalar.
Bora hocam şimdiye kadar hep sorulmuş olan soruları inceleyip sorun çözmeyi tercih ederdim çok uzun zamandan sonra ilk defa sorarak yardım almayı deniyorum kodlar bu linkte
https://stackoverflow.com/questions/59417013/asp-net-core-3-1-null-exception-error-when-reading-session-without-initial-value
ilgilenirseniz çok memnun olurum session bilgilerini rediste tutma olayına bunu çözdükten sonra bakabilrim. kolay gelsin..
Merhaba Hocam. .Net core’da bir mvc projem var. Api kullanarak login olduğumda, api tarafında session’a bazı değerler atıyorum. Sonra başka bir request attığım zaman api tarafında session boş oluyor. Ne önerirsiniz?
Session’yerine, USERID key olarak atayıp Session’a ne atıyorsan ilgili datayı Redis’e atmanı öneririm..
Bora Hocam Merhaba,
İki sorum olacak cevaplayabilirseniz sevinirim.
1- Sadece Redis kullanımında(Session kullanmadan) tıpkı Session kullanımında olduğu gibi timeout süresinin her işlemde yenilenmesini nasıl sağlayabilirim
2- Eskiden Global.asax’de Session_End ile Session bitiminde işlem yapabilirdik bunu Redis kurgusunda sağlayabiliyor muyuz? Amacım Logout yapmadan direk sayfayı kapatan kişilerin login sürelerini tahmin etmek ve cache temizlemek
Şimdiden Teşekkürler,
Saygılar
Selam Hasan,
Sorunun cevabında Redis’e Expire süresi ile Token atabilirsin. Zaten süre dolunca Token kaybolacağı için, Logout ayrıca yapmana gerek yok. Çünkü client’ı 403 forbidden hatası alınca Login ekranına redirect yapabilirsin. Ayrıca Client Side tarafda da Cookie’ye Expire süreli atabilirsin.
İyi çalışmalar.
Merhaba Bora Hocam,
Yazılarınız ve videolarınız çok bilgilendirici bunun için öncelikle teşekkür ediyorum.
Bir konuda fikrinizi almak istiyorum.
Uzun bir süredir Asp.Net Webforms ile geliştirdiğim ve makul seviyede kullanıcısı olan ve Azure App Service’te koşan bir uygulamam var. Bu uygulama da session saklamak için Azure Cache for Redis kullanıyorum ve performansından çok memnunum. Fakat ekibime yeni arkadaşlar katmak istediğimde mevcut uygulamam Webforms olduğu için istekli ekip arkadaşları bulamıyorum, dolayısıyla projeyi yavaş yavaş Angular & .Net Core API olacak şekilde migrate etmek istiyorum, fakat bunu tek seferde yapmam mümkün değil dolayısıyla yeni geliştirme gereken işleri bu yeni projede yapmak istiyorum. Buraya kadar herşey olması gerektiği gibi ilerliyor fakat bir noktada takıldım, yapmak istediğimde Webforms uygulamasından bir menüye veya bir butona basılınca yeni uygulamaya yönlendirmek fakat yeniden giriş yapmasına gerek olmamalı. Bu işlemi 2 farklı webforms uygulamasını aynı domain farklı subdomainler ile web.config’e ekleyerek yapabiliyorum fakat Webforms ve yeni proje ile bunu yapamıyorum. Çünkü yeni projedeki .Net Core API tarafına gelen request’te ASP.NET_SessionId’ye erişemiyorum.
.Net Core API projesinde StartUp.cs içindeki
ConfigureServices içine
services.AddDistributedRedisCache(options =>
{
options.Configuration = “redis’e erişim için gerekli bilgiler”;
});
services.AddSession(options => {
options.Cookie.Name = “ASP.NET_SessionId”;
options.Cookie.Domain = “.test.com”;
});
Configure içine de
app.UseSession();
ekliyorum.
Farkındayım özel bir durum ve net bir cevabınız olmayabilir, fakat böyle bir dönüşüm işi eminim bir çok kişinin yapmayı düşündüğü bir işlemdir, dolayısıyla konu hakkında bilgilerinizi paylaşırsanız sevinirim.
İyi günler.