مطالب
WebStorage: قسمت دوم
در این مقاله قصد داریم نحوه‌ی کدنویسی webstorage را با کتابخانه‌هایی که در مقاله قبل معرفی کردیم بررسی کنیم.
ابتدا روش ذخیره سازی و بازیابی متداول آن را بررسی میکنیم که تنها توسط دو تابع صورت می‌گیرد. مطلب زیر برگرفته از w3Schools است:
دسترسی به شیء webstorage به صورت زیر امکان پذیر است:
window.localStorage
window.sessionStorage
ولی بهتر است قبل از ذخیره و بازیابی، از پشتیبانی مرورگر از webstorage اطمینان حاصل نمایید:
if(typeof(Storage) !== "undefined") {
    // Code for localStorage/sessionStorage.
} else {
    // Sorry! No Web Storage support..
}
برای ذخیره سازی و سپس خواندن به شکل زیر عمل می‌کنیم:
localStorage.setItem("lastname", "Smith");

//========================
localStorage.getItem("lastname");
خواندن می‌تواند حتی به شکل زیر هم صورت بگیرد:
var a=localStorage.lastname;
استفاده از store.js برای مرورگرهایی که از webstorage پشتیبانی نمی‌کنند به شکل زیر است:
//ذخیره مقدار
store.set('username', 'marcus')

//بازیابی مقدار
store.get('username')

//حذف مقدار
store.remove('username')

//حذف تمامی مقادیر ذخیره شده
store.clear()

//ذخیره ساختار
store.set('user', { name: 'marcus', likes: 'javascript' })

//بازیابی ساختار به شکل قبلی
var user = store.get('user')
alert(user.name + ' likes ' + user.likes)

//تغییر مستقیم مقدار قبلی
store.getAll().user.name == 'marcus'

//بازخوانی تمام مقادیر ذخیر شده توسط یک حلقه
store.forEach(function(key, val) {
    console.log(key, '==', val)
})
همچنین بهتر هست از یک فلگ برای بررسی فعال بودن storage استفاده نمایید. به این دلیل که گاهی کاربرها از پنجره‌های private استفاده می‌کنند که ردگیری اطلاعات آن ممکن نیست و موجب خطا می‌شود.
<script src="store.min.js"></script>
<script>
    init()
    function init() {
        if (!store.enabled) {
            alert('Local storage is not supported by your browser. Please disable "Private Mode", or upgrade to a modern browser.')
            return
        }
        var user = store.get('user')
        // ... and so on ...
    }
</script>
در صورتیکه بخشی از داده‌ها را توسط localstorage ذخیره نمایید و بخواهید از طریق storage به آن دسترسی داشته باشید، خروجی string خواهد بود؛ صرف نظر از اینکه شما عدد، شیء یا آرایه‌ای را ذخیره کرده‌اید.
در صورتیکه ساختار JSON را ذخیره کرده باشید، می‌توانید رشته برگردانده شده را با json.stringify و json.parse بازیابی و به روز رسانی کنید.
در حالت cross browser تهیه‌ی یک sessionStorage امکان پذیر نیست. ولی می‌توان به روش ذیل و تعیین یک زمان انقضاء آن را محدود کرد:
var storeWithExpiration = {
// دریافت کلید و مقدار و زمان انقضا به میلی ثانیه
    set: function(key, val, exp) {
//ایجاد زمان فعلی جهت ثبت تاریخ ایجاد
        store.set(key, { val:val, exp:exp, time:new Date().getTime() })
    },
    get: function(key) {
        var info = store.get(key)
//در صورتی که کلید داده شده مقداری نداشته باشد نال را بر میگردانیم
        if (!info) { return null }
//تاریخ فعلی را منهای تاریخ ثبت شده کرده و در صورتی که
//از مقدار میلی ثاینه بیشتر باشد یعنی منقضی شده و نال بر میگرداند
        if (new Date().getTime() - info.time > info.exp) { return null }
        return info.val
    }
}

// استفاده عملی از کد بالا
// استفاده از تایمر جهت نمایش واکشی داده‌ها قبل از نقضا و بعد از انقضا
storeWithExpiration.set('foo', 'bar', 1000)
setTimeout(function() { console.log(storeWithExpiration.get('foo')) }, 500) // -> "bar"
setTimeout(function() { console.log(storeWithExpiration.get('foo')) }, 1500) // -> null

مورد بعدی استفاده از سورس  cross-storage  است. اگر به یاد داشته باشید گفتیم یکی از احتمالاتی که برای ما ایجاد مشکل می‌کند، ساب دومین هاست که ممکن است دسترسی ما به یک webstorage را از ساب دومین دیگر از ما بگیرد.
این کتابخانه به دو جز تقسیم شده است یکی هاب Hub و دیگری Client .
ابتدا نیاز است که هاب را آماده سازی و با ارائه یک الگو از آدرس وب، مجوز عملیات را دریافت کنیم. در صورتیکه این مرحله به فراموشی سپرده شود، انجام هر نوع عمل روی دیتاها در نظر گرفته نخواهد شد.
CrossStorageHub.init([
  {origin: /\.example.com$/,            allow: ['get']},
  {origin: /:\/\/(www\.)?example.com$/, allow: ['get', 'set', 'del']}
]);
حرف $ در انتهای عبارت باعث مشود که دامنه‌ها با دقت بیشتری در Regex بررسی شوند و دامنه زیر را معتبر اعلام کند:
valid.example.com
ولی دامنه زیر را نامعتبر اعلام می‌کند:
invalid.example.com.malicious.com
همچنین می‌توانید تنظیماتی را جهت هدر‌های CSP و CORS، نیز اعمال نمایید:
{
  'Access-Control-Allow-Origin':  '*',
  'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE',
  'Access-Control-Allow-Headers': 'X-Requested-With',
  'Content-Security-Policy':      "default-src 'unsafe-inline' *",
  'X-Content-Security-Policy':    "default-src 'unsafe-inline' *",
  'X-WebKit-CSP':                 "default-src 'unsafe-inline' *",
}
پس کار را بدین صورت آغاز می‌کنیم، یک فایل به نام hub.htm درست کنید و هاب را آماده سازید:
hub.htm
<script type="text/javascript" src="~/Scripts/cross-storage/hub.js"></script>
<script>
 CrossStorageHub.init([
      {origin: /.*localhost:300\d$/, allow: ['get', 'set', 'del']}
    ]);
  </script>
کد بالا فقط درخواست‌های هاست لوکال را از پورتی که ابتدای آن با 300 آغاز می‌شود، پاسخ می‌دهد و مابقی درخواست‌ها را رد می‌کند. متدهای ایجاد، ویرایش و حذف برای این آدرس معتبر اعلام شده است. 
در فایل دیگر که کلاینت شناخته می‌شود باید فایل hub معرفی شود تا تنظیمات هاب خوانده شود:
var storage = new CrossStorageClient('http://localhost:3000/example/hub.html');

var setKeys = function () {
      
return storage.set('key1', 'foo').then(function() {
return storage.set('key2', 'bar');
      });
};
در خط اول، فایل هاب معرفی شده و تنظیمات روی این صفحه اعمال می‌شود. سپس در خطوط بعدی داده‌ها ذخیره می‌شوند. از آنجا که با هر یکبار ذخیره، return صورت می‌گیرد و تنها اجازه‌ی ورود یک داده را داریم، برای حل این مشکل متد then پیاده سازی شده است. متغیر setKeys شامل یک آرایه از کلیدها خواهد بود.
نحوه‌ی ذخیره سازی بدین شکل هم طبق مستندات صحیح است:
storage.onConnect().then(function() {

  return storage.set('key', {foo: 'bar'});

}).then(function() {

  return storage.set('expiringKey', 'foobar', 10000);

});
در کد بالا ابتدا یک داده دائم ذخیره شده است و در کد بعد یک داده موقت که بعد از 10 ثانیه اعتبار خود را از دست می‌دهد.
برای خواندن داده‌های ذخیره شده به نحوه زیر عمل می‌کنیم:
storage.onConnect().then(function() {
  return storage.get('key1');
}).then(function(res) {
  return storage.get('key1', 'key2', 'key3');
}).then(function(res) {
  // ...
});
کد بالا نحوه‌ی خواندن مقادیر را به شکل‌های مختلفی نشان میدهد و مقدار بازگشتی آن‌ها یک آرایه از مقادیر است؛ مگر اینکه تنها یک مقدار برگشت داده شود. مقدار بازگشتی در تابع بعدی به عنوان یک آرگومان در دسترس است. در صورتی که خطایی رخ دهد، قابلیت هندل آن نیز وجود دارد:
storage.onConnect()
    .then(function() {
      return storage.get('key1', 'key2');
    })

.then(function(res) {
      console.log(res); // ['foo', 'bar']
    })['catch'](function(err) {
      console.log(err);
    });
برای باقی مسائل چون به دست آوردن لیست کلیدهای ذخیره شده، حذف کلید‌های مشخص شده، پاکسازی کامل داده‌ها و ... به مستندات رجوع کنید.
در اینجا جهت سازگاری با مرورگرهای قدیمی خط زیر را به صفحه اضافه کنید:
<script src="https://s3.amazonaws.com/es6-promises/promise-1.0.0.min.js"></script>
ذخیره‌ی اطلاعات به شکل یونیکد، فضایی دو برابر کدهای اسکی میبرد و با توجه به محدود بودن حجم webstorage به 5 مگابایت ممکن است با کمبود فضا مواجه شوید. در صورتیکه قصد فشرده سازی اطلاعات را دارید می‌توانید از کتابخانه lz-string استفاده کنید. ولی توجه به این نکته ضروری است که در صورت نیاز، عمل فشرده سازی را انجام دهید و همینطوری از آن استفاده نکنید.

IndexedDB API
آخرین موردی که بررسی می‌شود استفاده از IndexedDB API است که با استفاده از آن میتوان با webstorage همانند یک دیتابیس رفتار کرد و به سمت آن کوئری ارسال کرد. 
var request = indexedDB.open("library");

request.onupgradeneeded = function() {
  // The database did not previously exist, so create object stores and indexes.
  var db = request.result;
  var store = db.createObjectStore("books", {keyPath: "isbn"});
  var titleIndex = store.createIndex("by_title", "title", {unique: true});
  var authorIndex = store.createIndex("by_author", "author");

  // Populate with initial data.
  store.put({title: "Quarry Memories", author: "Fred", isbn: 123456});
  store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567});
  store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678});
};

request.onsuccess = function() {
  db = request.result;
};
کد بالا ابتدا به دیتابیس library متصل می‌شود و اگر وجود نداشته باشد، آن را می‌سازد. رویداد onupgradeneeded برای اولین بار اجرا شده و در آن می‌توانید به ایجاد جداول و اضافه کردن داده‌های اولیه بپردازید؛ یا اینکه از آن جهت به ارتقاء ورژن دیتابیس استفاده کنید. خصوصیت result، دیتابیس باز شده یا ایجاد شده را باز می‌گرداند. در خط بعدی جدولی با کلید کد ISBN کتاب تعریف شده است. در ادامه هم دو ستون اندیس شده برای عنوان کتاب و نویسنده معرفی شده است که عنوان کتاب را یکتا و بدون تکرار در نظر گرفته است. سپس در جدولی که متغیر store به آن اشاره می‌کند، با استفاده از متد put، رکوردها داخل آن درج می‌شوند. در صورتیکه کار با موفقیت انجام شود رویداد onSuccess فراخوانی می‌گردد.
برای انجام عملیات خواندن و نوشتن باید از تراکنش‌ها استفاده کرد:
var tx = db.transaction("books", "readwrite");
var store = tx.objectStore("books");

store.put({title: "Quarry Memories", author: "Fred", isbn: 123456});
store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567});
store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678});

tx.oncomplete = function() {
  // All requests have succeeded and the transaction has committed.
};
در خط  اول ابتدا یک خط تراکنشی بین ما و جدول books با مجوز خواندن و نوشتن باز می‌شود و در خط بعدی جدول books را در اختیار می‌گیریم و همانند کد قبلی به درج داده‌ها می‌پردازیم. در صورتیکه عملیات با موفقیت به اتمام برسد، متغیر تراکنش رویدادی به نام oncomplete فراخوانی می‌گردد. در صورتیکه قصد دارید تنها مجوز خواندن داشته باشید، عبارت readonly را به کار ببرید.
var tx = db.transaction("books", "readonly");
var store = tx.objectStore("books");
var index = store.index("by_author");

var request = index.openCursor(IDBKeyRange.only("Fred"));
request.onsuccess = function() {
  var cursor = request.result;
  if (cursor) {
    // Called for each matching record.
    report(cursor.value.isbn, cursor.value.title, cursor.value.author);
    cursor.continue();
  } else {
    // No more matching records.
    report(null);
  }
};
در دو خط اول مثل قبل، تراکنش را دریافت می‌کنیم و از آنجا که می‌خواهیم داده را بر اساس نام نویسنده واکشی کنیم، ستون اندیس شده نام نویسنده را دریافت کرده و با استفاده از متد opencursor درخواست خود را مبنی بر واکشی رکوردهایی که نام نویسنده fred است، ارسال میداریم. در صورتیکه عملیات با موفقیت انجام گردد و خطایی دریافت نکنیم رویداد onsuccess فراخوانی می‌گردد. در این رویداد با دو حالت برخورد خواهیم داشت؛ یا داده‌ها یافت می‌شوند و رکوردها برگشت داده می‌شوند یا هیچ رکوردی یافت نشده و مقدار نال برگشت خواهد خورد. با استفاده از cursor.continue می‌توان داده‌ها را به ترتیب واکشی کرده و مقادیر رکورد را با استفاده خصوصیت value  به سمت تابع report ارسال کرد.
کدهای بالا همه در مستندات معرفی شده وجود دارند و ما پیشتر توضیح ابتدایی در مورد آن دادیم و برای کسب اطلاعات بیشتر می‌توانید به همان مستندات معرفی شده رجوع کنید. برای idexedDB هم می‌توانید از این منابع +  +  +  استفاده کنید که خود w3c منبع فوق العاده‌تری است.

مطالب
روشی برای محدود کردن 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");
    }
}

نظرات اشتراک‌ها
آپدیت 2 ویژوال استودیو 2015
یک نکته‌ی تکمیلی در مورد یافتن لینک‌های آفلاین مواردی که قرار هست در حین نصب دریافت شوند:
بعد از نصب به روز رسانی دوم، اگر به اینترنت متصل نباشید، این خطاها را دریافت خواهید کرد:
Visual Studio Update 2 Extensibility Item Templates with Assembly References in Nuget Packages : This product did not download successfully: HTTP status 502: The server, while acting as a gateway or proxy to fulfill the request, received an invalid response from the upstream server it accessed.
Visual Studio 2015 Software Development Kit Update 2 : This product did not download successfully: HTTP status 502: The server, while acting as a gateway or proxy to fulfill the request, received an invalid response from the upstream server it accessed.
Developer Analytics Tools v5.2.0 : This product did not download successfully: HTTP status 502: The server, while acting as a gateway or proxy to fulfill the request, received an invalid response from the upstream server it accessed.
JavaScript Language Service for Visual Studio : This product did not download successfully: HTTP status 502: The server, while acting as a gateway or proxy to fulfill the request, received an invalid response from the upstream server it accessed.
JavaScript Project System for Visual Studio : This product did not download successfully: HTTP status 502: The server, while acting as a gateway or proxy to fulfill the request, received an invalid response from the upstream server it accessed.

لینک‌های مستقیم دریافت این بسته‌ها

Visual Studio Update 2 Extensibility Item Templates with Assembly References in Nuget Packages
Visual Studio 2015 Software Development Kit Update 2
Developer Analytics Tools v5.2.0
JavaScript Language Service for Visual Studio
JavaScript Project System for Visual Studio


روش یافتن این لینک‌ها هم به صورت زیر است:
الف) فایل XML مربوط به فید به روز رسانی دوم را دریافت کنید:
http://go.microsoft.com/fwlink/?LinkID=646969
ب) سپس عنوان مواردی را که ذکر شده از اینترنت دریافت نشده‌اند، در این فایل جستجو کنید. لینکی که در قسمت id هر کدام ذکر شده، دقیقا لینک دانلود آفلاین آن است.