مطالب
امکان استفاده از یک هارد SSD بجای RAM در SQL Server 2014
Buffer Pool یکی از مصرف کنندگان اصلی حافظه در SQL Server است. برای مثال زمانیکه اطلاعاتی را از بانک اطلاعاتی دریافت می‌کنید، این داده‌ها در Buffer Pool کش می‌شوند. همچنین SQL Server اطلاعات کلیه Execution Plans را نیز در Plan Cache که جزئی از Buffer Pool است، برای استفاده‌ی مجدد نگهداری می‌کند. هر چقدر حافظه‌ی فیزیکی سرور شما بیشتر باشد، مقدار Buffer Pool نیز به همین میزان افزایش خواهد یافت که البته حداکثر آن‌را می‌توان در تنظیمات حافظه‌ی سرور محدود کرد (Max Server Memory setting).
در دنیای واقعی میزان حافظه‌ی فیزیکی سرورها محدود است. در SQL Server 2014 راه حلی برای این مشکل تحت عنوان Buffer Pool Extensions ارائه شده‌است که محل قرارگیری آن‌را در تصویر ذیل مشاهده می‌کنید:


Buffer Pool Extensions از یک فایل ساده که به آن Extension File نیز گفته می‌شود، تشکیل شده‌است و امکان ذخیره سازی آن بر روی هاردهای سریعی مانند SSD Driveها میسر است. این فایل، ساختاری را همانند page file، در سیستم عامل ویندوز دارد. در این حالت بجای اضافه کردن RAM بیشتر به سرور، یک Extension File را می‌توان بکار گرفت. هر زمان که Buffer Pool اصلی تحت فشار قرار گیرد (به میزان حافظه‌ای بیش از حافظه‌ی فیزیکی سرور نیاز باشد)، از این افزونه‌ی فایلی استفاده خواهد شد.
اطلاعات جزئیات Buffer Pool را توسط کوئری ذیل می‌توان بدست آورد:
 Select * from sys.dm_os_buffer_descriptors


نحوه‌ی فعال سازی و تنظیم Buffer Pool Extensions

قبل از هر کاری بهتر است وضعیت افزونه‌ی Buffer pool را بررسی کرد:
 select * from sys.dm_os_buffer_pool_extension_configuration


همانطور که ملاحظه می‌کنید، در حالت پیش فرض غیرفعال است.
سپس یک فایل یک گیگابایتی را به عنوان افزونه‌ی Buffer pool ایجاد می‌کنیم.
 ALTER SERVER CONFIGURATION
SET BUFFER POOL EXTENSION ON
 (FILENAME = 'd:\BufferPoolExt.BPE', SIZE = 1GB);
توصیه شده‌است که این فایل را در یک درایور پر سرعت SSD قرار دهید؛ ولی محدودیتی از لحاظ محل قرارگیری ندارد (هر چند به نظر فقط در حالتیکه از SSD Drive استفاده شود واقعا کار می‌کند).
اینبار اگر کوئری اول را اجرا کنیم، چنین خروجی قابل مشاهده است:


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


تغییر اندازه‌ی افزونه‌ی Buffer pool

اگر سعی کنیم، یک گیگابایت را مثلا به 10 گیگابایت افزایش دهیم:
 ALTER SERVER CONFIGURATION
SET BUFFER POOL EXTENSION ON
 (FILENAME = 'd:\BufferPoolExt.BPE', SIZE = 10GB);
با خطای ذیل مواجه خواهیم شد:
 Could not change the value of the 'BPoolExtensionPath' property
برای رفع این مشکل، ابتدا باید افزونه‌ی Buffer pool را غیرفعال کرد:
 ALTER SERVER CONFIGURATION
SET BUFFER POOL EXTENSION OFF
سپس می‌توان مجددا اندازه و یا مسیر دیگری را مشخص کرد. بهتر است اندازه‌ی این فایل را حدود 16 برابر حداکثر میزان حافظه‌ی سرور (Max Server Memory) تعیین کنید.
همچنین توصیه شده‌است که پس از غیرفعال کردن این افزونه، بهتر است یکبار instance جاری را ری استارت کنید.


چه زمانی بهتر است از افزونه‌ی Buffer pool استفاده شود؟
در محیط‌های read-heavy OLTP، استفاده از یک چنین افزونه‌ای می‌تواند میزان کارآیی و پاسخگویی سیستم را به شدت افزایش دهد (تا 50 درصد).


سؤال: آیا غیرفعال کردن افزونه‌ی Buffer pool سبب از دست رفتن اطلاعات می‌شود؟
خیر. BPE، تنها clean pages را در خود ذخیره می‌کند؛ یعنی تنها اطلاعاتی که Commit شده‌اند در آن حضور خواهند داشت و در این حالت حذف آن یا ری استارت کردن سرور، سبب از دست رفتن اطلاعات نخواهند شد.


برای مطالعه بیشتر

Buffer Pool Extension
SQL Server 2014 Buffer Pool Extensions
Do you require a SSD to use the Buffer Pool Extension feature in SQL Server 2014
Buffer Pool Extensions in SQL Server 2014
SQL Server 2014 – Buffer Pool Extension
مطالب
آشنایی با الگوی M-V-VM‌ - قسمت پنجم

در این قسمت قصد داریم از امکانات جدید اعتبار سنجی تعریف شده در فضای نام استاندارد System.ComponentModel.DataAnnotations استفاده نمائیم. از سیلورلایت سه به بعد امکان استفاده از این فضای نام به سادگی در برنامه‌های سیلورلایت میسر است (همچنین در برنامه‌های ASP.Net MVC)؛ اما برای کار با آن در WPF نیاز به تعدادی متد کمکی می‌باشد...

فهرست مطالب:
فصل 5- تعیین اعتبار ورودی کاربر و الگوی MVVM
  • مقدمه
  • معرفی برنامه فصل
  • مدل برنامه‌ی فصل
  • ViewModel برنامه فصل
  • View برنامه فصل


دریافت قسمت پنجم
دریافت مثال قسمت پنجم


تعدادی از منابع و مآخذ مورد استفاده در این سری:

1. Model-View-ViewModel (MVVM) Explained
2. Model View ViewModel
3. DataModel-View-ViewModel pattern
4. 5 Minute Overview of MVVM in Silverlight
5. A Field Guide to WPF Presentation Patterns
6. An attempt at simple MVVM with WPF
7. WPF: If Heineken did MVVM Frameworks Part 1 of n
8. Modal dialogs with MVVM and Silverlight 4
9. How do I do… With the Model-View-ViewModel pattern
10. Intro to WPF MVVM
11. Introduction to MVVM pattern in WPF
12. Learning WPF M-V-VM
13. Binding Combo Boxes in WPF with MVVM
14. Model-View-ViewModel Pattern
15. Unit Testable WCF Web Services in MVVM and Silverlight 4
16. MVVM Part 1: Overview
17. Which came first, the View or the Model?
18. Stackoverflow's questions tagged with MVVM
19. WPF: MVVM (Model View View-Model) Simplified
20. WPF and MVVM tutorial 01, Introduction
21. WPF patterns : MVC, MVP or MVVM or…?
22. Silverlight, MVVM and Validation Part III
23. DotNetKicks.com - Stories recently tagged with 'MVVM'
24. DotNetShoutout - Stories tagged with MVVM
25. MVVM Light Toolkit
26. MVVM screen casts
27. What’s new in MVVM Light V3
28. Using RelayCommands in Silverlight 3 and WPF
29. WPF Apps With The Model-View-ViewModel Design Pattern
30. WPF MVVM and Showing Dialogs


مطالب
استفاده از BroadcastChannel در جاوا اسکریپت

یکی از API ‌های کاربردی و جدید در دنیای وب، BroadcastChannel است که امکان ارسال اطلاعات بین window‌ها ، Tab‌ها و iframe‌های مختلف را که در یک دامنه هستند، فراهم می‌کند.  برای مثال اگر شما در مرورگری در پنجره‌های مختلف یک سایت را باز کرده باشید، با تغییر در یکی از این پنجره‌ها، قادر خواهید بود سایر پنجر‌ها را هم مطلع کنید تا در صورت نیاز، مجددا بارگذاری شوند. 


چرا از این API استفاده کنیم؟ 

یکی از وب سایت‌های مورد علاقه‌ی خود را در مرورگر باز کنید. مثلا یوتیوب و لاگین کنید. حالا در پنجره‌ی جدیدی، همین وب سایت را مجددا باز کرده و لاگین کنید. حالا در یکی از پنجره‌ها، از یوتیوب Logout کنید. خب شما حالا در یکی از پنجره‌ها لاگین هستید و در یکی دیگر Logout کرده‌اید. حالا پنجره‌های مرورگر شما دارای دو وضعیت متفاوت هستند. Logged-in در برابر Logged-out و این گاهی باعث دردسر خواهد شد.

این وضعیت حتی میتواند باعث خطرهای امنیتی نیز بشود. تصور کنید که کاربری در یک فضای عمومی مثل یک کافی شاپ وارد سایت شما شده‌است و داشبرد مخصوص به خود را باز کرده‌است. بنا به دلایلی کاربر قصد ترک محل را کرده و طبیعتا از برنامه شما Logout خواهد کرد . در این حالت اگر این کاربر برنامه شما را در صفحات مختلف مرورگر باز کرده باشد و لاگین نیز کرده باشد، هر کسی که بعد از او قصد استفاده از این کامپیوتر را داشته باشد ،  میتواند به اطلاعات کاربر مورد نظر در آن صفحات دسترسی پیدا کند؛ چه این اطلاعات روی صفحه باشد و چه مثلا اطلاعات یک JWT token.  چون کاربر فراموش کرده در صفحات دیگر هم Logout کند.


کد نویسی BroadcastChannel API 

در نگاه اول، استفاده از این API  ممکن است سخت به نظر برسد؛ ولی در واقع خیلی راحت است. برای نمونه قطعه کد زیر را درنظر بگیرید: 

<!DOCTYPE html>
<body>
  <!-- The title will change to greet the user -->
  <h1 id="title">Hey</h1>
  <input id="name-field" placeholder="Enter Your Name"/>
</body>
<script>
var bc = new BroadcastChannel('gator_channel');
(()=>{
  const title = document.getElementById('title');
  const nameField = document.getElementById('name-field');
  const setTitle = (userName) => {
    title.innerHTML = 'Hey ' + userName;
  }
  bc.onmessage = (messageEvent) => {
    // If our broadcast message is 'update_title' then get the new title from localStorage
    if (messageEvent.data === 'update_title') {
      // localStorage is domain specific so when it changes in one window it changes in the other
      setTitle(localStorage.getItem('title'));
    }
  }

  // When the page loads check if the title is in our localStorage
  if (localStorage.getItem('title')) {
    setTitle(localStorage.getItem('title'));
  } else {
    setTitle('please tell us your name');
  }
  nameField.onchange = (e) => {
    const inputValue = e.target.value;
    // In the localStorage we set title to the user's input
    localStorage.setItem('title', inputValue);
    // Update the title on the current page
    setTitle(inputValue);
    // Tell the other pages to update the title
    bc.postMessage('update_title');
  }
})()
</script>

این کد شامل یک Input باکس و یک title است. وقتی کاربر نام خود را در Input باکس وارد می‌کند، برنامه آن را در Localstorage با کلیدی به نام userName ذخیره می‌کند و بعد title صفحه جاری را به سلام + userName تغییر می‌دهد. مثلا اگر کاربر در Input باکس، عبارت بابک را وارد کند، title صفحه به سلام بابک تغییر داده میشود.

بدون BroadcastChannel، چنانچه کاربر در پنجره‌های مختلف مرورگر، برنامه را باز کرده باشد، تغییری در Title آن صفحات داده نخواهد شد؛ مگر اینکه مجددا توسط کاربر بارگذاری شود. 

در کد فوق ما یک وهله از BroadcastChannel را به نام gator_channel ایجاد کرده‌ایم و بعد onmessage را مساوی متدی با یک آرگومان جهت دریافت پیام قرار داده‌ایم. در این متد چک شده که اگر نام پیام، مساوی update_title باشد، متغیر ذخیره شده‌ی در LocalStorage خوانده شود.

هربار که متد postMessage ، از BroadcastChannel را فراخوانی میکنیم، این متد، باعث اجرای متد onmessage در سایر پنجره‌ها می‌شود. پس اگر در پنجره‌ی جاری در Input باکس، کلمه فرهاد را بنویسیم، متد bc.postMessage('update_title') در پنجره جاری اجرا شده و باعث اجرای متد onmessage در سایر پنجره‌هایی که سایتمان در آن باز است میشود.


این API در چه حالت‌هایی کار میکند

برخلاف سایر API‌ها مثل window.postMessage، شما لازم نیست چیزی در مورد اینکه چند تا صفحه از سایتتان بر روی مرورگر جاری باز شده را بدانید. (توجه کنید که روی عبارت «مرورگر جاری» تاکید میکنم. چون اگر برنامه روی دو مرورگر مثلا Chrome و Firefox به صورت همزمان باز باشد، این API فقط روی صفحات باز مرورگر جاری فراخوانی خواهد شد و نه مرورگر دوم؛ توضیحات بیشتر در ادامه داده شده است)

BroadcastChannel فقط روی مرورگر جاری و صفحاتی از یک دامنه، اجرا خواهد شد. این به این معنا است که شما می‌توانید پیام‌هایتان را از دامنه مثلا : https://alligator.io به دامنه https://alligator.io/js/broadcast_channels ارسال کنید. تنها نکته‌ای که لازم است تا رعایت شود این است که آبجکت BroadcastChannel در هر دو صفحه، از یک نام برای channel استفاده کرده باشند:

const bc = new BroadcastChannel('alligator_channel');
bc.onmessage = (eventMessage) => {
  // do something different on each page
}


در حالتهای زیر این API کار نخواهد کرد:

هاست‌های متفاوت:

https://alligator.io 

https://www.aligator.io

پورت‌های متفاوت:

https://alligator.io 

https://alligator.io :8080

پروتکل‌های متفاوت:

https://alligator.io 

http://alligator.io 

و یا برای مثال اگر مثلا در مرورگر Chrome یکی از صفحات به صورت Incognito باز شده باشد.


سازگاری این API با مرورگرهای مختلف

با توجه به اطلاعات سایت caniuse.com، این API در 75.6% مرورگرها پشتیبانی میشود. ولی مرورگرهای Safari و Internet Explorer از این API پشتیبانی نمی‌کنند. همچنین امکان استفاده از این API توسط کتابخانه sysend.js نیز فراهم شده‌است.


چه نوع پیامهایی را می‌توانید به کمک این API ارسال کنید

  • تمامی تایپ‌ها (Boolean,Null, Undefined,Number,BigInt, String) به غیر از symbol
  • آبجکتهای Boolean و String
  • Dates
  • Regular Expressions
  • Blobs
  • Files, File Lists
  • Array Buffer, ArrayBufferViews
  • ImageBitmaps, ImageDates
  • Arrays,Objects,Maps and Sets
در صورت ارسال تایپی از نوع symbol، با خطایی در سمت ارسال کننده مواجه خواهید شد.

قطعه کد زیر، بجای string، یک object را ارسال می‌کند:

bc.onmessage = (messageEvent) => {
  const data = messageEvent.data
  // If our broadcast message is 'update_title' then get the new title from localStorage
  switch (data.type) {
    case 'update_title':
      if (data.title){
        setTitle(data.title);
      }
      else
        setTitle(localStorage.getItem('title'));
      break
    default:
      console.log('we received a message')
  }
};
// ... Skipping Code
bc.postMessage({type: 'update_title', title: inputValue});


چه کارهایی را میتوانید به کمک این API انجام دهید

چیزهای زیادی را میتوان مجسم کرد. محتمل‌ترین گزینه، به اشتراک گذاری state جاری برنامه است. برای مثال اگه از کتابخانه‌های flux یا redux برای مدیریت state برنامه استفاده می‌کنید، به کمک این API می‌توانید state جاری را در تمامی صفحات باز برنامه، بروز رسانی کنید. حتی می‌توانید به چیزی شبیه به machine state فکر کنید.

یا مثلا آخرین وضعیت سبد خرید کالای مشتری و یا موجودی کالاها، در یک سایت خرید آنلاین. همچنین به اشتراک گذاری فایلهای حجیم مثل عکس و غیره جهت جلوگیری از دانلود مجدد آن‌‌ها در سایر صفحات.

به کمک دستور ()bc.close در هر زمانی میتوانید channel باز شده را ببندید و مجددا بسته به وضعیت برنامه، آن را باز کنید.

مطالب
ایجاد اشیاء دفاعی با ES 6 Proxy
ممکن است برای شما نیز پیش آمده باشد که به یک خصوصیت از یک شیء که وجود ندارد، ارجاع داده باشید و متوجه علت خطای رخ داده نشده و مدتی را به دنبال علت خطا صرف کرده باشید. بعضی از افراد به همین علت از جاوااسکریپت متنفر هستند و می‌گویند اگر از یک زبان type-safe استفاده می‌کردیم آنگاه در صورتیکه به خصوصیتی ارجاع می‌دادیم که وجود ندارد، نبودن خصوصیت ارجاع داده شده را اعلام می‌کرد. این مشکل وجود داشت تا وقتی که ECMAScript 6 ارائه شد.

ECMAScript 5

در حالیکه ECMAScript 5 قابلیت‌های فوق العاده‌ای را برای کنترل کردن خصوصیات موجود در اشیاء، در اختیار شما قرار می‌دهد، اما هیچ راه کاری را برای خصوصیاتی که موجود نیستند، ندارد. شما می‌توانید برای خواص موجود، از رونویسی (تنظیم writable برابر false) و یا حذف شدن (تنظیم configurable برابر false) جلوگیری کنید. شما می‌توانید از اختصاص خصوصیات جدید به اشیاء با استفاده از ()Object.preventExtensions و یا تنظیم تمام خصوصیات به صورت فقط خواندنی و یا غیرقابل حذف ()Object.freeze جلوگیری کنید.

اگر شما نمی‌خواهید تمام خصوصیات را فقط خواندنی کنید می‌توانید از ()Object.seal استفاده کنید. این‌ها مانع از اضافه کردن خصوصیات و یا حذف کردن خصوصیات موجود می‌شوند. اگر به یک شیء مهر و موم شده (sealed)، زمانی که از strict mode استفاده می‌کنید، یک خصوصیت جدید اضافه کنید باعث ایجاد خطا می‌شود:

"use strict";

var person = {
    name: "Vahid Mohammad Taheri"
};

Object.seal(person);
person.age = 27;    // Error!
این کار باعث اطلاع شما می‌شود که در حال تلاش برای تغییر اینترفیس یک شیء، با استفاده از اضافه کردن یک ویژگی به آن هستید. هنگامیکه سعی در خواندن ویژگی از یک شیء که جزئی از اینترفیس آن نیست، دارید نیز با خطا مواجه می‌شوید.

نجات با Proxyها

پروکسی‌ها، دارای سابقه طولانی و پیچیده ای در ECMAScript 6 است. طرح اولیه آن توسط Firefox و Chrome قبل از تصمیم TC-39 به تغییر پروکسی‌ها، اجرا شده است. این تغییرات، برای بهتر و روان‌تر شدن پروکسی‌ها از طرح اولیه پروکسی‌ها انجام گرفت.

بزرگترین تغییر صورت گرفته، معرفی شیء هدف که می‌خواست با پروکسی در تعامل باشد، بود. به جای تعریف دام برای نوع‌های مختلفی از عملیات، با ایجاد پروکسی، مستقیما برای عملیات‌ی از قبل تعیین شده بر روی اشیاء هدف، این کار را انجام می‌دهیم.

این کار از طریق یک سری از روش‌هایی که به مخفی کردن عملیات در ECMAScript مطابقت دارند، انجام می‌شود. به عنوان مثال زمانیکه بر روی یک ویژگی از یک شیء، عمل خواندن انجام می‌شود، عملیات  [[Get]] در موتور جاوااسکریپت انجام می‌گیرد. نحوه‌ی رفتار [[Get]] را نمی‌توان تغییر داد؛ با این حال، با استفاده از پروکسی‌ها می‌توان دامی برای زمان فراخوانی [[Get]]  قرار داد و عملیات خاص مورد نظر خود را اعمال کرد. به مثال زیر توجه کنید:

var proxy = new Proxy({ name: "Vahid" }, {
    get: function(target, property) {
        if (property in target) {
            return target[property];
        } else {
            return 13;
        }
    }
});

console.log(proxy.time);        // 13
console.log(proxy.name);        // "Vahid"
console.log(proxy.title);       // 13
این پروکسی از یک شیء ساخته شده به عنوان هدف (آرگومان اول به ()Proxy) استفاده می‌کند. آرگومان دوم دامی را که می‌خواهید برای این شیء بسازید، تعریف می‌کند. با استفاده از متد get عملیات مربوط به [[Get]] به دام افتاده و تابع تعریف شده‌ی ما اجرا می‌شود (باقی عملیات به صورت عادی اجرا می‌شوند). دامی که برای شیء مورد نظر تعریف کرده‌ایم دو پارامتر دریافت می‌کند (اول شی هدف، دوم ویژگی مورد نظر). با استفاده از کد نوشته شده در آن ابتدا بررسی می‌شود که شیء مورد نظر دارای ویژگی ارسال شده است یا خیر؟ در صورتی که وجود داشته باشد، مقدار آن بازگشت داده می‌شود و در غیر اینصورت به صورت ثابت مقدار 13 برگشت داده می‌شود.

برای ایجاد اشیاء دفاعی لازم است چگونگی رهگیری عملیات [[Get]] را درک کنید و هدف از این کار صدور خطا در زمان دستیابی به ویژگی ای از شیءایی که وجود ندارد است.
function createDefensiveObject(target) {

    return new Proxy(target, {
        get: function(target, property) {
            if (property in target) {
                return target[property];
            } else {
                throw new ReferenceError("Property \"" + property + "\" does not exist.");
            }
        }
    });
}

تابع ()createDefensiveObject  یک شیء را به عنوان هدف می‌پذیرد و یک شیء دفاعی برای آن ایجاد می‌کند. پروکسی یک دام به نام get دارد؛ برای زمانی که عمل خواندن انجام می‌شود. اگر ویژگی خوانده شده در شیء وجود داشت، مقدار آن برگشت داده می‌شود و از سوی دیگر، وقتی ویژگی خوانده شده در شیء وجود نداشته باشد، سبب بروز خطا می‌شود. به مثال زیر توجه کنید:
var person = {
    name: "Vahid"
};

var defensivePerson = createDefensiveObject(person);

console.log(defensivePerson.name);        // "Vahid"
console.log(defensivePerson.age);         // Error!
در اینجا ویژگی name به طور معمول کار خواهد کرد؛ ولی ویژگی age باعث صدور خطا می‌شود.
اشیاء دفاعی باعث می‌شوند تا بر روی ویژگی‌هایی که در شیء وجود دارند، بتوان عمل خواندن را انجام داد و در ویژگی‌هایی که موجود نیستند در هنگام خواندن، باعث صدور پیام خطا می‌شوند. با این حال هنوز هم شما می‌توانید ویژگی‌های جدید را بدون خطا اضافه کنید:
var person = {
    name: "Vahid"
};

var defensivePerson = createDefensiveObject(person);

console.log(defensivePerson.name);        // "Vahid"

defensivePerson.age = 13;
console.log(defensivePerson.age);         // 13
بنابراین اشیاء توانایی خود را برای جهش و تغییر حفظ می‌کنند. در صورتی که شما چیزی را برای تغییر آنها انجام دهید، همیشه می‌توانید ویژگی‌هایی را به اشیاء اضافه کنید ولی عمل خواندن بر روی ویژگی‌های غیرموجود همیشه باعث صدور خطا و بازگشت مقدار undefined می‌شود.
روش‌های تشخیص ویژگی‌های استاندارد هنوز هم به طور معمول و بدون خطا کار می‌کنند.
var person = {
    name: "Vahid"
};

var defensivePerson = createDefensiveObject(person);

console.log("name" in defensivePerson);               // true
console.log(defensivePerson.hasOwnProperty("name"));  // true

console.log("age" in defensivePerson);                // false
console.log(defensivePerson.hasOwnProperty("age"));   // false
شما می‌توانید از اینترفیس یک شیء، زمانیکه دسترسی به یک ویژگی آن وجود ندارد، صورت می‌گیرد، با رد کردن اضافات و صدور پیام‌های خطا، دفاع کنید.
var person = {
    name: "Vahid"
};

Object.preventExtensions(person);

var defensivePerson = createDefensiveObject(person);


defensivePerson.age = 27;                 // Error!
console.log(defensivePerson.age);         // Error!
در این مورد، defensivePerson برای هر دو حالت خواندن و نوشتن ویژگی‌هایی که وجود ندارند، خطا صادر می‌کند.
شاید مفیدترین زمان برای استفاده از اشیاء دفاعی، در هنگام تعریف یک سازنده باشد و شما می‌توانید این کار را به عنوان یک قرارداد در نوشتن اشیاء حفظ کنید.
برای مثال:
function Person(name) {
    this.name = name;

    return createDefensiveObject(this);
}

var person = new Person("Vahid");
console.log(person.age);         // Error!
به وسیله فراخوانی تابع ()createDefensiveObject درون سازنده، می‌توانید اطمینان کامل داشته باشید که همه‌ی نمونه‌های ساخته شده‌ی از شیء Person، دارای حالت دفاعی می‌باشند.

مطالب
استفاده از API ترجمه گوگل

مطابق Ajax API ترجمه گوگل، برای ترجمه یک متن باید محتویات آدرس زیر را تحلیل کرد:
http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q={0}&langpair={1}|{2}
که در آن پارامتر اول، متن مورد نظر، پارامترهای 1 و 2 زبان‌های مبدا و مقصد می‌باشند. برای دریافت اطلاعات، ذکر ارجاع دهنده الزامی است (referrer)، اما ذکر کلید API گوگل اختیاری می‌باشد (که هر فرد می‌تواند کلید خاص خود را از گوگل دریافت کند).
بنابراین برای استفاده از آن تنها کافی است این URL را تشکیل داده و سپس محتویات خروجی آن‌را آنالیز کرد. فرمت نهایی دریافت شده از نوع JSON است. برای مثال اگر hello world! را به این سرویس ارسال نمائیم،‌ خروجی نهایی JSON‌ دریافت شده به صورت زیر خواهد بود:

//{\"responseData\": {\"translatedText\":\"سلام جهان!\"}, \"responseDetails\": null, \"responseStatus\": 200}

در کتابخانه‌ی System.Web.Extensions.dll دات نت فریم ورک سه و نیم، کلاس JavaScriptSerializer برای این منظور پیش بینی شده است. تنها کافی است به متد Deserialize آن، متن JSON دریافتی را پاس کنیم:

GoogleAjaxResponse result =
new JavaScriptSerializer().Deserialize<GoogleAjaxResponse>(jsonGoogleAjaxResponse);

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

//ResponseData.cs file
public class ResponseData
{
public string translatedText { get; set; }
}

//GoogleAjaxResponse.cs file
using System.Net;

/// <summary>
/// کلاسی جهت نگاشت اطلاعات جی سون دریافتی به آن
/// </summary>
public class GoogleAjaxResponse
{
public ResponseData responseData { get; set; }
public object responseDetails { get; set; }
public HttpStatusCode responseStatus { get; set; }
}
با این توضیحات، کلاس نهایی ترجمه گوگل ما به شکل زیر خواهد بود:

using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Web;
using System.Web.Script.Serialization;

//{\"responseData\": {\"translatedText\":\"سلام جهان!\"}, \"responseDetails\": null, \"responseStatus\": 200}

public class CGoogleTranslator
{
#region Fields (1)

/// <summary>
/// ارجاع دهنده
/// </summary>
private readonly string _referrer;

#endregion Fields

#region Constructors (1)

/// <summary>
/// مطابق مستندات نیاز به یک ارجاع دهنده اجباری می‌باشد
/// </summary>
/// <param name="referrer"></param>
public CGoogleTranslator(string referrer)
{
_referrer = referrer;
}

#endregion Constructors

#region Properties (2)

/// <summary>
/// ترجمه از زبان
/// </summary>
public CultureInfo FromLanguage { get; set; }

/// <summary>
/// ترجمه به زبان
/// </summary>
public CultureInfo ToLanguage { get; set; }

#endregion Properties

#region Methods (2)

// Public Methods (1)

/// <summary>
/// ترجمه متن با استفاده از موتور ترجمه گوگل
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public string TranslateText(string data)
{
//ساخت و انکدینگ آدرس مورد نظر
string url =
string.Format(
"http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q={0}&langpair={1}|{2}",
HttpUtility.UrlEncode(data), //needs a ref. to System.Web.dll
FromLanguage.TwoLetterISOLanguageName,
ToLanguage.TwoLetterISOLanguageName
);

//دریافت اطلاعات جی سون از گوگل
string jsonGoogleAjaxResponse = fetchWebPage(url);

//needs a ref. to System.Web.Extensions.dll
//نگاشت اطلاعات جی سون دریافت شده به کلاس مرتبط
GoogleAjaxResponse result =
new JavaScriptSerializer().Deserialize<GoogleAjaxResponse>(jsonGoogleAjaxResponse);

if (result != null && result.responseData != null && result.responseStatus == HttpStatusCode.OK)
{
return result.responseData.translatedText;
}
return string.Empty;
}
// Private Methods (1)

/// <summary>
/// دریافت محتویات جی سون بازگشتی از گوگل
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
string fetchWebPage(string url)
{
try
{
var uri = new Uri(url);
if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
{
var request = WebRequest.Create(uri) as HttpWebRequest;
if (request != null)
{
request.Method = WebRequestMethods.Http.Get;
request.Referer = _referrer;
request.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.0; ; rv:1.8.0.7) Gecko/20060917 Firefox/1.9.0.1";
request.AllowAutoRedirect = true;
request.Timeout = 1000 * 300;
request.KeepAlive = false;
request.ReadWriteTimeout = 1000 * 300;
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;

using (var response = request.GetResponse() as HttpWebResponse)
{
if (response != null)
{
using (var reader = new StreamReader(response.GetResponseStream()))
{
return reader.ReadToEnd().Trim();
}
}
}
}
}
return string.Empty;
}
catch (Exception ex)
{
Console.WriteLine(String.Format("fetchWebPage: {0} >> {1}", ex.Message, url), true);
return string.Empty;
}
}

#endregion Methods
}
مثالی در مورد نحوه‌ی استفاده از آن برای ترجمه یک متن از انگلیسی به فارسی:

string res = new CGoogleTranslator("https://www.dntips.ir/")
{
FromLanguage = CultureInfo.GetCultureInfo("en-US"),
ToLanguage = CultureInfo.GetCultureInfo("fa-IR")
}.TranslateText("Hello world!");

مطالب
روشی برای محدود کردن API ها که هر درخواست با یک Key جدید و منحصر به فرد قابل فراخوانی باشد ( Time-based One-time Password )
TOTPیک الگوریتمی است که از ساعت برای تولید رمزهای یکبارمصرف استفاده میکند. به این صورت که در هر لحظه یک کد منحصر به فرد تولید خواهد شد. اگر با برنامه Google Authenticator کار کرده باشید این مفهوم برایتان اشناست. 
در این مطلب میخواهیم سناریویی را پیاده سازی کنیم که برای فراخوانی API‌ها باید یک رمز منحصر به فرد همراه توکن ارسال کنند. برای انجام این کار هر کاربر و یا کلاینتی که بخواهد از API استفاده کند در ابتدا باید لاگین کند و بعد از لاگین یک ClientSecret به طول 16 کاراکتر به همراه توکن به او ارسال میکنیم. از ClientSecret برای رمزنگاری کد ارسال شده ( TOTP ) استفاده میکنیم. مانند Public/Private key یک Key ثابت در سمت سرور و یک Key ثابت در برنامه موبایل وجود دارد و هنگام رمزنگاری TOTP علاوه بر Key موجود در موبایل و سرور از ClientSecret خود کاربر هم استفاده میکنیم تا TOTP بوجود آمده کاملا منحصر به فرد باشد. ( مقدار TOTP را با استفاده از دو کلید Key و ClientSecret رمزنگاری میکنیم ).
در این مثال تاریخ فعلی را ( UTC )  قبل از فراخوانی API در موبایل میگیریم و Ticks آن را در یک مدل ذخیره میکنیم. سپس مدل که شامل تایم فعلی میباشد را سریالایز میکنیم و به string تبدیل میکنیم سپس رشته بدست آمده را با استفاده از الگوریتم AES رمزنگاری میکنیم با استفاده از Key ثابت و ClientSecret. سپس این مقدار بدست آمده را در هدر Request-Key قرار میدهیم. توجه داشته باشید باید ClientSecret هم در یک هدر دیگر به سمت سرور ارسال شود زیرا با استفاده از این ClientSecret عملیات رمزگشایی مهیا میشود. به عنوان مثال ساعت فعلی به صورت Ticks به این صورت میباشد "637345971787256752 ". این عدد از نوع long است و با گذر زمان مقدار آن بیشتر میشود. در نهایت مدل نهایی سریالایز شده به این صورت است :
{"DateTimeUtcTicks":637345812872371593}
و مقدار رمزنگاری شده برابر است با :
g/ibfD2M3uE1RhEGxt8/jKcmpW2zhU1kKjVRC7CyrHiCHkdaAmLOwziBATFnHyJ3
مدل ارسالی شامل یک پراپرتی به نام DateTimeUtcTicks است که از نوع long میباشد و این مدل را قبل از فراخوانی API ایجاد میکند و مقدار رمزنگاری شده بدست آمده را در هدر Request-Key قرار میدهد به همراه ClientSecret در هدر مربوط به ClientSecret. این عمل در سمت موبایل باید انجام شود و در سمت سرور باید با استفاده از ClientSecret ارسال شده و Key ثابت در سرور این هدر را رمزگشایی کنند. چون این کار زیاد وقت گیر نیست و نهایتا یک دقیقه اختلاف زمان بین درخواست ارسال شده و زمان دریافت درخواست در سرور وجود دارد, در سمت سرور مقدار بدست آمده ( مقدار ارسال شده DateTimeUtcTicks که رمزگشایی شده است ) را اینگونه حساب میکنیم که مقدار ارسال شده از یک دقیقه قبل زمان فعلی باید بیشتر یا مساوی باشد و از تاریخ فعلی باید کوچکتر مساوی باشد. به این صورت 
var dateTimeNow = DateTime.UtcNow;
var expireTimeFrom = dateTimeNow.AddMinutes(-1).Ticks;
var expireTimeTo = dateTimeNow.Ticks;

string clientSecret = httpContext.Request.Headers["ClientSecret"].ToString();
string decryptedRequestHeader = AesProvider.Decrypt(requestKeyHeader, clientSecret);
var requestKeyData = System.Text.Json.JsonSerializer.Deserialize<ApiLimiterDto>(decryptedRequestHeader);

if (requestKeyData.DateTimeUtcTicks >= expireTimeFrom && requestKeyData.DateTimeUtcTicks <= expireTimeTo)
و در نهایت اگر مقدار ارسال شده در بین این بازه باشد به معنی معتبر بودن درخواست میباشد در غیر این صورت باید خطای مربوطه را به کاربر نمایش دهیم. در ادامه برای پیاده  پیاده سازی این سناریو از یک Middleware استفاده کرده‌ایم که ابتدا بررسی میکند آیا درخواست ارسال شده حاوی هدر Request-Key و ClientSecret میباشد یا خیر؟ اگر هدر خالی باشد یا مقدار هدر نال باشد، خطای 403 را به کاربر نمایش میدهیم. برای جلوگیری از استفاده‌ی مجدد از هدر رمزنگاری شده، هنگامیکه اولین درخواست به سمت سرور ارسال میشود، رشته‌ی رمزنگاری شده را در کش ذخیره میکنیم و اگر مجددا همان رشته را ارسال کند، اجازه‌ی دسترسی به API را به او نخواهیم داد. کش را به مدت 2 دقیقه نگه میداریم؛ چون برای هر درخواست نهایتا یک دقیقه اختلاف زمانی را در نظر گرفته‌ایم. 
 در ادامه اگر رشته‌ی رمزنگاری شده در کش موجود باشد، مجددا پیغام "Forbidden: You don't have permission to call this api" را به کاربر نمایش میدهیم؛ زیرا به این معناست که رشته‌ی رمزنگاری شده قبلا ارسال شده است. سپس رشته رمزنگاری شده را رمزگشایی میکنیم و به مدل ApiLimiterDto دیسریالایز میکنیم و بررسی میکنیم که مقدار Ticks ارسال شده از طرف موبایل، از یک دقیقه قبل بیشتر بوده و از زمان حال کمتر باشد. اگر در بین این دو بازه باشد، یعنی درخواست معتبر هست و اجازه فراخوانی API را دارد؛ در غیر این صورت پیغام 403 را به کاربر نمایش میدهیم.
مدل ApiLimiterDto   :
public class ApiLimiterDto
{
    public long DateTimeUtcTicks { get; set; }
}
میان افزار :
public class ApiLimiterMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IDistributedCache _cache;

    public ApiLimiterMiddleware(RequestDelegate next, IDistributedCache cache)
    {
        _next = next;
        _cache = cache;
    }
    private const string requestKey = "Request-Key";
    private const string clientSecretHeader = "ClientSecret";
    public async Task InvokeAsync(HttpContext httpContext)
    {
        if (!httpContext.Request.Headers.ContainsKey(requestKey) || !httpContext.Request.Headers.ContainsKey(clientSecretHeader))
        {
            await WriteToReponseAsync();
            return;
        }

        var requestKeyHeader = httpContext.Request.Headers[requestKey].ToString();
        string clientSecret = httpContext.Request.Headers[clientSecretHeader].ToString();
        if (string.IsNullOrEmpty(requestKeyHeader) || string.IsNullOrEmpty(clientSecret))
        {
            await WriteToReponseAsync();
            return;
        }
        //اگر کلید در کش موجود بود یعنی کاربر از کلید تکراری استفاده کرده است
        if (_cache.GetString(requestKeyHeader) != null)
        {
            await WriteToReponseAsync();
            return;
        }
        var dateTimeNow = DateTime.UtcNow;
        var expireTimeFrom = dateTimeNow.AddMinutes(-1).Ticks;
        var expireTimeTo = dateTimeNow.Ticks;

        string decryptedRequestHeader = AesProvider.Decrypt(requestKeyHeader, clientSecret);
        var requestKeyData = System.Text.Json.JsonSerializer.Deserialize<ApiLimiterDto>(decryptedRequestHeader);

        if (requestKeyData.DateTimeUtcTicks >= expireTimeFrom && requestKeyData.DateTimeUtcTicks <= expireTimeTo)
        {
            //ذخیره کلید درخواست در کش برای جلوگیری از استفاده مجدد از کلید
            await _cache.SetAsync(requestKeyHeader, Encoding.UTF8.GetBytes("KeyExist"), new DistributedCacheEntryOptions
            {
                AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(2)
            });
            await _next(httpContext);
        }
        else
        {
            await WriteToReponseAsync();
            return;
        }

        async Task WriteToReponseAsync()
        {
            httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
            await httpContext.Response.WriteAsync("Forbidden: You don't have permission to call this api");
        }
    }
}
برای رمزنگاری و رمزگشایی، یک کلاس را به نام AesProvider ایجاد کرده‌ایم که عملیات رمزنگاری و رمزگشایی را فراهم میکند.
public static class AesProvider
{
    private static byte[] GetIV()
    {
        //این کد ثابتی است که باید در سمت سرور و موبایل موجود باشد
        return encoding.GetBytes("ThisIsASecretKey");
    }
    public static string Encrypt(string plainText, string key)
    {
        try
        {
            var aes = GetRijndael(key);
            ICryptoTransform AESEncrypt = aes.CreateEncryptor(aes.Key, aes.IV);
            byte[] buffer = encoding.GetBytes(plainText);
            string encryptedText = Convert.ToBase64String(AESEncrypt.TransformFinalBlock(buffer, 0, buffer.Length));
            return encryptedText;
        }
        catch (Exception)
        {
            throw new Exception("an error occurred when encrypting");
        }
    }
    private static RijndaelManaged GetRijndael(string key)
    {
        return new RijndaelManaged
        {
            KeySize = 128,
            BlockSize = 128,
            Padding = PaddingMode.PKCS7,
            Mode = CipherMode.CBC,
            Key = encoding.GetBytes(key),
            IV = GetIV()
        };
    }
    private static readonly Encoding encoding = Encoding.UTF8;
    public static string Decrypt(string plainText, string key)
    {
        try
        {
            var aes = GetRijndael(key);
            ICryptoTransform AESDecrypt = aes.CreateDecryptor(aes.Key, aes.IV);
            byte[] buffer = Convert.FromBase64String(plainText);

            return encoding.GetString(AESDecrypt.TransformFinalBlock(buffer, 0, buffer.Length));
        }
        catch (Exception)
        {
            throw new Exception("an error occurred when decrypting");
        }
    }
}
متد Decrypt و Encrypt یک ورودی به نام key دریافت میکنند که از هدر ClientSecret دریافت میشود. در سمت سرور عملا عمل Decrypt انجام میشود و Encrypt برای این مثال در سمت سرور کاربردی ندارد. 
برای رمزنگاری با استفاده از روش AES، چون از 128 بیت استفاده کرده‌ایم، باید طول متغییر key برابر 16 کاراکتر باشد و IV هم باید کمتر یا برابر 16 کاراکتر باشد.
در نهایت برای استفاده از این میان افزار میتوانیم از MiddlewareFilter استفاده کنیم که برای برخی از api‌های مورد نظر از آن استفاده کنیم.
کلاس ApiLimiterPipeline :
public class ApiLimiterPipeline
{
    public void Configure(IApplicationBuilder app)
    {
        app.UseMiddleware<ApiLimiterMiddleware>();
    }
}
نحوه استفاده از میان افزار برای یک اکشن خاص :
[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    [MiddlewareFilter(typeof(ApiLimiterPipeline))]
    public async Task<IActionResult> Get()
    {
        return Ok("Hi");
    }
}

مطالب
صفحه بندی اطلاعات در ASP.NET MVC به روش HashChange
یکی از مواردی که درپروژه‌‌ها زیاد مورد استفاده قرار میگیرد، نمایش داده‌های ذخیره شده‌ی در بانک اطلاعاتی، به صورت صفحه بندی شده به کاربر می‌باشد. قبلا در زمینه بحث Paging، مطلبی تهیه شده بود و در این مقاله قصد داریم کتابخانه‌ای را مورد بررسی قرار دهیم که علاوه بر ارسال داده به صورت Ajax ایی، بتواند همچنین پارامترهای مورد نظر را به صورت Query String نیز در آدرس بار نمایش دهد.
اگر به جستجوی گوگل دقت کرده باشید، به صورت Ajax ایی پیاده سازی شده‌است، با این تفاوت که بعد از هر تغییر درجستجوی مورد نظر، Url صفحه نیز تغییر میکند (برای مثال بعد از جستجوی عبارت dotNetTips  آدرس بار صفحه به شکل https://www.google.com/#q=dotNetTips&* تغییر می‌کند). برای پیاده سازی این ویژگی باید از تکنیکی به نام HashChange استفاده کرد. در نتیجه با این روش مشکل ارسال صفحه‌ای خاص در یک گرید برای دیگران، به صورت Ajax ایی و بدون مشکل انجام می‌شود. از این رو با توجه به داشتن Url‌های منحصر به فرد برای هر صفحه، تا حدی مشکل سئو سایت را نیز برطرف می‌کنیم.

برای استفاده از این ویژگی در ادامه قصد داریم پیاده سازی کتابخانه‌ی MvcAjaxPager را مورد بررسی قرار دهیم. ابتدا قبل از هر کاری، با استفاده از دستور زیر اقدام به نصب کتابخانه آن می‌نماییم:
 Install-Package MvcAjaxPager

در ادامه نحوه پیاده سازی آن را به همراه مثالی، مورد بررسی قرار می‌دهیم:

ابتدا یک مدل فرضی را همانند زیر تهیه می‌کنیم :
public class Topic
{
   public int Id;
   public string Title;
   public string Text;
}
و کلاسی را همانند زیر برای دریافت یک لیست از مطالب می‌نویسیم:
public class TopicService
{
    public static IEnumerable<Topic> Topics = new List<Topic>() {
       new Topic{Id=1,Title="Title 1",Text= "Text 1"},
       new Topic{Id=2,Title="Title 2",Text="Text 2"},
       new Topic{Id=3,Title="Title 3",Text="Text 3"},
       new Topic{Id=4,Title="Title 4",Text="Text 4"},
       new Topic{Id=5,Title="Title 5",Text="Text 5"},
       new Topic{Id=6,Title="Title 6",Text="Text 6"},
       new Topic{Id=7,Title="Title 7",Text="Text 7"},
       new Topic{Id=8,Title="Title 8",Text="Text 8"},
       new Topic{Id=9,Title="Title 9",Text="Text 9"},
       new Topic{Id=10,Title="Title 10",Text="Text 10"},
       new Topic{Id=11,Title="Title 11",Text="Text 11"},
       new Topic{Id=12,Title="Title 12",Text="Text 12"},
       new Topic{Id=13,Title="Title 13",Text="Text 13"},
       new Topic{Id=14,Title="Title 14",Text="Text 14"},
       new Topic{Id=15,Title="Title 15",Text="Text 15"},
       new Topic{Id=16,Title="Title 16",Text="Text 16"},
       new Topic{Id=17,Title="Title 17",Text="Text 17"},
       new Topic{Id=18,Title="Title 18",Text="Text 18"},
       new Topic{Id=19,Title="Title 19",Text="Text 19"},
       new Topic{Id=20,Title="Title 20",Text="Text 20"},
       new Topic{Id=21,Title="Title 21",Text="Text 21"},
       new Topic{Id=22,Title="Title 22",Text="Text 22"},
      };

    public static IEnumerable<Topic> GetAll()
    {
       return Topics.OrderBy(row => row.Id);
    }
}
همچنین کلاس زیر را اضافه میکنیم:
public class ListViewModel
{
   public IEnumerable<Topic> Topics { get; set; }
   public int PageIndex { get; set; }
   public int TotalItemCount { get; set; }
}
ابتدا یک کنترلر را ایجاد می‌کنیم به همراه اکشن متدی که قصد داریم لیستی از اطلاعات را به کاربر نمایش دهیم:
public ActionResult Index(int page = 1)
{
       var topics = TopicService.GetAll ();
       int totalItemCount = topics.Count();
       var model = new ListViewModel()
       {
              PageIndex = page,
              Topics = topics.OrderBy(p => p.Id).Skip((page - 1) * 10).Take(10).ToList(),
              TotalItemCount = totalItemCount
       };

       if (!Request.IsAjaxRequest())
       {
              return View(model);
       }

       return PartialView("_TopicList", model);
}
در اینجا بعد از واکشی اطلاعات، تعداد 10 رکورد را در هر صفحه نمایش می‌دهیم. 

و در Partial view مربوطه نیز داریم :
@using MvcAjaxPager
@model ListViewModel

@Html.AjaxPager(Model.TotalItemCount, 10, Model.PageIndex, "Index", "Home", null, new PagerOptions
   {
       ShowDisabledPagerItems = true,
       AlwaysShowFirstLastPageNumber = true,
       HorizontalAlign = "center",
       ShowFirstLast = false,
       CssClass = "NavigationBox",
       AjaxUpdateTargetId = "dvTopics",
       AjaxOnBegin = "AjaxStart",
       AjaxOnComplete = "AjaxStop"
   }, null, null)

<table>
    <tr>
        <th>
            @Html.DisplayName("ID")
        </th>
        <th>
            @Html.DisplayName("Title")
        </th>
        <th>
            @Html.DisplayName("Text")
        </th>
    </tr>

    @foreach (var topic in Model.Topics)
    {
        <tr>
            <td>
                @topic.Id
        </td>
        <td>
            @topic.Title
        </td>
        <td>
            @topic.Text
        </td>
    </tr>
    }
</table>

@Html.AjaxPager(Model.TotalItemCount, 10, Model.PageIndex, "Index", "Home", null, new PagerOptions
   {
       ShowDisabledPagerItems = true,
       AlwaysShowFirstLastPageNumber = true,
       HorizontalAlign = "center",
       ShowFirstLast = true,
       FirstPageText = "اولین",
       LastPageText = "آخرین",
       MorePageText = "...",
       PrevPageText = "قبلی",
       NextPageText = "بعدی",
       CssClass = "NavigationBox",
       AjaxUpdateTargetId = "dvTopics",
       AjaxOnBegin = "AjaxStart",
       AjaxOnComplete = "AjaxStop"
   }, null, null)

 حال برای استفاده از pager مورد نظر فقط کافیست متد AjaxPager آن را فراخوانی کنیم. این متد شامل 11  OverLoad مختلف هست.
در این قسمت TotalItemCount جمع کل رکورد‌ها، PageSize تعداد رکورد‌های هر صفحه و PageIndex آدرس صفحه جاری می‌باشد.

مهمترین بخش این pager  که قابلیت‌های زیادی را به کاربر می‌دهد، قسمت PagerOptions آن است و تعدادی از پارامتر‌های آن شامل AjaxOnBeginAjaxOnCompelte، AjaxOnSuccess ،  AjaxOnFailure میتوان تعیین کرد تا بعد از شروع، وقوع خطا، موفقیت و یا خاتمه عملیات جاوا اسکریپتی، اجرا شود. 

AlwaysShowFirstLastPageNumber جهت نمایش صفحه اول و آخر
FirstPageText جهت تعیین متن اولین صفحه
LastPageText جهت تعیین متن آخرین صفحه
CssClass ، Id  جهت تعیین Id خاص

و در انتها، در view مربوطه داریم:
@using MvcAjaxPager
@model ListViewModel
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div id="dvTopics">
        @{
            @Html.Partial("_TopicList", Model);
        }
    </div>

    <script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.7.2.min.js")"></script>
    <script type="text/javascript" src="@Url.Content("~/Scripts/path.min.js")"></script>
    <script type="text/javascript" src="@Url.Content("~/Scripts/jquery.pager-1.0.1.min.js")"></script>
    <script type="text/javascript">
        $('.NavigationBox').pager();

        //pagination before start
        function AjaxStart() {
            console.log('Start AJAX call. Loading message can be shown');
        }
        // pagination - after request
        function AjaxStop() {
            console.log('Stop AJAX call. Loading message can be hidden');
        };
    </script>
</body>
</html>
در انتهای صفحه مورد نظر می‌بایست دو فایل جاوااسکریپتی jquerypager و Path را که هنگام نصب Pager، به برنامه اضافه شده اند، فراخوانی کنیم و با استفاده از CssClass  یا Id که قبلا در بخش PagerOption تعیین کردیم، آن را انتخاب و متدpager را فراخوانی کنیم.
مطالب
رشته ها و پردازش متن در دات نت به زبان ساده
رشته، مجموعه‌ای از کاراکترهاست که پشت سرهم، در مکانی از حافظه قرار گرفته‌اند. هر کاراکتر حاوی یک شماره سریال در جدول یونیکد هست. به طور پیش فرض دات نت برای هر کاراکتر (نوع داده char) شانزده بیت در نظر گرفته است که برای 65536 کاراکتر کافی است.
برای نگهداری از رشته‌ها و انجام عملیات بر روی آنها در دات نت از نوع system.string استفاده می‌کنیم:
string greeting = "Hello, C#";

که در این حالت مجموعه‌ای از کاراکترها را ایجاد خواهد کرد:

اتفاقاتی که در داخل کلاس string رخ می‌دهد بسیار ساده است و ما را از تعریف []char بی‌نیاز می‌کند تا مجبور نشویم خانه‌های  آرایه را به ترتیب پر کنیم. از معایب استفاده از آرایه char میتوان موارد زیر را برشمارد:
  1. خانه‌های آن یک ضرب پر نمیشوند بلکه به ترتیب، خانه به خانه پر می‌شوند.
  2. قبل از انتساب متن باید باید از طول متن مطمئن شویم تا بتوانیم تعداد خانه‌ها را بر اساس آن ایجاد کنیم.
  3. همه عملیات آرایه‌ها از پر کردن ابتدای کار گرفته تا هر عملی، نیاز است به صورت دستی صورت بگیرد و تعداد خطوط کد برای هر کاری هم بالا می‌رود.
البته استفاده از string هم راه حل نهایی برای کار با متون نیست. در انتهای این مطلب مورد دیگری را نیز بررسی خواهیم کرد. از ویژگی دیگر رشته‌ها این است که آن‌ها شباهت زیادی به آرایه‌ای از کاراکتر‌ها دارند؛ ولی اصلا شبیه آن‌ها نیستند و نمی‌توانید به صورت یک آرایه آن‌ها را مقداردهی کنید. البته کلاس string امکاناتی را با استفاده از indexer [] مهیا کرده است که میتوانید بر اساس اندیس‌ها به کاراکترها به صورت جداگانه دسترسی داشته باشید ولی نمی‌توانید آن‌ها را مقدار دهی کنید. این اندیس‌ها از 0 تا طول آن length-1 ادامه دارند.
string str = "abcde";
char ch = str[1]; // ch == 'b'
str[1] = 'a'; // Compilation error!
ch = str[50]; // IndexOutOfRangeException
همانطور که میدانیم برای مقداردهی رشته‌ها از علامت‌های نقل قول "" استفاده میکنیم که باعث میشود اگر بخواهیم علامت " را در رشته‌ها داشته باشیم نتوانیم. برای حل این مشکل از علامت \ استفاده میکنیم که البته باعث استفاده از بعضی کاراکترهای خاص دیگر هم می‌شود:
string a="Hello \"C#\"";
string b="Hello \r\n C#"; //مساوی با اینتر
string c="C:\\a.jpg"; //چاپ خود علامت  \ -مسیردهی
البته اگر از علامت @ در قبل از رشته استفاده شود علامت \ بی اثر خواهد شد.
string c=@"C:\a.jpg";// == "C:\\a.jpg"

مقداردهی رشته‌ها و پایدار (تغییر ناپذیر) بودن آنها Immutable
رشته‌ها ساختاری پایدار هستند؛ به این معنی که به صورت reference مقداردهی می‌شوند. موقعی که شما مقداری را به یک رشته انتساب می‌دهید، مقدار متغیر در  String pool یا لینک در Heap ذخیره می‌شوند و اگر همین متغیر را به یک متغیر دیگر انتساب دهیم، متغیر جدید مقدار آن را دیگر در حافظه پویا (داینامیک) Heap به عنوان مقدار جدید ذخیره نخواهد کرد؛ بلکه تنها یک pointer خواهد بود که به آدرس حافظه متغیر اولی اشاره می‌کند. به مثال زیر دقت کنید. متغیر source مقدار some source را ذخیره می‌کند و بعد همین متغیر، به متغیر assigned انتساب داده میشود؛ ولی مقداری جابجا نمی‌شود. بلکه متغیر assign به آدرسی در حافظه اشاره می‌کند که متغیر source اشاره می‌کند. هرگاه که در یکی از متغیرها، تغییری رخ دهد، همان متغیری که تغییر کرده است، به آدرس جدید با محتوای تغییر داده شده اشاره می‌کند.
string source = "Some source";
string assigned = source;

این ویژگی نوع reference فقط برای ساختارهای Immutable به معنی پایدار رخ می‌دهد و نه برای ساختار‌های ناپایدار (تغییر پذیر)  mutable؛ به این خاطر که آن‌ها مقادیرشان را مستقیما تغییر میدهند و اشاره‌ای در حافظه صورت نمی‌گیرد. 
string hel = "Hel";
string hello = "Hello";
string copy = hel + "lo";

string hello = "Hello";
string same = "Hello";

برای اطلاعات بیشتر در این زمینه این لینک را مطالعه نمایید.


مقایسه رشته‌ها
برای مقایسه دو رشته میتوان از علامت == یا از متد Equals استفاده نماییم. در این حالت به خاطر اینکه کد حروف کوچک و بزرگ متفاوت است، مقایسه حروف هم متفاوت خواهد بود. برای اینکه حروف کوچک و بزرگ تاثیری بر مقایسه ما نگذارند و #c را با #C برابر بدانند باید از متد Equals به شکل زیر استفاده کنیم:
Console.WriteLine(word1.Equals(word2,
    StringComparison.CurrentCultureIgnoreCase));
برای اینکه بزرگی و کوچکی اعداد را مشخص کنیم از علامت‌های < و > استفاده میکنیم ولی برای رشته‌ها از متد CompareTo بهره می‌بریم که چینش قرارگیری آن‌ها را بر اساس حروف الفبا مقایسه می‌کند و سه عدد، می‌تواند خروجی آن باشند. اگر 0 باشد یعنی برابر هستند، اگر -1 باشد رشته اولی قبل از رشته دومی است و اگر 1 باشد رشته دومی قبل از رشته اولی است.
string score = "sCore";
string scary = "scary";
 
Console.WriteLine(score.CompareTo(scary));
Console.WriteLine(scary.CompareTo(score));
Console.WriteLine(scary.CompareTo(scary));
 
// Console output:
// 1
// -1
// 0
 اینبار هم برای اینکه حروف کوچک و بزرگ، دخالتی در کار نداشته باشند، میتوانید از داده شمارشی StringComparison در متد ایستای (string.Compare(s1,s2,StringComparison استفاده نمایید؛ یا از نوع داده‌ای boolean برای تعیین نوع مقایسه استفاده کنید.
string alpha = "alpha";
string score1 = "sCorE";
string score2 = "score";
 
Console.WriteLine(string.Compare(alpha, score1, false));
Console.WriteLine(string.Compare(score1, score2, false));
Console.WriteLine(string.Compare(score1, score2, true));
Console.WriteLine(string.Compare(score1, score2,
    StringComparison.CurrentCultureIgnoreCase));
// Console output:
// -1
// 1
// 0
// 0
نکته : برای مقایسه برابری  دو رشته از متد Equals یا == استفاده کنید و فقط برای تعیین کوچک یا بزرگ بودن از compare‌ها استفاده نمایید. دلیل آن هم این است که برای مقایسه از فرهنگ culture فعلی سیستم استفاده میشود و نظم جدول یونیکد را رعایت نمی‌کنند و ممکن است بعضی رشته‌های نابرابر با یکدیگر برابر باشند. برای مثال در زبان آلمانی دو رشته "SS" و "ß " با یکدیگر برابر هستند.

عبارات با قاعده Regular Expression
این عبارات الگوهایی هستند که قرار است عبارات مشابه الگویی را در رشته‌ها پیدا کنند. برای مثال الگوی +[A-Z0-9] مشخص می‌کند که رشته مورد نظر نباید خالی باشد و حداقل با یکی از حروف بزرگ یا اعداد پرشده باشد. این الگوها میتوانند برای واکشی داده‌ها یا قالب‌های خاص در رشته‌ها به کار بروند. برای مثال شماره تماس‌ها ، پست الکترونیکی و ...
در اینجا میتواند نحوه‌ی الگوسازی را بیاموزید. کد زیر بر اساس یک الگو، شماره تماس‌های مورد نظر را یافته و البته با فیلتر گذاری آن‌ها را نمایش می‌دهد:
string doc = "Smith's number: 0898880022\nFranky can be " +
    "found at 0888445566.\nSteven's mobile number: 0887654321";
string replacedDoc = Regex.Replace(
    doc, "(08)[0-9]{8}", "$1********");
Console.WriteLine(replacedDoc);
// Console output:
// Smith's number: 08********
// Franky can be found at 08********.
// Steven' mobile number: 08********
سه شماره تماس در رشته‌ی بالا با الگوی ما همخوانی دارند که بعد با استفاده از متد replace در شی Regex عبارات دلخواه خودمان را جایگزین شماره تماس‌ها خواهیم کرد. الگوی بالا شماره تماس‌هایی را میابد که با 08 آغاز شده‌اند و بعد از آن 8 عدد دیگر از 0 تا 9 قرار گرفته‌اند. بعد از اینکه متن مطابق الگو یافت شد، ما آن را با الگوی ********1$ جایگزین می‌کنیم که علامت $ یک placeholder برای یک گروه است. هر عبارت () در عبارات با قاعده یک گروه حساب میشود و اولین پرانتر 1$ و دومین پرانتز یا گروه میشود 2$ که در عبارت بالا (08) میشود 1$ و به جای مابقی الگو، 8 علامت ستاره نمایش داده میشود.

اتصال رشته‌ها در Loop
برای اتصال رشته‌ها ما از علامت + یا متد ایستای string.concat استفاده می‌کنیم ولی استفاده‌ی از آن در داخل یک حلقه باعث کاهش کارآیی برنامه خواهد شد. برای همین بیایید ببینم در حین اتتقال رشته‌ها در حافظه چه اتفاقی رخ میدهد. ما در اینجا دو رشته str1 و str2 داریم که عبارات "super" و "star" را نگه داری می‌کنند و در واقع دو متغیر هستند که به حافظه‌ی پویای Heap اشاره می‌کنند. اگر این دو را با هم جمع کنیم و نتیجه را در متغیر result قرار دهیم، سه متغیر میشوند که هر کدام به حافظه‌ای جداگانه در heap اشاره می‌کنند. در واقع برای این اتصال، قسمت جدیدی از حافظه تخصصیص داده شده و مقدار جدید در آن نشسته‌است. در این حالت یک متغیر جدید ساخته شد که به آدرس آن اشاره می‌کند. کل این فرآیند یک فرآیند کاملا زمانبر است که با تکرار این عمل موجب از دست دادن کارآیی برنامه می‌شود؛ به خصوص اگر در یک حلقه این کار صورت بگیرد.
سیستم دات نت همانطور که میدانید شامل GC یا سیستم خودکار پاکسازی حافظه است که برنامه نویس را از dispose کردن بسیاری از اشیاء بی نیاز می‌کند. موقعی‌که متغیری به قسمتی از حافظه اشاره می‌کند که دیگر بلا استفاده است، سیستم GC به صورت خودکار آنها را پاکسازی می‌کند که این عمل زمان بر هم خودش موجب کاهش کارآیی می‌شود. همچنین انتقال رشته‌ها از یک مکان حافظه به مکانی دیگر، باز خودش یک فرآیند زمانبر است؛ به خصوص اگر رشته مورد نظر طولانی هم باشد.
مثال عملی: در تکه کد زیر قصد داریم اعداد 1 تا 20000 را در یک رشته الحاق کنیم:
 DateTime dt = DateTime.Now;
            string s = "";
        for (int index = 1; index <= 20000; index++)
        {
            s += index.ToString();
        }
            Console.WriteLine(s);
            Console.WriteLine(dt);
            Console.WriteLine(DateTime.Now);
            Console.ReadKey();
کد بالا تاز زمان نمایش کامل، بسته به قدرت سیستم ممکن است یکی دو ثانیه طول بکشد. حالا عدد را به 200000 تغییر دهید (یک صفر اضافه تر). برنامه را اجرا کنید و مجددا تست بزنید. در این حالت چند دقیقه ای بسته به قدرت سیستم زمان خواهد برد؛ مثلا دو دقیقه یا سه دقیقه یا کمتر و بیشتر.
عملیاتی که در حافظه صورت میگیرد این چند گام را طی میکند:
  • قسمتی از حافظه به طور موقت برای این دور جدید حلقه، گرفته میشود که به آن بافر میگوییم.
  • رشته قبلی به بافر انتقال میابد که بسته به مقدار آن زمان بر و کند است؛ 5 کیلو یا 5 مگابایت یا 50 مگابایت و ...
  • شماره تولید شده جدید به بافر چسبانده میشود.
  • بافر به یک رشته تبدیل میشود وجایی برای خود در حافظه Heap میگیرد.
  • حافظه رشته قدیمی و بافر دیگر بلا استفاده شده‌اند و توسط GC پاکسازی میشوند که ممکن است عملیاتی زمان بر باشد.

String Builder
این کلاس ناپایدار و تغییر پذیر است. به کد و شکل زیر دقت کنید:
string declared = "Intern pool";
string built = new StringBuilder("Intern pool").ToString();

این کلاس دیگر مشکل الحاق رشته‌ها یا دیگر عملیات پردازشی را ندارد. بیایید مثال قبل را برای این کلاس هم بررسی نماییم:
 StringBuilder sb = new StringBuilder();
      sb.Append("Numbers: ");

            DateTime dt = DateTime.Now;
        for (int index = 1; index <= 200000; index++)
        {
            sb.Append(index);
        }
            Console.WriteLine(sb.ToString());
            Console.WriteLine(dt);
            Console.WriteLine(DateTime.Now);
            Console.ReadKey();
اکنون همین عملیات چند دقیقه‌ای قبل، در زمانی کمتر، مثلا دو ثانیه انجام میشود.
حال این سوال پیش می‌آید مگر کلاس stringbuilder چه میکند که زمان پردازش آن قدر کوتاه است؟
همانطور که گفتیم این کلاس mutable یا تغییر پذیر است و برای انجام عملیات‌های ویرایشی نیازی به ایجاد شیء جدید در حافظه ندارد؛ در نتیجه باعث کاهش انتقال غیرضروری داده‌ها برای عملیات پایه‌ای چون الحاق رشته‌ها میگردد.
stringbuilder شامل یک بافر با ظرفیتی مشخص است (به طور پیش فرض 16 کاراکتر). این کلاس آرایه‌هایی از کاراکترها را پیاده سازی میکند که برای عملیات و پردازش‌هایش  از یک رابط کاربرپسند برای برنامه نویسان استفاده می‌کند. اگر تعداد کاراکترها کمتر از 16 باشد مثلا 5 ، فقط 5 خانه آرایه استفاده میشود و مابقی خانه‌ها خالی میماند و با اضافه شدن یک کاراکتر جدید، دیگر شیء جدیدی در حافظه درست نمی‌شود؛ بلکه در خانه ششم قرار می‌گیرد و اگر تعداد کاراکترهایی که اضافه می‌شوند باعث شود از 16 کاراکتر رد شود، مقدار خانه‌ها دو برابر میشوند؛ هر چند این عملیات دو برابر شدن resizing عملیاتی کند است ولی این اتفاق به ندرت رخ می‌دهد.
کد زیر یک آرایه 15 کاراکتری ایجاد می‌کند و عبارت #Hello C را در آن قرار می‌دهد.
StringBuilder sb = new StringBuilder(15);
sb.Append("Hello, C#!");

در شکل بالا خانه هایی خالی مانده است Unused و  جا برای کاراکترهای جدید به اندازه خانه‌های unused هست و اگر بیشتر شود همانطور که گفتیم تعداد خانه‌ها 2 برابر می‌شوند که در اینجا میشود 30.

استفاده از متد ایستای string.Format
از این متد برای نوشتن یک متن به صورت قالب و سپس جایگزینی مقادیر استفاده می‌شود:
DateTime date = DateTime.Now;
string name = "David Scott";
string task = "Introduction to C# book";
string location = "his office";
 
string formattedText = String.Format(
    "Today is {0:MM/dd/yyyy} and {1} is working on {2} in {3}.",
    date, name, task, location);
Console.WriteLine(formattedText);
در کد بالا ابتدا ساختار قرار گرفتن تاریخ را بر اساس الگو بین {} مشخص می‌کنیم و متغیر date در آن قرار می‌گیرد و سپس برای {1},{2},{3} به ترتیب قرار گیری آن‌ها متغیرهای name,last,location قرار میگیرند.
از ()ToString. هم می‌توان برای فرمت بندی خروجی استفاده کرد؛ مثل همین عبارت MM/dd/yyyy در خروجی نوع داده تاریخ و زمان.
اشتراک‌ها
دوره کامل Docker

Complete Docker Course - From BEGINNER to PRO! (Learn Containers)
Learn Docker and containers to improve your software systems! 🐳 📦
This course covers everything from getting started all the way through building a containerized web application and deploying it to the cloud!
Timestamps:
00:00 - Introduction
04:40 - History and motivation
30:27 - Technology overview
40:30 - Installation and set up
47:15 - Using 3rd party container images
48:06 - Understanding container data and docker volumes
1:13:00 - Demo application
1:28:37 - Building container images
2:23:46 - Container registries
2:33:45 - Running containers
3:02:36 - Container security
3:06:58 - Interacting with Docker objects
3:18:36 - Development workflow
3:52:05 - Ephemeral environments with Shipyard
4:07:17 - Deploying containers
4:42:59 - Final wrap up
 

دوره کامل Docker
اشتراک‌ها
سری کار با IdentityServer 5

- Creating an IdentityServer 5 Project
- Adding JWT Bearer Authentication to an ASP.NET Core 5 API
- Adding Policy-Based Authorisation to an ASP.NET Core 5 API
- Authenticating a .NET 5 Console Application using IdentityServer 5
- Adding API Resources to IdentityServer 5
- Containerising a PostgreSQL 13 Database
- Adding Entity Framework Core 5 to IdentityServer 5
- Adding an OAuth 2.0 Security Scheme to an ASP.NET Core 5 API
- Adding ASP.NET Core 5 Identity to IdentityServer 5
- Seeding an ASP.NET Core 5 Identity Database with Users
- Authenticating an ASP.NET Core 5 Web Application using IdentityServer 5
- Authenticating an Angular 11 Single-Page Application using IdentityServer 5
- Authenticating a Flutter Android Application using IdentityServer 5
- Containerising an IdentityServer 5 Project
- Containerising an IdentityServer 5 Project with API Resources and Clients
 

سری کار با IdentityServer 5