Azure Üzerinde Redis ile .Net Core Mvc Cache
Selamlar,
Bu makalede Azure üzerinde Redis oluşturup, .Net Core WebApi için Cache olarak kullanacağız.
1-) Öncelikle https://portal.azure.com‘dan, yeni bir Redis Cache’in oluşturulması için, aşağıda görüldüğü gibi arama kutucuğuna redis yazılır ve Redis Cache seçilir.
2-) Karşımıza aşağıdaki gibi bir ekran gelir. “Create” butonuna basılır.
3-) Karşımıza aşağıdaki gibi bir ekran gelir:
- DNS name: Redis sunucuya erişilecek isim belirlenir.
- Subscription: Hangi hesap ile alınmak isteniyorsa belirlenir.
- Location: Türkiye’ye en yakın lokasyon, North Europe seçilir.
- Pricing tier: İstenen makina özellikleri, projenin ihtiyacına göre buradan seçilir. Her konfigürasyonun, ödeme planı farklıdır.
Azure üzerinde yaratılan Redis Cache detayına gidildiğinde, aşağıdaki gibi bir ekran ile karşılaşılır.
Buradan “_Console” butonuna tıklandığında, aşağıdaki gibi bir ekran ile karşılaşılır. Eğer Redis Cache ayağa kalkmış ise, “Ping” komutuna karşılık “PONG” cevabının alınması gerekmektedir.
Bu örnek Macbook üzerinden yapılacaktır. Bu link’den https://sourceforge.net/projects/redis-desktop-manager.mirror/ Redis Desktop Manager indirilir.
Add New Connection buttonuna basılarak aşağıdaki gibi bir ekran ile karşılaşılır. İlgili alanlar Azure üzerinde connection string’e bakılarak doldurulur.
Azure üzerinde “Host name” ve “Şifre – Auth (Keys)” alanından istenen bilgiler, aşağıda görüldüğü gibi öğrenilebilir.
Not: Macbook’dan Azure üzerindeki Redis’e erişilemeyince Non-SSL port (6379)’a, aşağıdaki gibi izin verilmiştir. Böylece uzaktan Azure Redis’e, Redis Desktop Manager ile bağlanılmıştır.
Local Redis Desktop Manager’dan Azure Redis’e bağlanıldığında, System console ekranına aşağıdaki gibi gelinir:
Şimdi sıra geldi .Net Core Mvc Projenin Oluşturulmasına: Aşağıdaki komut ile azureNews projesi oluşturulur. Bu projede Redis üzerinden çekilen haber başlıkları, Mvc bir sayfada listelenecektir. Daha sonra tıklanan bir haberin detayının çekmesi için, tekrardan Redis’e gidilip, ilgili ID’ye göre Haber detay çekilecektir.
1 |
dotnet new mvc -o azureNews |
.Net Core üzerinde Redis’in kullanılabilmesi için ServiceStack, kullanılabilecek apilerden biridir. İlgili kütüphane aşağıdaki komut satırı ile projeye eklenir.
1 |
dotnet add package ServiceStack.Redis.Core |
appsettings.json: Proje içinde redis ile ilgili tanımların yapıldığı config dosyası, aşağıda görüldüğü gibi düzenlenir. (RedisConfig)
- Host: Azure üzerindeki Host name.
- Port: Ssl kapalı olduğu için 6379
- Password : Azure üzerindeki Primary keydir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "RedisConfig": { "Host": "customerRedis.redis.cache.windows.net", "Port": 6379, "Name": "master", "Password": "***************" }, "AllowedHosts": "*" } |
Projeyede kullanılanacak News modeli aşağıdaki gibidir.
NewsModel.cs:
1 2 3 4 5 6 7 8 9 10 |
using System; public class NewsModel { public int Id { get; set; } public string title { get; set; } public string description { get; set; } public DateTime createdDate { get; set; } public string imageUrl { get; set; } } |
HomeController.cs / HomeController():
- “IConfiguration _configuration”: “appsettings.json”‘dan tanımlı RedisConfig, propertylerinin çekilmesi için kullanılan Interfacedir.
- “RedisEndpoint conf” : Azure üzerindeki Redis’e bağlanmak için kullanılan ServiceStack sınıfıdır. “appsettings.json”‘dan çekilen konfigürasyon parametreleri ile Azure Redis’e bağlanılır.
- “List<NewsModel> newsModel” : İlgili Contructer’da, öncelikle static bir list yaratılır ve eğer boş ise dummy List<Newsmodel> datası ile doldurulur.
- “newsModel.Count == 0” : Sayfa ilk yüklendiğinde, ilgili static liste boş olucağı için dummy Haber datası ile doldurulur.
- “using (IRedisClient client = new RedisClient(conf))” : Azure Redis’e, connection açılır.
- “if (client.SearchKeys(“flag*”).Count == 0)” : Eğer proje ilk kez çalıştırılmış ve Redis’de Dummy haber datası yok ise, Redis üzerine tüm haber datası “flagId” şeklinde atılır. Yani flag* ile başlıyan tüm data sayısı Azure üzerindeki Redis’de aranır. “O” ise ilgili dummy data redis’e tek tek key-value olarak atanır.
- “var news = client.As<NewsModel>()” : NewsModel’in ServiceStack karşılığı, boş şablon oluşturulur. Kısaca Redis’e atılacak data tipi ile tanımlanır.
- “news.SetValue(“flag” + data.Id, data)” : Belirlenen Key : “flagId” ve value : “NewsModel” şeklinde Redis’e ilgili haber atılır. “flag” kelimesi ve gelen haber bilgisine ait “ID” değeri ile bir key oluşturulur ve NewsModel bilgisi, bu key’e value olarak atanır. Başında “flag” kelimisinin bulunma amacı ilgili keyleri, Redis’de guruplamaktır. Yani istendiğinde, “flag” kelimesi içeren tüm keyler toplu olarak çekilebilecektir.
NOT: Redis’de “Key – Value” haricinde yukarıda görüldüğü gibi, saklanabilecek farklı bir çok data tip vardır. Burada Redise List<NesModel> dummy data, toplu olarak tek HSET olarak da atılabilirdi. Ama o zaman, tek bir haber detaya gitmek için, önce ilgili HSET çekilecek ve daha sonra istenen ID’li haber içinde filitreleme yapılması gerekecekti. Bu da büyük bir zaman ve performance kaybına neden olacaktı.
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using azureNews.Models; using Microsoft.Extensions.Configuration; using ServiceStack.Redis; public class HomeController : Controller { IConfiguration _configuration; RedisEndpoint conf; public static List<NewsModel> newsModel = new List<NewsModel>(); public HomeController(IConfiguration configuration) { _configuration = configuration; conf = new RedisEndpoint() { Host = _configuration.GetValue<string>("RedisConfig:Host"), Password = _configuration.GetValue<string>("RedisConfig:Password"), Port = _configuration.GetValue<int>("RedisConfig:Port") }; #region Dummy Data if (newsModel.Count == 0) { newsModel.AddRange(new NewsModel[] { new NewsModel(){ Id = 1, title = "What does it actually mean for a commercial plane to hit 801 mph?", description = @"That’s extraordinarily fast, and in fact, the plane reportedly landed early, a nice perk for everyone on board.", createdDate = DateTime.Now, imageUrl="ross-parmly.jpg" }, new NewsModel(){ Id = 2, title = "Someone built an electric Harley-Davidson motorcycle in 1978", description = @"Let's turn back the clock hands to 1978, when Steve Fehr of the Transitron Electronic Corporation built a one-off Harley-Davidson MK2 electric motorcycle prototype.", createdDate = DateTime.Now, imageUrl="electric_teaser.jpg" }, new NewsModel(){ Id = 3, title = "This fisheye lens weighs more than 25 pounds and can see behind itself.", description = @"Lenses don’t have to be complicated. In fact, you can punch a pinhole in an old oatmeal canister, add some film on the other side, and start making pictures.", createdDate = DateTime.Now, imageUrl="fisheye_handheld.jpg" }, new NewsModel(){ Id = 4, title = "This copy of Super Mario Bros. sold for more than $100,000", description = @"TVideo game collecting is serious business. Recently, a pristine copy of Super Mario Bros. broke the $100,000 mark at auction.", createdDate = DateTime.Now, imageUrl="dims-7_0.jpg" } }); } #endregion using (IRedisClient client = new RedisClient(conf)) { if (client.SearchKeys("flag*").Count == 0) { foreach (NewsModel data in newsModel) { var news = client.As<NewsModel>(); news.SetValue("flag" + data.Id, data); } } } } ... } |
Tüm keyler Redis’e atıldıktan sonra, Azure üzerinde “keys *” ile yapılan sorgu sonucu, aşağıdaki gibidir.
HomeController.cs /Index(): Ana sayfada gösterilecek, tüm haber datası Azure üzerindeki Redis’den çekilir.
- “using (IRedisClient client = new RedisClient(conf))” : Azure Redis’e bağlanılır.
- “List<string> allKeys = client.SearchKeys(“flag*”)” : Redis üzerindeki tüm haber datasının key’i, “flag*” keyword’ü ile çekilir.
- “foreach (string key in allKeys)” : Tüm keyler, tek tek gezilir.
- “dataList.Add(client.Get<NewsModel>(key))” : Redisden herbir key’e ait haber datası çekilip, “List<NewsModel>” dataList’e atanır ve Index view’ına data model olarak dönülür.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public IActionResult Index() { using (IRedisClient client = new RedisClient(conf)) { List<NewsModel> dataList = new List<NewsModel>(); List<string> allKeys = client.SearchKeys("flag*"); foreach (string key in allKeys) { dataList.Add(client.Get<NewsModel>(key)); } return View(dataList); } } |
index.cshtml:
Index.cshtml: Tüm haber listesi List<NewsModel> ekrana, yukarıda görüldüğü gibi 2şerli olarak listelenir.
- @Url.FriendlyUrl : Sayfa içinde ilgili haber detaya gidilmesi için gerekli, url temizleme işinin yapıldığı( Url’de istenmeyen karakterlerin temizlendiği method) makalenin devamında ilgili method anlatılacaktır.
- “class=”table”” : İlgili tablonun sayfanın ortasında gözükmesi için custom css yazılmıştır. Makalenin devamında anlatılacaktır.
- @:<tr> : Razor View Enginde kullanılan, C# kodları arasına Html yazabilmesini sağlar.
- if (count % 2 == 0) { @:</tr><tr> } : Eğer tablo için 2 kolon yazılmış ise ilgili satırın kapanıp yeni bir satıra geçilmesi sağlanı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 |
@model List<NewsModel> <div class="container"> <div class="jumbotron"> <h2>Technology News on borakasmer.com</h2> </div> <div align="right"><b>@Model.First().createdDate</b></div> <div> <table class="table"> @{ @:<tr> var count=0; foreach(var news in Model) { if (count % 2 == 0) { @:</tr><tr> } <td> <a href="/@Url.FriendlyUrl(news.title)/@news.Id" target="_blank" style="text-decoration: none;"> <h2> @news.title</h2> <div> <img src="/images/@news.imageUrl" width="500px" height="250px"> </div> </a> </td> count++; } @:</tr> } </table> </div> </div> |
Share/UrlExtension: Haber Detay’a gidilecek Url içinde geçen title, yani haber başlığındaki istenmeyen karakterlerin(Türkçe karakterlerin) temizlendiği methoddur.
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 33 34 35 36 37 38 39 40 41 42 43 44 |
using System.Text; using System.Text.RegularExpressions; public static class UrlExtension { public static string FriendlyUrl(this Microsoft.AspNetCore.Mvc.IUrlHelper helper, string url) { if (string.IsNullOrEmpty(url)) return ""; url = url.ToLower(); url = url.Trim(); if (url.Length > 100) { url = url.Substring(0, 100); } url = url.Replace("İ", "I"); url = url.Replace("ı", "i"); url = url.Replace("ğ", "g"); url = url.Replace("Ğ", "G"); url = url.Replace("ç", "c"); url = url.Replace("Ç", "C"); url = url.Replace("ö", "o"); url = url.Replace("Ö", "O"); url = url.Replace("ş", "s"); url = url.Replace("Ş", "S"); url = url.Replace("ü", "u"); url = url.Replace("Ü", "U"); url = url.Replace("'", ""); url = url.Replace("\"", ""); char[] replacerList = @"$%#@!*?;:~`+=()[]{}|\'<>,/^&"".".ToCharArray(); for (int i = 0; i < replacerList.Length; i++) { string strChr = replacerList[i].ToString(); if (url.Contains(strChr)) { url = url.Replace(strChr, string.Empty); } } Regex r = new Regex("[^a-zA-Z0-9_-]"); url = r.Replace(url, "-"); while (url.IndexOf("--") > -1) url = url.Replace("--", "-"); return url; } } |
wwwroot/css/table.css: Sayfa içinde kullanılan Table’ın, sayfayı ortalaması sağlanır.
1 2 3 4 5 6 7 8 9 10 11 12 |
.table th { text-align: center; } .table { border-radius: 5px; width: 50%; margin: auto; float: none; } |
Haberlere ait resimler, yukarıda da görüldüğü gibi “wwwroot/images” altına önceden atılmıştır.
Startup.cs/Configure : HaberDetay sayfasına gidilmesi için Mvc’de tanımlanması gereken Routingdir. “title / id” şablonu ile gelindiğinde “Home” Controller’ın “Detail()” Action’ına gidilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { . . . app.UseMvc(routes => { routes.MapRoute( name: "detal", template: "{title}/{id}", defaults: new { controller = "Home", action = "Detail" }); routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } |
HomeController.cs/Detail(): Detay sayfasına Title ve ID parametreleri ile gelinir. Not: Dikkat ederseniz “Title” parametresi hiç kullanılmamıştır. Ama SEO amaçlı ilgili haber, detayına başlığı ile gidilmekte ve bu da Google tarafından indexlenirken, aranan kelimelere göre daha üst sırada çıkmasını sağlamaktadır.
- using (IRedisClient client = new RedisClient(conf)): Azure Redis’e bağlanılır.
- var data = client.Get<NewsModel>($”flag{ID}”) : Haber Detay “NewsModel”, “flagId” keyword’üne göre Redis’den çekilir.
- “data = data == null ? new NewsModel() : data” : İlgili datanın, Redis’de olmama ihtimaline göre şablon boş kayıt yaratılır.
- return View(data) : Çekilen Data, “Detay” sayfasına gönderilir.
1 2 3 4 5 6 7 8 9 |
public IActionResult Detail(string Title, int ID) { using (IRedisClient client = new RedisClient(conf)) { var data = client.Get<NewsModel>($"flag{ID}"); data = data == null ? new NewsModel() : data; return View(data); } } |
Detail.cshtml:
Detail.cshtml: İlgili ID’değerine göre çekilen Haber “NewsModel”, ekrana yukarıda görüldüğü gibi basılır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@model NewsModel <link rel="stylesheet" href="~/css/table.css" /> <div class="container"> <div class="jumbotron"> <h2>Bora News @Model.title</h2> </div> <div align="right"><b>@Model.createdDate</b></div> <div> <div> <h3>@Model.description</h3> </div> <table class="table"> <tr> <td> <div> <img src="/images/@Model.imageUrl" width="500px"> </div> </td> </tr> </table> </div> </div> |
Azure üzerinde, Redis’i Monitor Edebileceğiniz Ekran Görüntüsü Aşağıdaki Gibidir:
Geldik bir makalenin daha sonuna. Bu makalede, Redis’in Azure üzerinde nasıl kullanılabileceğini, daha web servisi katmanına gelmeden, ilgili haber listesinin ve detayının nasıl çekileceği araştırılmıştır. Siz de ilgili haberin güncellenmesi durumunda, Redis Cache’in nasıl bir mekanizma ile güncellenmesi gerektiğini ve cache’e atılacak datanın, timeout süresinin neye göre belirlenmesi gerektiğini araştırabilirsiniz.
Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Source : https://docs.microsoft.com/tr-tr/azure/azure-cache-for-redis/cache-web-app-howto
Source Code:
Hocam öncelikle bu güzel içerik için teşekkürler. Merak ettiğim şu, bir e-ticaret sitesi için redis i bir veritabanı olarak kullanmak ne kadar doğru olur ?
Selamlar,
Öncelikle teşekkürler. Sadece Redis’i bir e-ticaret sitesinde kullanmak yeterli olmaz. Bir relational DB’nin önünde çalışması daha doğru olur.
NOT: Unutulmaması gerekene bir durum da, eğer Redis kapanır ise, herseyin DB üzerinden de çalışmaya devam etmesi gerekmektedir.
İyi çalışamalar.
Merhabalar hocam,
Redis teki verileri belli aralıklarda sql servera kaydediyorlar bunu hangfire ile mi yapıyorlar yoksa farklı bir kütüphane mi kullanıyorlar
Selamlar,
Onla da olur. Ben olsam Microservices yapar, Quartzla falan geceleri çalıştırır Redis’den DB’ye atardım.
İyi çalışmalar.