در نگارشهای پیشین ASP.NET MVC با استفاده از Output Cache، امکان کش کردن خروجی یک اکشن متد، وجود دارد. مکانیزم Output Cache از ASP.NET Core حذف شدهاست؛ اما جایگزینهای قابل توجهی برای آن تدارک دیده شدهاند.
معرفی Response Cache
جایگزین ویژگی حذف شدهی OutputCache در ASP.NET Core، ویژگی جدیدی است به نام ResponseCache و هدف آن تنظیم هدرهای مرتبط با caching مخصوص HTTP Response ارائه شدهاست. به همین جهت با مکانیزم OutputCache قدیمی ASP.NET MVC که اطلاعات را در حافظهی سرور کش میکرد، کاملا متفاوت است.
البته قرار است میان افزار OutputCache را در نگارشهای آتی ASP.NET Core نیز ارائه کنند.
[ResponseCache(Duration = 60)]
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
در اینجا مثالی را از نحوهی تعریف این ویژگی جدید، ملاحظه میکنید که در آن مقدار خاصیت مدت زمان کش شدن، برحسب ثانیه است. استفادهی از آن سبب خواهد شد تا هدر HTTP ذیل به خروجی از سرور اضافه شود:
Cache-Control: public,max-age=60
یک نکته: این ویژگی را هم میتوان به کل کنترلر اعمال کرد و هم به یک اکشن متد خاص. اگر این ویژگی هم به کنترلر و هم به اکشن متدی در آن کنترلر اعمال شده باشد، تنظیمات در سطح متدها، تنظیمات در سطح کلاس را بازنویسی میکنند.
تعیین مکان کش شدن خروجی یک اکشن متد
در هدر فوق، عبارت public را مشاهده میکنید. این public بودن به این معنا است که امکان کش شدن این خروجی، توسط کش سرورهای اشتراکی بین راه هم وجود دارد.
اگر میخواهید این امکان را غیرفعال کنید، نیاز است این public به private تنظیم شود:
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)]
تنظیم Location فوق به Client به معنای private شدن هدر تنظیم شده و صرفا کش شدن خروجی، توسط کش مرورگر کاربر میباشد.
غیرفعال کردن کش شدن خروجی یک اکشن متد
اگر خواستید از کش شدن خروجی یک اکشن متد تحت هر حالتی جلوگیری کنید، مکان آنرا به None و NoStore آنرا به true تنظیم کنید:
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View();
}
این تنظیم سبب افزوده شدن یک چنین هدر HTTP ایی به خروجی از سرور میشود:
Cache-Control: no-store,no-cache
Pragma: no-cache
امکان تعریف پروفایلهای کش
بجای اینکه تنظیمات کش کردن تکراری را به انواع و اقسام اکشن متدها اعمال کنیم، میتوان برای آنها پروفایل ایجاد کرده و از نام این پروفایل، جهت به اشتراک گذاری تنظیمات استفاده کنیم. برای این منظور به کلاس آغازین برنامه مراجعه کرده و جایی که سرویس ASP.NET MVC را فعال سازی کردهاید، پروفایل کش جدیدی را تعریف کنید:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.CacheProfiles.Add("PrivateCache",
new CacheProfile
{
Duration = 60,
Location = ResponseCacheLocation.Client
});
});
پس از آن برای استفادهی از این تنظیمات اشتراکی، فقط کافی است تا نام پروفایل مرتبطی را ذکر کنیم:
[ResponseCache(CacheProfileName = "PrivateCache")]
معرفی سرویس کش درون حافظهای
در نگارشهای پیشین ASP.NET، متدهایی برای کش کردن موقتی اطلاعات در حافظه و سپس بازیابی آنها وجود داشتند. در ASP.NET Core، این متدها توسط سرویس ارائه کنندهی IMemoryCache در اختیار برنامه قرار میگیرند. برای فعال سازی این سرویس جدید باید مراحل ذیل طی شوند:
الف) ابتدا بستهی Microsoft.Extensions.Caching.Memory را به لیست وابستگیهای پروژه در فایل project.json اضافه کنید:
{
"dependencies": {
//same as before
"Microsoft.Extensions.Caching.Memory": "1.0.0"
},
ب) سپس به کلاس آغازین برنامه مراجعه کرده و سرویس آنرا معرفی و ثبت کنید:
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
ج) پس از آن سرویس پیاده سازی کنندهی IMemoryCache، در تمام اجزای برنامه در دسترس خواهد بود. برای مثال:
[Route("DNT/[controller]")]
public class AboutController : Controller
{
private readonly IMemoryCache _memoryCache;
public AboutController(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
[Route("")]
public ActionResult Hello()
{
string cacheKey = "my-cache-key";
string greeting;
if (!_memoryCache.TryGetValue(cacheKey, out greeting))
{
greeting = "Hello";
// store in the cache
_memoryCache.Set(cacheKey, greeting,
new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(1)));
}
return Content($"{greeting} from DNT!");
}
در مثال فوق، ابتدا وابستگی سرویس کش درون حافظهای، به سازندهی کنترلر تزریق شدهاست. تامین آن هم توسط سرویسی که در کلاس آغازین برنامه ثبت کردیم، انجام میشود. پس از آن در اکشن متد Hello، سعی کردهایم بر اساس کلید کشی که مشخص کردهایم، مقداری را بازیابی کنیم. اگر این مقدار وجود نداشته باشد، آنرا توسط متد Set تنظیم خواهیم کرد تا برای دفعات آتی فراخوانی این متد، مورد استفاده قرار گیرد.
تنظیمات منقضی شدن کش نیز به حالت absolute تنظیم شدهاست. یعنی پس از یک دقیقه حتما منقضی میشود. اگر فراخوانیهای این متد زیاد است، میتوان حالت منقضی شدن sliding را تنظیم کرد:
new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(5))
در این حالت اگر پیش از اتمام 5 دقیقهی تنظیم شده، درخواستی به سرور رسید، این کش برای 5 دقیقهی بعد نیز مجددا تمدید میشود.
اگر خواستیم تا این کش سر ساعت منقضی شود، اما در طی این یک ساعت به صورت sliding عمل کند، میتوان از ترکیب دو حالت مطلق و لغزشی استفاده کرد:
new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(5))
.SetAbsoluteExpiration(TimeSpan.FromHours(1))
یک نکته: اگر فشار حافظهی سرور زیاد شود، مدیر حافظهی این کش، شروع به منقضی کردن آیتمهایی با حق تقدم پایین میکند. بالاترین حق تقدم را حالت NeverRemove ذیل دارد:
new MemoryCacheEntryOptions()
.SetPriority(CacheItemPriority.NeverRemove))
معرفی Tag Helpers مخصوص کش کردن قسمتی از صفحه در ادامهی مبحث معرفی Tag Helpers، تعدادی از آنها جهت کش کردن محتوای قسمتی از صفحه، طراحی شدهاند:
<cache expires-after="@TimeSpan.FromMinutes(10)">
@Html.Partial("_WhatsNew")
*last updated @DateTime.Now.ToLongTimeString()
</cache>
تگ جدید cache محتوای دربرگیرندهی آنرا «در حافظهی سرور» کش میکند (و در پشت صحنه از همان کش درون حافظهای که پیشتر بحث شد، استفاده میکند). تگ cache در خروجی HTML نهایی مشاهده نمیشود و صرفا مفهومی سمت سرور است.
برای نمونه در مثال فوق، محتوای پارشال ویوو رندر شده و همچنین تاریخی که پس از آن نمایش داده شدهاست، به مدت 10 دقیقه در حافظهی سرور کش میشوند. اگر این زمان تنظیم نشود، تا زمانیکه برنامه در سرور مشغول به کار است، این قسمت منقضی نخواهد شد.
در اینجا اگر expires-after ذکر شده بود، یعنی پس از این مدت زمان، کش منقضی میشود.
<cache expires-after="@TimeSpan.FromSeconds(5)">
<!--View Component or something that gets data from the database-->
*last updated @DateTime.Now.ToLongTimeString()
</cache>
اگر expires-on آن ذکر شود، میتوان تاریخ و زمان مشخصی را در اینجا ذکر کرد (برای مثال فردا ساعت 10، با فراخوانی DateTime.Today.AddDays).
<cache expires-on="@DateTime.Today.AddDays(1).AddTicks(-1)">
<!--View Component or something that gets data from the database-->
*last updated @DateTime.Now.ToLongTimeString()
</cache>
همچنین میتوان از expires-sliding نیز استفاده کرد. به این معنا که اگر در طی مدتی خاص این صفحه درخواست نشد، آنگاه این کشی منقضی میشود.
<cache expires-sliding="@TimeSpan.FromMinutes(5)">
<!--View Component or something that gets data from the database-->
*last updated @DateTime.Now.ToLongTimeString()
</cache>
همچنین در اینجا میتوان کش کردن را به ازای کاربران مختلف، کوئری استرینگهای مختلف و امثال آن انجام داد (با ارائهی محتوای متفاوتی به ازای پارامترهای مختلف):
<cache vary-by-user="true">
<!--View Component or something that gets data from the database-->
*last updated @DateTime.Now.ToLongTimeString()
</cache>
در این حالت دیگر نیازی نیست تا نگران این باشیم که آیا محتوای قسمت کش شدهی از صفحه برای تمام کاربران در دسترس است یا خیر؟ در اینجا هر کاربر لاگین شدهی به سیستم، نگارش کش شدهی خاص خودش را دریافت میکند.
<cache vary-by-route="id">
<!--View Component or something that gets data from the database-->
*last updated @DateTime.Now.ToLongTimeString()
</cache>
در اینجا به ازای پارامتر آیدی مسیریابی، نگارشهای مختلف کش شدهای از صفحه تامین میشوند. در اینجا میتوان لیستی از پارامترهای جدا شدهی با کاما را مشخص کرد.
<cache vary-by-query="search">
<!--View Component or something that gets data from the database-->
*last updated @DateTime.Now.ToLongTimeString()
</cache>
امکان کش کردن محتوای صفحه به ازای کوئری استرینگهای مختلف تنظیم شده نیز وجود دارد.
<cache vary-by-cookie="MyAppCookie">
<!--View Component or something that gets data from the database-->
*last updated @DateTime.Now.ToLongTimeString()
</cache>
در اینجا به ازای محتواهای مختلف کوکی خاصی به نام MyAppCookie، نگارشهای مختلف کش شدهای از صفحه ذخیره میشوند.
<cache vary-by-header="User-Agent">
<!--View Component or something that gets data from the database-->
*last updated @DateTime.Now.ToLongTimeString()
</cache>
در اینجا میتوان به ازای هدرهای مختلف پروتکل HTTP نگارشهای کش شدهی متفاوتی را ارائه داد.
<cache vary-by="@ViewBag.ProductId">
<!--View Component or something that gets data from the database-->
*last updated @DateTime.Now.ToLongTimeString()
</cache>
اگر خواستید کلید کش را خودتان تعیین کنید از vary-by استفاده کنید.
<cache vary-by-user="true" vary-by-route="id">
<!--View Component or something that gets data from the database-->
*last updated @DateTime.Now.ToLongTimeString()
</cache>
امکان ترکیب این موارد با هم نیز وجود دارد.
به علاوه چون زیر ساخت این Tag Helper همان Microsoft.Extensions.Caching.Memory است، امکان تنظیم حق تقدم حذف شدن آیتمهای کش شده نیز وجود دارد:
<cache expires-sliding="@TimeSpan.FromMinutes(10)"
priority="@Microsoft.Extensions.Caching.Memory.CacheItemPriority.NeverRemove">
<!--View Component or something that gets data from the database-->
*last updated @DateTime.Now.ToLongTimeString()
</cache>
مبحث تکمیلی
امکان ذخیره سازی آیتمهای کش شده در بانک اطلاعاتی (بجای حافظهی فرار) نیز پیش بینی شدهاست که تحت عنوان «کش توزیع شده» در دسترس است.
Working with a Distributed Cache