مطالب
React 16x - قسمت 23 - ارتباط با سرور - بخش 2 - شروع به کار با Axios
پس از نصب Axios در قسمت قبل، جزئیات کار با آن‌را در این بخش مرور می‌کنیم.


دریافت اطلاعات از سرور، توسط Axios

- ابتدا به پوشه‌ی sample-22-backend ای که در قسمت قبل ایجاد کردیم، مراجعه کرده و فایل dotnet_run.bat آن‌را اجرا کنید، تا endpointهای REST Api آن، قابل دسترسی شوند. برای مثال باید بتوان به مسیر https://localhost:5001/api/posts در مرورگر دسترسی یافت (و یا همانطور که عنوان شد، از آدرس https://jsonplaceholder.typicode.com/posts نیز می‌توانید استفاده کنید؛ چون ساختار یکسانی دارند).

-سپس در برنامه‌ی React ای که در قسمت قبل ایجاد کردیم، فایل app.js آن‌را گشوده و ابتدا کتابخانه‌ی Axios را import می‌کنیم:
import axios from "axios";
در قسمت 9 که Lifecycle hooks را در آن بررسی کردیم، عنوان شد که در اولین بار نمایش یک کامپوننت، بهترین مکان دریافت اطلاعات از سرور و سپس به روز رسانی UI، متد componentDidMount است. به همین جهت میانبر cdm را در VSCode نوشته و دکمه‌ی tab را فشار می‌دهیم تا به صورت خودکار این متد را ایجاد کند. در ادامه این متد را به صورت زیر تکمیل می‌کنیم:
  componentDidMount() {
    const promise = axios.get("https://localhost:5001/api/posts");
    console.log(promise);
  }
متد axios.get، کار دریافت اطلاعات از سرور را انجام می‌دهد و اولین آرگومان آن، URL مدنظر است. این متد، یک Promise را بازگشت می‌دهد. یک Promise، شیءای است که نتیجه‌ی یک عملیات async را نگهداری می‌کند و یک عملیات async، عملیاتی است که قرار است در آینده تکمیل شود. زمانیکه یک HTTP GET را ارسال می‌کنیم، وقفه‌ای تا زمان بازگشت اطلاعات از سرور وجود خواهد داشت و این عملیات، آنی نیست. بنابراین حالت آغازین یک Promise، در وضعیت pending قرار می‌گیرد. پس از پایان عملیات async، این وضعیت به یکی از حالات resolved (در حالت موفقیت آمیز بودن عملیات) و یا rejected (در حالت شکست عملیات) تغییر پیدا می‌کند.



تنظیمات CORS مخصوص React در برنامه‌های ASP.NET Core 3x

همانطور که مشاهده می‌کنید، پس از ذخیره سازی تغییرات، با اجرای برنامه، این Promise در حالت pending قرار گرفته و همچنین پس از پایان آن، حاوی نتیجه‌ی عملیات نیز می‌باشد که در اینجا rejected است. علت شکست عملیات را در سطر بعدی آن ملاحظه می‌کنید که عنوان کرده‌است «CORS policy» مناسبی در سمت سرور، برای این درخواست وجود ندارد؛ چرا؟ چون برنامه‌ی React ما در مسیر http://localhost:3000/ اجرا می‌شود و برنامه‌ی Web API در مسیر دیگری https://localhost:5001/ که شماره‌ی پورت این‌دو یکی نیست. به همین جهت عنوان می‌کند که نیاز است در سمت سرور، هدرهای خاصی برای پردازش این نوع درخواست‌های با Origin متفاوت وجود داشته باشد، تا مرورگر اجازه‌ی دسترسی به آن‌را بدهد. برای رفع این مشکل، برنامه‌ی sample-22-backend را گشوده و تغییرات زیر را اعمال می‌کنیم:
ابتدا تنظیمات AddCors را با تعریف یک CORS policy جدید مخصوص آدرس http://localhost:3000، به متد ConfigureServices کلاس آغازین برنامه اضافه می‌کنیم:
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
       options.AddPolicy("ReactCorsPolicy",
          builder => builder
            .AllowAnyMethod()
            .AllowAnyHeader()
            .WithOrigins("http://localhost:3000")
            .AllowCredentials()
            .Build());
    });
    services.AddSingleton<IPostsDataSource, PostsDataSource>();
    services.AddControllers();
}
سپس میان‌افزار آن‌را با فراخوانی UseCors که باید بین UseRouting و UseEndpoints تعریف شود، فعال می‌کنیم:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
      app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    //app.UseAuthentication();
    //app.UseAuthorization();

    app.UseCors("ReactCorsPolicy");

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
       endpoints.MapControllers();
    });
}
اکنون اگر صفحه‌ی برنامه‌ی React را ریفرش کنیم، به نتیجه‌ی زیر خواهیم رسید:


اینبار Promise بازگشت داده شده، در حالت resolved قرار گرفته‌است که به معنای موفقیت آمیز بودن عملیات async است. وجود [[PromiseStatus]] به معنای یک internal property است که توسط dot notation قابل دسترسی نیست. در اینجا [[PromiseValue]] نیز یک internal property غیرقابل دسترسی است که نتیجه‌ی عملیات (response دریافتی از سرور) در آن قرار می‌گیرد. برای مثال در data آن، آرایه‌ی مطالب دریافتی از سرور، قابل مشاهده‌است و یا status=200 به معنای موفقیت آمیز بودن پردازش درخواست، از سمت سرور است.

البته زمانیکه درخواست افزودن رکورد جدیدی را به سمت سرور ارسال می‌کنیم، می‌توان دو درخواست را در برگه‌ی network ابزارهای توسعه دهندگان مرورگر، مشاهده کرد:


در اولین درخواست، Request Method: OPTIONS را داریم که دقیقا مرتبط است با بررسی CORS توسط مرورگر.


دریافت اطلاعات شیء response از یک Promise و نمایش آن

همانطور که عنوان شد، [[PromiseValue]] نیز یک internal property غیرقابل دسترسی است. بنابراین اکنون این سؤال مطرح می‌شود که چگونه می‌توان به اطلاعات آن دسترسی یافت؟
این شیء Promise، دارای متدی است به نام then است که نتیجه‌ی عملیات async را بازگشت می‌دهد. البته این روش قدیمی کار کردن با Promiseها است و ما از آن در اینجا استفاده نخواهیم کرد. در جاوا اسکریپت مدرن، می‌توان از واژه‌ی کلیدی await برای دسترسی به شیء response دریافتی از سرور، استفاده کرد:
  async componentDidMount() {
    const promise = axios.get("https://localhost:5001/api/posts");
    console.log(promise);
    const response = await promise;
    console.log(response);
  }
هر جائیکه از واژه‌ی کلیدی await استفاده می‌شود، متد جاری را باید با واژه‌ی کلیدی async نیز مزین کرد. پس از این تغییرات، اکنون شیء response، حاوی اطلاعات اصلی و واقعی دریافتی از سرور است؛ برای مثال خاصیت data آن، حاوی آرایه‌ی مطالب می‌باشد:



البته قطعه کد نوشته شده، صرفا جهت توضیح مراحل مختلف عملیات، به این صورت چند مرحله‌ای نوشته شد، وگرنه می‌توان واژه‌ی کلیدی await را پیش از فراخوانی متدهای Axios نیز قرار داد:
  async componentDidMount() {
    const response = await axios.get("https://localhost:5001/api/posts");
    console.log(response);
  }
با توجه به اینکه اطلاعات اصلی شیء response، در خاصیت data آن قرار دارد، می‌توان با استفاده از Object Destructuring، خاصیت data آن‌را دریافت و سپس تغییر نام داد:
class App extends Component {
  state = {
    posts: []
  };

  async componentDidMount() {
    const { data: posts } = await axios.get("https://localhost:5001/api/posts");
    this.setState({ posts }); // = { posts: posts }
  }
پس از مشخص شدن آرایه‌ی posts دریافتی از سرور، اکنون می‌توان با فراخوانی متد setState و به روز رسانی خاصیت posts آن، سبب رندر مجدد این کامپوننت و در نتیجه نمایش اطلاعات نهایی شد:



ایجاد یک مطلب جدید توسط Axios

در برنامه‌ی React ای ایجاد شده، یک دکمه‌ی Add نیز برای افزودن مطلبی جدید درنظر گرفته شده‌است. در یک برنامه‌ی واقعی‌تر، معمولا فرمی وجود دارد و نتیجه‌ی آن در حین submit، به سمت سرور ارسال می‌شود. در اینجا این سناریو را شبیه سازی خواهیم کرد:
const apiEndpoint = "https://localhost:5001/api/posts";

class App extends Component {
  state = {
    posts: []
  };

  async componentDidMount() {
    const { data: posts } = await axios.get(apiEndpoint);
    this.setState({ posts });
  }

  handleAdd = async () => {
    const newPost = {
      title: "new Title ...",
      body: "new Body  ...",
      userId: 1
    };
    const { data: post } = await axios.post(apiEndpoint, newPost);
    console.log(post);

    const posts = [post, ...this.state.posts];
    this.setState({ posts });
  };
توضیحات:
- چون قرار است از آدرس https://localhost:5001/api/posts در قسمت‌های مختلف برنامه استفاده کنیم، فعلا آن‌را به صورت یک ثابت تعریف کرده و در متدهای get و post استفاده کردیم.
- در متد منتسب به خاصیت handleAdd، یک شیء جدید post را با ساختاری مشابه آن ایجاد کرده‌ایم. این شیء جدید، دارای Id نیست؛ چون قرار است از سمت سرور پس از ثبت در بانک اطلاعاتی دریافت شود.
- سپس این شیء جدید را توسط متد post کتابخانه‌ی Axios، به سمت سرور ارسال کرده‌ایم. این متد نیز یک Promise را باز می‌گرداند. به همین جهت از واژه‌ی کلیدی await برای دریافت نتیجه‌ی واقعی آن استفاده شده‌است. همچنین هر زمانیکه await داریم، نیاز به ذکر واژه‌ی کلیدی async نیز هست. اینبار این واژه باید پیش از قسمت تعریف پارامتر متد قرار گیرد و نه پیش از نام handleAdd؛ چون handleAdd در واقع یک خاصیت است که متدی به آن انتساب داده شده‌است.
- نتیجه‌ی دریافتی از متد axios.post را اینبار به post، بجای posts تغییر نام داده‌ایم و همانطور که در تصویر زیر مشاهده می‌کنید، خاصیت id آن در سمت سرور مقدار دهی شده‌است:


- در آخر برای افزودن این رکورد، به مجموعه‌ی رکوردهای موجود، از روش spread operator استفاده کرده‌ایم تا ابتدا شیء post دریافتی از سمت سرور درج شود و سپس مابقی اعضای آرایه‌ی posts موجود در state، در این آرایه گسترده شده و یک آرایه‌ی جدید را تشکیل دهند. سپس این آرایه‌ی جدید را جهت به روز رسانی state و در نتیجه‌ی آن، به روز رسانی UI، به متد setState ارسال کرده‌ایم، که نتیجه‌ی آن درج این رکورد جدید، در ابتدای لیست است:


 
به روز رسانی اطلاعات در سمت سرور

در اینجا پیاده سازی متد put را مشاهده می‌کنید:
  handleUpdate = async post => {
    post.title = "Updated";
    const { data: updatedPost } = await axios.put(
      `${apiEndpoint}/${post.id}`,
      post
    );
    console.log(updatedPost);

    const posts = [...this.state.posts];
    const index = posts.indexOf(post);
    posts[index] = { ...post };
    this.setState({ posts });
  };
- با کلیک بر روی دکمه‌ی update هر ردیف نمایش داده شده، شیء post آن ردیف را در اینجا دریافت و سپس برای مثال خاصیت title آن‌را به مقداری جدید به روز رسانی می‌کنیم.
- اکنون امضای متد axios.put هرچند مانند متد post است، اما متد Update تعریف شده‌ی در سمت API سرور، یک چنین مسیری را نیاز دارد api/Posts/{id}. به همین جهت ذکر id مطلب، در URL نهایی نیز ضروری است.
- در اینجا نیز از واژه‌های await و async برای دریافت نتیجه‌ی واقعی عملیات put و همچنین عملیات گذاری این متد به صورت async، استفاده شده‌است.
- در آخر، ابتدا آرایه‌ی posts موجود در state را clone می‌کنیم. چون می‌خواهیم در آن، در ایندکسی که شیء post جاری قرار دارد، مقدار به روز رسانی شده‌ی آن‌را قرار دهیم. سپس این آرایه‌ی جدید را جهت به روز رسانی state و در نتیجه‌ی آن، به روز رسانی UI، به متد setState ارسال کرده‌ایم:



حذف اطلاعات در سمت سرور

برای حذف اطلاعات در سمت سرور، نیاز است یک HTTP Delete را به آن ارسال کنیم که اینکار را می‌توان توسط متد axios.delete انجام داد. URL ای را که دریافت می‌کند، شبیه به URL ای است که برای حالت put ایجاد کردیم:
  handleDelete = async post => {
    await axios.delete(`${apiEndpoint}/${post.id}`);

    const posts = this.state.posts.filter(item => item.id !== post.id);
    this.setState({ posts });
  };
پس از به روز رسانی وضعیت سرور، در چند سطر بعدی، کار فیلتر سمت کلاینت مطالبی را انجام می‌دهیم که id مطلب حذف شده، در آن‌ها نباشد. سپس state را جهت به روز رسانی UI، با این آرایه‌ی جدید posts، به روز رسانی می‌کنیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-22-backend-part-02.zip و sample-22-frontend-part-02.zip
مطالب
OpenCVSharp #15
تشخیص چهره به کمک OpenCV

OpenCV به کمک الگوریتم‌های machine learning (در اینجا Haar feature-based cascade classifiers) و داد‌ه‌های مرتبط با آن‌ها، قادر است اشیاء پیچیده‌ای را در تصاویر پیدا کند. برای پیگیری مثال بحث جاری نیاز است کتابخانه‌ی اصلی OpenCV را دریافت کنید؛ از این جهت که به فایل‌های XML موجود در پوشه‌ی opencv\sources\data\haarcascades آن نیاز داریم. در اینجا از دو فایل haarcascade_eye_tree_eyeglasses.xml و haarcascade_frontalface_alt.xml آن استفاده خواهیم کرد (این دوفایل جهت سهولت کار، به همراه مثال این بحث نیز ارائه شده‌اند).
فایل haarcascade_frontalface_alt.xml اصطلاحا trained data مخصوص یافتن چهره‌ی انسان در تصاویر است و فایل haarcascade_eye_tree_eyeglasses.xml کمک می‌کند تا بتوان در چهره‌ی یافت شده، چشمان شخص را نیز با دقت بالایی تشخیص داد؛ چیزی همانند تصویر ذیل:



مراحل تشخیص چهره توسط OpenCVSharp

ابتدا همانند سایر مثال‌های OpenCV، تصویر مدنظر را به کمک کلاس Mat بارگذاری می‌کنیم:
var srcImage = new Mat(@"..\..\Images\Test.jpg");
Cv2.ImShow("Source", srcImage);
Cv2.WaitKey(1); // do events
 
var grayImage = new Mat();
Cv2.CvtColor(srcImage, grayImage, ColorConversion.BgrToGray);
Cv2.EqualizeHist(grayImage, grayImage);
همچنین در اینجا جهت بالا رفتن سرعت کار و بهبود دقت تشخیص چهره، این تصویر اصلی به یک تصویر سیاه و سفید تبدیل شده‌است و سپس متد Cv2.EqualizeHist بر روی آن فراخوانی گشته‌است. این متد وضوح تصویر را جهت یافتن الگوها بهبود می‌بخشد.
سپس فایل xml یاد شده‌ی در ابتدای بحث را توسط کلاس CascadeClassifier بارگذاری می‌کنیم:
var cascade = new CascadeClassifier(@"..\..\Data\haarcascade_frontalface_alt.xml");
var nestedCascade = new CascadeClassifier(@"..\..\Data\haarcascade_eye_tree_eyeglasses.xml");
 
var faces = cascade.DetectMultiScale(
    image: grayImage,
    scaleFactor: 1.1,
    minNeighbors: 2,
    flags: HaarDetectionType.Zero | HaarDetectionType.ScaleImage,
    minSize: new Size(30, 30)
    );
 
Console.WriteLine("Detected faces: {0}", faces.Length);
پس از بارگذاری فایل‌های XML اطلاعات نحوه‌ی تشخیص چهره و اعضای آن، با فراخوانی متد DetectMultiScale، کار تشخیص چهره و استخراج آن از grayImage انجام خواهد شد. در اینجا minSize، اندازه‌ی حداقل چهره‌ی مدنظر است که قرار هست تشخیص داده شود. نواحی کوچکتر از این اندازه، به عنوان نویز در نظر گرفته خواهند شد و پردازش نمی‌شوند.
خروجی این متد، مستطیل‌ها و نواحی یافت شده‌ی مرتبط با چهره‌های موجود در تصویر هستند. اکنون می‌توان حلقه‌ای را تشکیل داد و این نواحی را برای مثال با مستطیل‌های رنگی، متمایز کرد:
var rnd = new Random();
var count = 1;
foreach (var faceRect in faces)
{
    var detectedFaceImage = new Mat(srcImage, faceRect);
    Cv2.ImShow(string.Format("Face {0}", count), detectedFaceImage);
    Cv2.WaitKey(1); // do events
 
    var color = Scalar.FromRgb(rnd.Next(0, 255), rnd.Next(0, 255), rnd.Next(0, 255));
    Cv2.Rectangle(srcImage, faceRect, color, 3);
  
    count++;
}
 
Cv2.ImShow("Haar Detection", srcImage);
Cv2.WaitKey(1); // do events
در اینجا علاوه بر رسم یک مستطیل، به دور ناحیه‌ی تشخیص داده شده، نحوه‌ی استخراج تصویر آن ناحیه را هم در سطر var detectedFaceImage مشاهده می‌کنید.

همچنین اگر علاقمند باشیم تا در این ناحیه‌ی یافت شده، چشمان شخص را نیز استخراج کنیم، می‌توان به نحو ذیل عمل کرد:
var rnd = new Random();
var count = 1;
foreach (var faceRect in faces)
{
    var detectedFaceImage = new Mat(srcImage, faceRect);
    Cv2.ImShow(string.Format("Face {0}", count), detectedFaceImage);
    Cv2.WaitKey(1); // do events
 
    var color = Scalar.FromRgb(rnd.Next(0, 255), rnd.Next(0, 255), rnd.Next(0, 255));
    Cv2.Rectangle(srcImage, faceRect, color, 3);
 
 
    var detectedFaceGrayImage = new Mat();
    Cv2.CvtColor(detectedFaceImage, detectedFaceGrayImage, ColorConversion.BgrToGray);
    var nestedObjects = nestedCascade.DetectMultiScale(
        image: detectedFaceGrayImage,
        scaleFactor: 1.1,
        minNeighbors: 2,
        flags: HaarDetectionType.Zero | HaarDetectionType.ScaleImage,
        minSize: new Size(30, 30)
        );
 
    Console.WriteLine("Nested Objects[{0}]: {1}", count, nestedObjects.Length);
 
    foreach (var nestedObject in nestedObjects)
    {
        var center = new Point
        {
            X = Cv.Round(nestedObject.X + nestedObject.Width * 0.5) + faceRect.Left,
            Y = Cv.Round(nestedObject.Y + nestedObject.Height * 0.5) + faceRect.Top
        };
        var radius = Cv.Round((nestedObject.Width + nestedObject.Height) * 0.25);
        Cv2.Circle(srcImage, center, radius, color, thickness: 3);
    }
 
    count++;
}
 
Cv2.ImShow("Haar Detection", srcImage);
Cv2.WaitKey(1); // do events
کدهای ابتدایی آن همانند توضیحات قبل است. تنها تفاوت آن، استفاده از nestedCascade بارگذاری شده‌ی در ابتدای بحث می‌باشد. این nestedCascade حاوی trained data استخراج چشمان اشخاص، از تصاویر است. پارامتر ورودی آن‌را نیز تصویر سیاه و سفید ناحیه‌ی چهره‌ی یافت شده‌، قرار داده‌ایم تا سرعت تشخیص چشمان شخص، افزایش یابد.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
پیاده سازی Unobtrusive Ajax در ASP.NET Core 1.0
ممنون از راهنمایی شما
ولی متاسفانه بعد از دیباگ شی attachFile رو null میفرسته سمت سرور( اکشن متد داخل کنترلر ) و فقط بقیه input‌ها رو در قالب مدل ارسال میکنه که کل عملیات با شکست مواجه میشه چون تصویری برای ذخیره شدن موجود نیست و NullRefrenceException اعلام میشه.
تو بخش client : 
<form asp-controller="Admin" asp-action="CreateBlog" enctype="multipart/form-data" data-ajax="true" data-ajax-method="post" data-ajax-loading="#Progress" data-ajax-complete="onComplete"
      data-ajax-failure="onFailed" data-ajax-success="onSuccess">
 در سمت سرور :
public async Task<IActionResult> CreateBlog(MyViewModel vm, IFormFile attachFile)

یکی از دوستان در نظرات این لینک مقاله شما رو پیشنهاد کرده بودن. منتها گفتم شاید در دات نت core بشه بدون افزونه خاصی متد ارسال فایل رو با بقیه اطلاعات فرم بصورت UnObtrusive پیاده سازی کرد.
مطالب
آموزش Cache در ASP.NET Core - (قسمت اول : مفاهیم اولیه)
امروزه در وب‌سایت‌های شخصی و تجاری، یکی از مهم‌ترین پارامتر‌ها، سرعت پاسخگویی درخواست‌ها به وب‌سایت است. طبق آمار، کاربران آنلاین کنونی که ما با آن‌ها طرفیم، سطح تحملشان به سه ثانیه در یک صفحه میرسد؛ پس ما باید بتوانیم سرعت وب‌سایت‌های خودمان را تا حد ممکن بهبود بخشیم. از طرفی پارامتر سرعت، روی سئو گوگل هم تاثیر بسزایی دارد و Ranking وب‌سایت شمارا تا حد زیادی افزایش می‌دهد. قطعا همه می‌دانید که سرعت وبسایت و برنامه چقدر مهم هست؛ پس زیاده گویی نمی‌کنیم و می‌رویم سراغ اصل مطلب.
یکی از کارهایی که میتوانیم برای افزایش سرعت برنامه انجام دهیم، استفاده از Cache هست. بطور خیلی ساده، Cache یعنی قرار دادن دیتای پرکاربرد، در یک حافظه‌ی نزدیک‌تر از دیتابیس که هروقت به آن نیاز داشتیم، به آن دسترسی سریعی داشته باشیم و سرعت واکشی اطلاعات، از سرعتی که دیتابیس به ما می‌دهد، بیشتر باشد تا درخواست‌های ما با پاسخ سریع‌تری همراه شوند.
این حافظه، Ram هست و عمل Caching به اینصورت خواهد بود که هر وقت دیتای مورد نظر یکبار از دیتابیس واکشی شود، از دفعات بعد، آن دیتا را در Ram ذخیره میکند و برای درخواست‌های بعدی به دیتابیس Query نمیزند و دیتای مورد نیازش را از Ram میگیرد.
این امر در کنار مزایایی که دارد ، حساسیت بالایی هم بهمراه خواهد داشت؛ چرا که حافظه مورد استفاده Ram، یک حافظه محدود هست همچنین میتواند برای هر سخت افزاری متفاوت باشد. پس پیاده سازی این سیستم نیاز به دو دو تا چهارتا و ساختار درست دارد؛ در غیر اینصورت Cache کردن دیتای غلط میتواند به تنهایی وب‌سایتتان را Down کند؛ پس خیلی باید به این موضوع دقت داشت.

چه زمانی بهتر است از کش استفاده کنیم؟
  • وقتی دیتایی داریم که به تکرار از آن در برنامه استفاده میکنیم.
  • وقتی بعد از گرفتن دیتایی از دیتابیس، محاسباتی بر روی آن انجام میدهیم و پاسخ نهایی محاسبه را به کاربر نمایش میدهیم، میتوانیم یکبار پاسخ را کش کنیم تا از محاسبه‌ی هر باره‌ی آن جلوگیری شود.

آیا تمام اطلاعات را میتوان کش کرد؟
خیر.
  • سخت افزاری که برای کش استفاده میکنیم یعنی Ram، بسیار گران‌تر از دیتابیس برای ما تمام میشود؛ چرا که محدود است.
  • اگر همه دیتاهارا کش کنید، عمل سرچ میان آن زمان بیشتری خواهد برد.
پس اکنون میدانید که میتوانیم داده‌های بی نهایتی را در دیتابیس ذخیره کنیم و فقط با ارزش‌ترین‌ها و پر مصرف‌ترین هارا در حافظه کش، ذخیره میکنیم.

عملیات Cache در Asp.Net Core توسط اینترفیس‌های IMemoryCache و IDistributedCache مدیریت میشود و میتوانید با تزریق این اینترفیس‌ها براحتی از متدهایشان استفاده کنید؛ اما قبل از استفاده لازم است با عملکرد هر یک از آن‌ها آشنا شویم.

روش اول : In-memory Caching (Local Caching)
معمول‌ترین و ابتدایی‌ترین روش برای کش کردن اطلاعات، روش Local Caching و بصورت In-Memory است که اطلاعات را در حافظه Ram همان سروری که برنامه در آن اجرا میشود، کش میکند.

این روش تا زمانیکه برنامه‌ی ما برای اجرا شدن، تنها از یک سرور استفاده کند، بهترین انتخاب خواهد بود؛ چرا که به دلیل نزدیک بودن، سریع‌ترین بازخورد را نیز به درخواست‌ها ارائه میدهد.


اما شرایطی را فرض کنید که برنامه از چندین سرور برای اجرا شدن استفاده میکند و به طبع هر سرور درخواست‌های خودش را داراست که ما باید برای هر یک بصورت جداگانه‌ای یک کش In-Memory را در حافظه Ram هرکدام ایجاد کنیم. 

فرض کنید دیتای ما 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 باشد. بخشی از دیتا در Server 1 کش میشود (1 , 3 , 5 , 9) و بخشی دیگر در Server 2 کش خواهد شد (2 , 4 , 6 ,7 , 8 , 10).


در اینجا مشکلات و ضعف هایی به وجود خواهد آمد :


  • برای مثال اگر Server 1 به هر دلیلی از بین برود یا Down شود، اطلاعات کش درون آن نیز پاک خواهد شد و بعد از راه اندازی باید همه آن را دوباره از دیتابیس بخواند.
  • هر کدام از سرور‌ها کش‌های جدایی دارند و باهم Sync نیستند و امکان وجود یک داده‌ی حیاتی در یکی و عدم وجود آن در دیگری، بالاست. فرض کنید برنامه برای هر درخواست، نیاز به اطلاعات دسترسی کاربری را دارد. دسترسی‌های کاربر، در Server 1 کش شده، اما در Server 2 موجود نیست. در Server 2 به دلیل عدم وجود این کش، برنامه برای درخواست‌های معمول خود و چک کردن دسترسی کاربر یا باید هربار به دیتابیس درخواستی را ارسال کند که این برخلاف خواسته ماست و یا باید دیتای مربوط به دسترسی‌های کاربر را بعد از یکبار درخواست، از دیتابیس در خودش کش کند که این‌هم دوباره کاری به حساب میاید و دوبار کش کردن یک دیتا، امر مطلوبی نخواهد بود.

روش هایی وجود دارد که بتوان از سیستم Local Caching در حالت چند سروری هم استفاده کرد و این مشکلات را از بین برد، اما روش استاندارد در حالت چند سروری، استفاده از Distributed Cache‌ها است.


روش دوم : Distributed Caching

در این روش برنامه‌ی ما برای اجرا شدن از چندین سرور شبکه شده به هم، در حال استفاده هست و Cache برنامه، توسط سرورها به اشتراک گذاشته شده. 

در این حالت سرور‌های ما از یک کش عمومی استفاده میکنند که مزایای آن شامل :

■ درخواست‌ها به چندین سرور مختلف از هم ارسال شده، اما دیتای کش بصورت منسجم در هریک وجود خواهد داشت.

■ با خراب شدن یا Down شدن یک سرور، کش موجود در سرور‌های دیگر پاک نمیشود و کماکان قابل استفاده است.

■ به حافظه Ram یک سرور محدود نیست و مشکلات زیادی همچون کمبود سخت افزاری و محدودیت‌های حافظه‌ی Ram را تا حد معقولی کاهش میدهد.


طریقه استفاده از Cache در Asp.Net Core :

  • بر خلاف ASP.NET web forms و ASP.NET MVC در نسخه‌های Core به بعد، Cache بصورت از پیش ثبت شده، وجود ندارد. کش در Asp.Net Core با فراخوانی سرویس‌های مربوطه‌ی آن قابل استفاده است و نیاز است قبل از استفاده، سرویس آن را در کلاس Startup برنامه فراخوانی کنید. 
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddMemoryCache();
}

  • اینترفیس IMemoryCache از سیستم تزریق وابستگی‌ها در Core استفاده میکند و برای استفاده از اینترفیس آن، پس از اضافه کردن MemoryCache به Startup ، باید در کنترلر، عمل تزریق وابستگی (DI) را انجام دهید؛ سپس متد‌های مورد نیاز برای کش، در دسترس خواهد بود. 
public class HomeController : Controller
{
    private readonly IMemoryCache  _cache;
    public HomeController(IMemoryCache  cache)
    {
        _cache = cache;
    }
    ....
}

  • برای ذخیره‌ی کش میتوانید از متد Set موجود در این اینترفیس استفاده کنید. 
public IActionResult Set()
{
  _cache.Set("CacheKey", data , TimeSpan.FromDays(1));
  return View();
}

در پارامتر اول این متد (CacheKey)، یک کلید، برای اطلاعاتی که میخواهیم کش کنیم قرار میدهیم. دقت کنید که این کلید، شناسه‌ی دیتای شماست و باید طوری آن را در نظر گرفت که با صدا زدن این کلید از سرویس کش، همان دیتای مورد نظر را برگشت دهد (هر Object دیتا، باید کلید Unique خود را داشته باشد).


در پارامتر دوم، دیتای مورد نظر را که میخواهیم کش کنیم، به متد میدهیم و در پارامتر سوم نیز زمان اعتبار و تاریخ انقضای دیتای کش شده را وارد میکنیم؛ به این معنا که دیتای کش شده، بعد از مدت زمان گفته شده، از حافظه کش(Ram) حذف شود و برای دسترسی دوباره و کش کردن دوباره اطلاعات، نیاز به خواندن مجدد از دیتابیس باشد.


  • برای دسترسی به اطلاعات کش شده میتوانید از متد Get استفاده کنید. 
public IActionResult Get()
{
  string data = _cache.Get("CacheKey");
  return View(data);
}

تنها پارامتر ورودی این متد، کلید از قبل نسبت داده شده به اطلاعات کش هست که با استفاده از یکسان بودن کلید در ورودی این متد و کلید Set شده از قبل در حافظه Ram، دیتا مربوط به آن را برگشت میدهد.


  • متد TryGetValue برای بررسی وجود یا عدم وجود یک کلید در حافظه کش هست و یک Boolean را خروجی میدهد. 
public IActionResult Set()
  {
        DateTime data;
       // Look for cache key.
       if (!_cache.TryGetValue( "CacheKey" , out data))
       {
              // Key not in cache, so get data.
              data= DateTime.Now;

            // Save data in cache and set the relative expiration time to one day
             _cache.Set( "CacheKey" , data, TimeSpan.FromDays(1));
        }
        return View(data);
  }

این متد ابتدا بررسی میکند که کلیدی با نام "CacheKey" وجود دارد یا خیر؟ در صورت عدم وجود، آن را میسازد و دیتای مورد نظر را به آن نسبت میدهد.


  • با استفاده از متد GetOrCreate میتوانید کار متد‌های Get و Set را باهم انجام دهید و در یک متد، وجود یا عدم وجود کش را بررسی و در صورت وجود، مقداری را return و در صورت عدم وجود، ابتدا ایجاد کش و بعد return مقدار کش شده را انجام دهید. 
 public IActionResult GetOrCreate()
{
         var data = _cache.GetOrCreate( "CacheKey" , entry =>
         entry.SlidingExpiration = TimeSpan.FromSeconds(3);
         return View(data);
});
    return View(data);
}

  • برای مدیریت حافظه‌ی Ram شما باید یک Expiration Time را برای کش‌های خود مشخص کنید؛ تا هم حافظه Ram را حجیم نکنید و هم در هر بازه‌ی زمانی، دیتای بروز را از دیتابیس بخوانید. برای این کار option‌های متفاوتی از جمله absolute expiration و sliding expiration وجود دارند.

در اینجا absolute expiration به این معنا است که یک زمان قطعی را برای منقضی شدن کش‌ها مشخص میکند؛ به عبارتی میگوییم کش با کلید فلان، در تاریخ و ساعت فلان حذف شود. اما در sliding expiration یک بازه زمانی برای منقضی شدن کش‌ها مشخص میکنیم؛ یعنی میگوییم بعد از گذشت فلان دقیقه از ایجاد کش، آن را حذف کن و اگر در طی این مدت مجددا خوانده شد، طول مدت زمان آن تمدید خواهد شد.

این تنظیمات را میتوانید در قالب یک option زمان Set کردن یک کش، به آن بدهید. 

MemoryCacheEntryOptions options = new MemoryCacheEntryOptions();
options.AbsoluteExpiration = DateTime.Now.AddMinutes(1);
options.SlidingExpiration = TimeSpan.FromMinutes(1);
_cache.Set("CacheKey", data, options );

در مثال بالا هردو option اضافه شده یک کار را انجام میدهند؛ با این تفاوت که absolute expiration تاریخ now را گرفته و یک دقیقه بعد را به آن اضافه کرده و تاریخ انقضای کش را با آن تاریخ set میکند. اما sliding expiration از حالا بمدت یک دقیقه اعتبار دارد.


  • یکی از روش‌های مدیریت حافظه Ram در کش‌ها این است که برای حذف شدن کش‌ها از حافظه، اولویت بندی‌هایی را تعریف کنید. اولویت‌ها در چهار سطح قابل دسترسی است: 

  1.  NeverRemove = 3
  2.  High = 2
  3. Normal = 1
  4.  Low = 0 

این اولویت بندی‌ها زمانی کاربرد خواهند داشت که حافظه اختصاصی Ram، برای کش‌ها پر شده باشد و در این حالت سیستم کشینگ بصورت خودمختار، کش‌های با الویت پایین را از حافظه حذف میکند و کش‌های با الویت بیشتر، در حافظه باقی میمانند. این با شماست که الویت را برای دیتا‌های خود تعیین کنید؛ پس باید با دقت و فکر شده این کار را انجام دهید. 

MemoryCacheEntryOptions options = new MemoryCacheEntryOptions();
// Low / Normal / High / NeverRemove
options.Priority = CacheItemPriority.High;
cache.Set("CacheKey", data, options);

به این صورت میتوانید الویت‌های متفاوت را در قالب option به کش‌های خود اختصاص دهید. 

در این مقاله سعی شد مفاهیم اولیه Cache، طوری گفته شود، تا برای افرادی که میخواهند به تازگی این سیستم را بیاموزند و در پروژه‌های خود استفاده کنند، کاربردی باشد و درک نسبی را نسبت به مزایا و محدودیت‌های این سیستم بدست آورند.


در قسمت دوم همین مقاله بطور تخصصی‌تر به این مبحث میپردازیم و یک پکیج آماده را معرفی میکنیم که خیلی راحت‌تر و اصولی‌تر کش را برای ما پیاده سازی میکند.

نظرات مطالب
ASP.NET MVC #17
با سلام و عرض خسته نباشید.
من در پروژه ام به شکل ذیل عمل کردم؛ آیا اشتباه است؟
1- بجای آنکه ابتدای هر فرم Html.AntiForgeryToken@ را قرار بدهم، فقط یکبار در Layout.cshtml_ و در Body آنرا ذکر کردم.
2-تمامی ارسال‌ها به سرور را توسط Post Ajax انجام میدهم؛ چه درخواست نمایش یک View باشد چه ثبت کالای جدید در دیتابیس همه توسط کدی مثل کد ذیل به سرور Post میشوند:
var token = $('[name=__RequestVerificationToken]').val();
data.__RequestVerificationToken = token;
...
...
...
 $.ajax({
  url: url,
  type: "POST",
  data: data
});
ممنون
نظرات مطالب
صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC
سلام
برای فعال سازی jqGrid در ASP.NET Core 3 به نحوی که بتونه اطلاعات جداول رو نمایش بده توسط متد POST چه تنظیمات دیگری نیازه؟
تنظیمات ارتقای از ASP.NET2.2 به ASP.NET3.0 رو از این لینک مطالعه کردم و همینطور همه بازخوردهای سایت که در زمینه ارتقای به ASP.NET Core3.0 بود رو مطالعه کردم ولی بازم اطلاعات رو نمایش نمیده؟!
خطای زیر رو نمایش می‌ده که گویا نتونسته آدرس دهی رو پیدا کنه!

همینطور بسته‌ی نیوگت Microsoft.AspNetCore.Mvc.NewtonsoftJson را هم برای بخشی که اطلاعات به صورت json به صفحه ارسال میشه نصب کردم ولی اطلاعات رو در جداول نمایش نمیده.
روی ASP.NET Core 2.2 به خوبی کار می‌کرد ولی با ارتقا به ASP . NET Core 3.0 این مشکل پیش اومده.
کار خاص دیگرهم باید انجام بدیم؟
ممنون میشم راهنمایی بفرمایید.

نظرات مطالب
طراحی افزونه پذیر با ASP.NET MVC 4.x/5.x - قسمت دوم
در همان برگه‌ی مربوط به developer tools، خروجی response مربوط به Internal Server Error را بررسی کنید. اطلاعات کامل صفحه‌ی زرد رنگ نمایش خطاهای ASP.NET را با جزئیات خطای رخ داده، به همراه دارد.

نظرات مطالب
Best Practice ی برای تأیید اعتبار کردن کاربران در ASP.NET MVC 4
ممکن هست در اون صفحه دوم داری چیزی رو نمایش می‌دی که ارجاعی داره به یک اکشن متد محافظت شده. مثلا صفحه‌ات چند قسمتی هست. یک قسمت داره از یک اکشن متد غیر عمومی شده اطلاعات دریافت می‌کنه.
نظرات مطالب
مباحث تکمیلی مدل‌های خود ارجاع دهنده در EF Code first
کم هزینه‌ترین روش: جمع‌ها رو سمت کلاینت مدیریت کنید و اصلا اطلاعات جمع آن‌ها رو سمت سرور ثبت نکنید. زمانیکه این TreeView در حال رندر هست، جمع گره‌های والد رو بر اساس فرزندان محاسبه و نمایش بدید.
نظرات مطالب
چک لیست تهیه یک برنامه ASP.NET MVC
سلام. در مورد "تمام گزارشات و نمایش اطلاعات غیرعمومی برنامه به HttpGet محدود شوند" یعنی برای action‌ها حتماً [HttpGet] رو ذکر کنیم؟ چون به صورت پیش فرض اگر ذکر نکنیم هم HttpGet درنظر گرفته خواهد شد.
ممنون.