ASP.NET Web API da Redis Keshni ishlatish

Caching — bu fayllar yoki ma’lumotlarning nusxasini cache (vaqtinchalik saqlash joyi)da saqlash jarayonidir, shunda ularga tezroq murojaat qilish mumkin bo‘ladi. Ya’ni ma’lumot yoki fayl ma’lum muddatga temporary storage’da saqlanadi. Keyingi safar ma’lumot so‘ralganda, u database yoki asl fayllardan emas, balki shu cache’dan olinadi.
Ushbu maqolada biz Windows tizimida Redis cache ni o‘rnatish va undan foydalanishni ko‘rib chiqamiz. ASP.NET 6.0 Web API loyiha yaratib, Windows’da Redis dan foydalanamiz. Shuningdek, Azure portal orqali Azure Cache for Redis yaratish va uni dasturimiz bilan ishlatishni ham ko‘rib chiqamiz.
Mundarija:
Taqsimlangan cache
ASP.NET Core da taqsimlangan cache ishlatish
Redis cache nima
Windowsda Redis serverni ishga tushirish
Visual Studio yordamida ASP.NET Core Web API yaratish
Xulosa
Taqsimlangan cache
Distributed cache — bu bir nechta application server tomonidan umumiy ishlatiladigan cache hisoblanadi. Distributed cache dastur tezligi va kengayish imkoniyatini yaxshilaydi, chunki u bir xil ma’lumotni bir nechta serverlarga bir xil tarzda yetkazib bera oladi. Agar serverlardan biri qayta ishga tushsa yoki ishlamay qolsa ham, cache da saqlangan ma’lumot boshqa serverlar uchun odatdagidek mavjud bo‘lib qoladi.

ASP.NET Core da taqsimlangan cache ishlatish
IDistributedCache interfeysi sizga cache ustida ishlash uchun quyidagi metodlarni beradi:
Get, GetAsync — string kalit (key) asosida cache serverdan qiymatni oladi. Bu metodlar kalitni qabul qiladi va saqlangan ma’lumotni byte[] ko‘rinishida qaytaradi.
Set, SetAsync — string kalit va qiymatni qabul qilib, uni cache serverga saqlaydi. Bu metodlar ma’lumotni (byte[] ko‘rinishida) berilgan kalit orqali cache ga qo‘shadi.
Refresh, RefreshAsync — sliding expiration vaqtini qayta tiklaydi. Bu metodlar kalit asosida cache dagi ma’lumotni yangilaydi va uning sliding expiration muddatini boshqatdan hisoblaydi.
Remove, RemoveAsync — string kalit asosida cache dagi ma’lumotni o‘chiradi.
ASP.NET Core da distributed cache ishlatish uchun bir nechta tayyor (built-in) va uchinchi tomon (third-party) variantlar mavjud:
Distributed SQL Server cache — SQL Server asosidagi distributed cache ishlatish uchun maxsus paket kerak bo‘ladi.
Distributed Redis cache — Redis asosidagi distributed cache ishlatish uchun maxsus paket kerak bo‘ladi.
Distributed NCache cache — NCache asosidagi distributed cache ishlatish uchun maxsus paket kerak bo‘ladi.
Redis Cache nima?
Redis — bu open source (BSD litsenziyali), in-memory ma’lumotlar tuzilmasini saqlovchi tizim bo‘lib, u database, cache, message broker va streaming engine sifatida ishlatiladi. Redis quyidagi ma’lumot tuzilmalarini qo‘llab-quvvatlaydi: strings, hashes, lists, sets, sorted sets (range queries bilan), bitmaps, hyperloglogs, geospatial indexes va streams. Redis ichida replication, Lua scripting, LRU eviction, transactions va diskka saqlashning turli darajalari (on-disk persistence) mavjud. Shuningdek, Redis Sentinel orqali yuqori ishonchlilik (high availability) va Redis Cluster orqali avtomatik bo‘lish (automatic partitioning) imkoniyatini beradi.
Redis’ni ko‘plab dasturlash tillarida ishlatish mumkin. U juda mashhur cache tizimi bo‘lgani sababli Microsoft Azure ham uning bulutdagi versiyasini Azure Cache for Redis nomi bilan taqdim etadi.
Windowsda Redis serverni ishga tushirish
Redis serverning Windows versiyasini quyidagi havola orqali yuklab olib, kompyuteringizning istalgan joyiga joylashtirishingiz mumkin(Windows uchun misol Redis 3.0.504 bilan keltirilgan, lekin ishlab chiqarish yoki yangi versiya ishlatadiganlar uchun WSL yoki Docker orqali Redis o‘rnatish tavsiya qilinadi.):
https://github.com/microsoftarchive/redis/releases/tag/win-3.0.504

Ushbu Zip faylda 16 ta fayllar mavjud.

Biz Redis serverni ishga tushirish uchun redis-server.exe ni ochamiz.
.png)
Biz bitta ogohlantirish xabarini olayapmiz: “no config file is specified” (hech qanday konfiguratsiya fayli ko‘rsatilmagan). Ushbu ogohlantirishni oddiygina e’tiborsiz qoldirishimiz mumkin. Standart holatda, Redis 6379 portida ishlaydi.
Redis yana bir CLI (command-line interface) ni qo‘llab-quvvatlaydi va shu CLI yordamida cache’ga kirish imkoniyatini tekshirsa bo‘ladi. Shu maqsadda, redis-cli.exe faylini shu papkada ishga tushirishimiz mumkin. Agar siz ping kabi buyruq kiritsangiz, javob sifatida PONG olasiz. Bu oddiy test orqali cache ishlayotgani tekshiriladi.
.png)
Bu oddiy test orqali cache ishlayotgani tekshiriladi.
Biz quyidagi buyruqlar yordamida qiymatlarni o‘rnatish va olishimiz mumkin:

setbuyrug‘i berilgan kalit (key) yordamida qiymat saqlaydi.getbuyrug‘i shu kalit bo‘yicha cache’dagi qiymatni qaytaradi.
Endi Web API loyihasini yaratishimiz mumkin.
Visual Studio yordamida ASP.NET Core Web API yaratish
Biz Visual Studio 2026 ni ochib, yangi loyiha yaratamiz, ASP.NET Core Web API shablonidan foydalanamiz.

Loyihaga mos nom beramiz va .NET 10.0 framework’ini tanlaymiz. “Create” tugmasini bosganimizdan so‘ng loyiha yaratiladi.

Keyin quyidagi kutubxonalarni NuGet package manager orqali o‘rnatishimiz kerak:
HtmlAgilityPack
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Microsoft.Extensions.Caching.StackExchangeRedis
HtmlAgilityPack web scraping uchun ishlatiladi, Caching.StackExchangeRedis Redis caching uchun ishlatiladi. Qolgan ikkita kutubxona esa Entity Framework orqali ma’lumotlar bazasi operatsiyalari uchun kerak bo‘ladi.
appsettings faylida quyidagilarni qo‘shamiz: database connection string, Redis connection URL va parallel task count.
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"RedisCacheUrl": "127.0.0.1:6379",
//"RedisCacheUrl":"legee.redis.cache.windows.net:6380,password=k6CRosKzTY9vXMqH76F8rbl7m8PntopEwAzCaPcTyeM=,ssl=True,abortConnect=False", // For Azure Redis Cache. Currently this resource is not available
"ConnectionStrings": {
"ConnStr": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=AnalyticsDB;Integrated Security=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
},
"ParallelTasksCount": 20
} Database connection string Entity Framework tomonidan SQL bazasiga ulanish uchun ishlatiladi, parallel task count esa web scraping’ning parallel foreach kodida ishlatiladi. RedisCacheUrl ilova va Redis server o‘rtasida ulanishni sozlash uchun ishlatiladi.
Models papkasida Feed klassini yaratish mumkin. Ushbu klass C# Corner RSS feed’laridan kerakli ma’lumotlarni olish uchun ishlatiladi.
namespace RedisPr.Models
{
public class Feed
{
public string Link { get; set; }
public string Title { get; set; }
public string FeedType { get; set; }
public string Author { get; set; }
public string Content { get; set; }
public DateTime PubDate { get; set; }
public Feed()
{
Link = "";
Title = "";
FeedType = "";
Author = "";
Content = "";
PubDate = DateTime.Today;
}
}
}Models papkasida ArticleMatrix klassini yaratish mumkin. Ushbu klass web scraping orqali olingan har bir maqola, blog yoki yangilik ma’lumotlarini saqlash va ulardan foydalanish uchun ishlatiladi.
ArticleMatrix.cs
using System.ComponentModel.DataAnnotations.Schema;
namespace RedisPr.Models
{
public class ArticleMatrix
{
public int Id { get; set; }
public string? AuthorId { get; set; }
public string? Author { get; set; }
public string? Link { get; set; }
public string? Title { get; set; }
public string? Type { get; set; }
public string? Category { get; set; }
public string? Views { get; set; }
[Column(TypeName = "decimal(18,4)")]
public decimal ViewsCount { get; set; }
public int Likes { get; set; }
public DateTime PubDate { get; set; }
}
}Entity Framework uchun DB context klassini yaratishimiz mumkin. Ushbu klass ma’lumotlar bazasi bilan bog‘lanish va ma’lumotlarni saqlash, o‘qish, yangilash hamda o‘chirish operatsiyalarini bajarish uchun ishlatiladi.
MyDbContext.cs
using Microsoft.EntityFrameworkCore;
using System.Reflection.Emit;
namespace RedisPr.Models
{
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
}
public DbSet<ArticleMatrix>? ArticleMatrices { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
}Keyinchalik ma’lumotlarni bazaga saqlash uchun MyDbContext klassidan foydalanamiz.
Shuningdek, AnalyticsController API controller yaratib, uning ichiga web scraping va caching bilan bog‘liq kodlarni qo‘shishimiz mumkin.
AnalyticsController.cs
using HtmlAgilityPack;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
using RedisPr.Models;
using System.Globalization;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Xml;
using System.Xml.Linq;
namespace RedisPr.Controllers
{
[Route("[controller]")]
[ApiController]
public class AnalyticsController : ControllerBase
{
readonly CultureInfo culture = new("en-US");
private readonly MyDbContext _dbContext;
private readonly IConfiguration _configuration;
private static readonly object _lockObj = new();
private readonly IDistributedCache _cache;
public AnalyticsController(MyDbContext context, IConfiguration configuration, IDistributedCache cache)
{
_dbContext = context;
_configuration = configuration;
_cache = cache;
}
[HttpPost]
[Route("CreatePosts/{authorId}")]
public async Task<bool> CreatePosts(string authorId)
{
try
{
XDocument doc = XDocument.Load("https://www.c-sharpcorner.com/members/" + authorId + "/rss");
if (doc == null)
{
return false;
}
var entries = from item in doc.Root.Descendants().First(i => i.Name.LocalName == "channel").Elements().Where(i => i.Name.LocalName == "item")
select new Feed
{
Content = item.Elements().First(i => i.Name.LocalName == "description").Value,
Link = (item.Elements().First(i => i.Name.LocalName == "link").Value).StartsWith("/") ? "https://www.c-sharpcorner.com" + item.Elements().First(i => i.Name.LocalName == "link").Value : item.Elements().First(i => i.Name.LocalName == "link").Value,
PubDate = Convert.ToDateTime(item.Elements().First(i => i.Name.LocalName == "pubDate").Value, culture),
Title = item.Elements().First(i => i.Name.LocalName == "title").Value,
FeedType = (item.Elements().First(i => i.Name.LocalName == "link").Value).ToLowerInvariant().Contains("blog") ? "Blog" : (item.Elements().First(i => i.Name.LocalName == "link").Value).ToLowerInvariant().Contains("news") ? "News" : "Article",
Author = item.Elements().First(i => i.Name.LocalName == "author").Value
};
List<Feed> feeds = entries.OrderByDescending(o => o.PubDate).ToList();
string urlAddress = string.Empty;
List<ArticleMatrix> articleMatrices = new();
_ = int.TryParse(_configuration["ParallelTasksCount"], out int parallelTasksCount);
Parallel.ForEach(feeds, new ParallelOptions { MaxDegreeOfParallelism = parallelTasksCount }, feed =>
{
urlAddress = feed.Link;
var httpClient = new HttpClient
{
BaseAddress = new Uri(urlAddress)
};
var result = httpClient.GetAsync("").Result;
string strData = "";
if (result.StatusCode == HttpStatusCode.OK)
{
strData = result.Content.ReadAsStringAsync().Result;
HtmlDocument htmlDocument = new();
htmlDocument.LoadHtml(strData);
ArticleMatrix articleMatrix = new()
{
AuthorId = authorId,
Author = feed.Author,
Type = feed.FeedType,
Link = feed.Link,
Title = feed.Title,
PubDate = feed.PubDate
};
string category = "Videos";
if (htmlDocument.GetElementbyId("ImgCategory") != null)
{
category = htmlDocument.GetElementbyId("ImgCategory").GetAttributeValue("title", "");
}
articleMatrix.Category = category;
var view = htmlDocument.DocumentNode.SelectSingleNode("//span[@id='ViewCounts']");
if (view != null)
{
articleMatrix.Views = view.InnerText;
if (articleMatrix.Views.Contains('m'))
{
articleMatrix.ViewsCount = decimal.Parse(articleMatrix.Views[0..^1]) * 1000000;
}
else if (articleMatrix.Views.Contains('k'))
{
articleMatrix.ViewsCount = decimal.Parse(articleMatrix.Views[0..^1]) * 1000;
}
else
{
_ = decimal.TryParse(articleMatrix.Views, out decimal viewCount);
articleMatrix.ViewsCount = viewCount;
}
}
else
{
var newsView = htmlDocument.DocumentNode.SelectSingleNode("//span[@id='spanNewsViews']");
if (newsView != null)
{
articleMatrix.Views = newsView.InnerText;
if (articleMatrix.Views.Contains('m'))
{
articleMatrix.ViewsCount = decimal.Parse(articleMatrix.Views[0..^1]) * 1000000;
}
else if (articleMatrix.Views.Contains('k'))
{
articleMatrix.ViewsCount = decimal.Parse(articleMatrix.Views[0..^1]) * 1000;
}
else
{
_ = decimal.TryParse(articleMatrix.Views, out decimal viewCount);
articleMatrix.ViewsCount = viewCount;
}
}
else
{
articleMatrix.ViewsCount = 0;
}
}
var like = htmlDocument.DocumentNode.SelectSingleNode("//span[@id='LabelLikeCount']");
if (like != null)
{
_ = int.TryParse(like.InnerText, out int likes);
articleMatrix.Likes = likes;
}
lock (_lockObj)
{
articleMatrices.Add(articleMatrix);
}
}
});
_dbContext.ArticleMatrices.RemoveRange(_dbContext.ArticleMatrices.Where(x => x.AuthorId == authorId));
foreach (ArticleMatrix articleMatrix in articleMatrices)
{
if (articleMatrix.Category == "Videos")
{
articleMatrix.Type = "Video";
}
articleMatrix.Category = articleMatrix.Category.Replace("&", "&");
await _dbContext.ArticleMatrices.AddAsync(articleMatrix);
}
await _dbContext.SaveChangesAsync();
await _cache.RemoveAsync(authorId);
return true;
}
catch
{
return false;
}
}
[HttpGet]
[Route("GetAll/{authorId}/{enableCache}")]
public async Task<List<ArticleMatrix>> GetAll(string authorId, bool enableCache)
{
if (!enableCache)
{
return _dbContext.ArticleMatrices.Where(x => x.AuthorId == authorId).OrderByDescending(x => x.PubDate).ToList();
}
string cacheKey = authorId;
// Trying to get data from the Redis cache
byte[] cachedData = await _cache.GetAsync(cacheKey);
List<ArticleMatrix> articleMatrices = new();
if (cachedData != null)
{
// If the data is found in the cache, encode and deserialize cached data.
var cachedDataString = Encoding.UTF8.GetString(cachedData);
articleMatrices = JsonSerializer.Deserialize<List<ArticleMatrix>>(cachedDataString);
}
else
{
// If the data is not found in the cache, then fetch data from database
articleMatrices = _dbContext.ArticleMatrices.Where(x => x.AuthorId == authorId).OrderByDescending(x => x.PubDate).ToList();
// Serializing the data
string cachedDataString = JsonSerializer.Serialize(articleMatrices);
var dataToCache = Encoding.UTF8.GetBytes(cachedDataString);
// Setting up the cache options
DistributedCacheEntryOptions options = new DistributedCacheEntryOptions()
.SetAbsoluteExpiration(DateTime.Now.AddMinutes(5))
.SetSlidingExpiration(TimeSpan.FromMinutes(3));
// Add the data into the cache
await _cache.SetAsync(cacheKey, dataToCache, options);
}
return articleMatrices;
}
}
}Yuqoridagi controller ichida ikki metod mavjud. “CreatePosts” metodi berilgan C# Corner muallifining postlarini olib, ularni ma’lumotlar bazasiga saqlaydi.
AnalyticsController.cs dagi GetAll metodi
[HttpGet]
[Route("GetAll/{authorId}/{enableCache}")]
public async Task<List<ArticleMatrix>> GetAll(string authorId, bool enableCache)
{
if (!enableCache)
{
return _dbContext.ArticleMatrices.Where(x => x.AuthorId == authorId).OrderByDescending(x => x.PubDate).ToList();
}
string cacheKey = authorId;
// Trying to get data from the Redis cache
byte[] cachedData = await _cache.GetAsync(cacheKey);
List<ArticleMatrix> articleMatrices = new();
if (cachedData != null)
{
// If the data is found in the cache, encode and deserialize cached data.
var cachedDataString = Encoding.UTF8.GetString(cachedData);
articleMatrices = JsonSerializer.Deserialize<List<ArticleMatrix>>(cachedDataString);
}
else
{
// If the data is not found in the cache, then fetch data from database
articleMatrices = _dbContext.ArticleMatrices.Where(x => x.AuthorId == authorId).OrderByDescending(x => x.PubDate).ToList();
// Serializing the data
string cachedDataString = JsonSerializer.Serialize(articleMatrices);
var dataToCache = Encoding.UTF8.GetBytes(cachedDataString);
// Setting up the cache options
DistributedCacheEntryOptions options = new DistributedCacheEntryOptions()
.SetAbsoluteExpiration(DateTime.Now.AddMinutes(5))
.SetSlidingExpiration(TimeSpan.FromMinutes(3));
// Add the data into the cache
await _cache.SetAsync(cacheKey, dataToCache, options);
}
return articleMatrices;
}“GetAll” metodi berilgan muallif uchun ma’lumotni bazadan yoki cache’dan oladi. Ushbu metodga enableCache parametri qo‘shilgan bo‘lib, caching tezligini sinash uchun ishlatiladi.
Muallif ma’lumotini olish jarayonida avval cache tekshiriladi, author id kalit sifatida ishlatiladi. Agar ma’lumot cache’da mavjud bo‘lsa, u modelga deserialize qilinadi va qaytariladi. Agar ma’lumot yo‘q yoki muddati tugagan bo‘lsa, u bazadan olinadi va Redis cache’ga saqlanadi.
Shuningdek, Program.cs klassini o‘zgartirish kerak, shunda Entity Framework va Redis cache ulanishi to‘g‘ri sozlangan bo‘ladi.
Program.cs
using Microsoft.EntityFrameworkCore;
using RedisPr.Models;
var builder = WebApplication.CreateBuilder(args);
ConfigurationManager configuration = builder.Configuration;
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<MyDbContext>(options => options.UseSqlServer(configuration.GetConnectionString("ConnStr")));
builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = configuration["RedisCacheUrl"]; });
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();SQL ma’lumotlar bazasi va jadvalni migration buyruqlari orqali yaratishimiz mumkin.
Package Manager Console orqali migration skriptini yaratish uchun quyidagi buyruq ishlatiladi:
PM> add-migration InitialScriptUshbu buyruq yangi migration skriptini yaratadi. Keyin bazani va jadvalni yaratish uchun quyidagi buyruq ishlatiladi:
PM> update-databaseIlovani ishga tushiring.
Shuningdek, mahalliy Redis cache serverini ham ishga tushirish kerak (redis-server.exe faylini ochish orqali).
Xulosa
Ushbu postda biz tarqatilgan keshlash (distributed caching) va Redis cache haqida muhokama qildik. Biz Windows mashinasida Redis cache serverini qanday o‘rnatish va sinashni ko‘rdik. Shundan so‘ng, ASP.NET Web API yaratdik va uni Windowsdagi lokal Redis server bilan ishlatdik. Shuningdek, Azure portalda Redis uchun Azure cache yaratishni va uni ilovamizda qanday ishlatishni ham ko‘rdik.
(Maqola to'liq emas, yaqin orada to'liq qilinadi.)
Mamba: C# Corner
by LeGee