نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 4 - فعال سازی پردازش فایل‌های استاتیک
یک موضوعی رو میخواستم مطرح کنم :
طبق یکی از مقالات سری ASP.net MVC سایت با استفاده از Controller فایل‌های آپلود شده رو با یک کلید ،خروجی میداد.

بنده همین موضوع رو در تکنولوژی جدید پیاده سازی کردم اما با مشکل عدم نمایش فایل یا تصویر در خروجی مواجه شدم


موجود بودن فیزیکی فایل هم در مسیر wwwroot/StaticImages/ و هم مسیر MyStaticImages/ :

و نحوه آدرس دهی :
<img src='@Url.Action("DownloadFile", "ImageHandler", new {Area = "", id = item.BaseFileGuids, imgSize = ImageHandlerController.ImgSize.M})' alt=""/>

مسیر به درستی نمایش داده شده و فایل هم پس از بررسی توسط : System.IO.File.Exists = true  می‌باشد.
اما در نمایش چه ادرس مستقیم و چه تگ <img>  خطای زیر نمایش داده میشود :


هر دو مسیر تست شده با قطعه کد زیر ، اما خطا مشابه می‌باشد
چه این گزینه hostingEnvironment.WebRootPath_
و چه این گزینه hostingEnvironment.ContentRootPath _ 
public IActionResult DownloadFile([FromRoute]string id, [FromQuery] ImgSize imgSize)
        {
            var result = _baseFileService.GetFileNameAndFileNameOnDsAndFileType(id);
            if (result == null) return View("Error");

            var fileName = result.Item1;
            string userAgent = Request.Headers["User-Agent"];
            if (IsInternetExplorer(userAgent))
            {
                var htencode = HtmlEncoder.Create();
                var attachment = string.Format("attachment; filename=\"{0}\"", htencode.Encode(fileName));
                _httpContext.HttpContext.Response.Headers.Add("Content-Disposition", attachment);
            }
            var rootPath = Path.Combine(_hostingEnvironment.WebRootPath, _settingsAppPathConfig.Value.ServerImagesRootPath);
            var filepath = Path.Combine(rootPath, imgSize.ToString().ToLower(), result.Item2);
            if (!System.IO.File.Exists(filepath))
            {
                const string notFoundImage = "notFound.jpg";
                var notFoundpath = Path.Combine(rootPath , notFoundImage);
                string contentType;
                new FileExtensionContentTypeProvider().TryGetContentType(notFoundImage, out contentType);
                return File(notFoundpath, contentType, notFoundImage);
            }
            string contentTypebase;
            new FileExtensionContentTypeProvider().TryGetContentType(result.Item3, out contentTypebase);
            return File(filepath, contentTypebase, fileName);
        }
نظرات مطالب
استفاده از لوسین برای برجسته سازی عبارت جستجو شده در نتایج حاصل
چطور می‌شود چندین جدول مختلف رو ایندکس کرد؟ هربار ایندکس میکنم اطلاعات قبلی از بین میره
 private static FSDirectory _shopDirectory;

 private static FSDirectory ShopDirectory
        {
            get
            {
                if (_shopDirectory == null)
                    _shopDirectory = FSDirectory.Open(new DirectoryInfo(ShopPath));
                if (IndexWriter.IsLocked(_shopDirectory))
                    IndexWriter.Unlock(_shopDirectory);
                var lockFilePath = Path.Combine(ShopPath, "write.lock");
                if (File.Exists(lockFilePath))
                    File.Delete(lockFilePath);
                return _shopDirectory;
            }
        }

 public static void CreateProducSkustIndex(List<ProductSkuLuceneViewModel> productSkuLucenes)
        {
            var directory = ShopDirectory;
            var analyzer = new StandardAnalyzer(_version, PersianStopwords.List);
            using (var writer = new IndexWriter(directory, analyzer, create: true, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
            {
                productSkuLucenes.ForEach(row => writer.AddDocument(MapProductToDocument(row)));

                writer.Optimize();
                writer.Commit();
                writer.Close();
            }
        }
 public static void CreateProducCategoriestIndex(List<CategoryLuceneViewModel> categoryLucenes)
        {
            var directory = ShopDirectory;
            var analyzer = new StandardAnalyzer(_version, PersianStopwords.List);
            using (var writer = new IndexWriter(directory, analyzer, create: true, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
            {
                categoryLucenes.ForEach(row => writer.AddDocument(MapProductCategoryToDocument(row)));

                writer.Optimize();
                writer.Commit();
                writer.Close();
            }
        }

مطالب
مدیریت کلیدهای کیبرد در جاوا اسکریپت
با پیشرفت بسترهای موجود در زمینه شبکه و اینترنت، گرایش به استفاده از اپلیکیشنهای تحت وب روز به روز بیشتر میشود. با گسترش این برنامه‌ها نیازها و درنتیجه ابزارهای موجود توسعه پیدا می‌کنند. درحال حاضر ابزارها و نیز محیطهای توسعه مختلفی برای تولید این اپلیکیشنها وجود دارد. به دلیل نوع رابط کاربری موجود در این برنامه‌ها (اکثراً مرورگرهای وب مثل اینترنت اکسپلورر، گوگل کروم، فایرفاکس و ...) استفاده از زبانهای سمت کلاینت (مثل جاوا اسکریپت که در تمامی مرورگرهای مدرن پشتیبانی کاملی از آن میشود) جایگاه ویژه ای در این نوع برنامه‌ها دارد. درضمن وقتی صحبت از اپلیکیشن به میان می‌آید استفاده از کلیدهای میانبر کیبرد برای راحتی کار کاربران کاربرد ویژه ای دارد. اما متاسفانه زبان جاوا اسکریپت به دلیل محدودیتهایی منطقی موجود، پشتیبانی مناسبی از رویدادهای کیبرد ندارد و مشکل تفاوتها و تناقضات میان سخت افزارها، سیستم عامل‌ها و مرورگرها هم به این مسئله بیشتر دامن میزند. در مطلب جاری هدف این است تا آشنایی مقدماتی با این مبحث فراهم شود.
رویدادهای کیبرد
در جاوا اسکریپت سه رویداد زیر برای کلیدهای کیبرد وجود دارد (به ترتیب زمان رخ دادن):
keydown: زمانی که یک کلید فشرده می‌شود.
keypress: زمانی که یک کلید کاراکتری فشرده می‌شود.
keyup: زمانی که یک کلیدِ فشرده شده، رها می‌شود.
یک تفاوت اساسی میان رویدادهای keydown و keypress در جاوا اسکریپت وجود دارد: رویداد keydown پس از فشردن هر کلیدی روی کیبرد رخ میدهد و یک کد مخصوص آن کلید (scan code ^) را ارائه میدهد. اما رویداد keypress که بعد از keydown رخ میدهد کد کاراکتر آن کلید (char code) را ارائه میدهد، بنابراین تنها برای کلیدهای کاراکتری بدرستی کار میکند. برای درک بهتر کد زیر را در یک فایل html ذخیره کرده و در مرورگرهای مختلف آزمایش کنید:
<html>
<body>
  <div>
    Prevent default:
    <input type="checkbox" id="keydownStop" value="1" />
    keydown&nbsp;&nbsp;&nbsp;
    <input type="checkbox" id="keypressStop" value="1" />
    keypress&nbsp;&nbsp;&nbsp;
    <input type="checkbox" id="keyupStop" value="1" />
    keyup
  </div>
  Ignore:
  <input type="checkbox" id="keydownIgnore" value="1" />
  keydown &nbsp;&nbsp;&nbsp;
  <input type="checkbox" id="keypressIgnore" value="1" />
  keypress &nbsp;&nbsp;&nbsp;
  <input type="checkbox" id="keyupIgnore" value="1" />
  keyup
  <div>
    Focus on the input below and press any key.
  </div>
  <div>
    <input type="text" style=" width: 600px" id="keyInput" />
  </div>
  Log:
  <div>
    <textarea id="keyLogger" rows="18" onfocus="this.blur()" style="width: 600px; border: 1px solid black"></textarea>
  </div>
  <input type="button" value="Clear" onclick="clearLog()" />
  <script type="text/javascript">
    document.getElementById('keyInput').onkeydown = keyHandler;
    document.getElementById('keyInput').onkeyup = keyHandler;
    document.getElementById('keyInput').onkeypress = keyHandler;
    document.getElementById('keyInput').focus();
    function keyHandler(e) {
      e = e || window.event;
      if (document.getElementById(e.type + 'Ignore').checked) return;
      var evt = e.type;
      while (evt.length < 10) evt += ' ' +
        log(evt +
          ' keyCode=' + e.keyCode +
          ' which=' + e.which +
          ' charCode=' + e.charCode +
          ' char=' + String.fromCharCode(e.keyCode || e.charCode) +
          (e.shiftKey ? ' +shift' : '') +
          (e.ctrlKey ? ' +ctrl' : '') +
          (e.altKey ? ' +alt' : '') +
          (e.metaKey ? ' +meta' : ''));
      if (document.getElementById(e.type + 'Stop').checked) {
        e.preventDefault ? e.preventDefault() : (e.returnValue = false);
      }
    }
    function clearLog() {
      document.getElementById('keyLogger').value = '';
      document.getElementById('keyInput').focus();
    }
    function log(text) {
      var area = document.getElementById('keyLogger');
      area.value += text + '\n';
      area.scrollTop = area.scrollHeight;
    }
  </script>
</body>
</html>
نکته: برای جلوگیری از اجرای مرورگرها در حالت Quirks حتما از تگ doctype در ابتدای فایلهای html خود استفاده کنید. درغیراینصورت رفتارهای غیرمنتظره ای (مخصوصا در IE) مشاهده خواهید کرد. برای اجرای مرورگرها در حالت استاندارد html5 (بهترین حالت در حال حاضر) میتوانید از تگ زیر استفاده کنید:
<!doctype html>
دقت کنید که قبل از این خط هیچ چیز دیگری نوشته نشود وگرنه در IE از آن صرفنظر میشود!
یا اینکه در IE با استفاده از developer tools (دکمه F12) برای Document Mode گزینه ای غیر از Quirks mode (بهتر است از حالت IE9 یا بالاتر استفاده کنید) را انتخاب کنید.

برای کسب اطلاعات بیشتر راجع به doctypeهای مختلف و نیز حالت quirks میتوانید به ^ و ^ و ^ و ^ رجوع کنید. پیشنهاد میکنم که این منابع را حتما مطالعه کنید.
نکته: در کد بالا متد preventDefault در  -8 IE تعریف نشده است (درواقع در IE تنها در نسخه 9 تعریف شده است). همچنین استفاده از پراپرتی returnValue در فایرفاکس و IE9 کار نمیکند! از این خط کد برای جلوگیری از رفتار پیشفرض رویداد استفاده شده است. همانطور که در ادامه میخوانید راه حل ساده‌تری نیز برای اینکار وجود دارد.
متد String.fromCharCode برای نمایش کاراکتر کلید فشرده شده استفاده شده است. البته اگر کلید غیرکاراکتری فشرده شود ممکن است با نتایج غیرمنتظره ای روبرو شوید.
با استفاده از html تولیدی در مرورگرهای مختلف سعی کنید موارد زیر را آزمایش کنید:
کلیدهای کاراکتری چون a / | { 6 را  بفشارید. در این حالت رویدادهای keydown و سپس keypress رخ خواهند داد. پس از رها کردن کلیدها نیز رویداد keyup رخ میدهد.
یکی از کلیدهای غیرکاراکتری مثل ctrl یا alt را بفشارید. در این حالت تنها رویدادهای keydown و keyup رخ خواهند داد و خبری از رویداد keypress نیست.
نکته: مرورگرهای FireFox و Opera در مورد بیشتر کلیدهای غیرکاراکتری نیز رویداد keypress را صدا خواهند زد! مرورگر IE این رفتار را تنها در مورد کلید Esc نشان میدهد. همچنین در IE و Opera کلید PrtScr هیچ رویدادی را فرا نمیخواند. ظاهرا تنها مرورگر Chrome بدرستی عمل میکند. 
درحالت کلی فشردن کلیدهای غیرکاراکتری نباید رویداد keypress را فراخوانی کند.
بنابراین:
keydown و keyup برای همه کلیدها
keypress برای کلیدهای کاراکتری

پراپرتی‌های رویدادهای کیبرد
برخلاف نسخه‌های قدیمی مرورگرها که هرکدام راه و روش خودشان را برای تعامل با این رویدادها برگزیده بودند، امروزه تمامی مرورگرها تقریبا از یک روش استاندارد و مشترک برای اینکار استفاده میکنند. در تصاویر زیر تمام اجزای آبجکت KeyboardEvent با استفاده از کد زیر در مرورگرهای اپرا و کروم نشان داده شده است. استفاده از کد زیر در مرورگرهای فایرفاکس و IE نتایج جالبی مانند تصاویر زیر فراهم نمیکند!
    document.onkeydown = function (e) {
      e = e || event;
      console.log(e);
    }

همانطور که مشاهده میکنید تفاوتهایی بین مرورگرها در این آبجکت به چشم میخورد. برای کسب اطلاعات بیشتر در مورد این آبجکت و اجزای استاندارد آن در DOM Level 3 به اینجا مراجعه کنید. در ادامه به بررسی پراپرتی‌های مهم آرگومان این رویدادها (همان KeyboardEvent) میپردازیم.
keyCode
همان scan code کلید فشرده شده است. برای مثال اگر کلید a فشرده شود کاراکتر تولیدی ممکن است a یا A یا 'ش' (یا کاراکتری دیگر در زبانهای مختلف) باشد اما در تمامی حالات scan code مربوطه یا همان keyCode همیشه یکسان (65 برای کلید a) خواهد بود. این کد تنها به کلید فشرده شده بستگی دارد و نه به کاراکتر حاصله! البته در IE به هنگام رخ دادن رویداد keypress کد کاراکتر (همان char code) کلید فشرده شده در این پراپرتی قرار میگیرد!
در این بین میان مرورگرهای مختلف تفاوتهایی وجود دارد که با یک جستجو در اینترنت میتوان به تمامی این کدها دسترسی پیدا کرد. خواندن مقاله کامل JavaScript Madness: Keyboard Events نیز خالی از لطف نیست.
charCode
همان کد ASCII (یا کد UTF-16 برای کاراکترهای یونیکد. اطلاعات بیشتر ^ و ^) کاراکتر کلید فشرده شده است. ممکن است با keyCode برابر باشد. این پراپرتی در IE و Opera تعریف نشده است.
در عمل ممکن است keyCode و charCode در پلتفرمهای مختلف و حتی بین سیستم عامل‌های مختلف در یک سخت افزار نتایج متفاوتی ارائه دهند. بنابراین آزمودن هر مورد مشکوک قبل از ریلیز نهایی محصول میتواند مفید باشد.
which
یک پراپرتی نسبتا غیراستاندارد! است که ترکیبی از keyCode و charCode را برمیگرداند (اطلاعات بیشتر در ^ و ^). این پراپرتی در IE تعریف نشده است.
shiftKey, ctrlKey, altKey, metaKey 
پراپرتی هایی از نوع بولین که وضعیت کلیدهای Shift, Ctrl, Alt و Command (تنها در مک) را نشان میدهند. 
نکته: بدستن آوردن کد درست کاراکتر فشرده شده با توجه به وضعیت کلیدها و زبان انتخابی تنها با استفاده از رویداد keypress امکان پذیر است. با توجه به تفاوتهایی که بین مرورگرهای مختلف وجود دارد برای یافتن کاراکتر فشرده شده در رویداد keypress استفاده از متد زیر توصیه میشود:
function getCharacter(event) {
  if (event.which == null)
     return String.fromCharCode(event.keyCode);    // IE
  else if (event.which != 0 && event.charCode != 0)
     return String.fromCharCode(event.which);  // All others
  return null; // special key
}
توضیحات بیشتر و کاملتر این مبحث را میتوانید از Document Object Model (DOM) Level 3 Events Specification که آخرین نسخه آن در زمان تهیه این مطلب در تاریخ 14 June 2012 انتشار یافته تهیه کنید. بطور ویژه برای مبحث رویدادهای کیبرد (^) اطلاعات بسیار بیشتری در دسترس است.
نکته: با توجه به اطلاعاتی که در سند فوق وجود دارد، به دلیل ماهیت همزمانی (Sync) رویدادهای کیبرد تا زمانی که تمام عملیات موجود در متد تعیین شده برای این رویدادها انجام نشود، رویداد بعدی (با توجه به ترتیبی که در ابتدای این مطلب آورده شده است) رخ نخواهد داد. برای تست این موضوع قطعه کد زیر را آماده کردم:
<html>
<body>
  <input id="inputText" type="text"  />
  <script>
    document.onkeydown = keyDown;
    document.onkeypress = keyPress;
    document.onkeyup = keyUp;
    function keyDown(e) {
      var input = document.getElementById('inputText');
      input.value = 'keyDown started ...';
      input.disabled = true;
      var j = 0;
      for (var i = 0; i < 999999999; i++) {
        j = i - j;
      }
      console.log(j);
      //alert('keyDown');
      input.value = 'keyDown finished.';
      input.disabled = false;
    }
    function keyPress(e) {
      alert('keyPress');
      //console.log('keyPress');
    } function keyUp(e) {
      alert('keyUp');
      //console.log('keyUp');
    }
  </script>
</body>
</html>
اگر کد بالا در مرورگرهای مختلف امتحان کنید مشاهده میکنید که انجام عملیات سنگین در رویدادهای کیبرد موجب ایجاد وقفه در فراخوانی سایر رویدادها میشود.
با اجرای کد فوق در مرورگرهای مختلف نکات جالب زیر بدست آمد:
- در IE و کروم نمایش یک alert موجب از دست دادن فوکس document شده و بنابراین رویدادهای کیبرد بعد از نمایش alert کار نخواهند کرد! مثلا اگر در keydown یک alert نمایش داده شود چون رویداد keyup بر روی پنجره alert رخ میدهد بنابراین keyup فراخوانی نمیشود ولی چون رویداد keypress با keydown همزمان است این اتفاق برای keypress نمی‌افتد. این مشکل در فایرفاکس پیش نمی‌آید. در اپرا در این حالت رویداد keypress هم رخ نمیدهد! البته رفتار IE در اجرای کد فوق کمی غیرمنتظره‌تر است و ظاهرا رویداد keypress هم رخ نمیدهد.
- در اجرای کد فوق در FireFox ظاهرا alert مربوط به keyup قبل از keypress نمایش داده میشود. البته اگر به جای alert از console.log (البته نیاز به نصب Firebug است) استفاده شود این به هم خوردگی ترتیب رویدادها وجود ندارد.
- در مرورگر Opera پس از فشردن کلید enter در نوار آدرس و پس از بارگذاری صفحه، فوکس بلافاصله به عنصر document سپرده میشود طوریکه که رویداد keyup کلید enter در document بارگذاری شده فراخوانی میشود و درصورت سرعت بالای بارگذاری صفحه، کدهای مروبوط به این رویداد اجرا میشوند. در سایر مرورگرها این مورد مشاهده نشد. 
- ظاهرا در تمام مرورگرها به غیر Opera کدهای جاوا اسکریپ در ثرد UI اجرا شده و موجب قفل شدن document میشود. بنابراین وسط اجرای کدهای سنگین نمیتوان مثلا خواص عناصر UI را تغییر داد. درواقع مرورگر اپرا برخلاف سایر مرورگرها، رفتار ویژه ای در برخورد با جاوا اسکریپت و رویدادها و تغییرات عناصر DOM دارد. برای کسب اطلاعات بیشتر در این زمینه به اینجا مراجعه کنید. درضمن این مبحث کمی پیچیده‌تر از آن است که به نظر می‌آید(^). برای بررسی بیشتر میتوانید کد زیر را در مرورگرهای مختلف آزمایش کنید:
<html>
<head>
  <script type="text/javascript">
    function process() {
      var above = 0, below = 0;
      for (var i = 0; i < 200000; i++) {
        if (Math.random() * 2 > 1) {
          above++;
        }
        else {
          below++;
        }
      }
    }
    function test() {
      var result1 = document.getElementById('log');
      var start = new Date().getTime();
      console.log('start');
      for (var i = 0; i < 200; i++) {
        result1.value = 'time=' + (new Date().getTime() - start) + ' [i=' + i + ']';
        process();
      }
      result1.value = 'time=' + (new Date().getTime() - start) + ' [done]';
      console.log('end');
    }
    window.onload = test;
  </script>
</head>
<body>
  <input id='log' />
</body>
</html>
اگر کد فوق را در مرورگرهایی غیر از اپرا اجرا کنید میبینید که تنها نتیجه نهایی نمایش داده میشود و فرایندهای میانی درون حلقه نمیتوانند تغییری در محتوای UI ایجاد کنند. طبق انتظار کروم از بقیه بسیار سریعتر بوده و سپس IE و پس از آن فایرفاکس قرار دارد. اما در مورد Opera وضع کاملا فرق میکند و به دلیل به روز رسانی همزمان UI عملیات بسیار بسیار کندتر از بقیه مرورگرها به اتمام میرسد.
نکته: حالات استثنایی دیگری هم در اجرای کدهای مشابه در مرورگرهای مختلف پیش می‌آید که به دلیل پیچیده کردن بیش از حد بحث آورده نشده اند. فقط ذکر این نکته الزامی است که درحال حاضر میزان تفاوت رفتار مرورگرهای مختلف دربرخورد با کدهای یکسان قابل ملاحظه است. بنابراین در هنگام توسعه سعی کنید حداقل در این چهار مرورگر معروف آزمایشات خود را به سرانجام برسانید.
و در ادامه چند مثال ...

غیرفعال کردن ورودی کاربر
برای اینکار فقط کافی است در رویدادهای keydown یا keypress مقدار false برگشت داده شود. البته در مروگر Opera برخی از کلیدها از این رفتار پیروی نمیکنند. مثل کاراکترهای '`' و '+' و '=' که درصورت برگشت false در رویداد keydown باز هم رفتار پیش فرض را از خود نشان میدهد. اما برگرداندن مقدار false در رویداد keypress این مشکل را حل میکند(این موارد در نسخه 11.51 تست شدند). میتوانید با استفاده از کد زیر در تمام مرورگرها این موارد را آزمایش کنید:
<input onkeydown="return false" />
<input onkeypress="return false" /> 

تبدیل به حروف بزرگ
document.getElementById('myInputText').onkeypress = function (e) {
  var char = getCharacter(e || window.event);
  if (!char) return; // special key
  this.value += char.toUpperCase();
  return false; // برای اینکه کاراکتر اضافی نمایش داده نشود
}
در کد فوق متد getCharacter در بالا در قسمت پراپرتی‌های آرگومان رویداد نشان داده شده است. کد فوق چندان کامل نیست و همیشه کاراکتر فشرده شده را بدون توجه به موقعیت کرسر کیبرد در انتهای متن قرار میدهد.
نکته: استفاده از عبارتی چون e || window.event به این دلیل است که در مرورگر IE آرگومان رویداد (همان e که به آن implicit event object نیز میگویند) به متد مربوطه ارسال نمیشود (تا نسخه 9 که تست کردم مسئله به همین صورت است) در عوض پراپرتی‌های این آرگومان از طریق window.event (یا همان event که به آن explicit event object نیز میگویند) در دسترس هستند. این فیلد در واقع آرگومان آخرین رویداد رخ داده در پنجره جاری را در خود ذخیره میکند. اما در سایر مرورگرها به صورت استاندارد مقدار این آرگومان به عنوان پارامتر رویداد به متد مربوطه ارسال میشود. جالب است که بدانید مرورگرهای Opera و Chrome از هر دو روش پشتیبانی میکنند. مرورگر فایرفاکس تنها از ارسال آرگومان رویداد به متد مربوطه پشتیبانی میکند.
عبارت e ||window.event درواقع شکل دیگر عبارت زیر است:
e ? e : window.event;
در جاوا اسکریپت اگر عملگر مقایسه ای در عبارت مقایسه آورده نشود مقدار عبارت با false مقایسه میشود. این مقایسه از نوع abstract است (در ادامه این مطلب توضیح داده شده است). در جاوا اسکریپت 0 و رشته خالی و null و undefined و امثال اینها در مقایسه abstract برابر false درنظر گرفته میشوند. بنابراین در عبارت مقایسه بالا اگر مقدار e مثلا undefined (در IE) باشد مقدار window.event بازگشت داده میشود و درغیراینصورت خود e برگشت داده میشود.

تنها عدد
document.getElementById('numberInputText').onkeypress = function (e) {
  e = e || window.event;
  var chr = getCharacter(e);
  if (!isNumeric(chr) && chr !== null) return false;
}
function isNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}
function isNumber(val) {
  return val !== "NaN" && (+val) + '' === val + ''
}
در کد بالا متد isNumeric از اینجا گرفته شده است. متد isNumeric جی کوئری (که از نسخه 1.7 اضافه شده است) هم دقیقا از این روش استفاده میکند.
متد دوم یعنی  isNumber (که در کد فوق از آن استفاده نشده است) روش دیگری را برای اطمینان از مقدار عددی بودن استفاده میکند. در این روش درصورتیکه val یک مقدار عددی نباشد val+ برابر NaN میشود که در نهایت عبارت مقایسه ای مقدار false را برمیگرداند. البته به غیر از خود مقدار NaN که در شرط اول مورد بررسی قرار گرفته است. برای کسب اطلاعات بیشتر در مورد رفتار نسبتا عجیب جاوا اسکریپت با NaN و متد isNaN به اینجا سر بزنید.
نکته: تفاوت == با === (یا =! و ==!) - در جاوا اسکریپت دو روش برای مقایسه مقادیر متغیرها وجود دارد. اولی (== یا =!) مقایسه را با تبدل نوع داده‌ها انجام میدهد (که اصطلاحا به آن type-converting equality comparison یا abstract comparison میگویند) و دومی (=== یا ==!) مقایسه را با مقادیر واقعی و بدون تبدیل نوع داده انجام میدهد (که به آن equality without type coercion یا strict equality comparison گفته میشود). در واقع در مقایسه strict تنها وقتی که دو متغیر از یک نوع باشند ممکن است مقدار true برگشت داده شود. برای روشنتر شدن مطلب به مثالهای زیر توجه کنید (^):
0==false   // true
0===false  // false, because they are of a different type
1=="1"     // true, auto type coercion
1==="1"    // false, because they are of a different type 
توضیحات بهتری در اینجا آورده شده است.
برای کسب اطلاعات کاملتر میتوانید به ECMAScript Language Specification و قسمت مقایسه strict و abstract مراجعه کنید. (رابطه‌ها و تفاوت‌های میان ECMAScript و JavaScript و JScript و ActionScript در اینجا آورده شده است.)

کار با Scan Code و Char Code
همانطور که قبلا هم اشاره شد میان کد کاراکتر و کلید فشرده شده بر روی کیبرد تفاوت وجود دارد. برای بهره برداری از کلیدهای میانبر در صفحات وب به کد کلید فشرده شده (همان scan code) نیاز است و نه به کد کاراکتر آن. همچنین کلیدهای ویژه و غیرکاراکتری دارای کد کاراکتر نیستند. بیشتر مرورگرها رویداد Keypress را برای کلیدهای غیرکاراکتری فرا نمیخوانند. بنابراین برای این موارد رویدادهای keydown و keyup مفید هستند. امروز تمام مرورگرها از جدول کدهای یکسانی برای scan codeها استفاده میکنند که نمونه‌های آن را میتوانید در ^ و ^ و ^ مشاهده کنید. نکته ای که باید درباره پراپرتی keyCode یادآوری شود این است که جدای از وضعیت کیبرد (مثل زبان یا موقعیت کلید capsLock) در تمام حالات در ازای فشردن شده یک کلید خاص (یا ترکیبی از کلیدهای کنترلی با سایر کلیدها) همواره باید یک کد یکسان برگشت داده شود.
پس یک charCode کد یونیکد کاراکتر کلید فشرده شده است که تنها در رویداد keypress در دسترس است. یک keyCode کد خود کلید فشرده شده یا همان scan code است که در رویدادهای keydown و keyup در دسترس است.
نکته: برای تمام کلیدهای الفبایی-عددی scan code با char code یکی است. مثلا برای حروف الفبایی scan code یک کلید با کد ASCII حرف انگلیسی بزرگ آن کلید برابر است.
بنابراین برای بررسی کلید ترکیبی ctrl + a میتوان از کد زیر در رویداد keydown استفاده کرد:
e.ctrlKey && e.keyCode == 'A'.charCodeAt(0)
و فرقی نمیکند که کاراکتر حاصله 'a' یا 'A' یا 'ش' باشد. 
نکته: برای تمام کلیدهای کیبرد به غیر از ';' و '=' و '-' تمام مرورگرها از کدهای یکسانی استفاده میکنند. میتوانید این کلیدها را با استفاده از قطعه کد اول این مطلب در مرورگرهای مختلف آزمایش کنید.
نکته: با برگشت مقدار false در رویداد keydown یا keypress رفتار پیشفرض کلید یا ترکیب کلیدهای مربوطه غیرفعال میشود. البته به غیر از عملیاتهای سطح سیستم عامل (مثل alt+F4).
چند مثال دیگر در ادامه ...

جابجایی تصویر
کدهای زیر را در یک فایل html ذخیره کرده و توسط یک مرورگر فایل حاصله را باز کنید.
<html>
<body>
  <div id="dotnettips" style="width: 35px; height: 35px; background-image: url(https://www.dntips.ir/favicon.ico);
    position: absolute; left: 10px; top: 10px;" tabindex="0">
  </div>
  <script>
    document.getElementById('dotnettips').onkeydown = function (e) {
      e = e || event;
      switch (e.keyCode) {
        case 37: // left
          this.style.left = parseInt(this.style.left) - this.offsetWidth + 'px';
          return false;
        case 38: // up
          this.style.top = parseInt(this.style.top) - this.offsetHeight + 'px';
          return false;
        case 39: // right
          this.style.left = parseInt(this.style.left) + this.offsetWidth + 'px';
          return false;
        case 40: // down
          this.style.top = parseInt(this.style.top) + this.offsetHeight + 'px';
          return false;
      }
    }
  </script>
</body>
</html>
بر روی تصویر کلید کرده (یا با استفاده از Tab فوکس را روی تصویر قرار دهید) و با استفاده از کلیدهای مکان نما (Arrow keys) سعی کنید تصویر را در صفحه جابجا کنید.
در کد فوق برای جلوگیری از رفتار پیش فرض کلیدهای مکان نما (جابجایی محتوای صفحه در صورت وجود اسکرول) مقدار false برگشت داده شده است.
نکته: استفاده از خاصیت tabindex برای امکان فوکس بر روی div اجباری است.
نکته: استفاده از event به جای window.event تا زمانی که یک متغیر در اسکوپ جاری با نام event وجود نداشته باشد مشکلی ایجاد نمیکند.

Caps Lock
متاسفانه راه مستقیمی برای دریافت وضعیت کلید caps lock در جاوا اسکریپت وجود ندارد. ظاهرا تنها راه حل موجود برای این مسئله بررسی کد کاراکتر کلید فشرده شده در رویداد keypress و بررسی آن با توجه به وضعیت پراپرتی shift این رویداد است. بدین ترتیب که اگر کد کاراکتر مربوط به حروف بزرگ بوده درحالیکه کلید شیفت نگه داشته نشده است بنابراین کلید Caps Lock روشن است و بالعکس. البته با ترکیب این روش و نیز رصد scan code کلید Caps Lock (کد آن برابر 20 است) در رویداد keydown میتوان وضعیت بهتری پدید آورد. قطعه کد زیر برای اینکار است. در این کد از یک متغیر گلوبال برای نگهداری وضعیت دکمه caps lock استفاده میشود.
<html>
<body>
  <input id="inputText" type="text" />
  <div id="divCapsLock" style="color: Red; display: none;">Caps Lock is ON!</div>
  <script>
    var capsLock = null;
    document.onkeypress = keyPress;
    document.onkeydown = keyDown;
    function keyDown(e) {
      e = e || event;
      capsLock = (e.keyCode == 20 && capsLock !== null) ? !capsLock : capsLock;
      document.getElementById('divCapsLock').style.display = capsLock ? 'block' : 'none';
    }
    function keyPress(e) {
      if (capsLock != null) return;
      e = e || window.event;
      var charCode = e.charCode || e.keyCode;
      capsLock = (charCode >= 97 && charCode <= 122 && e.shiftKey) || (charCode >= 65 && charCode <= 90 && !e.shiftKey);
      document.getElementById('divCapsLock').style.display = capsLock ? 'block' : 'none';
    }
  </script>
</body>
</html>
در متد رویداد kepress تکست باکس ابتدا بررسی میشود که آیا قبلا متغیر گلوبال capsLock مقداری غیرنال دارد. اگر مقدار غیرنال داشته باشد دیگر نیازی نیست تا کد کاراکتر کلید بررسی شود زیرا وضعیت جاری کلید معلوم است. بنابراین در ادامه کار صرفه جویی میشود. دلیل استفاده از رویدادهای سطح docment به جای خود تکست باکس این است تا از کوچکترین فرصتها! برای تعیین وضعیت جاری کلید caps lock استفاده شود. یعنی بتوان پس از فشرده شدن اولین کلید کاراکتری بدون توجه به موقعیت فوکس در صفحه، این وضعیت را از حالت نامشخص خارج کرد.
نکته: در سلسله مراتب رویدادهای اجزای یک document همیشه ابتدا رویدادهای مربوط به عناصر فرزند فراخوانده میشود و رویدادهای مربوط به عناصر document و window در پایان صدا زده میشوند. برای آزمودن این مورد قطعه کد زیر را در مرورگرهای مختلف امتحان کنید:
<html>
<body>
  <div id='divInput'>
    <input id="inputText" type="text" />
  </div>
  <script>
    document.getElementById('inputText').onkeydown = inputKeyDown;
    document.getElementById('divInput').onkeydown = divKeyDown;
    document.onkeydown = documentKeyDown;
    window.onkeydown = windowKeyDown;
    function divKeyDown(e) {
      console.log('divKeyDown');
    }
    function inputKeyDown(e) {
      console.log('inputKeyDown');
    }
    function documentKeyDown(e) {
      console.log('documentKeyDown');
    }
    function windowKeyDown(e) {
      console.log('windowKeyDown');
    }
  </script>
</body>
</html>
نکته: اگر از تگ doctype استفاده نشود (همانطور که در ابتدای این مطلب اشاره شد)، در IE رویدادهای عنصر window فراخوانی نمیشوند.
درهرصورت برای مشخص کردن وضعیت کلید caps lock نیاز است تا کاربر ابتدا کلیدی را بفشارد و در حال حاضر روش و راه حل دیگری وجود ندارد! درضمن اگر صفحه کلیدی غیر از انگلیسی استفاده شود روش فوق جواب نخواهد داد و باید بررسی‌های بیشتری انجام شود. البته در مورد زیانهایی چون فارسی که روشن یا خاموش بودن caps lock تاثیری در کاراکتر حاصله ندارد کاری نمیتوان کرد و نمیتوان از وضعیت caps lock باخبر شد! هرچند در این موارد معمولا وضعیت این دکمه مهم نیست زیرا بیشترین کاربرد این گونه هشدارها در ورودی‌های رمز عبور است در صورتیکه زبان صفحه کلید انگلیسی (یا مشابه آن) باشد. برای اینکار کد زیر به عنوان راه حل بهتر پیشنهاد میشود:
<html>
<body>
  <input id="inputText" type="text" />
  <div id="divCapsLock" style="color: Red; display: none;">Caps Lock is ON!</div>
  <script>
    var capsLock = null;
    var hasFocus = false;
    document.onkeyup = keyUp;
    document.onkeypress = keyPress;
    document.getElementById('inputText').onfocus = focus;
    document.getElementById('inputText').onblur = focus;
    function warnCapsLock() {
      document.getElementById('divCapsLock').style.display = (capsLock != null && capsLock && hasFocus) ? 'block' : 'none';
    }
    function focus() {
      hasFocus = !hasFocus;
      warnCapsLock();
    }
    function keyUp(e) {
      e = e || event;
      capsLock = (e.keyCode == 20 && capsLock !== null) ? !capsLock : capsLock;
      warnCapsLock();
    }
    function keyPress(e) {
      if (capsLock != null) return;
      e = e || window.event;
      var charCode = e.charCode || e.keyCode;
      capsLock = (charCode >= 97 && charCode <= 122 && e.shiftKey) || (charCode >= 65 && charCode <= 90 && !e.shiftKey);
      warnCapsLock();
    }
  </script>
</body>
</html>
اگر دقت کنین میبیند که رویداد keydown در این کد نهایی با keyup جایگزین شده است. درصورت استفاده از رویداد keypress یک مشکل کوچک بوجود می‌آید و آن این است که اگر کاربر کلید caps lock را فشرده و سپس آن را در حالت فشرده نگه دارد اتفاق بدی خواهد افتاد! در این حال چون رویداد keydown مرتبا فراخوانده میشود وضعیت متغیر capsLock در این کد کاملا نامعتبر خواهد بود. بنابراین بهتر است تا از رویداد keyup که تنها یکبار فراخوانده میشود استفاده شود.
نکته: اگر کاربر پس از مشخص شدن وضعیت کلید Capa Lock در این کد، فوکس را به پنجره دیگری تغییر داده و سپس وضعیت این دکمه در آنجا تغییر دهد این کد دیگر درست کار نخواهد کرد. برای حل این مشکل هم راه حل زیر وجود دارد:
window.onblur = function () { capsLock = null; }
با این کار وضعیت متغیر capsLock ریست میشود.
نکته: در آزمایش این کد دقت کنید که زبان کیبرد حتما انگلیسی باشد.

مدیریت کلیدها
برای مدیریت کلیدهای کیبرد راههای متنوعی وجود دارد. مثلا میتوان از قطعه کد زیر استفاده کرد:
var Client = {};
Client.Keyboard = {};
Client.Keyboard.EnableKeyDown = true;
Client.Keyboard.EnableKeyUp = false;
Client.Keyboard.CurrentKeyEvent = null;

window.onkeydown = function (event) {
  if (!Client.Keyboard.EnableKeyDown) return true;
  return KeyboardEvents(event);
};

window.onkeyup = function (event) {
  if (!Client.Keyboard.EnableKeyUp) return true;
  return KeyboardEvents(event);
};

function Rise(event) {
  var e = Client.Keyboard.CurrentKeyEvent;
  if (event) {
    var data = { shift: e.shiftKey, ctrl: e.ctrlKey, alt: e.altKey };
    if (!event(data)) return false;
    return event(data);
  }
  return true;
}

function KeyboardEvents(e) {
  e = e || window.event;
  Client.Keyboard.CurrentKeyEvent = e;
  switch (e.keyCode) {
    case 8:
      return Rise(Client.Keyboard.Backspace);
    case 9:
      return Rise(Client.Keyboard.Tab);
    case 13:
      return Rise(Client.Keyboard.Enter);
    case 16:
      return Rise(Client.Keyboard.Shift);
    case 17:
      return Rise(Client.Keyboard.Ctrl);
    case 18:
      return Rise(Client.Keyboard.Alt);
    case 19:
      return Rise(Client.Keyboard.Pause);
    case 20:
      return Rise(Client.Keyboard.CapsLock);
    case 27:
      return Rise(Client.Keyboard.Esc);
    case 33:
      return Rise(Client.Keyboard.PageUp);
    case 34:
      return Rise(Client.Keyboard.PageDown);
    case 35:
      return Rise(Client.Keyboard.End);
    case 36:
      return Rise(Client.Keyboard.Home);
    case 37:
      return Rise(Client.Keyboard.Left);
    case 38:
      return Rise(Client.Keyboard.Up);
    case 39:
      return Rise(Client.Keyboard.Right);
    case 40:
      return Rise(Client.Keyboard.Down);
    case 44:
      return Rise(Client.Keyboard.PrtScr);
    case 45:
      return Rise(Client.Keyboard.Insert);
    case 46:
      return Rise(Client.Keyboard.Delete);
      //////////////////////////////////////////////////////////////////////////////////////////////////
    case 48:
      return Rise(Client.Keyboard.Num0);
    case 49:
      return Rise(Client.Keyboard.Num1);
    case 50:
      return Rise(Client.Keyboard.Num2);
    case 51:
      return Rise(Client.Keyboard.Num3);
    case 52:
      return Rise(Client.Keyboard.Num4);
    case 53:
      return Rise(Client.Keyboard.Num5);
    case 54:
      return Rise(Client.Keyboard.Num6);
    case 55:
      return Rise(Client.Keyboard.Num7);
    case 56:
      return Rise(Client.Keyboard.Num8);
    case 57:
      return Rise(Client.Keyboard.Num9);
      //////////////////////////////////////////////////////////////////////////////////////////////////
    case 65:
      return Rise(Client.Keyboard.A);
    case 66:
      return Rise(Client.Keyboard.B);
    case 67:
      return Rise(Client.Keyboard.C);
    case 68:
      return Rise(Client.Keyboard.D);
    case 69:
      return Rise(Client.Keyboard.E);
    case 70:
      return Rise(Client.Keyboard.F);
    case 71:
      return Rise(Client.Keyboard.G);
    case 72:
      return Rise(Client.Keyboard.H);
    case 73:
      return Rise(Client.Keyboard.I);
    case 74:
      return Rise(Client.Keyboard.J);
    case 75:
      return Rise(Client.Keyboard.K);
    case 76:
      return Rise(Client.Keyboard.L);
    case 77:
      return Rise(Client.Keyboard.M);
    case 78:
      return Rise(Client.Keyboard.N);
    case 79:
      return Rise(Client.Keyboard.O);
    case 80:
      return Rise(Client.Keyboard.P);
    case 81:
      return Rise(Client.Keyboard.Q);
    case 82:
      return Rise(Client.Keyboard.R);
    case 83:
      return Rise(Client.Keyboard.S);
    case 84:
      return Rise(Client.Keyboard.T);
    case 85:
      return Rise(Client.Keyboard.U);
    case 86:
      return Rise(Client.Keyboard.V);
    case 87:
      return Rise(Client.Keyboard.W);
    case 88:
      return Rise(Client.Keyboard.X);
    case 89:
      return Rise(Client.Keyboard.Y);
    case 90:
      return Rise(Client.Keyboard.Z);
      ////////////////////////////////////////////////////////////////////////////
    case 91:
      //case 219: // opera
      return Rise(Client.Keyboard.LeftWindow);
    case 92:
      return Rise(Client.Keyboard.RightWindow);
    case 93:
      return Rise(Client.Keyboard.ContextMenu);
      ////////////////////////////////////////////////////////////////////////////
    case 96:
      return Rise(Client.Keyboard.NumPad0);
    case 97:
      return Rise(Client.Keyboard.NumPad1);
    case 98:
      return Rise(Client.Keyboard.NumPad2);
    case 99:
      return Rise(Client.Keyboard.NumPad3);
    case 100:
      return Rise(Client.Keyboard.NumPad4);
    case 101:
      return Rise(Client.Keyboard.NumPad5);
    case 102:
      return Rise(Client.Keyboard.NumPad6);
    case 103:
      return Rise(Client.Keyboard.NumPad7);
    case 104:
      return Rise(Client.Keyboard.NumPad8);
    case 105:
      return Rise(Client.Keyboard.NumPad9);
      ////////////////////////////////////////////////////////////////////////////
    case 106:
      return Rise(Client.Keyboard.Multiply);
    case 107:
      return Rise(Client.Keyboard.Add);
    case 109:
      return Rise(Client.Keyboard.Subtract);
    case 110:
      return Rise(Client.Keyboard.DecimalPoint);
    case 111:
      return Rise(Client.Keyboard.Divide);
      ////////////////////////////////////////////////////////////////////////////
    case 112:
      return Rise(Client.Keyboard.F1);
    case 113:
      return Rise(Client.Keyboard.F2);
    case 114:
      return Rise(Client.Keyboard.F3);
    case 115:
      return Rise(Client.Keyboard.F4);
    case 116:
      return Rise(Client.Keyboard.F5);
    case 117:
      return Rise(Client.Keyboard.F6);
    case 118:
      return Rise(Client.Keyboard.F7);
    case 119:
      return Rise(Client.Keyboard.F8);
    case 120:
      return Rise(Client.Keyboard.F9);
    case 121:
      return Rise(Client.Keyboard.F10);
    case 122:
      return Rise(Client.Keyboard.F11);
    case 123:
      return Rise(Client.Keyboard.F12);
      ////////////////////////////////////////////////////////////////////////////
    case 144:
      return Rise(Client.Keyboard.NumLock);
    case 145:
      return Rise(Client.Keyboard.ScrollLock);
    case 186:
    case 59: // opera & firefox
      return Rise(Client.Keyboard.SemiColon);
    case 187:
    case 61: // opera
      //case 107: //firefox
      return Rise(Client.Keyboard.Equal);
    case 188:
      return Rise(Client.Keyboard.Camma);
    case 189:
      return Rise(Client.Keyboard.Dash);
    case 190:
      return Rise(Client.Keyboard.Period);
    case 191:
      return Rise(Client.Keyboard.Slash);
    case 192:
      return Rise(Client.Keyboard.GraveAccent);
    case 219:
      return Rise(Client.Keyboard.OpenBracket);
    case 220:
      return Rise(Client.Keyboard.BackSlash);
    case 221:
      return Rise(Client.Keyboard.CloseBracket);
    case 222:
      return Rise(Client.Keyboard.SingleQuote);
  }
};
نحوه استفاده از این کد به صورت زیر است:
  // ctrl + s
  Client.Keyboard.S = function (e) {
    if (e.ctrl) {
      // انجام عملیات موردنظر
    }
    return true;
  }
اگر در متد الصاق شده به پراپرتیهای Client.Keyboard هیچ مقداری برگشت داده نشود، باتوجه به کد موجود در متد Rise، عملیات پیشفرض کلید مربوطه در پنجره مرورگر غیرفعال خواهد شد. بنابراین در کد بالا بعد از شرط، مقدار true برگشت داده شده است.
هرچند سعی کردم که تفاوت میان مرورگرهای مختلف را درنظر بگیرم ولی احتمال اینکه برخی از کدها به دلیل تفاوت میان مرورگرهای مختلف در کد بالا آورده نشده باشد وجود دارد!
نکته: کد فوق بیشتر برای روشن‌تر شدن موضوع ارائه شده است چون راه حل‌های بهتری هم برای مدیریت کلیدهای کیبرد وجود دارد. مثل استفاده از کتابخانه های Mousetrap یا HotKey یا jQuery Hotkeys یا KeyboardJS. البته در این میان بهترین کتابخانه موجود به نظر من همان Mousetrap است که تنها کتابخانه موجود است که علاوه بر پشتیبانی از ترکیب کلیدها با کلیدهای کنترلی، از توالی کلیدها (keys sequence) نیز پشتیبانی میکند که کار جالبی است.
در پایان این نکته را یادآور میشوم که امروزه توسعه اپلیکیشنهای تحت وب بدون استفاده مناسب از امکانات سمت کلاینت طرفداری ندارد. پس بهتر است هرچه بیشتر در مورد این زبان مرموز و پر از رمز و راز (JavaScript) بدانیم.
مطالب
افزودن ASP.NET Identity به یک پروژه Web Forms
  • با نصب و اجرای Visual Studio 2013 Express for Web یا Visual Studio 2013 شروع کنید.
  • یک پروژه جدید بسازید (از صفحه شروع یا منوی فایل)
  • گزینه #Visual C و سپس ASP.NET Web Application را انتخاب کنید. نام پروژه را به "WebFormsIdentity" تغییر داده و OK کنید.

  • در دیالوگ جدید ASP.NET گزینه Empty را انتخاب کنید.

دقت کنید که دکمه Change Authentication غیرفعال است و هیچ پشتیبانی ای برای احراز هویت در این قالب پروژه وجود ندارد.


افزودن پکیج‌های ASP.NET Identity به پروژه

روی نام پروژه کلیک راست کنید و گزینه Manage NuGet Packages را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده عبارت "Identity.E" را وارد کرده و  این پکیج را نصب کنید.

دقت کنید که نصب کردن این پکیج وابستگی‌ها را نیز بصورت خودکار نصب می‌کند: Entity Framework و ASP.NET Idenity Core.


افزودن فرم‌های وب لازم برای ثبت نام کاربران

یک فرم وب جدید بسازید.

در دیالوگ باز شده نام فرم را به Register تغییر داده و تایید کنید.

فایل ایجاد شده جدید را باز کرده و کد Markup آن را با قطعه کد زیر جایگزین کنید.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Register.aspx.cs" Inherits="WebFormsIdentity.Register" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body style="
    <form id="form1" runat="server">
    <div>
        <h4 style="Register a new user</h4>
        <hr />
        <p>
            <asp:Literal runat="server" ID="StatusMessage" />
        </p>                
        <div style="margin-bottom:10px">
            <asp:Label runat="server" AssociatedControlID="UserName">User name</asp:Label>
            <div>
                <asp:TextBox runat="server" ID="UserName" />                
            </div>
        </div>
        <div style="margin-bottom:10px">
            <asp:Label runat="server" AssociatedControlID="Password">Password</asp:Label>
            <div>
                <asp:TextBox runat="server" ID="Password" TextMode="Password" />                
            </div>
        </div>
        <div style="margin-bottom:10px">
            <asp:Label runat="server" AssociatedControlID="ConfirmPassword">Confirm password</asp:Label>
            <div>
                <asp:TextBox runat="server" ID="ConfirmPassword" TextMode="Password" />                
            </div>
        </div>
        <div>
            <div>
                <asp:Button runat="server" OnClick="CreateUser_Click" Text="Register" />
            </div>
        </div>
    </div>
    </form>
</body>
</html>

این تنها یک نسخه ساده شده Register.aspx است که از چند فیلد فرم و دکمه ای برای ارسال آنها به سرور استفاده می‌کند.

فایل کد این فرم را باز کرده و محتویات آن را با قطعه کد زیر جایگزین کنید.

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using System;
using System.Linq;

namespace WebFormsIdentity
{
   public partial class Register : System.Web.UI.Page
   {
      protected void CreateUser_Click(object sender, EventArgs e)
      {
         // Default UserStore constructor uses the default connection string named: DefaultConnection
         var userStore = new UserStore<IdentityUser>();
         var manager = new UserManager<IdentityUser>(userStore);

         var user = new IdentityUser() { UserName = UserName.Text };
         IdentityResult result = manager.Create(user, Password.Text);

         if (result.Succeeded)
         {
            StatusMessage.Text = string.Format("User {0} was created successfully!", user.UserName);
         }
         else
         {
            StatusMessage.Text = result.Errors.FirstOrDefault();
         }
      }
   }
}

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

کلاس IdentityUser پیاده سازی پیش فرض EntityFramework از قرارداد IUser است. قرارداد IUser تعریفات حداقلی یک کاربر در ASP.NET Identity Core را در بر می‌گیرد.

کلاس UserStore پیاده سازی پیش فرض EF از یک فروشگاه کاربر (user store) است. این کلاس چند قرارداد اساسی ASP.NET Identity Core را پیاده سازی می‌کند: IUserStore, IUserLoginStore, IUserClaimStore و IUserRoleStore.

کلاس UserManager دسترسی به API‌های مربوط به کاربران را فراهم می‌کند. این کلاس تمامی تغییرات را بصورت خودکار در UserStore ذخیره می‌کند.

کلاس IdentityResult نتیجه یک عملیات هویتی را معرفی می‌کند (identity operations).

پوشه App_Data را به پروژه خود اضافه کنید.

فایل Web.config پروژه را باز کنید و رشته اتصال جدیدی برای دیتابیس اطلاعات کاربران اضافه کنید. این دیتابیس در زمان اجرا (runtime) بصورت خودکار توسط EF ساخته می‌شود. این رشته اتصال شبیه به رشته اتصالی است که هنگام ایجاد پروژه بصورت خودکار برای شما تنظیم می‌شود.

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
   <connectionStrings>
      <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\WebFormsIdentity.mdf;Initial Catalog=WebFormsIdentity;Integrated Security=True"
            providerName="System.Data.SqlClient" />
   </connectionStrings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="v11.0" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
</configuration>

همانطور که مشاهده می‌کنید نام این رشته اتصال DefaultConnection است.

روی فایل Register.aspx کلیک راست کنید و گزینه Set As Start Page را انتخاب کنید. اپلیکیشن خود را با کلیدهای ترکیبی Ctrl + F5 اجرا کنید که تمام پروژه را کامپایل نیز خواهد کرد. یک نام کاربری و کلمه عبور وارد کنید و روی Register کلیک کنید.

ASP.NET Identity از اعتبارسنجی نیز پشتیبانی می‌کند، مثلا در این مرحله می‌توانید از اعتبارسنج هایی که توسط ASP.NET Identity Core عرضه می‌شوند برای کنترل رفتار فیلد‌های نام کاربری و کلمه عبور استفاده کنید. اعتبارسنج پیش فرض کاربران (User) که UserValidator نام دارد خاصیتی با نام AllowOnlyAlphanumericUserNames دارد که مقدار پیش فرضش هم true است. اعتبارسنج پیش فرض کلمه عبور (MinimumLengthValidator) اطمینان حاصل می‌کند که کلمه عبور حداقل 6 کاراکتر باشد. این اعتبارسنج‌ها بصورت property‌ها در کلاس UserManager تعریف شده اند و می‌توانید آنها را overwrite کنید و اعتبارسنجی سفارشی خود را پیاده کنید. از آنجا که الگوی دیتابیس سیستم عضویت توسط Entity Framework مدیریت می‌شود، روی الگوی دیتابیس کنترل کامل دارید، پس از Data Annotations نیز می‌توانید استفاده کنید.


تایید دیتابیس LocalDbIdentity که توسط EF ساخته می‌شود

از منوی View گزینه Server Explorer را انتخاب کنید.

گره (DefaultConnection (WebFormsIdentity و سپس Tables را باز کنید. روی جدول AspNetUsers کلیک راست کرده و Show Table Data را انتخاب کنید.


پیکربندی اپلیکیشن برای استفاده از احراز هویت OWIN

تا این مرحله ما تنها امکان ایجاد حساب‌های کاربری را فراهم کرده ایم. حال نیاز داریم امکان احراز هویت کاربران برای ورود آنها به سایت را فراهم کنیم. ASP.NET Identity برای احراز هویت مبتنی بر فرم (forms authentication) از OWIN Authentication استفاده می‌کند. OWIN Cookie Authentication مکانیزمی برای احراز هویت کاربران بر اساس cookie‌ها و claim‌ها است (claims-based). این مکانیزم می‌تواند توسط Entity Framework روی OWIN یا IIS استفاده شود.
با چنین مدلی، می‌توانیم از پکیج‌های احراز هویت خود در فریم ورک‌های مختلفی استفاده کنیم، مانند ASP.NET MVC و ASP.NET Web Forms. برای اطلاعات بیشتر درباره پروژه Katana و نحوه اجرای آن بصورت Host Agnostic به لینک Getting Started with the Katana Project مراجعه کنید.


نصب پکیج‌های احراز هویت روی پروژه

روی نام پروژه خود کلیک راست کرده و Manage NuGet Packages را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده عبارت "Identity.Owin" را وارد کنید و این پکیج را نصب کنید.

به دنبال پکیجی با نام Microsoft.Owin.Host.SystemWeb بگردید و آن را نیز نصب کنید.

پکیج Microsoft.Aspnet.Identity.Owin حاوی یک سری کلاس Owin Extension است و امکان مدیریت و پیکربندی OWIN Authentication در پکیج‌های ASP.NET Identity Core را فراهم می‌کند.

پکیج Microsoft.Owin.Host.SystemWeb حاوی یک سرور OWIN است که اجرای اپلیکیشن‌های مبتنی بر OWIN را روی IIS و با استفاده از ASP.NET Request Pipeline ممکن می‌سازد. برای اطلاعات بیشتر به OWIN Middleware in the IIS integrated pipeline مراجعه کنید.


افزودن کلاس‌های پیکربندی Startup و Authentication

روی پروژه خود کلیک راست کرده و گزینه Add و سپس Add New Item را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده عبارت "owin" را وارد کنید. نام کلاس را "Startup" تعیین کرده و تایید کنید.

فایل Startup.cs را باز کنید و قطعه کد زیر را با محتویات آن جایگزین کنید تا احراز هویت OWIN Cookie Authentication پیکربندی شود.

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;

[assembly: OwinStartup(typeof(WebFormsIdentity.Startup))]

namespace WebFormsIdentity
{
   public class Startup
   {
      public void Configuration(IAppBuilder app)
      {
         // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
         app.UseCookieAuthentication(new CookieAuthenticationOptions
         {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Login")
         });
      }
   }
}

این کلاس حاوی خاصیت OwinAttribute است که کلاس راه انداز OWIN را نشانه گذاری می‌کند. هر اپلیکیشن OWIN یک کلاس راه انداز (startup) دارد که توسط آن می‌توانید کامپوننت‌های application pipeline را مشخص کنید. برای اطلاعات بیشتر درباره این مدل، به OWIN Startup Class Detection مراجعه فرمایید.


افزودن فرم‌های وب برای ثبت نام و ورود کاربران

فایل Register.cs را باز کنید و قطعه کد زیر را وارد کنید. این قسمت پس از ثبت نام موفقیت آمیز کاربر را به سایت وارد می‌کند.
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Owin.Security;
using System;
using System.Linq;
using System.Web;

namespace WebFormsIdentity
{
   public partial class Register : System.Web.UI.Page
   {
      protected void CreateUser_Click(object sender, EventArgs e)
      {
         // Default UserStore constructor uses the default connection string named: DefaultConnection
         var userStore = new UserStore<IdentityUser>();
         var manager = new UserManager<IdentityUser>(userStore);
         var user = new IdentityUser() { UserName = UserName.Text };

         IdentityResult result = manager.Create(user, Password.Text);

         if (result.Succeeded)
         {
            var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
            var userIdentity = manager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
            authenticationManager.SignIn(new AuthenticationProperties() { }, userIdentity);
            Response.Redirect("~/Login.aspx");
         }
         else
         {
            StatusMessage.Text = result.Errors.FirstOrDefault();
         }
      }
   }
}
از آنجا که ASP.NET Identity و OWIN Cookie Authentication هر دو مبتنی بر Claims هستند، فریم ورک از برنامه نویس اپلیکیشن انتظار دارد تا برای کاربر یک آبجکت از نوع ClaimsIdentity تولید کند. این آبجکت تمام اطلاعات اختیارات کاربر را در بر می‌گیرد، مثلا اینکه کاربر به چه نقش هایی تعلق دارد. همچنین در این مرحله می‌توانید اختیارات (Claims) جدیدی به کاربر اضافه کنید.
شما با استفاده از AuthenticationManager که متعلق به OWIN است می‌توانید کاربر را به سایت وارد کنید. برای این کار شما متد SignIn را فراخوانی می‌کنید و آبجکتی از نوع ClaimsIdentity را به آن پاس می‌دهید. این کد کاربر را به سایت وارد می‌کند و یک کوکی برای او می‌سازد. این فراخوانی معادل همان FormAuthentication.SetAuthCookie است که توسط ماژول FormsAuthentication استفاده می‌شود.
روی پروژه خود کلیک راست کرده، فرم وب جدیدی با نام Login بسازید.

فایل Login.aspx را باز کنید و کد Markup آن را مانند قطعه کد زیر تغییر دهید.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="WebFormsIdentity.Login" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
   <title></title>
</head>
<body style="font-family: Arial, Helvetica, sans-serif; font-size: small">
   <form id="form1" runat="server">
      <div>
         <h4 style="font-size: medium">Log In</h4>
         <hr />
         <asp:PlaceHolder runat="server" ID="LoginStatus" Visible="false">
            <p>
               <asp:Literal runat="server" ID="StatusText" />
            </p>
         </asp:PlaceHolder>
         <asp:PlaceHolder runat="server" ID="LoginForm" Visible="false">
            <div style="margin-bottom: 10px">
               <asp:Label runat="server" AssociatedControlID="UserName">User name</asp:Label>
               <div>
                  <asp:TextBox runat="server" ID="UserName" />
               </div>
            </div>
            <div style="margin-bottom: 10px">
               <asp:Label runat="server" AssociatedControlID="Password">Password</asp:Label>
               <div>
                  <asp:TextBox runat="server" ID="Password" TextMode="Password" />
               </div>
            </div>
            <div style="margin-bottom: 10px">
               <div>
                  <asp:Button runat="server" OnClick="SignIn" Text="Log in" />
               </div>
            </div>
         </asp:PlaceHolder>
         <asp:PlaceHolder runat="server" ID="LogoutButton" Visible="false">
            <div>
               <div>
                  <asp:Button runat="server" OnClick="SignOut" Text="Log out" />
               </div>
            </div>
         </asp:PlaceHolder>
      </div>
   </form>
</body>
</html>

محتوای فایل Login.aspx.cs را نیز مانند لیست زیر تغییر دهید.

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Owin.Security;
using System;
using System.Web;
using System.Web.UI.WebControls;

namespace WebFormsIdentity
{
   public partial class Login : System.Web.UI.Page
   {
      protected void Page_Load(object sender, EventArgs e)
      {
         if (!IsPostBack)
         {
            if (User.Identity.IsAuthenticated)
            {
               StatusText.Text = string.Format("Hello {0}!", User.Identity.GetUserName());
               LoginStatus.Visible = true;
               LogoutButton.Visible = true;
            }
            else
            {
               LoginForm.Visible = true;
            }
         }
      }

      protected void SignIn(object sender, EventArgs e)
      {
         var userStore = new UserStore<IdentityUser>();
         var userManager = new UserManager<IdentityUser>(userStore);
         var user = userManager.Find(UserName.Text, Password.Text);

         if (user != null)
         {
            var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
            var userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);

            authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, userIdentity);
            Response.Redirect("~/Login.aspx");
         }
         else
         {
            StatusText.Text = "Invalid username or password.";
            LoginStatus.Visible = true;
         }
      }

      protected void SignOut(object sender, EventArgs e)
      {
         var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
         authenticationManager.SignOut();
         Response.Redirect("~/Login.aspx");
      }
   }
}
  • متد Page_Load حالا وضعیت کاربر جاری را بررسی می‌کند و بر اساس وضعیت Context.User.Identity.IsAuthenticated تصمیم گیری می‌کند. 
نمایش نام کاربر جاری: فریم ورک ASP.NET Identity روی  System.Security.Principal.Identity  متدهایی نوشته است که به شما امکان دریافت نام و شناسه کاربر جاری را می‌دهد. این متدها در اسمبلی Microsoft.AspNet.Identity.Core وجود دارند. این متدها جایگزین  HttpContext.User.Identity.Name  هستند.
  • متد SignIn
این متد، متد CreateUser_Click را که پیشتر بصورت خودکار ایجاد شده جایگزین می‌کند و پس از ایجاد موفقیت آمیز حساب کاربری، کاربر جاری را به سایت وارد می‌کند. فریم ورک OWIN متدهایی روی System.Web.HttpContext افزوده است که به شما این امکان را می‌دهند که یک ارجاع از نوع IOwinContext بگیرید. این متد‌ها در اسمبلی Microsoft.Owin.Host.SystemWeb وجود دارند. کلاس OwinContext خاصیتی از نوع IAuthenticationManager دارد که امکانات احراز هویت موجود برای درخواست جاری را معرفی می‌کند.
  • پروژه را با Ctrl + F5 اجرا کنید و کاربر جدیدی بسازید. پس از وارد کردن نام کاربری و کلمه عبور و کلیک کردن دکمه Register باید بصورت خودکار به سایت وارد شوید و نام خود را مشاهده کنید.

  • همانطور که مشاهده می‌کنید در این مرحله حساب کاربری جدید ایجاد شده و به سایت وارد شده اید. روی Log out کلیک کنید تا از سایت خارج شوید. پس از آن باید به صفحه ورود هدایت شوید.
  • حالا یک نام کاربری یا کلمه عبور نامعتبر وارد کنید و روی Log in کلیک کنید. 
متد UserManager.Find مقدار null بر می‌گرداند، بنابراین پیام خطای "Invalid username or password" نمایش داده خواهد شد.

اشتراک‌ها
بررسی پیشنهادهای C# 7 بخش دوم
public int Calculate(int someInput)
{
    int Factorial(int i)
    {
        if (i <= 1)
            return 1;
        return i * Factorial(i - 1);
    }
    var input = someInput + ... // Other calcs
 
    return Factorial(input);
}
بررسی پیشنهادهای C# 7 بخش دوم
نظرات مطالب
ASP.NET MVC #13
public class MyViewModel
{
    [Required]
    public string CategoryId { get; set; }

    public IEnumerable<Category> Categories { get; set; }
}

Controller:

public ActionResult Index()
{
   var model = new MyViewModel
   {
       Categories = GetCategories()
   }
   return View(model);
}

View:

@Html.DropDownListFor(
    x => x.CategoryId, 
    new SelectList(Model.Categories, "ID", "CategoryName"), 
    "-- Please select a category --"
)
@Html.ValidationMessageFor(x => x.CategoryId)
مطالب
شروع به کار با DNTFrameworkCore - قسمت 4 - پیاده‌سازی CRUD API موجودیت‌ها
پس از معرفی DNTFrameworkCore، طراحی موجودیت‌های سیستم و پیاده‌سازی DTOها، اعتبارسنج‌ها و سرویس‌های متناظر آنها، در این مطلب روش پیاده سازی CRUD API یکسری موجودیت فرضی را با استفاده از امکانات این زیرساخت بررسی خواهیم کرد.

برای شروع لازم است بسته نیوگت زیر را نصب کنید:
PM> Install-Package DNTFrameworkCore.Web

همچنین برای اعمال خودکار مهاجرت‌های بانک اطلاعاتی، بسته نیوگت زیر را نصب کنید:
PM> Install-Package DNTFrameworkCore.Web.EntityFramework

سپس با استفاده از متد الحاقی MigrateDbContext به شکل زیر می‌توان فرآیند مذکور را خودکار کرد:
public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build()
            .MigrateDbContext<ProjectDbContext>()
            .Run();
    }

//...
}

مثال اول: پیاده سازی CRUD API یک موجودیت ساده
[Route("api/[controller]")]
public class BlogsController : CrudController<IBlogService, int, BlogModel>
{
    public BlogsController(IBlogService service) : base(service)
    {
    }

    protected override string CreatePermissionName => PermissionNames.Blogs_Create;
    protected override string EditPermissionName => PermissionNames.Blogs_Edit;
    protected override string ViewPermissionName => PermissionNames.Blogs_View;
    protected override string DeletePermissionName => PermissionNames.Blogs_Delete;
}
کار با ارث‌بری از CrudController جنریک شروع می‌شود؛ سپس نیاز است نوع سرویس، نوع مدل و همچنین نوع شناسه مرتبط با موجودیت مورد‎‌نظر را از طریق Type Parameter مشخص کنید. این کنترلر پایه تعاریف مختلفی دارد که برحسب نیاز خود می‌توانید از آنها استفاده کنید. در ادامه نیز برای اعمال دسترسی خاصی برای عملیات CRUD، نیاز است نام دسترسی‌ها را مشخص کنید. کار تمام است؛ برای استفاده از آن می‌توانید با اجرای پروژه DNTFrameworkCore.TestAPI به شکل زیر عمل کنید:
ابتدا نیاز است با استفاده از افزونه‌ی Postman یک Environment جدید ایجاد کنید.

در اینجا دو متغیر endpoint و token ‌برای سرعت بخشیدن به فرآیند تست API تولیدی ایجاد شده‌اند. مقدار endpoint آن از ابتدا مشخص می‌باشد؛ ولی برای مقداردهی token، از ترفند زیر می‌توان استفاده کرد:

در برگه Tests آن می‌توان متغیر تعریف شده در Environment ایجاد شده را با قطعه کد زیر مقداردهی کرد:
 var data = JSON.parse(responseBody)
pm.environment.set("token", data.token);
و برای استفاده از این متغیر به شکل زیر عمل کنید:

حال برای ارسال درخواست‌های HTTP به BlogsController به شکل زیر عمل کنید:

پشت صحنه اکشن GET مربوط به BlogsController از متد سرویس ReadPagedListAsync استفاده می‌شود که خروجی آن در صورت مشخص نکردن TReadModel، برای یک موجودیت ساده مانند واحد سنجش، از همان TModel استفاده خواهد شد. در تصویر بالا لیست صفحه بندی شده موجودیت Blog را مشاهده می‌کنید. برای درخواست صفحه دیگر و جستجوی پویا می‌توان به شکل زیر عمل کرد:
query={"page":1,"pageSize":100,"filter":{"logic":"and","filters":[{"field":"title","value":"Blog1","operator":"startswith"}]}}
همانطور که در مطالب گذشته اشاره شد، ورودی متدهای Read موجود در سرویس‌ها از نوع IFilteredPagedQueryModel می‌باشد. یک ModelBinder سفارشی هم برای بایند خودکار این کوئری استرینک با محتوای یک شیء JSON، در زیرساخت طراحی شده است.

در پشت صحنه اکشن POST از متد CreateAsync سرویس مرتبط استفاده می‌شود و همانطور که در قسمت قبلی عنوان شد، Id و RowVersion مدل ارسالی، مقداردهی خواهد شد. 

در پشت صحنه اکشن ‎‎‎‎‎‎GET/{id}‎ از متد FindAsync سرویس مرتبط استفاده می‌شود و خروجی آن از نوع TModel می‌باشد. 

در پشت صحنه اکشن PUT از متد EditAsync سرویس مرتبط استفاده می‌شود که ورودی آن نوع TModel می‌باشد. همانطور که قبلا اشاره شده بود و در خروجی حاصل از درخواست ویرایش بالا مشخص می‎‌باشد، مکانیزم مدیریت استثناهای حاصل از مباحث همزمانی نیز به درستی انجام شده است.
برای حذف یک Blog می‌توان با ارسال درخواست DELETE به آدرس زیر به این هدف رسید:
{{endpoint}}/blogs/10
در پشت صحنه این اکشن نیز از متد DeleteAsync سرویس مرتبط استفاده می‌شود.
‌‌‌
مثال دوم: پیاده سازی و استفاده از CRUD API در سناریوهای Master-Detail
[Route("api/[controller]")]
public class UsersController : CrudController<IUserService, long, UserReadModel, UserModel>
{
    private readonly ILookupService _lookupService;

    public UsersController(IUserService service, ILookupService lookupService) : base(service)
    {
        _lookupService = lookupService ?? throw new ArgumentNullException(nameof(lookupService));
    }

    protected override string CreatePermissionName => PermissionNames.Users_Create;
    protected override string EditPermissionName => PermissionNames.Users_Edit;
    protected override string ViewPermissionName => PermissionNames.Users_View;
    protected override string DeletePermissionName => PermissionNames.Users_Delete;

    [HttpGet("[action]")]
    [PermissionAuthorize(PermissionNames.Users_Create, PermissionNames.Users_Edit)]
    public async Task<IActionResult> RoleList()
    {
        var result = await _lookupService.ReadRolesAsync();
        return Ok(result);
    }
}
‌‌
موجودیت User از جمله موجودیت‌هایی می‌باشد که نیاز است ReadModel مجزایی برای آن درنظر گرفت؛ چرا که در زمان نمایش لیستی کاربران، نیاز به واکشی گروه‌های کاربری متصل و دسترسی‌های خاص آن، نمی‌باشد. همچنین اکشن متد RoleList برای دریافت لیست گروه‌های کاربری موجود در سیستم نیز پیاده‌سازی شده است. باتوجه به اینکه نیاز است از این اکشن متد در عملیات ثبت و ویرایش استفاده کرد، بر روی آن با استفاده از فیلتر سفارشی PermissionAuthorize، بررسی شده است که کاربر جاری یکی از دسترسی‌های Users_Create یا Users_Edit را داشته باشد.
‎‎‎‎‎
درخواست‌های GET و DELETE مشابه مثال اول می‌باشد؛ برای درخواست‌های POST و PUT آن می‌توان به شکل زیر عمل کرد:

باتوجه به اینکه UserRole به عنوان یکی از وابستگی‌های موجودیت User محسوب می‌شود، در پاسخ درخواست GET مرتبط با کاربری با شناسه 2، roles آن، لیستی از UserRoleModel هستند که به عنوان یک DetailModel طراحی شده است. به عنوان مثال برای حذف اتصال یک گروه کاربری باید درخواست PUT را به شکل زیر ارسال کنید:

این‌بار اگر برای درخواست GET کاربر با شناسه 2 اقدام کنیم، به خروجی زیر خواهم رسید:

{
    "userName": "rabbal",
    "displayName": "غلامرضا ربال",
    "password": null,
    "isActive": false,
    "roles": [],
    "permissions": [],
    "ignoredPermissions": [],
    "rowVersion": "AAAAAAACGxI=",
    "id": 2
}


برای بررسی بیشتر، پیشنهاد می‌کنم پروژه  DNTFrameworkCore.TestAPI  موجود در مخزن این زیرساخت را بازبینی کنید.  
مطالب
روش بهینه‌ی بررسی خالی بودن مجموعه‌ها و آرایه‌ها در NET 5.0.
پیشتر مطلب «Count یا Any » را در این سایت مطالعه کرده‌اید که در پایان آن این نتیجه گیری صورت گرفته‌است:
«از این پس حین استفاده از انواع و اقسام لیست‌ها، آرایه‌ها، IEnumerable‌ها و امثال آن‌ها، جهت بررسی خالی بودن یا نبودن آن‌ها تنها از متد Any فراهم شده توسط LINQ استفاده نمائید.»

اکنون پس از سال‌ها، قصد داریم صحت این مساله را با NET 5.0. بررسی کنیم که آیا هنوز هم متد Any، بهترین متد بررسی خالی بودن مجموعه‌ها و آرایه‌های NET 5.0. است یا خیر؟


نحوه‌ی بررسی کارآیی روش‌های مختلف خالی بودن مجموعه‌ها و آرایه‌ها در C# 9.0

در ابتدا یک لیست، یک Enumerable و یک آرایه را به صورت زیر مقدار دهی می‌کنیم و هر سه‌ی این‌ها می‌توانند نال هم باشند:
private IList<int>? _idsList;
private IEnumerable<int>? _idsEnumerable;
private int[]? _idsArray;

[GlobalSetup]
public void Setup()
{
    _idsEnumerable = Enumerable.Range(0, 10000);
    _idsList = _idsEnumerable.ToList();
    _idsArray = _idsEnumerable.ToArray();
}

اکنون که C# 9.0 در اختیار ما است به همراه pattern matching و همچنین Null Conditional Operator و غیره، می‌توان روش‌های زیر را برای بررسی خالی بودن این مجموعه‌ها و آرایه‌ها بکار گرفت:
1- استفاده از Null coalescing برای بررسی نال بودن مجموعه و سپس استفاده از متد Any برای بررسی خالی بودن آن:
var list = _idsList ?? new List<int>();
if (list.Any() is false) { }

2- استفاده از pattern matching برای بررسی نال بودن مجموعه و سپس استفاده از متد Any برای بررسی خالی بودن آن:
if (_idsList is null || _idsList.Any() is false) { }

3- استفاده از روش سنتی مقایسه‌ی مستقیم با null و سپس استفاده از متد Any برای بررسی خالی بودن آن:
if (_idsList == null || _idsList.Any() is false) { }

4- استفاده از Null Conditional Operator برای بررسی نال بودن و سپس استفاده از متد Any برای بررسی خالی بودن آن:
if (_idsList?.Any() is false) { }

5- استفاده از pattern matching برای بررسی مقدار خاصیت Count یک لیست یا آرایه. البته Enumerable‌ها به همراه این خاصیت نیستند و یا باید آن‌ها را به لیست و یا آرایه تبدیل کرد و یا می‌توان متد ()Count آن‌ها را فراخوانی نمود:
if (_idsList is { Count: > 0 } is false) { }

6- استفاده از Null Conditional Operator برای بررسی نال بودن و سپس استفاده از مقدار خاصیت Count لیست، برای بررسی خالی بودن آن:
if (_idsList?.Count == 0) { }

7- استفاده از روش سنتی مقایسه‌ی مستقیم با null و سپس استفاده از مقدار خاصیت Count لیست، برای بررسی خالی بودن آن:
if (_idsList == null || _idsList.Count == 0) { }


کدهای کامل این بررسی به صورت زیر هستند: AnyCountBenchmark.zip

ابتدا ارجاعی به BenchmarkDotNet به برنامه اضافه شده‌است:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <Nullable>enable</Nullable>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
  </ItemGroup>
</Project>

و سپس کدهای زیر، بررسی کارآیی روش‌های مختلف تعیین خالی بودن مجموعه‌ها را انجام می‌دهند:
using BenchmarkDotNet.Running;

namespace AnyCountBenchmark
{
    public static class Program
    {
        static void Main(string[] args)
        {
#if DEBUG
            System.Console.WriteLine("Please set the project's configuration to Release mode first.");
#else
            BenchmarkRunner.Run<Scenarios>();
#endif
        }
    }
}

به همراه سناریوهای مختلف زیر:
using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Order;

namespace AnyCountBenchmark
{
    [SimpleJob(RuntimeMoniker.NetCoreApp50)]
    [Orderer(SummaryOrderPolicy.FastestToSlowest, MethodOrderPolicy.Declared)]
    [RankColumn]
    public class Scenarios
    {
        private IList<int>? _idsList;
        private IEnumerable<int>? _idsEnumerable;
        private int[]? _idsArray;

        [GlobalSetup]
        public void Setup()
        {
            _idsEnumerable = Enumerable.Range(0, 10000);
            _idsList = _idsEnumerable.ToList();
            _idsArray = _idsEnumerable.ToArray();
        }

        #region Any_With_Null_coalescing
        [Benchmark]
        public void List_Any_With_Null_coalescing()
        {
            var list = _idsList ?? new List<int>();
            if (list.Any() is false) { }
        }

        [Benchmark]
        public void Array_Any_With_Null_coalescing()
        {
            var array = _idsArray ?? Array.Empty<int>();
            if (array.Any() is false) { }
        }

        [Benchmark]
        public void Enumerable_Any_With_Null_coalescing()
        {
            var enumerable = _idsEnumerable ?? Enumerable.Empty<int>();
            if (enumerable.Any() is false) { }
        }
        #endregion

        #region Any_With_Is_Null_Check
        [Benchmark]
        public void List_Any_With_Is_Null_Check()
        {
            if (_idsList is null || _idsList.Any() is false) { }
        }

        [Benchmark]
        public void Array_Any_With_Is_Null_Check()
        {
            if (_idsArray is null || _idsArray.Any() is false) { }
        }

        [Benchmark]
        public void Enumerable_Any_With_Is_Null_Check()
        {
            if (_idsEnumerable is null || _idsEnumerable.Any() is false) { }
        }
        #endregion

        #region Any_Any_With_Null_Equality_Check
        [Benchmark]
        public void List_Any_With_Null_Equality_Check()
        {
            if (_idsList == null || _idsList.Any() is false) { }
        }

        [Benchmark]
        public void Array_Any_With_Null_Equality_Check()
        {
            if (_idsArray == null || _idsArray.Any() is false) { }
        }

        [Benchmark]
        public void Enumerable_Any_With_Null_Equality_Check()
        {
            if (_idsEnumerable == null || _idsEnumerable.Any() is false) { }
        }
        #endregion

        #region Any_With_Null_Conditional_Operator
        [Benchmark]
        public void List_Any_With_Null_Conditional_Operator()
        {
            if (_idsList?.Any() is false) { }
        }

        [Benchmark]
        public void Array_Any_With_Null_Conditional_Operator()
        {
            if (_idsArray?.Any() is false) { }
        }

        [Benchmark]
        public void Enumerable_Any_With_Null_Conditional_Operator()
        {
            if (_idsEnumerable?.Any() is false) { }
        }
        #endregion

        #region Count_With_Pattern_Matching
        [Benchmark]
        public void List_Count_With_Pattern_Matching()
        {
            if (_idsList is { Count: > 0 } is false) { }
        }

        [Benchmark]
        public void Array_Length_With_Pattern_Matching()
        {
            if (_idsArray is { Length: > 0 } is false) { }
        }

        [Benchmark]
        public void Enumerable_Count_With_Pattern_Matching()
        {
            var list = _idsEnumerable?.ToList();
            if (list is { Count: > 0 } is false) { }
        }
        #endregion

        #region Count_With_Null_Conditional_Operator
        [Benchmark]
        public void List_Count_With_Null_Conditional_Operator()
        {
            if (_idsList?.Count == 0) { }
        }

        [Benchmark]
        public void Array_Length_With_Null_Conditional_Operator()
        {
            if (_idsArray?.Length == 0) { }
        }

        [Benchmark]
        public void Enumerable_Count_With_Null_Conditional_Operator()
        {
            if (_idsEnumerable?.Count() == 0) { }
        }
        #endregion

        #region Count_With_Null_Equality_Check
        [Benchmark]
        public void List_Count_With_Null_Equality_Check()
        {
            if (_idsList == null || _idsList.Count == 0) { }
        }

        [Benchmark]
        public void Array_Length_With_Null_Equality_Check()
        {
            if (_idsArray == null || _idsArray.Length == 0) { }
        }

        [Benchmark]
        public void Enumerable_Count_With_Null_Equality_Check()
        {
            if (_idsEnumerable == null || _idsEnumerable.Count() == 0) { }
        }
        #endregion
    }
}
یکبار اجرای آن، نتیجه‌ی زیر را به همراه داشت:


نتایج حاصل:

- بررسی خالی بودن آرایه‌ها، بسیار سریعتر از بررسی خالی بودن لیست‌ها و این مورد نیز سریعتر از Enumerable‌ها است.
- اگر از آرایه‌ها و یا لیست‌ها استفاده می‌کنید، بررسی خاصیت Length و یا خاصیت Count آن‌ها، بسیار سریعتر از بکارگیری متد Any بر روی آن‌ها است.
- اگر از Enumerableها استفاده می‌کنید، استفاده از متد Any بر روی آن‌ها، بسیار سریعتر از بکارگیری متد ()Count و یا تبدیل آن‌ها به لیست و سپس بررسی خاصیت Count آن‌ها است.
- بررسی نال بودن با pattern matching یا همان is null، نسبت به روشی سنتی استفاده‌ی از null ==، سریعتر است. علت آن‌را در مطلب «روش ترجیح داده شده‌ی مقایسه مقادیر اشیاء با null از زمان C# 7.0 به بعد» می‌توانید مطالعه کنید.

بنابراین برای بررسی خالی بودن آرایه‌ها و لیست‌ها، بهتر است از خاصیت Length و یا Count آن‌ها استفاده کرد و برای Enumerableها از متد ()Any.
نظرات مطالب
کوئری نویسی در EF Core - قسمت هشتم - کوئری‌های بازگشتی
راه حل بهتر!
کتابخانه‌ی « linq2db » از CTEها و recursive CTE پشتیبانی می‌کند. می‌توان این کتابخانه را توسط « linq2db.EntityFrameworkCore » با EF-Core یکی کرد. برای کار با آن ابتدا نیاز است بسته‌ی نیوگت آن‌را نصب کنید:
dotnet add package linq2db.EntityFrameworkCore
سپس در ابتدای برنامه یکبار آ‌ن‌را فعال کنید:
LinqToDB.EntityFrameworkCore.LinqToDBForEFTools.Initialize();
LinqToDB.Data.DataConnection.TurnTraceSwitchOn();
پس از آن به صورت زیر می‌توان از CTEها در کوئری‌های معمولی EF-Core استفاده کرد. برای مثال:

راه حل مثال 1 با استفاده از یک recursive CTE
می‌خواهیم لیست IDهای parent و childها را توسط یک recursive CTE تولید کنیم. به همین جهت ابتدا مدل معادل آن‌را تهیه می‌کنیم:
public class MemberHierarchyCTE
{
   public int ChildId { set; get; }
   public int? ParentId { set; get; }
}
سپس CTE زیر، این لیست را تهیه می‌کند:
var memberHierarchyCte =
                    context.CreateLinqToDbContext().GetCte<MemberHierarchyCTE>(memberHierarchy =>
                    {
                        return
                            (
                                from member in context.Members
                                select new MemberHierarchyCTE
                                {
                                    ChildId = member.MemId,
                                    ParentId = member.RecommendedBy
                                }
                            )
                            .Concat
                            (
                                from member in context.Members
                                from hierarchy in memberHierarchy
                                            .InnerJoin(hierarchy => member.MemId == hierarchy.ParentId)
                                select new MemberHierarchyCTE
                                {
                                    ChildId = hierarchy.ChildId,
                                    ParentId = member.RecommendedBy
                                }
                            );
                    });
که به این صورت ترجمه خواهد شد:
WITH [memberHierarchy] ([ChildId], [ParentId])
AS
(
    SELECT
        [member_1].[MemId],
        [member_1].[RecommendedBy]
    FROM
        [Members] [member_1]
    UNION ALL
    SELECT
        [hierarchy_1].[ChildId],
        [member_2].[RecommendedBy]
    FROM
        [Members] [member_2]
            INNER JOIN [memberHierarchy] [hierarchy_1] ON [member_2].[MemId] = [hierarchy_1].[ParentId]
)
و با کوئری گرفتن از آن برای مثال می‌توان لیست والدهای id=27 را تولید کرد (همان مثال 1):


راه حل مثال 2 با استفاده از یک recursive CTE 
و یا می‌توان لیست فرزندان id=1 را با کوئری گرفتن از این CTE تولید کرد (همان مثال 2):

مطالب
CoffeeScript #8

اصطلاحات عمومی CoffeeScript

Includes

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

var included = (array.indexOf("test") != -1)
CoffeeScript برای حل این مشکل، کلمه‌ی کلیدی in را ارائه کرده است:
included = "test" in array
متاسفانه برای چک کردن یک کلمه در یک متن می‌بایست از indexOf استفاده کرد و از کلمه‌ی کلیدی in نمی‌توان استفاده کرد. همچنین در صورتیکه بخواهید نبود چیزی را چک کنید نیز باید از indexOf استفاده کنید.
included = "a long test string".indexOf("test") isnt -1
و روش بهتر بجای مقایسه با مقدار 1-، استفاده از هک‌های اپراتور بیتی است:
string   = "a long test string"
included = !~ string.indexOf "test"

تکرار Propertyها

در صورتی که به خصوصیات یک شیء چندین بار نیاز داشته باشید، در جاوااسکریپت باید از کلمه‌ی کلیدی in استفاده کنید:

var object = {one: 1, two: 2}
for(var key in object) alert(key + " = " + object[key])
در حالیکه برای پیاده سازی توسط CoffeeScript باید از کلمه‌ی کلیدی of استفاده کرد:
object = {one: 1, two: 2}
alert("#{key} = #{value}") for key, value of object
نتیجه‌ی کامپایل آن می‌شود:
var key, object, value;

object = {
  one: 1,
  two: 2
};

for (key in object) {
  value = object[key];
  alert(key + " = " + value);
}
همانطور که در مثال بالا مشاهده می‌کنید، شما می‌توانید برای دسترسی به کلید و مقدار خصوصیات موجود در شیء، متغیری را برای هر کدام تعریف کنید که ما در اینجا از key  و value استفاده کرده‌ایم.

Min/Max

درست است که این تکنیک مخصوص CoffeeScript نیست، اما اشاره به آن می‌تواند مفید باشد. تابع Math.max و Max.min می‌توانند چندین آرگومان یا یک آرایه را به عنوان ورودی گرفته و بر روی آن محاسبات خود را انجام داده و خروجی را نشان دهند:

Math.max [14, 35, -7, 46, 98]... # 98
Math.min [14, 35, -7, 46, 98]... # -7
نتیجه‌ی آن پس از کامپایل می‌شود:
Math.max.apply(Math, [14, 35, -7, 46, 98]);

Math.min.apply(Math, [14, 35, -7, 46, 98]);
نکته: در صورتیکه آرگومان‌ها یا تعداد خانه‌های آرایه ارسالی زیاد باشند، چون مرورگرها محدودیتی را در تعداد پارامترهای ارسالی به یک تابع دارند، خروجی تولید نخواهد شد.