مطالب
رسم نمودار توسط Kendo Chart
پیشتر مطالبی در سایت، درباره KenoUI و همچنین ویجت‌های وب آن منتشر گردید. در این مطلب نگاهی خواهیم داشت بر تعدادی از ویجت‌های Kendo UI جهت رسم نمودار. توسط Kendo UI می‌توانیم نمودار‌های زیر را ترسیم کنیم:
  • Bar and Column
  • Line and Vertical Line
  • Area and Vertical Area
  • Bullet
  • Pie and Donut
  • Scatter
  • Scatter Line
  • Bubble
  • Radar and Polar

برای رسم نمودار می‌توانیم به صورت زیر عمل کنیم:

1- ابتدا باید استایل‌های مربوط به Data Visualization را به صفحه اضافه کنیم:

<link href="Content/kendo.dataviz.min.css" rel="stylesheet" />
<link href="Content/kendo.dataviz.default.min.css" rel="stylesheet" />
2- سپس یک عنصر را بر روی صفحه جهت نمایش نمودار، تعیین می‌کنیم:
<div id="chart"></div>
برای عنصر فوق می‌توانیم درون CSS و یا به صورت inline طول و عرضی را برای چارت تعیین کنیم:
<div id="chart" style="width: 400px; height: 600px"></div>
با فراخوانی تابع KendoChart، چارت بر روی صفحه نمایش داده می‌شود:
$("#chart").kendoChart();

همانطور که مشاهده می‌کنید هیچ داده‌ایی را هنوز برای چارت تعیین نکرده‌ایم؛ در نتیجه همانند تصویر فوق یک چارت خالی بر روی صفحه نمایش داده می‌شود. برای چارت فوق می‌توانیم خواصی از قبیل عنوان و ... را تعیین کنیم:
$("#chart").kendoChart({
    title: {
         text: "چارت آزمایشی"
    }
});

نمایش داده‌ها بر روی چارت:

داده‌ها را می‌توان هم به صورت local و هم به صورت remote دریافت و بر روی چارت نمایش داد. اینکار را می‌توانیم توسط قسمت series انجام دهیم:
$("#chart").kendoChart({
    title: {
         text: "عنوان چارت"
    },
    series: [
         { name: "داده‌های چارت", data: [200, 450, 300, 125] }
    ]
});
برای تعیین برچسب برای هر یک از داده‌ها نیز می‌توانیم خاصیت category axis را مقداردهی کنیم:
$("#chart").kendoChart({
                title: {
                    text: "عنوان چارت"
                },
                series: [
                     {
                         name: "داده‌های چارت",
                         data: [200, 450, 300, 125]
                     }
                ],
                categoryAxis: {
                    categories: [2000, 2001, 2002, 2003]
                }
            });

دریافت اطلاعات از سرور:
کدهای سمت سرور:
public class ProductsController : ApiController
    {
        public IEnumerable<ProductViewModel> Get()
        {
            var products = _productService.GetAllProducts();
            var query = products.GroupBy(p => p.Name).Select(p => new ProductViewModel
            {
                Value = p.Key,
                Count = p.Count()
            });
            return query;
        }
    }

    public class ProductViewModel
    {
        public string Value { get; set; }
        public int Count { get; set; }
    }

سپس برای دریافت اطلاعات از سمت سرور باید DataSource مربوط به چارت را مقداردهی کنیم:
var productsDataSource = new kendo.data.DataSource({
                transport: {
                    read: {
                        url: "api/products",
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    }
                },
                error: function (e) {
                    alert(e.errorThrown.stack);
                },
                pageSize: 5,
                sort: { field: "Id", dir: "desc" }
            });

            $("#chart").kendoChart({
                title: {
                    text: "عنوان چارت"
                },
                dataSource: productsDataSource,
                series: [
                    {
                        field: "Count",
                        categoryField: "Value",
                        aggregate: "sum"
                    }
                ]
            });
همانطور که مشاهده می‌کنید در این حالت باید برای سری، field و categoryField را مشخص کنیم.
موارد فوق را می‌توانیم به صورت یک افزونه نیز کپسوله کنیم.

کدهای افزونه jquery.ChartAjax:
(function($) {
    $.fn.ShowChart = function(options) {
        var defaults = {
            url: '/',
            text: 'نمودار دایره ایی',
            theme: 'blueOpal',
            font: '13px bbc-nassim-bold',
            legendPosition: 'left',
            seriesField: 'Count',
            seriesCategoryField: 'Value',
            titlePosition: 'top',
            chartWidth: 400,
            chartHeight: 400
        };
        var options = $.extend(defaults, options);
        return this.each(function() {
            var chartDataSource = new kendo.data.DataSource({
                transport: {
                    read: {
                        url: options.url,
                        dataType: "json",
                        contentType: 'application/json; charset=utf-8',
                        type: 'GET'
                    }
                },
                error: function (e) {
                    // handle error
                }
            });
            $(this).kendoChart({
                chartArea: {
                    height: options.chartHeight
                },
                theme: options.theme,
                title: {
                    text: options.text,
                    font: options.font,
                    position: options.titlePosition
                },
                legend: {
                    position: options.legendPosition,
                    labels: {
                        font: options.font
                    }
                },
                seriesDefaults: {
                    labels: {
                        visible: false,
                        format: "{0}%"
                    }
                },
                dataSource: chartDataSource,
                series: [
                    {
                        type: "pie",
                        field: options.seriesField,
                        categoryField: options.seriesCategoryField,
                        aggregate: "sum",
                    }
                ],
                tooltip: {
                    visible: true,
                    template: "${category}: ${value}",
                    font: options.font
                }
            });
            
        });
    };
})(jQuery);
برای افزونه فوق موارد زیر در نظر گرفته شده است:
chartArea : جهت تعیین طول و عرض چارت
theme : جهت تعیین قالب‌های از پیش‌تعریف شده:
  • Black
  • BlueOpal
  • Bootstrap
  • Default
  • Flat
  • HighContrast
  • Material
  • MaterialBlack
  • Metro
  • MetroBlack
  • Moonlight
  • Silver
  • Uniform

title : جهت تعیین عنوان چارت

legend : جهت تنظیم ویژگی‌های قسمت گروه‌بندی چارت

tooltip : جهت تنظیم ویژگی‌های مربوط به نمایش tooltip در هنگام hover بر روی چارت.

لازم به ذکر است در قسمت series می‌توانید نوع چارت را تعیین کنید.

نحوه استفاده از افزونه فوق:
$('#chart').ShowChart({
                        url: "/Report/ByUnit",
                        legendPosition: "bottom"
});


دریافت سورس مثال جاری (KendoChart.zip)

مطالب
MVC Scaffolding #2
از آنجائیکه اصل کار با MVC Scaffolding از طریق خط فرمان پاورشل انجام می‌شود، بنابراین بهتر است در ادامه با گزینه‌ها و سوئیچ‌های مرتبط با آن بیشتر آشنا شویم.
دو نوع پارامتر حین کار با MVC Scaffolding مهیا هستند:

الف) سوئیچ‌ها
مانند پارامترهای boolean عمل کرده و شامل موارد ذیل می‌باشند. تمام این پارامترها به صورت پیش فرض دارای مقدار false بوده و ذکر هرکدام در دستور نهایی سبب true شدن مقدار آن‌ها می‌گردد:
Repository: برای تولید کدها بر اساس الگوی مخزن
Force: برای بازنویسی فایل‌های موجود.
ReferenceScriptLibraries: ارجاعاتی را به اسکریپت‌های موجود در پوشه Scripts، اضافه می‌کند.
NoChildItems: در این حالت فقط کلاس کنترلر تولید می‌شود و از سایر ملحقات مانند تولید Viewها، DbContext و غیره صرفنظر خواهد شد.

ب) رشته‌ها
این نوع پارامترها، رشته‌ای را به عنوان ورودی خود دریافت می‌کنند و شامل موارد ذیل هستند:
ControllerName: جهت مشخص سازی نام کنترلر مورد نظر
ModelType: برای ذکر صریح کلاس مورد استفاده در تشکیل کنترلر بکار می‌رود. اگر ذکر نشود، از نام کنترلر حدس زده خواهد شد.
DbContext: نام کلاس DbContext تولیدی را مشخص می‌کند. اگر ذکر نشود از نامی مانند ProjectNameContex استفاده خواهد کرد.
Project: پیش فرض آن پروژه جاری است یا اینکه می‌توان پروژه دیگری را برای قرار دادن فایل‌های تولیدی مشخص کرد. (برای مثال هربار یک سری کد مقدماتی را در یک پروژه جانبی تولید کرد و سپس موارد مورد نیاز را از آن به پروژه اصلی افزود)
CodeLanguage: می‌تواند cs یا vb باشد. پیش فرض آن زبان جاری پروژه است.
Area: اگر می‌خواهید کدهای تولیدی در یک ASP.NET MVC area مشخص قرار گیرند، نام Area مشخصی را در اینجا ذکر کنید.
Layout: در حالت پیش فرض از فایل layout اصلی استفاده خواهد شد. اما اگر نیاز است از layout دیگری استفاده شود، مسیر نسبی کامل آن‌را در اینجا قید نمائید.

یک نکته:
نیازی به حفظ کردن هیچکدام از موارد فوق نیست. برای مثال در خط فرمان پاورشل، دستور Scaffold را نوشته و پس از یک فاصله، دکمه Tab را فشار دهید. لیست پارامترهای قابل اجرای در این حالت ظاهر خواهند شد. اگر در اینجا برای نمونه Controller انتخاب شود، مجددا با ورود یک فاصله و خط تیره و سپس فشردن دکمه Tab، لیست پارامترهای مجاز و همراه با سوئیچ کنترلر ظاهر می‌گردند.


MVC Scaffolding و مدیریت روابط بین کلاس‌ها

مثال قسمت قبلی بسیار ساده و شامل یک کلاس بود. اگر آن‌را کمی پیچیده‌تر کرده و برای مثال روابط one-to-many و many-to-many را اضافه کنیم چطور؟
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcApplication1.Models
{
    public class Task
    {
        public int Id { set; get; }

        [Required]
        public string Name { set; get; }

        [DisplayName("Due Date")]
        public DateTime? DueDate { set; get; }

        [ForeignKey("StatusId")]
        public virtual Status Status { set; get; } // one-to-many
        public int StatusId { set; get; }

        [StringLength(450)]
        public string Description { set; get; }

        public virtual ICollection<Tag> Tags { set; get; } // many-to-many
    }

    public class Tag
    {
        public int Id { set; get; }

        [Required]
        public string Name { set; get; }

        public virtual ICollection<Task> Tasks { set; get; } // many-to-many
    }

    public class Status
    {
        public int Id { set; get; }

        [Required]
        public string Name { set; get; }
    }
}
کلاس Task تعریف شده اینبار دارای رابطه many-to-many با برچسب‌های مرتبط با آن است. همچنین یک رابطه one-to-many با کلاس وضعیت هر Task نیز تعریف شده است. به علاوه نکته تعریف «کار با کلیدهای اصلی و خارجی در EF Code first» نیز در اینجا لحاظ گردیده است.
در ادامه دستور تولید کنترلر‌های Task، Tag و Status ساخته شده با الگوی مخزن را در خط فرمان پاورشل ویژوال استودیو صادر می‌کنیم:
PM> Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
PM> Scaffold Controller -ModelType Tag -ControllerName TagsController -DbContextType TasksDbContext -Repository -Force
PM> Scaffold Controller -ModelType Status -ControllerName StatusController -DbContextType TasksDbContext -Repository -Force
اگر به کارهایی که در اینجا انجام می‌شود دقت کنیم، می‌توان صرفه جویی زمانی قابل توجهی را شاهد بود؛ خصوصا در برنامه‌هایی که از ده‌ها فرم ورود اطلاعات تشکیل شده‌اند. فرض کنید قصد استفاده از ابزار فوق را نداشته باشیم. باید به ازای هر عملیات CRUD دو متد را ایجاد کنیم. یکی برای نمایش و دیگری برای ثبت. بعد بر روی هر متد کلیک راست کرده و Viewهای متناظری را ایجاد کنیم. سپس مجددا یک سری پیاده سازی «مقدماتی» تکراری را به ازای هر متد جهت ثبت یا ذخیره اطلاعات تدارک ببینیم. اما در اینجا پس از طراحی کلاس‌های برنامه، با یک دستور، حجم قابل توجهی از کدهای «مقدماتی» که بعدها مطابق نیاز ما سفارشی سازی و غنی‌تر خواهند شد، تولید می‌گردند.

چند نکته:
- با توجه به اینکه مدل‌ها تغییر کرده‌اند، نیاز است بانک اطلاعاتی متناظر نیز به روز گردد. مطالب مرتبط با آن‌را در مباحث Migrations می‌توانید مطالعه نمائید.
- View تولیدی رابطه many-to-many را پشتیبانی نمی‌کند. این مورد را باید دستی اضافه و طراحی کنید: (^ و ^)
- رابطه one-to-many به خوبی با View متناظری دارای یک drop down list تولید خواهد شد. در اینجا لیست تولیدی به صورت خودکار با مقادیر خاصیت Name کلاس Status پر می‌شود. اگر این نام دقیقا Name نباشد نیاز است توسط ویژگی به نام DisplayColumn که بر روی نام کلاس قرار می‌گیرد، مشخص کنید از کدام خاصیت باید استفاده شود.
@Html.DropDownListFor(model => model.StatusId,
((IEnumerable<Status>)ViewBag.PossibleStatus).Select(option => new SelectListItem {
  Text = (option == null ? "None" : option.Name),
  Value = option.Id.ToString(),
  Selected = (Model != null) && (option.Id == Model.StatusId)
}), "Choose...")
@Html.ValidationMessageFor(model => model.StatusId)


تولید آزمون‌های واحد به کمک MVC Scaffolding

MVC Scaffolding امکان تولید خودکار کلاس‌ها و متدهای آزمون واحد را نیز دارد. برای این منظور دستور زیر را در خط فرمان پاورشل وارد نمائید:
 PM> Scaffold MvcScaffolding.ActionWithUnitTest -Controller TasksController -Action ArchiveTask -ViewModel Task
دستوری که در اینجا صادر شده است نسبت به حالت‌های کلی قبلی، اندکی اختصاصی‌تر است. این دستور بر روی کنترلری به نام TasksController، جهت ایجاد اکشن متدی به نام ArchiveTask با استفاده از کلاس ViewModel ایی به نام Task اجرا می‌شود. حاصل آن ایجاد اکشن متد یاد شده به همراه کلاس TasksControllerTest است؛ البته اگر حین ایجاد پروژه جدید در ابتدای کار، گزینه ایجاد پروژه آزمون‌های واحد را نیز انتخاب کرده باشید. نام پروژه پیش فرضی که جستجوی می‌شود YourMvcProjectName.Test/Tests است.
 نکته مهم آن، عدم حذف یا بازنویسی کامل کنترلر یاد شده است. کاری هم که در تولید متد آزمون واحد متناظر انجام می‌شود، تولید بدنه متد آزمون واحد به همراه تولید کدهای اولیه الگوی Arrange/Act/Assert است. پر کردن جزئیات بیشتر آن با برنامه نویس است.
و یا به صورت خلاصه‌تر:
 PM> Scaffold UnitTest Tasks Delete
در اینجا متد آزمون واحد کنترلر Tasks و اکشن متد Delete آن، تولید می‌شود.

کار مقدماتی با MVC Scaffolding و امکانات مهیای در آن همینجا به پایان می‌رسد. در قسمت‌های بعد به سفارشی سازی این مجموعه خواهیم پرداخت.
مطالب
مدیریت کلیدهای کیبرد در جاوا اسکریپت
با پیشرفت بسترهای موجود در زمینه شبکه و اینترنت، گرایش به استفاده از اپلیکیشنهای تحت وب روز به روز بیشتر میشود. با گسترش این برنامه‌ها نیازها و درنتیجه ابزارهای موجود توسعه پیدا می‌کنند. درحال حاضر ابزارها و نیز محیطهای توسعه مختلفی برای تولید این اپلیکیشنها وجود دارد. به دلیل نوع رابط کاربری موجود در این برنامه‌ها (اکثراً مرورگرهای وب مثل اینترنت اکسپلورر، گوگل کروم، فایرفاکس و ...) استفاده از زبانهای سمت کلاینت (مثل جاوا اسکریپت که در تمامی مرورگرهای مدرن پشتیبانی کاملی از آن میشود) جایگاه ویژه ای در این نوع برنامه‌ها دارد. درضمن وقتی صحبت از اپلیکیشن به میان می‌آید استفاده از کلیدهای میانبر کیبرد برای راحتی کار کاربران کاربرد ویژه ای دارد. اما متاسفانه زبان جاوا اسکریپت به دلیل محدودیتهایی منطقی موجود، پشتیبانی مناسبی از رویدادهای کیبرد ندارد و مشکل تفاوتها و تناقضات میان سخت افزارها، سیستم عامل‌ها و مرورگرها هم به این مسئله بیشتر دامن میزند. در مطلب جاری هدف این است تا آشنایی مقدماتی با این مبحث فراهم شود.
رویدادهای کیبرد
در جاوا اسکریپت سه رویداد زیر برای کلیدهای کیبرد وجود دارد (به ترتیب زمان رخ دادن):
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) بدانیم.
مطالب
Blazor 5x - قسمت هشتم - مبانی Blazor - بخش 5 - تامین محتوای نمایشی کامپوننت‌های فرزند توسط کامپوننت والد
تا اینجا با نحوه‌ی تعریف کامپوننت‌ها و انتقال اطلاعات به آن‌ها و برعکس، آشنا شدیم. در ادامه علاقمندیم اگر کامپوننتی را به صورت زیر تعریف کردیم:
<Comp1>This is a content coming from the parent</Comp1>
محتوایی را که بین تگ‌های این کامپوننت فرزند، توسط کامپوننت والد تنظیم شده‌است، در قسمت مشخصی از UI کامپوننت فرزند نمایش دهیم.


معرفی مفهوم Render Fragment

برای درج محتوای تامین شده‌ی توسط کامپوننت والد در یک کامپوننت فرزند، از ویژگی به نام Render Fragment استفاده می‌شود. مثالی جهت توضیح جزئیات آن:
در ابتدا یک کامپوننت والد جدید را در مسیر Pages\LearnBlazor\ParentComponent.razor به صورت زیر تعریف می‌کنیم:
@page "/ParentComponent"

<h1 class="text-danger">Parent Child Component</h1>

<ChildComponent Title="This title is passed as a parameter from the Parent Component">
    A `Render Fragment` from the parent!
</ChildComponent>

<ChildComponent Title="This is the second child component"></ChildComponent>

@code {

}
- در اینجا یک کامپوننت متداول با مسیریابی منتهی به ParentComponent/ تعریف شده‌است.
- سپس دوبار کامپوننت فرضی ChildComponent به همراه پارامتر Title و یک محتوای جدید قرار گرفته‌ی در بین تگ‌های آن، در صفحه تعریف شده‌اند.
- بار دومی که ChildComponent در صفحه قرار گرفته‌است، به همراه محتوای جدیدی در بین تگ‌های خود نیست.

برای دسترسی به این کامپوننت از طریق منوی برنامه، مدخل منوی آن‌را به کامپوننت Shared\NavMenu.razor اضافه می‌کنیم:
<li class="nav-item px-3">
    <NavLink class="nav-link" href="ParentComponent">
       <span class="oi oi-list-rich" aria-hidden="true"></span> Parent/Child Relation
    </NavLink>
</li>
اکنون می‌خواهیم کامپوننت ChildComponent را افزوده و انتظارت یاد شده (دریافت یک پارامتر و نمایش محتوای سفارشی تنظیم شده) را پیاده سازی کنیم. به همین جهت فایل جدید Pages\LearnBlazor\LearnBlazor‍Components\ChildComponent.razor را با محتوای زیر ایجاد می‌کنیم:
<div>
    <div class="alert alert-info">@Title</div>
    <div class="alert alert-success">
        @if (ChildContent == null)
        {
            <span> Hello, from Empty Render Fragment </span>
        }
        else
        {
            <span>@ChildContent</span>
        }
    </div>
</div>


@code {
    [Parameter]
    public string Title { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }
}
توضیحات:

- خاصیت عمومی Title که توسط ویژگی Parameter مزین شده‌است، امکان تنظیم مقدار مشخصی را توسط کامپوننت دربرگیرنده‌ی ChildComponent میسر می‌کند.
- در اینجا پارامتر عمومی دیگری نیز تعریف شده‌است که اینبار از نوع ویژه‌ی RenderFragment است. توسط آن می‌توان به محتوایی که در کامپوننت والد ChildComponent در بین تگ‌های آن تنظیم شده‌است، دسترسی یافت. همچنین اگر این محتوا توسط کامپوننت والد تنظیم نشده باشد، مانند دومین باری که ChildComponent در صفحه قرار گرفته‌است، می‌توان با بررسی نال بودن آن، یک محتوای پیش‌فرض را نمایش داد.


با این خروجی:



روش دیگری برای فراخوانی Event Call Back ها

در قسمت قبل روش انتقال اطلاعات را از کامپوننت‌های فرزند، به والد مشاهده کردیم. فراخوانی آن‌ها در سمت Child Component نیاز به یک متد اضافی داشت و همچنین تنها یک پارامتر را هم ارسال کردیم. برای ساده سازی این عملیات از روش زیر نیز می‌توان استفاده کرد:
<button class="btn btn-danger"
        @onclick="@(() => OnClickBtnMethod.InvokeAsync((1, "A message from child!")))">
   Show a message from the child!
</button>


@code {
    // ...

    [Parameter]
    public EventCallback<(int, string)> OnClickBtnMethod { get; set; }
}
- در اینجا برای تعریف و ارسال بیش از یک پارامتر، از tuples استفاده شده‌است.
- همچنین فراخوانی OnClickBtnMethod.InvokeAsync را نیز در محل تعریف onclick@ بدون نیازی به یک متد اضافی، مشاهده می‌کنید. نکته‌ی مهم آن، قرار دادن این قطعه کد داخل ()@ است تا ابتدا و انتهای کدهای #C مشخص شود؛ وگرنه کامپایل نمی‌شود.

در سمت کامپوننت والد برای دسترسی به OnClickBtnMethod که اینبار یک tuple را ارسال می‌کند، می‌توان به صورت زیر عمل کرد:
@page "/ParentComponent"

<h1 class="text-danger">Parent Child Component</h1>

<ChildComponent
    OnClickBtnMethod="ShowMessage"
    Title="This title is passed as a parameter from the Parent Component">
    A `Render Fragment` from the parent!
</ChildComponent>

<ChildComponent Title="This is the second child component">
    <p><b>@MessageText</b></p>
</ChildComponent>

@code {
    string MessageText = "";

    private void ShowMessage((int Value, string Message) args)
    {
        MessageText = args.Message;
    }
}
در اینجا OnClickBtnMethod تعریف شده، به متد ShowMessage متصل شده‌است که یک tuple از جنس (int, string) را دریافت می‌کند. سپس با انتساب خاصیت رشته‌ای آن به فیلد جدید MessageText، سبب نمایش آن می‌شویم.


امکان تعریف چندین RenderFragment

تا اینجا یک RenderFragment را در کامپوننت فرزند تعریف کردیم. امکان تعریف چندین RenderFragment در ChildComponent.razor نیز وجود دارند:
@code {
    // ...

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public RenderFragment DangerChildContent { get; set; }
}
در یک چنین حالتی، روش استفاده‌ی از این پارامترهای RenderFragment در سمت والد (ParentComponent.razor) به صورت زیر خواهد بود:
<ChildComponent
    OnClickBtnMethod="ShowMessage"
    Title="This title is passed as a parameter from the Parent Component">
    <ChildContent>
        A `Render Fragment` from the parent!
    </ChildContent>
    <DangerChildContent>
        A danger content from the parent!
    </DangerChildContent>
</ChildComponent>
که به همراه ذکر صریح نام پارامترها به صورت تگ‌هایی جدید، داخل تگ اصلی است.

از آنجائیکه ذکر این تگ‌ها اختیاری است، نیاز است در ChildComponent.razor بر اساس null بودن آن‌ها، تصمیم به رندر محتوایی پیش‌فرض گرفت:
    @if(DangerChildContent == null)
    {
        @if (ChildContent == null)
        {
            <span> Hello, from Empty Render Fragment </span>
        }
        else
        {
            <span>@ChildContent</span>
        }
    }
    else
    {
        <span>@DangerChildContent</span>
    }




کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-08.zip
مطالب
الگوی شیء نال Null Object Pattern
این الگو شاید به نظر ساده برسد، ولی در بعضی موارد می‌تواند در سطوح بالاتر، کدهای تمیزتر و خلوت‌تری را در اختیار شما بگذارد. در مورد این الگو، در کتاب توسعه چابک عمو باب نیز آمده است. بسیاری ممکن است نسبت به این الگو جبهه بگیرند و بگویند که بررسی نال بودن یک شیء بهتر است و یا حتی رخ دادن خطای Null Pointer Exception در برنامه باعث می‌شود بتوانیم باگ‌ها را پیدا کنیم. در جواب باید گفت که این الگو قرار نیست در همه جا مورد استفاده قرار گیرد. در مثال زیر می‌توانید تا حدی به جایگاه استفاده از این الگو برسید. اینکه چگونه و در کجا از یک الگو استفاده کنید، به عهده برنامه نویس است.
کار این الگو در یک جمله این است که اگر متدی نتواند خروجی مناسبی را بدهد و به جای آن قرار باشد نال را برگشت دهد، به جای برگشت دادن نال، از یک شیء که هیچ رفتاری ندارد استفاده می‌کند و آن شیء را برمی‌گرداند تا در ادامه کد، بررسی نال بودن، یا خطای NPE رخ ندهد.

به عنوان مثال فرض کنید قرار است یک کاربر با نام کاربری Ali به سیستم وارد شود؛ در اینجا سه حالت وجود دارد:
  1. این کاربر یافت شده و اجازه دسترسی دارد.
  2. این کاربر یافت شده و اجازه دسترسی ندارد.
  3. این کاربر یافت نمی‌شود.

اگر در حالتیکه کاربر یافت نشود، بخواهیم نال برگردانیم، در ادامه‌ی کد باید بررسی نال بودن و یا گاها انتظار خطای NPE را داشته باشیم؛ یا اینکه در عوض از الگوی شیء نال بهره ببریم.

بدون استفاده از الگو
در این مثال ابتدا کلاس یوزر را می‌سازیم:
 public class User
    {
        public String Usernam { get; set; }
        public bool Authenticated { get; set; }
    }
در لایه سرویس هم خروجی را برمی‌گردانیم:
 public User GeUser(string uname)
        {
            if (uname == "Ali")
            {
                return new User()
                {
                    Usernam = "Ali",
                    Authenticated = true
                };
            }
            else if (uname == "Reza")
            {
                return new User()
                {
                    Usernam = "Reza",
                    Authenticated = false
                };
            }
            else
            {
                return null;
            }
        }
در این حالت کد بعدی شما باید اینگونه باشد:
 var userServices=new UserServices();
            var user = userServices.GeUser("Ali");
            if (user != null && user.Authenticated)
            {
                Console.WriteLine("You are Authorized");
            }
همانطور که می‌بینید یک خط کد شرطی به سیستم اضافه شد و در یک سیستم بزرگتر این بررسی‌ها بیشتر شده و حتی در بعضی نقاط ممکن است با یک عدم بررسی، وقوع خطای NPE را افزایش دهید. حالا همین مثال بالا را با همین الگو جلو می‌بریم.

استفاده از الگو

ابتدا یک کلاس جدید را با ارث بری از کلاس یوزر می‌سازیم:
public class NullUser:User
    {
        public NullUser()
        {
            Authenticated = false;
        }
    }
این کلاس همان شیء نالی است که قرار است به جای خود عبارت Null برگشت دهیم:
  public User GeUser(string uname)
        {
            if (uname == "Ali")
            {
                return new User()
                {
                    Usernam = "Ali",
                    Authenticated = true
                };
            }
            else if (uname == "Reza")
            {
                return new User()
                {
                    Usernam = "Reza",
                    Authenticated = false
                };
            }
                return new NullUser();
        }
بدین ترتیب در ادامه کد الزامی به بررسی نال نیست:
var userServices=new UserServices();
            var user = userServices.GeUser("xxx");
            if (user.Authenticated)
                Console.WriteLine("You are Authorized");

یک نکته اضافه تر اینکه، در صورتی که قصد دارید متدی را در کلاس پدر تحریف کنید، بهتر است یک اینترفیس یا کلاس انتزاعی را تعریف و هر دو کلاس را از آن ارث بری کنید که برای مثال بالا می‌شود اینترفیس IUser و  دو کلاس User و NullUser هم مشتقات آن.
نظرات مطالب
PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند
با فرض اینکه layout برنامه تعاریف لازم را دارد:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link href="~/Content/PersianDatePicker.css" rel="stylesheet" />
</head>
<body>
    @RenderBody()
    <script src="~/Scripts/jquery-1.10.2.min.js"></script>
    <script src="~/Scripts/PersianDatePicker.js"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>
فایلی را به نام PersianDatePicker.cshtml در مسیر Views\Shared\EditorTemplates ایجاد کنید؛ با این محتوا:
@using System.Globalization
@model DateTime?
@{
    Func<DateTime, string> toPersianDate = date =>
    {
        var dateTime = new DateTime(date.Year, date.Month, date.Day, new GregorianCalendar());
        var persianCalendar = new PersianCalendar();
        return persianCalendar.GetYear(dateTime) + "/" + 
               persianCalendar.GetMonth(dateTime).ToString("00") + "/" + 
               persianCalendar.GetDayOfMonth(dateTime).ToString("00");
    };
    var today = toPersianDate(DateTime.Now);
    var name = this.ViewContext.ViewData.ModelMetadata.PropertyName;
    var value = Model.HasValue ? toPersianDate(Model.Value) : string.Empty;
}
<input type="text" dir="ltr" 
       name="@name" id="@name" 
       value="@value" 
       onclick="PersianDatePicker.Show(this,'@today');" />
بعد از این تنها کاری که باید انجام شود، استفاده از ویژگی UIHint است:
// Location: Views\Shared\EditorTemplates\PersianDatePicker.cshtml
[UIHint("PersianDatePicker")]
public DateTime AddDate { set; get; }
به صورت خودکار به متد EditorFor اعمال می‌شود:
<div>
    <label>تاریخ:</label>
    @Html.EditorFor(model => model.AddDate)
</div>

همچنین اگر نیاز است مقدار رشته تاریخ شمسی آن به یک DateTime میلادی انتساب داده شود (در حین ارسال اطلاعات به سرور)، باید از یک model binder سفارشی برای اینکار در ASP.NET MVC استفاده کنید. یک نمونه پیاده سازی شده آن‌را در بحث PersianDateModelBinder می‌توانید مشاهده و استفاده کنید. 
مطالب دوره‌ها
استفاده از IL Code Weaving برای تولید ویژگی‌های تکراری مورد نیاز در WCF
با استفاده از IL Code Weaving علاوه بر مدیریت اعمال تکراری پراکنده در سراسر برنامه مانند ثبت وقایع، مدیریت استثناءها، کش کردن داده‌ها و غیره، می‌توان قابلیتی را به کدهای موجود نیز افزود. برای مثال یک برنامه معمول WCF را درنظر بگیرید.
using System.Runtime.Serialization;

namespace AOP03.DataContracts
{
    [DataContract]
    public class User
    {
        [DataMember]
        public int Id { set; get; }

        [DataMember]
        public string Name { set; get; }
    }
}
نیاز است کلاس‌ها و خواص آن توسط ویژگی‌های DataContract و DataMember مزین شوند. در این بین نیز اگر یکی فراموش گردد، کار دیباگ برنامه مشکل خواهد شد و در کل حجم بالایی از کدهای تکراری در اینجا باید در مورد تمام کلاس‌های مورد نیاز انجام شود. در ادامه قصد داریم تولید این ویژگی‌ها را توسط PostSharp انجام دهیم. به عبارتی یک پوشه خاص به نام DataContracts را ایجاد کرده و کلاس‌های خود را به نحوی متداول و بدون اعمال ویژگی خاصی تعریف کنیم. در ادامه پس از کامپایل آن، به صورت خودکار با ویرایش کدهای IL توسط PostSharp، ویژگی‌های لازم را به اسمبلی نهایی اضافه نمائیم.


تهیه DataContractAspect جهت اعمال خودکار ویژگی‌های DataContract و DataMember

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using PostSharp.Aspects;
using PostSharp.Extensibility;
using PostSharp.Reflection;

namespace AOP03
{
    [Serializable]
    //این ویژگی تنها نیاز است به کلاس‌ها اعمال شود
    [MulticastAttributeUsage(MulticastTargets.Class)]
    public class DataContractAspect : TypeLevelAspect, IAspectProvider
    {
        public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
        {
            var targetType = (Type)targetElement; //همان نوعی است که ویژگی جاری به آن اعمال خواهد شد

            //این سطر معادل است با درخواست تولید ویژگی دیتاکانترکت
            var introduceDataContractAspect = new CustomAttributeIntroductionAspect(
                new ObjectConstruction(typeof(DataContractAttribute).GetConstructor(Type.EmptyTypes)));

            //این سطر معادل است با درخواست تولید ویژگی دیتاممبر
            var introduceDataMemberAspect = new CustomAttributeIntroductionAspect(
                new ObjectConstruction(typeof(DataMemberAttribute).GetConstructor(Type.EmptyTypes)));

            //در اینجا کار اعمال ویژگی دیتاکانترکت به کلاسی که به عنوان پارامتر متد جاری
            //دریافت شده انجام خواهد شد
            yield return new AspectInstance(targetType, introduceDataContractAspect);

            //مرحله بعد کار اعمال ویژگی دیتاممبر به خواص کلاس است
            foreach (var property in targetType.GetProperties(BindingFlags.Public |
                                                          BindingFlags.DeclaredOnly |
                                                          BindingFlags.Instance))
            {
                if (property.CanWrite)
                    yield return new AspectInstance(property, introduceDataMemberAspect);
            }
        }
    }
}
توضیحات مرتبط با قسمت‌های مختلف این Aspect سفارشی، به صورت کامنت در کدهای فوق ارائه شده‌اند.
برای اعمال آن به سراسر برنامه تنها کافی است به فایل AssemblyInfo.cs پروژه مراجعه و سپس سطر زیر را به آن اضافه کنیم:
 [assembly: DataContractAspect(AttributeTargetTypes = "AOP03.DataContracts.*")]
به این ترتیب در زمان کامپایل پروژه، Aspect تعریف شده به تمام کلاس‌های موجود در فضای نام AOP03.DataContracts اعمال خواهند شد.

در این حالت اگر کلیه ویژگی‌های کلاس User فوق را حذف و برنامه را کامپایل کنیم، با مراجعه به برنامه ILSpy می‌توان صحت اعمال ویژگی‌ها را به کمک PostSharp بررسی کرد:
 

نظرات مطالب
کار با کلیدهای اصلی و خارجی در EF Code first
من 3تا جدول زیر رو در بانک ساختم :

و کلاسها به صورت زیر تعریف کردم:
 public class Tb1 
    {
        public Tb1()
        {
            ListTb2 = new List<Tb2>();
        }
        public int Id { get; set; }
        public string NameTb1 { get; set; }

        public virtual ICollection<Tb2> ListTb2 { get; set; }
    }
    public class Tb2 
    {
        public Tb2()
        {
            ListTb1 = new List<Tb1>();
        }
        public int Id { get; set; }
        public string NameTb2 { get; set; }

        public virtual ICollection<Tb1> ListTb1 { get; set; }
    }
و همینطور mapping :
    public class Tb1Map : EntityTypeConfiguration<Tb1>
    {
        public Tb1Map()
        {
            this.HasKey(x => x.Id);

            this.HasMany(x => x.ListTb2)
                .WithMany(xx => xx.ListTb1)
                .Map
                (
                    x =>
                        {
                            x.MapLeftKey("Tb1Id");
                            x.MapRightKey("Tb2Id");
                            x.ToTable("Tb1Tb2");
                        }
                );

        }
    }

    public class Tb2Map : EntityTypeConfiguration<Tb2>
    {
        public Tb2Map()
        {
            this.HasKey(x => x.Id);
        }
    }
موقعی که در برنامه به صورت زیر استفاده می‌کنم:
            var sv1 = new TableService<Tb1>(_uow);
            var sv2 = new TableService<Tb2>(_uow);

            var t1 = new Tb1 { NameTb1 = "T111" };
            sv1.Add(t1);
            //var res1= _uow.SaveChanges();
            
            var t2 = new Tb2 { NameTb2 = "T222" };
            sv2.Add(t2);
            //var res2 = _uow.SaveChanges();

            t1.ListTb2.Add(t2);
            var result = _uow.SaveChanges();
 هنگام SaveChanges این خطا رو می‌ده: 
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
همراه با innerException زیر:
{"The INSERT statement conflicted with the FOREIGN KEY constraint \"FK_Tb1Tb2_Tb2\". The conflict occurred in database \"dbTest\", table \"dbo.Tb2\", column 'Id'.\r\nThe statement has been terminated."}
در واقع همینطور که مشخصه من می‌خوام اون جدول رابطه رو در codeFirst حذف کنم یجورایی و رابطه رو بین 2 جدول اصلی بیارم. کجای کارم اشتباهه؟ و راهکارش چیه؟
من با پروفایلر هم نگاه کردم همه چی تا آخر داره پیش می‌ره!
(آیا ForeignKey رو باید طور دیگه ای تعریف کنم؟) 
با تشکر
مطالب
یافتن آدرس نهایی یک Url پس از Redirect

برای مثال آدرس http://feedproxy.google.com/~r/nettuts/~3/tWCksueANyY/ را در نظر بگیرید. گوگل پس از خرید feedburner ، از feedproxy.google.com جهت ردیابی آدرس‌های فیدها استفاده می‌کند؛‌ مثلا فید شما چند نفر خواننده دارد، کدام موارد بیشتر خوانده شده، با چه مرورگرهایی، از چه مکان‌هایی و مواردی از این دست. البته می‌توان در تنظیمات فیدبرنر این نوع آدرس دهی را خاموش کرد ولی به صورت پیش فرض فعال است. مشکل feedproxy.google.com هم این است که در ایران فیلتر است. بنابراین یافتن آدرس اصلی این نوع لینک‌ها پس از Redirect نهایی می‌تواند جهت ارائه عمومی آن‌ها مفید باشد. با استفاده از قطعه کد زیر می‌توان این آدرس را یافت:

using System;
using System.Net;

namespace Linq2Rss
{
public class RedirectFinder
{
public CookieContainer Cookies { set; get; }
public string Url { get; set; }

public string GetRedirectUrl()
{
var hops = 1;
const int MaxRedirects = 20;

do
{
var request = (HttpWebRequest)WebRequest.Create(Url);
request.UserAgent = "MyUA";
request.KeepAlive = true;
request.Referer = Url;
if (Cookies == null) Cookies = new CookieContainer();
request.CookieContainer = Cookies;
request.AllowAutoRedirect = false;

using (var webResp = request.GetResponse() as HttpWebResponse)
{
if ((webResp.StatusCode == HttpStatusCode.Found) ||
(webResp.StatusCode == HttpStatusCode.Redirect) ||
(webResp.StatusCode == HttpStatusCode.Moved) ||
(webResp.StatusCode == HttpStatusCode.MovedPermanently))
{
var newLocation = webResp.Headers["Location"];
if (newLocation.StartsWith("/"))
{
var uri = new Uri(Url);
Url = string.Format("{0}://{1}:{2}{3}", uri.Scheme, uri.Host, uri.Port, newLocation);
}
else
Url = newLocation;
}
else
{
if (webResp.StatusCode == HttpStatusCode.OK)
return Url;
}
}

hops++;
} while (hops <= MaxRedirects);
return Url;
}
}
}


برای یافتن آدرس واقعی یک Url پس از Redirect راهی بجز درخواست آن از وب سرور اولیه وجود ندارد. سپس وضعیت پاسخ داده شده بررسی می‌شود؛ اگر حاوی Found ، Moved یا Redirect بود، به این معنا است که باید آدرس جدید را از هدر پاسخ دریافتی استخراج کنیم. این آدرس، در کلید Location ذخیره می‌شود. اکنون یکبار دیگر نیاز است تا این آدرس جدید بررسی شود، زیرا ممکن است این مورد هم به آدرس دیگری اشاره کند. در کل، کد فوق 20 بار این بررسی را انجام خواهد داد (هر چند عموما در دو یا سه سعی به جواب خواهیم رسید).
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 16 - کار با Sessions
سشن‌ها نیز همانند تمام قسمت‌های دیگر یک برنامه‌ی ASP.NET Core، به صورت پیش فرض غیرفعال هستند و نیاز به مراحل خاصی است تا امکان استفاده‌ی از آن‌ها فراهم شود. همچنین روش کار کردن با آن‌ها نیز متفاوت است با نگارش‌های قبلی ASP.NET (تمام نگارش‌ها).


سشن چیست؟

شیء سشن، مجموعه‌ای از اشیاء serialized مرتبط با جلسه‌ی کاری جاری یک کاربر است. این اشیاء عموما در حافظه‌ی محلی سرور ذخیره می‌شوند؛ اما امکان ذخیره سازی توزیع شده‌ی آن‌ها در بانک‌های اطلاعاتی نیز پیش بینی شده‌است.
عموما استفاده‌ی از اشیاء سشن توصیه نمی‌شوند. از این جهت که این نوع اشیاء بسیار شبیه هستند به متغیرهای سراسری و وجود این نوع متغیرها اساسا ضعف طراحی شیء‌گرا به حساب می‌آیند. اما با توجه به ماهیت stateless بودن برنامه‌های وب، به این معنا که با پایان رندر یک صفحه، تمام اشیاء مرتبط با آن‌ها نیز در سمت سرور تخریب می‌شوند، نیاز است برای یک سری از داده‌های عمومی کاربر، راه حلی را پیدا کرد تا بتوان از اطلاعات آن‌ها استفاده‌ی مجدد کرد. برای مثال نگهداری رشته‌ی اتصالی بانک اطلاعاتی که کاربر در حین لاگین به سیستم آن‌را انتخاب کرده‌است (اگر برنامه به ازای هر سال از یک بانک اطلاعاتی مجزا استفاده می‌کند) و یا زمانیکه کاربری captcha را پر می‌کند و مقدار آن‌را به سمت سرور ارسال می‌کند، نیاز است مقدار ارسالی او را با مقدار ابتدایی captcha مقایسه کرد. یک چنین اطلاعاتی نباید با پایان رندر صفحه تخریب شوند و نیاز است تا زمانیکه جلسه‌ی کاری کاربر به پایان نرسیده‌است، در دسترس باشند. به همین جهت است که مفهومی را به نام «اشیاء سشن» طراحی کرده‌اند.
درکل خارج از این موارد بهتر است از سشن استفاده نکنید و در جای جای برنامه‌ی خود ردپای آن‌را باقی نگذارید و به خاطر داشته باشید:
متغیر سشن = متغیر سراسری = ضعف طراحی شی‌‌ءگرا


توصیه‌ی به استفاده‌ی از روش‌های سبک وزن‌تر

سشن‌ها تنها روش به اشتراک گذاری اطلاعات نیستند. اگر می‌خواهید اطلاعاتی را در بین میان افزارهای برنامه در طی یک درخواست به اشتراک بگذارید، شاید سشن هم یک راه حل باشد؛ اما راه حلی سنگین وزن. راه حل بهتر برای این موارد، استفاده‌ی از HttpContext.Items است. HttpContext.Items نیز همانند سشن، یک key/value store است؛ اما طول عمر آن محدود است به طول عمر درخواست جاری و در تمام میان افزارهای برنامه در دسترس است.
برای مثال در یک میان افزار آن‌را تنظیم می‌کنید:
app.Use(async (context, next) =>
{
   context.Items["isVerified"] = true;
   await next.Invoke();
});
و سپس در میان افزاری دیگر از آن استفاده خواهید کرد:
app.Run(async (context) =>
{
   await context.Response.WriteAsync("Verified request? " + context.Items["isVerified"]);
});


فعال سازی سشن‌ها در ASP.NET Core

ASP.NET Core یک choose-what-you-need framework است. به این معنا که تا زمانیکه قابلیتی را به صورت صریح فعال سازی نکرده باشید، در دسترس نخواهد بود. همین مساله در نهایت به کاهش مصرف منابع این نوع برنامه‌ها و همچنین طراحی ماژولار سیستم ختم می‌شوند. برای مثال در نگارش‌های قبلی ASP.NET (تمام نگارش‌ها)، سشن‌ها به صورت پیش فرض فعال هستند، مگر آنکه HTTP Module آن‌را در فایل web.config حذف کنید؛ اما در اینجا برعکس است.
اگر تنها در موارد خاصی که ذکر شد، نیاز به استفاده‌ی از متغیرهای سشن را داشتید، روش فعال سازی آن به صورت ذیل است:
الف) نصب بسته‌ی نیوگت Microsoft.AspNetCore.Session
برای این منظور وابستگی ذیل را به فایل project.json اضافه کنید:
{
    "dependencies": {
      //same as before
      "Microsoft.AspNetCore.Session": "1.0.0"
    }
}
ب) انجام تنظیمات آغازین برنامه
برای این کار به کلاس آغازین برنامه مراجعه کرده و ابتدا سرویس سشن‌ها را فعال کنید:
public void ConfigureServices(IServiceCollection services)
{
    services.AddSession();
و سپس در متد Configure، استفاده‌ی از سشن‌ها را نیز باید ذکر کنید:
public void Configure(IApplicationBuilder app)
{
    app.UseSession();
در اینجا امکان سفارشی سازی مقادیر پیش فرض مدیریت سشن‌ها نیز وجود دارند. برای مثال زمان پیش فرض انقضای سشن کاربر، پس از 20 دقیقه عدم فعالیت او است که قابل تغییر می‌باشد:
app.UseSession(options: new SessionOptions
{
    IdleTimeout = TimeSpan.FromMinutes(30),
    CookieName = ".MyApplication"
});


روش استفاده‌ی از سشن‌ها

در مثال ذیل نحوه‌ی ذخیره سازی اطلاعات را در شیء سشن جلسه‌ی جاری یک کاربر، ملاحظه می‌کنید:
public ActionResult TestSession()
{
 
   this.HttpContext.Session.Set("key-1", BitConverter.GetBytes(DateTime.Now.Ticks));
   this.HttpContext.Session.SetInt32("key-2", 1);
   this.HttpContext.Session.SetString("key-3", "DNT");
 
   return Content("OK!");
}
به همراه نحوه‌ی بازیابی این اطلاعات در متدهای دیگر برنامه:
 public IActionResult Index()
{
   byte[] key1 = this.HttpContext.Session.Get("key-1");
   long key1Value = BitConverter.ToInt64(key1, 0);
 
   int? key2Value = this.HttpContext.Session.GetInt32("key-2");
   string key3Value = this.HttpContext.Session.GetString("key-3");
 
   return Content("OK!");
}
همانطور که ملاحظه می‌کنید، سه متد Set، SetInt32 و SetString در اینجا برای تنظیم key/valueهای سشن، از پیش موجود هستند.
حالت Set آن آرایه‌ای از بایت‌ها را دریافت می‌کند و می‌توان برای حالت serialization اشیاء، مفید باشد.
دقیقا معادل همین سه متد، متدهای Get، GetInt32 و GetString برای بازیابی مقادیر سشن طراحی شده‌اند و باید دقت داشت که خروجی‌های این‌ها می‌توانند نال نیز باشند. به همین جهت خروجی GetInt32 آن نال پذیر است.


توسعه‌ی متدهای پیش فرض کار با سشن‌ها

سه متد یاد شده‌ی کار با سشن‌ها در ASP.NET Core هرچند ضروری هستند، اما کافی نیستند. برای توسعه‌ی آن‌ها می‌توان متدهای الحاقی را تدارک دید که نمونه‌ای از آن‌ها را ذیل مشاهده می‌کنید:
using System;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
 
namespace Core1RtmEmptyTest.StartupCustomizations
{
    public static class SessionExts
    {
        public static void SetDateTime(this ISession collection, string key, DateTime value)
        {
            collection.Set(key, BitConverter.GetBytes(value.Ticks));
        }
 
        public static DateTime? GetDateTime(this ISession collection, string key)
        {
            var data = collection.Get(key);
            if (data == null)
            {
                return null;
            }
            var dateInt = BitConverter.ToInt64(data, 0);
            return new DateTime(dateInt);
        }
 
        public static void SetObject(this ISession session, string key, object value)
        {
            var stringValue = JsonConvert.SerializeObject(value);
            session.SetString(key, stringValue);
        }
 
        public static T GetObject<T>(this ISession session, string key)
        {
            var stringValue = session.GetString(key);
            return JsonConvert.DeserializeObject<T>(stringValue);
        }
    }
}
در اینجا با استفاده از کلاس BitConverter و امکان سریالایز مقادیر توسط آن به آرایه‌ای از بایت‌ها، امکان کار با متد‌های عمومی Set و Get را یافته‌ایم.
و یا جهت کار با اشیاء پیچیده‌تر می‌توان از کتابخانه‌ی JSON.NET استفاده کرد. به عبارتی در این نگارش از ASP.NET، کار سریالایز و دی‌سریالایز اشیاء، به برنامه نویس واگذار شده‌است و اینکه در پشت صحنه از چه کتابخانه‌ای می‌خواهید استفاده کنید، در اختیار خودتان است.
البته باید دقت داشت که در اینجا وابستگی JSON.NET به صورت خودکار در دسترس است. از این جهت که بسیاری از وابستگی‌های ASP.NET Core مانند مورد ذیل، به JSON.NET وابسته‌اند و نصب آن‌ها به معنای نصب خودکار JSON.NET نیز هست:
{
    "dependencies": {
     //same as before
     "Microsoft.Extensions.Configuration.Json": "1.0.0"
   }
}
اگر لیست بسته‌های وابسته‌ی به JSON.NET را می‌خواهید مشاهده کنید، فایل project.lock.json را گشوده و در آن Newtonsoft.Json را جستجو کنید.


یک مطلب تکمیلی

در اینجا نیز امکان ذخیره سازی سشن‌ها در بانک اطلاعاتی بجای حافظه‌ی فرار سرور درنظر گرفته شده‌است و برای این حالت، بانک‌های اطلاعاتی NoSQL ویژه‌ای به نام key/value stores مانند بانک اطلاعاتی فوق سریع Redis پیشنهاد می‌شود؛ هرچند امکان کار با SQL Server نیز در اینجا وجود دارد، اما برای کش سرورهای مبتنی بر key/value ها، بانک اطلاعاتی Redis، انتخاب اول است.
Managing Application State