3 راه برای بهبود عملکرد استفاده از Caching در ASP.Net MVC

Caching یک تکنیک است که به ما در ذخیره سازی داده های خود درجایی که از قبل داریم ، کمک می کند و اگر به همان داده دوباره نیاز داشتیم می توانیم آن را از داده های ذخیره شده بگیریم. داده را می توانیم در سمت کلاینت همانند سمت سرور ذخیره کنیم این موضوع کاملا به احتیاجات شما بستگی دارد نه انتخاب شما. بنابراین در این مقاله سه راه برای بهبود عملکرد استفاده از Caching در ASP.NET MVC Applications را بررسی خواهیم کرد.

3 راه برای بهبود عملکرد استفاده از Caching در ASP.Net MVC

امروزه بازدید از یک وبسایت با یک کامپیوتر رومیزی یا با موبایل از کارهای روزمره ی ماست هربار که ما از یک وبسایت یا وب اپلیکیشن بازدید می کنیم انتظار داریم که به سرعت بین 5 تا 10 ثانیه رندر شود اما متاسفانه تعداد اندکی از وبسایت ها وجود دارند که به کمینه ی زمان برای باز شدن نیاز دارند که دلایل آن به موارد زیر می باشد:

1.محتوای زیاد

2.تصاویر بزرگ و چندگانه

3.منطق های پیچیده

4. request های زیاد از سرور

5. اتصالات ضعیف

6.لایه ی دسترسی به داده و غیره

بنابراین دلایل متعددی برای کندی یک وبسایت وجود دارد حال کاری که می توانیم برای حل این موضوعات که مانع عملکرد وبسایت است انجام دهیم استفاده از Caching است. بله با استفاده از Caching می نوانیم داده ها را ذخیره کنیم و اگر کاربر یک request دیگر برای همان نوع از داده درست کند دیگر نیاز نداریم که دوباره همان داده را از سرور دریافت کنیم و همچنین دیگر نیاز نداریم همان منطق ها را دوباره و دوباره استفاده کنیم.

Caching یک تکنیک است که به ما در ذخیره سازی داده های خود درجایی که از قبل داریم، کمک می کند و اگر به همان داده دوباره نیاز داشتیم می توانیم آن را از داده های ذخیره شده بگیریم. داده را می توانیم در سمت کلاینت همانند سمت سرور ذخیره کنیم این موضوع کاملا به احتیاجات شما بستگی دارد نه انتخاب شما.

مزایای Caching

اگر بخواهیم مزایای Caching را در یک کلمه تعریف کنیم می تواند "عملکرد" باشد و می تواند در دست یابی به کمینه کردن موارد زیر استفاده شود:

-کمینه کردن round trip های سرور (سرور هاست شده یا سرور دیتابیس یا هر سرور دیگری)

-کمینه کردن ترافیک شبکه (فراخوانی http به سرور)

-از تکرار یک منطق الحاق داده جلوگیری می کنیم

نکات و ترفند های استفاده از ASP.NET MVC caching

1.از caching برای داده هایی که دائما تغییر می کند استفاده نکنید.

2.از caching برای منطق اهراز هویت استفاده نکنید.

3.از caching برای داده هایی که بر هر کاربر یکتا است استفاده نکنید.

4.از caching برای داده هایی که ندرتا استفاده می شود نظیر صفحه ی حفظ حریم خصوصی استفاده نکنید.

5.از caching برای صفحه ی ارور ها استفاده نکنید.

6.از caching برای داده هایی که غالبا استفاده می شود و یک داده می تواند توسط همه ی کاربران استفاده شود استفاده کنید.

7.همیشه از caching برای تصاویر یا فایل های مدیا استفاده کنیم.

عمدتا می توانیم داده های خود را به سه روش در ASP.NET MVC ذخیره کنیم.

1.ذخیره ی داده های استاتیک

2.ذخیره ی همه یا قسمتی از صفحه که از ویژگی OutputCache استفاده می کند.

3.ذخیره ی داده های به اشتراک گذاشته شده

ذخیره ی داده های استاتیک

زمان طراحی هر وبسایت، ما از محتوا های استاتیک استفاده می کنیم. عمدتا محتوا های استاتیک یعنی بخشی از داده که به صورت دینامیک تغییر نمی کند مثل تصاویر، فایل های CSS و JavaScript و غیره. این موارد واقعا سنگین هستند و بارگذاری آن ها از سرور کار زمان بری است. آیا می دانید چه مقدار زمان برای بارگذاری این محتوا های ثابت صرف می شود؟ این زمان بیش از 60% زمانی است که ما مصرف می کنیم اما این طبیعی است اما اگر از محتوا های ثابت بیشتری استفاده کنیم بنابراین بطور حتم زمان بیشتری برای بارگذاری نیاز است.

حال این سوال پیش می آید که "آیا باید این محتوا ها را هر بار که از یک صفحه روی یک مرورگر بارها و بارها استفاده می کنیم دانلود کنیم؟"

جواب خیر است. فرایند دانلود باید بخشی از دفعه ی اول باشد نه هربار این کار را انجام دهیم. بنابراین کاری که می توانیم انجام دهیم استفاده از Static Content Caching در ASP.NET MVC است. اینجا برای ذخیره سازی محتوا های استاتیک ذخیره شده در حافظه و برای دسترسی به همان صفحه بارها و بارها به جای دانلود کردن دوباره ی همه ی محتوا های ثابت از سرور داده ها را از حافظه ی cache دریافت می کنیم.

اجازه دهید این موضوع را با اینکه چگونه می توانیم به محتوا های استاتیک با استفاده از caching در ASP.NET MVC دست بیابیم بیشتر متوجه شویم.

اینجا یک اپلیکیشن ASP.NET MVC 4 ساخته ایم. اگر نمیدانید که چگونه یک اپلیکیشن Asp.Net MVC بسازید کافی است به مقاله ی قبلی مراجعه کنید. پس از ساخت یک اپلیکیشن به Index View در HomeController که به صورت خودکار با استفاده از کد زیر ساخته می شود بروید. اینجا می توانید ببینید که فقط سه عکس اضافه کردیم. برای نمایش این کار سه عکس از گوگل دانلود کردیم و شما می توانید این کار را با هر عکسی انجام بدهید بنابراین وقتی اپلیکیشن را اجرا می کنیم این عکس ها در هر بار از سرور دانلود می شوند.

@{  
    ViewBag.Title = "Home Page";  
}  
<style>  
    table td {  
        padding: 10px;  
        border: 2px solid #808080;  
        text-align: center;  
    }  
</style>  
<div class="jumbotron">  
    <h3>Static Content Caching in Asp.Net MVC</h3>  
    <table>  
        <tr>  
            <td><img src="~/Content/img/image1.png" width="300" height="300" />Image 1</td>  
            <td><img src="~/Content/img/image2.jpg" width="300" height="300" />Image 2</td>  
            <td><img src="~/Content/img/image3.png" width="300" height="300" />Image 3</td>  
        </tr>  
    </table>  
</div>  

اینجا متد Index Action که مسئول رندر کردن Index View است،می باشد.

//Static content caching  
public ActionResult Index()  
{  
    return View();  
}  

حال زمان اجرای اپلیکیشن است تا ببینیم چه چیزی اتفاق می افتد زمانی که اپلیکیشن را اجرا کردیم به ابزار developer می رویم > در قسمت Networks هریک از محتوا های استاتیک مانند تصاویر،فایل های CSS یا JS دانلود شده را می توانید پیدا کنید اما اگر همین صفحه را refresh کنیم دوباره همین روند تکرار می شود و فایل ها دوباره دانلود می شوند.

برای ذخیره ی محتوا های استاتیک باید تعدادی پیکربندی در فایل Web.Config همانطور که در ادامه نشان داده شده اضافه کنیم. در اینجا می توانید ببینید که پسوند فایل ها را از نوع mime تعریف کرده ایم که نیاز است ذخیره شود. برای تعریف مدت زمان ذخیره سازی از ویژگی "cacheControlMaxAge" از "clientCache" استفاده می کنیم . زمان این جا تعریف می شود که در این جا ما 1 دقیقه برای این کار صرف کرده ایم.

<system.webServer>  
    <staticContent>  
      <clear/>  
      <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="00:01:00" />  
      <mimeMap fileExtension=".jpg" mimeType="image/jpg"/>  
      <mimeMap fileExtension=".png" mimeType="image/jpg"/>  
      <mimeMap fileExtension=".css" mimeType="text/css"/>  
      <mimeMap fileExtension=".js" mimeType="text/javascript"/>  
    </staticContent>  
    <validation validateIntegratedModeConfiguration="false" />  
    <modules>  
      <remove name="ApplicationInsightsWebTracking" />  
      <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler" />  
    </modules>  
</system.webServer>  

پس از پیکربندی caching برای محتوا های استاتیک دوباره زمان اجرای اپلیکیشن می باشد. زمانی که ما اپلیکیشن را برای بار اول اجرا می کنیم نتیجه ای مانند تصویر بالا مشاهده خواهیم کرد که نشان دهنده ی این است که هر کامپوننتی در یک زمان مشخص دانلود شده است اما اگر اپلیکیشن را دوباره اجرا کنیم در یک دقیقه نتایجی مانند نتایج زیر را خواهیم دید.

تصاویر زیر نشان می دهد که هر کامپوننت استاتیک به جای دانلود شدن از سرور از حافظه ی cache گرفته می شود. بنابراین اینجا به تکنیک های caching استاتیک دست یافتیم. حال از این موضوع استفاده می کنیم و می توانیم محتوا های استاتیک را ذخیره کنیم و دیگر نیاز نیست که هربار محتوا های ثابت را دانلود کنیم.

ذخیره خروجی

در ASP.NET MVC می توانیم response های صفحه را با استفاده از ویژگی "OutputCache" ذخیره کنیم. این نوع از ذخیره سازی اساسا برای ذخیره کردن محتوا های تولید شده توسط یک متد عملیاتی استفاده می شود. ویژگی "OutputCache" می تواند با سطح عملیاتی یا سطح کنترل کننده استفاده شود. در ذخیره سازی خروجی می توانیم مدت ذخیره سازی را برحسب ثانیه اندازه گیری کنیم در اینجا حتی می توانیم مکان ذخیره سازی را هم تعریف کنیم که می تواند هم سمت سرور و هم سمت کلاینت باشد اگر می خواهیم که محتوا را با استفاده از برخی پارامتر ها ذخیره کنیم می توانیم با استفاده از "VaryByParam" تعریف کنیم.

بنابراین اجازه دهید با یک مثال بهتر متوجه شویم. حال می خواهیم یک متد عملیاتی جدید به عنوان "Index1" مانند زیر بسازیم. دراینجا می خواهیم یک لیست از پست های وبلاگ که شامل 1000 رکورد است درست کنیم.

//Output caching  
[OutputCache(Duration = 60, VaryByParam = "none", Location = System.Web.UI.OutputCacheLocation.Server)]  
public ActionResult Index1()  
{  
 var postModel = new List < BlogPost > ();  
  
 //Suppose getting blog post data form API call.    
 for (int i = 0; i < 1000; i++) {  
  postModel.Add(new BlogPost() {  
   PostId = i, Title = "Blog Post Title " + i, Category = "Category " + i, Content = "Content for Blog Post " + i  
  });  
 }  
  
foreach(var item in postModel)   
{  
  if (item.PostId % 2 == 0) {  
   item.UserName = "Mukesh Kumar";  
  } else {  
   item.UserName = "Admin";  
  }  
 }  
  
  
 return View(postModel);  
}  

کد زیر برای entity class با نام "BlogPost" برای وبلاگ است.

namespace CachingInMVC.Models  
{  
    public class BlogPost  
    {  
        public int PostId { get; set; }  
        public string Title { get; set; }  
        public string Category { get; set; }  
        public string Content { get; set; }  
        public string UserName { get; set; }  
    }  
}  

حال نیاز داریم که یک view به نام "Index1" در پوشه ی Home از View بسازیم. این View اساسا تمام پست های وبلاگ را در نمای جدولی رندر می کند. ما تاریخ را برای دیدن زمان به روزرسانی تعریف کرده ایم.

@model IEnumerable<CachingInMVC.Models.BlogPost>  
@{  
    ViewBag.Title = "Home Page";  
}  
<style>  
    table td {  
        padding: 10px;  
        border: 2px solid #808080;  
        text-align: center;  
    }  
</style>  
<div class="jumbotron">  
    <h2>Output Caching in Asp.Net MVC</h2>  
    <h3>Last Update Data: <strong style="color:red;">@DateTime.Now.ToString()</strong></h3>  
    <br />  
    <table style="border:3px solid #808080;">  
        <tr>  
            <th>ID</th>  
            <th>Title</th>  
            <th>Category</th>  
            <th>Content</th>  
            <th>User</th>  
        </tr>  
        @foreach (var post in Model)  
        {  
            <tr style="border:1px solid #808080">  
                <td>@post.PostId</td>  
                <td>@post.Title</td>  
                <td>@post.Category</td>  
                <td>@post.Content</td>  
                <td>@post.UserName</td>  
            </tr>  
        }  
    </table>  
</div>  

زمانی که ما اپلیکیشن را اجرا می کنیم می توانیم خروجی ها را به صورت زیر ببینیم و در آن می توانیم آخرین به روزرسانی داده های پست وبلاگ را در فرمت جدولی ببینیم همانند بالا با متد عملیاتی ما در حال استفاده از Output Caching در طی 60 ثانیه هستیم بنابراین اگر ما این صفحه را دوباره ظرف 60 ثانیه دوباره بارگذاری کنیم همه ی کد های "Index1" دوباره پردازش نمی شود و داده از کش رندر می شود.

اینجا ما دوباره صفحه را بارگذاری کرده ایم . صفحه بسیار سریع رندر شد و می توانیم ببینیم که داده ها همانند نتیجه ی قبلی هستند و زمان به روز رسانی قبلی داده ها نیز همان است زیرا همه چیز از سرور بازگرفته می شود تا همه ی کد برای "Index1" دوباره اجرا شود.

ذخیره سازی های داده های مشترک

حال اگر ما به اشتراک گذاری داده های مشترک با متد های عملیاتی نیاز داریم و می خواهیم این نوع از داده ها را به کش اضافه کنیم می توانیم از "HttpContext.Cache" برای تنظیمات یا گرفتن داده ها استفاده کنیم. همانطور که می توانیم از کد زیر ببینیم ما از همان کد ها که قبلا در مثال Output Caching استفاده کردیم استفاده می کنیم اما اینجا model data را حذف کرده ایم و منطق را در یک متد ایجاد می کنیم.

در ابتدا باید بررسی کنیم که آیا داده در کش با استفاده از کلید تعریف شده وجود دارد یا نه اگر داده وجود داشت داده را روی view رندر خواهیم کرد و اگر وجود نداشت داده را از متد "getThousandsPost" خواهیم گرفت و آن را با مدل الحاق خواهیم کرد اما قبل از رندر کردن داده باید اطمینان حاصل کنیم که این داده را به کش اضافه کرده ایم بنابراین دفعه ی بعد نیاز نداریم که همان منطق را دوباره و دوباره اجرا کنیم.

گرفتن داده از کش

var postModel = HttpContext.Cache.Get("ThousandsPost") as List < BlogPost > ;  

تنظیم داده در کش

HttpContext.Cache.Insert("ThousandsPost", postModel, null, DateTime.Now.AddMinutes(1), Cache.NoSlidingExpiration);  
   
//Common data caching  
public ActionResult Index2()   
{  
 var postModel = HttpContext.Cache.Get("ThousandsPost") as List < BlogPost > ;  
   
 if (postModel == null) {  
  postModel = this.getThousandsPost();  
  HttpContext.Cache.Insert("ThousandsPost", postModel, null, DateTime.Now.AddMinutes(1), Cache.NoSlidingExpiration);  
 }  
  
 return View(postModel);  
}  
  
private List < BlogPost > getThousandsPost()   
{  
 List < BlogPost > post = new List < BlogPost > ();  
  
 //Suppose getting blog post data form API call.    
 for (int i = 0; i < 1000; i++)   
 {  
  post.Add(new BlogPost() {  
   PostId = i, Title = "Blog Post Title " + i, Category = "Category " + i, Content = "Content for Blog Post " + i  
  });  
 }  
  
 foreach(var item in post)   
 {  
  if (item.PostId % 2 == 0) {  
   item.UserName = "Mukesh Kumar";  
  } else {  
   item.UserName = "Admin";  
  }  
 }  
 return post;  
}  

اینجا از view جدید "Index2" به صورت زیر استفاده می کنیم

@model IEnumerable<CachingInMVC.Models.BlogPost>  
@{  
    ViewBag.Title = "Home Page";  
}  
<style>  
    table td {  
        padding: 10px;  
        border: 2px solid #808080;  
        text-align: center;  
    }  
</style>  
<div class="jumbotron">  
  
    <h2>Data Caching in Asp.Net MVC</h2>  
    <h3>Last Update Data: <strong style="color:red;">@DateTime.Now.ToString()</strong></h3>  
    <br />  
    <table style="border:3px solid #808080;">  
        <tr>  
            <th>ID</th>  
            <th>Title</th>  
            <th>Category</th>  
            <th>Content</th>  
            <th>User</th>  
        </tr>  
        @foreach (var post in Model)  
        {  
            <tr style="border:1px solid #808080">  
                <td>@post.PostId</td>  
                <td>@post.Title</td>  
                <td>@post.Category</td>  
                <td>@post.Content</td>  
                <td>@post.UserName</td>  
            </tr>  
        }  
    </table>  
</div>  

زمانی که ما اپلیکیشن را اجرا می کنیم متوجه خواهیم شد که زمان داده ها هربار عوض می شوند این به این دلیل است که زمان داده ها بخشی از کش نیست و ما آن را در View تعریف کرده ایم. برای بررسی این که آیا داده را از کش بارگذاری کرده ایم یا نه فقط یک breakpoint در متد "getThousandsPost()" قرار دهید سپس متوجه خواهیم شد که زمانی که برای بار اول این متد اجرا می شود داده ذخیره سازی می شود و دفعه ی بعد داده را از کش خواهیم گرفت.