مطالب
ASP.NET Web API - قسمت سوم
در قسمت اول به دلایل ایجاد Web API پرداخته شد و در قسمت دوم مثالی ساده از Web API را بررسی کردیم. در این قسمت، مثال قبل را تست کرده و نحوه‌ی تعامل jQuery با آن را بررسی می‌کنیم.

فراخوانی Web API از طریق مرورگر

با فشردن کلید F5، پروژه را اجرا کنید. شکل ذیل ظاهر می‌شود.


صفحه ای که ظاهر می‌شود، یک View است که توسط HomeController و متد Index آن برگشت داده شده است. برای فراخوانی متدهای موجود در کلاس Controller مثال قسمت قبل که مربوط به Web API است، باید به یکی از آدرس‌های اشاره شده در قسمت قبل برویم. به عنوان مثال، برای به دست آوردن لیست تمامی محصولات، به آدرس http://localhost:xxxx/api/products بروید. xxxx، شماره‌ی پورتی است که Web Server داخلی Visual Studio در هنگام اجرای پروژه به آن اختصاص می‌دهد. آن را نسبت به پروژه‌ی خود تغییر دهید.
نتیجه‌ی دریافتی بستگی به نوع مرورگری دارد که استفاده می‌کنید. Internet Explorer از شما در مورد باز کردن یا ذخیره‌ی فایلی با نام products پرسش می‌کند (شکل ذیل).


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

[{"Id":1,"Name":"Tomato soup","Category":"Groceries","Price":1.39},{"Id":2,"Name":
"Yo-yo","Category":"Toys","Price":3.75},{"Id":3,"Name":"Hammer","Category":
"Hardware","Price":16.99}]
اما مرورگر Firefox، محصولات را در قالب XML نشان می‌دهد (شکل ذیل).


دلیل تفاوت در نتیجه‌ی دریافتی این است که مرورگر Internet Explorer و Firefox، هر یک مقدار متفاوتی را در هدر Accept درخواست، ارسال می‌کنند. بنابراین، Web API نیز مقدار متفاوتی را در پاسخ برگشت می‌دهد.

حال به آدرس‌های ذیل بروید: 

http://localhost:xxxx/api/products/1
http://localhost:xxxx/api/products?category=hardware

اولین آدرس، باید محصولی با مشخصه‌ی 1 را برگشت دهد و دومین آدرس، لیستی از تمامی محصولاتی که در دسته‌ی hardware قرار دارند را برگشت می‌دهد (در مثال ما فقط یک آیتم این شرط را دارد).

نکته: در صورتی که در هنگام فراخوانی هر یک از متدهای Web API با خطای ذیل مواجه شدید، دستور [("AcceptVerbs("GET", "POST] را به ابتدای متدها اضافه کنید.

The requested resource does not support http method 'GET'
 

فراخوانی Web API با استفاده از کتابخانه‌ی jQuery

در قسمت قبل، متدهای Web API را مستقیماً از طریق وارد کردن آدرس آنها در نوار آدرس مرورگر فراخوانی کردیم. اما در اکثر اوقات، این متدها با روش‌های برنامه نویسی توسط یک Client فراخوانی می‌شوند. اجازه بدهید Clientیی ایجاد کنیم که با استفاده از jQuery، متدهای ما را فراخوانی می‌کند.
در Solution Explorer، از پوشه‌ی Views و سپس Home، فایل Index.cshtml را باز کنید.

تمامی محتویات این View را حذف و کدهای ذیل را در آن قرار دهید.  

<!DOCTYPE html>
<html>
<head>
    <title>ASP.NET Web API</title>
    <script src="../../Scripts/jquery-1.7.2.min.js" 
        type="text/javascript"></script>
</head>
<body>
    <div>
        <h1>All Products</h1>
        <ul id='products' />
    </div>
    <div>
        <label for="prodId">ID:</label>
        <input type="text" id="prodId" size="5"/>
        <input type="button" value="Search" onclick="find();" />
        <p id="product" />
    </div>
</body>
</html>


بازیابی لیستی از محصولات

برای بازیابی لیستی از محصولات، فقط کافی است تا یک درخواست از نوع GET به آدرس "/api/products" بفرستید. این کار با jQuery به صورت ذیل انجام می‌شود. 

<script type="text/javascript">
    $(document).ready(function () {
        // Send an AJAX request
        $.getJSON("api/products/",
        function (data) {
            // On success, 'data' contains a list of products.
            $.each(data, function (key, val) {

                // Format the text to display.
                var str = val.Name + ': $' + val.Price;

                // Add a list item for the product.
                $('<li/>', { html: str })    
                .appendTo($('#products'));   
            });
        });
    });
</script>

متد getJSON، یک درخواست AJAX از نوع GET را ارسال می‌کند و پاسخ دریافتی آن نیز با فرمت JSON خواهد بود. دومین پارامتر متد getJSON، یک callback است که پس از دریافت موفقیت آمیز پاسخ اجرا می‌شود.


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

برای بازیابی یک محصول با استفاده از مشخصه‌ی آن، یک درخواست از نوع GET به آدرس "api/products/id/" ارسال کنید. id، مشخصه‌ی محصول است. کد ذیل را در ادامه‌ی کد قبل و پیش از تگ <script/> قرار دهید.

function find() {
    var id = $('#prodId').val();
    $.getJSON("api/products/" + id,
        function (data) {
            var str = data.Name + ': $' + data.Price;
            $('#product').html(str);
        })
    .fail(
        function (jqXHR, textStatus, err) {
            $('#product').html('Error: ' + err); 
        });
}


باز هم از متد getJSON استفاده کردیم، اما این بار مقدار id برای آدرس از یک Text Box خوانده و آدرس ایجاد می‌شود. پاسخ دریافتی، یک محصول در قالب JSON است.


اجرای پروژه

پروژه را با فشردن کلید F5 اجرا کنید. پس از نمایش فرم، تمامی محصولات بر روی صفحه نمایش داده می‌شوند. عدد 1 را وارد و بر روی دکمه‌ی Search کلیک کنید، محصولی که مشخصه‌ی آن 1 است نمایش داده می‌شود (شکل ذیل).

اگر مشخصه ای را وارد کنید که وجود ندارد، خطای 404 با مضمون "Error: Not Found" بر روی صفحه نمایش داده می‌شود و در صورتی که به جای عدد، عبارتی غیر عددی وارد کنید، خطای 400 با مضمون: "Error: Bad Request" نمایش داده می‌شود. در Web API، تمامی پاسخ‌ها باید در قالب کدهای وضعیت HTTP باشند (شکل ذیل). این یکی از اصول اساسی کار با وب سرویس‌ها است. وفادار ماندن به مفاهیم پایه‌ی وب، دید بهتری در مورد اتفاقاتی که می‌افتد به شما می‌دهد. 

در قسمت بعد با مفهوم مسیریابی در ASP.NET Web API آشنا می‌شوید.

مطالب
مدیریت ساده‌تر امور Async (غیر همزمان) در نسخه‌ی بعدی زبان‌های دات نتی

چندی قبل مطلبی را در این سایت در مورد معرفی الگویی که توسط آن می‌توان اعمال غیر همزمان را به صورت پی در پی انجام داد، مطالعه کردید:

و بحث اصلی مطالب فوق هم این است:
"در برنامه نویسی متداول همیشه عادت داریم که اعمال به صورت A –> B –> C انجام شوند. اما در Async programming ممکن است ابتدا C انجام شود، سپس A و بعد B یا هر حالت دیگری صرفنظر از تقدم و تاخر آن‌ها در حین معرفی متدهای مرتبط در یک قطعه کد. همچنین میزان خوانایی این نوع کدنویسی نیز مطلوب نیست...."

خبر خوش آن است که پشتیبانی از این نوع مدل پی در پی برنامه نویسی در نگارش‌های بعدی سی شارپ و VB.NET اضافه شده است.



لیستی از مقالات منتشر شده در این مورد را در ادامه ملاحظه خواهید کرد:

علاوه بر آن یک سری ویدیوی مرتبط با این بحث نیز منتشر شده است:

مطالب
برنامه نویسی اندروید با Xamarin.Android - قسمت دوم
اولین برنامه‌ی Xamarin:
پروژه‌ی جدیدی را در ویژوال استودیو از نوع Android(Blank) Project ایجاد نمایید. اگر در حال حاضر برنامه را اجرا نمایید، ویژوال استودیو شبیه ساز مورد نظر را اجرا می‌کند و بعد از آن Package برنامه‌ی شما را ساخته و برنامه را در شبیه ساز اجرا می‌کند (ما در قسمت قبل Xamarin Android Player را معرفی کردیم).
بیایید یک نگاهی به Solution برنامه بیندازیم. برنامه از یک پروژه تشکیل شده است. پروژه شامل بخش‌های مختلفی می‌باشد.
یکی از بخش‌های مهم آن، Properties می‌باشد که شامل چندین بخش می‌شود. قسمت Application در قسمت اول توضیح داده شد. قسمت‌های دیگر هم به مرور بررسی می‌شوند.
فولدر Asset می‌تواند شامل فایل‌ها، فونت‌ها و هر چیزی که برنامه‌ی ما احتیاج دارد باشد (حتی دیتابیس).
فولدر Resource که در زیر مجموعه‌ی آن:
- فولدر drawable وجود دارد که حاوی آیکن‌ها و تصاویر و همچنین استایل‌های ما می‌باشد.
- فولدر layout که  طرح های(layout) برنامه را شامل می‌شود.
- فولدر value که شامل مجموعه‌ای از فایل‌های XML می‌باشد که می‌تواند شامل stringها، colorها و مقادیر عددی ثابت باشد.
- فولدر menu که منوهای برنامه را در خود جای داده است.
و البته منابع(Resources) دیگری که به صورت پیش فرض تعبیه شده‌اند و می‌توان از آن‌ها استفاده کرد مانند: anim، animator، color، raw، xml.
Resourceها مزایای زیادی برای ما دارند! کدهای برنامه را از تصاویر، متن‌ها، آیکن‌ها، منوها و انیمیشن‌ها و ... جدا می‌کند و به راحتی پشتیبانی از تنظیمات مختلف دستگاه‌ها را برای ما فراهم می‌نماید. برای مثال بدون نیاز به کدنویسی می‌توانید با دستگاه‌های مختلفی از لحاظ سایز و Local و ... ارتباط برقرار نمایید.
Resourceها به صورت static در اختیار کدهای برنامه قرار می‌گیرند و در زمان کامپایل چک می‌شوند و احتیاجی به اجرای برنامه برای اطمینان از صحت آن‌ها وجود ندارد.
 فایل Resource.Designer.cs که برای دسترسی از طریق کد به منابع تعبیه شده و به ازای هر یک از منابع مقداری را از طریق پروپرتی‌های static در اختیار ما قرار می‌دهد. شما به هیچ وجه آن را تغییر ندهید؛ اما اجازه‌ی مشاهده‌ی کلاس را دارید!
public partial class Resource {
    public partial class Attribute
    {
    }
    public partial class Drawable {
        public const int Icon=0x7f020000;
    }
    public partial class Id
    {
        public const int Textview=0x7f050000;
    }
    public partial class Layout
    {
        public const int Main=0x7f030000;
    }
    public partial class String
    {
        public const int App_Name=0x7f040001;
        public const int Hello=0x7f040000;
    }
}
نحوه‌ی استفاده از Resourceها در کد به این صورت می‌باشد:
@[<PackageName>.]Resource.<ResourceType>.<ResourceName>
که از سمت چپ به ترتیب شامل:
  • نام Package که برای منابع پروژه‌ی جاری نیازی به ذکر آن نیست.
  • Resource که همیشه قید می‌شود.
  • <ResourceType> نوع منبع را مشخص می‌کند و می‌تواند Id، String، Color، Layout،Drawable و ... باشد.
  • <ResourceName> نام منبع را مشخص مینماید.

برای استفاده‌ی از منابع در XML به صورت زیر عمل می‌کنیم:
@[<PackageName>:]<ResourceType>/<ResourceName>
به سلوشن برمی‌گردیم و به سراغ کلاس‌های Activity می‌رویم.
Activityها اساس ساختمان برنامه‌های اندرویدی می‌باشند و در واقع Screen‌های ما هستند و در طول عمرشان (از ایجاد تا تخریب) شامل حالت‌های زیادی می‌باشند. نحوه‌ی اجرای برنامه‌ها در اندروید بسیار متفاوت است با برنامه‌های رایج. معمولا برای شروع یک برنامه، تابعی static با نام main وجود دارد که نقطه‌ی شروع برنامه می‌باشد. در اندروید هر کلاسی می‌تواند به عنوان نقطه‌ی شروع برنامه باشد. البته فقط یک Activity می‌تواند شروع کننده باشد. اما اگر برنامه crash کند و یا توسط اندروید متوقف شود، سیستم عامل نیز می‌تواند از همان نقطه‌ی توقف و یا هر نقطه‌ی دیگری برنامه را دوباره اجرا نماید. 
Activityها برای هر حالت دارای یک متد هستند و به اندروید کمک می‌کنند تا Activityهایی را که زمان زیادی مورد استفاده قرار نگرفته‌اند، تشخیص داده و حافظه و منابع را مدیریت کند. در شکل زیر حالت‌های مختلف یک Activity را می‌توانید مشاهده نمایید.

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

OnCreate: اولین متدی می‌باشد که موقع ایجاد Activity فراخوانی می‌شود. این متد همیشه برای مقداردهی اولیه override می‌شود. شما می‌توانید برای ساخت ویوها، مقداردهی متغیرها و همچنین مقداردهی لیست‌ها از آن استفاده نمایید. آرگومان ورودی این متد (bundle) از نوع کلاس Bundle می‌باشد و در صورتی که null نباشد، یعنی برنامه Restart شده (با توجه به تصویر حالت‌های مختلف Activity) و اگر null باشد، یعنی برنامه شروع به کار نموده است. از این bundle که در واقع یک دیکشنری است می‌توان برای نگهداری حالت‌های برنامه استفاده نمود.
OnStart: همیشه بعد از OnCreate اجرا می‌شود و برای انجام کارهایی که لازم داریم قبل از نمایش Activity به کاربر مورد استفاده قرار می‌گیرد.
OnResume: بعد از نمایش Activity به کاربر این متد اجرا می‌شود. از این متد می‌توان برای اجرای انیمیشن‌ها، گوش دادن به بروزرسانی‌های GPS، نمایش پیغام و ... در ابتدای نمایش Activity استفاده کرد.
OnPause: این متد موقعی اجرا می‌شود که برنامه به Background برود. شما اگر می‌خواهید کارهایی مانند:
  • آزادسازی منابع
  • بستن دیالوگ‌های باز شده!
  • ذخیره سازی اطلاعات تایید نشده
  • توقف انیمیشن‌ها و ...
را انجام دهید باید این متد را override نمایید. نکته‌ی مهم این است که یکی از دو متد OnResume و OnStop امکان دارد بعد از این متد اجرا شوند (به همین دلیل یکی از مهمترین متدهای برنامه، OnResume می‌باشد).
OnStop: زمانیکه یک Activity، دیگر به کاربر نمایش داده نمی‌شود، این متد اجرا می‌شود و در یکی از حالت‌ها زیر این اتفاق می‌افتد:
  • یک Activity جدید اجرا شود.
  • فعالیت یک Activity که قبلا اجرا شده، ادامه پیدا کند.
  • برنامه متوقف گردد.
ممکن است در بعضی مواقع به دلیل کمبود حافظه این متد اجرا نشود.
OnDestroy: زمانی که برنامه کاملا از حافظه پاک شود، این متد اجرا می‌گردد.
OnRestart: این متد بعد از Stop شدن یک Activity و قبل از Start دوباره‌ی آن اجرا می‌شود.

خوب! به سراغ متد OnCreate داخل Activity که به صورت اتوماتیک ایجاد شده می‌رویم. در جاوا باید تمام Activityها را در Manifest معرفی نماییم و نقطه‌ی شروع را مشخص کنیم. اما همیشه در سی شارپ کار برای ما راحت‌تر بوده است! نحوه‌ی کار به این صورت است که با اجرای برنامه‌ی ما، آن کلاسی که از Activity ارث برده باشد و با ActivityAttribute با مقدار ورودی  MainLauncher =  true  مزین شده باشد، اجرا می‌شود.
با استفاده از SetContentView می‌توانیم یک ویو (View) را برای نحوه‌ی نمایش Activity مشخص کنیم و باید از قبل ویو را در پوشه‌ی Layout ساخته باشیم که البته این کار بصورت اتوماتیک انجام شده است.

مطالب
آموزش زمانبندی کارها با HangFire در Asp.Net Core

تسک‌های پس زمینه (Background Job) چیست؟

بطور کلی تسک‌های پس زمینه، کارهایی هستند که برنامه باید بصورت خودکار در زمان‌های مشخصی آن‌ها را انجام دهد؛ برای مثال شرایطی را در نظر بگیرید که متدی را با حجم زیادی از محاسبات پیچیده دارید که وقتی کاربر درخواست خود را ارسال میکند، شروع به محاسبه میشود و کاربر چاره‌ای جز انتظار نخواهد داشت؛ اما اگر اینکار در زمانی دیگر، قبل از درخواست کاربر محاسبه میشد و صرفا نتیجه‌اش به کاربر نمایش داده میشد، قطعا تصمیم گیری بهتری نسبت به محاسبه‌ی آنی آن متد، در زمان درخواست کاربر بوده.
در سناریوی دیگری تصور کنید میخواهید هر شب در ساعتی مشخص، خلاصه‌ای از مطالب وب‌سایتتان را برای کاربران وبسایت ایمیل کنید. در این حالت برنامه باید هر شب در ساعتی خاص اینکار را برای ما انجام دهد و تماما باید این اتفاق بدون دخالت هیچ اراده‌ی انسانی و بصورت خودکار توسط برنامه انجام گیرد.
همچنین شرایطی از این قبیل، ارسال ایمیل تایید هویت، یک ساعت بعد از ثبت نام، گرفتن بک آپ از اطلاعات برنامه بصورت هفتگی و دیگر این موارد، همه در دسته‌ی تسک‌های پس زمینه (Background Job) از یک برنامه قرار دارند.


سؤال : HangFire چیست؟

همانطور که دانستید، تسک‌های پس زمینه نیاز به یک سیستم مدیریت زمان دارند که کارها را در زمان‌های مشخص شده‌ای به انجام برساند. HangFire یک پکیج متن باز، برای ایجاد سیستم زمانبندی شده‌ی کارها است و اینکار را به ساده‌ترین روش، انجام خواهد داد. همچنین HangFire در کنار Quartz که سیستم دیگری جهت پیاده سازی زمانبندی است، از معروف‌ترین پکیج‌ها برای زمانبندی تسک‌های پس زمینه بشمار میروند که در ادامه بیشتر به مزایا و معایب این دو می‌پردازیم. 

مقایسه HangFire و Quartz 

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

دیتابیس :

تفاوتی که میتوان از آن نام برد، وجود قابلیت Redis Store در HangFire است که Quratz چنین قابلیتی را از سمت خودش ارائه نداده و برای استفاده از Redis در Quartz باید شخصا این دو را باهم کانفیگ کنید. دیتابیس Redis بخاطر ساختار دیتابیسی که دارد، سرعت و پرفرمنس بالاتری را ارائه میدهد که استفاده از این قابلیت، در پروژه‌هایی با تعداد تسک‌ها و رکورد‌های زیاد، کاملا مشهود است. البته این ویژگی در HangFire رایگان نیست و برای داشتن آن از سمت HangFire، لازم است هزینه‌ی آن را نیز پرداخت کنید؛ اما اگر هم نمیخواهید پولی بابتش بپردازید و همچنان از آن استفاده کنید، یک پکیج سورس باز برای آن نیز طراحی شده که از این لینک میتوانید آن‌را مشاهده کنید. 

ساختار :

پکیج HangFire از ابتدا با دات نت و معماری‌های دات نتی توسعه داده شده، اما Quartz ابتدا برای زبان جاوا نوشته شده بود و به نوعی از این زبان، ریلیزی برای دات نت تهیه شد و این موضوع طبعا تاثیرات خودش را داشته و برخی از معماری‌ها و تفکرات جاوایی در آن مشهود است که البته مشکلی را ایجاد نمیکند و محدودیتی نسبت به HangFire از لحاظ کارکرد، دارا نیست. شاید تنها چیزی که میتوان در این باب گفت، DotNet Friendly‌تر بودن HangFire است که کار با متد‌های آن، آسان‌تر و به اصطلاح، خوش دست‌تر است.

داشبورد :

هردو پکیج از داشبورد، پشتیبانی میکنند که میتوانید در این داشبورد و ui اختصاصی که برای نمایش تسک‌ها طراحی شده، تسک‌های ایجاد شده را مدیریت کنید. داشبورد HangFire بصورت پیشفرض همراه با آن قرار دارد که بعد از نصب HangFire میتوانید براحتی داشبورد سوار بر آن را نیز مشاهده کنید. اما در Quartz ، داشبورد باید بصورت یک Extension، در پکیجی جدا به آن اضافه شود و مورد استفاده قرار گیرد. در لینک‌های 1 و 2، دوتا از بهترین داشبورد‌ها برای Quartz را مشاهده میکنید که در صورت نیاز میتوانید از آن‌ها استفاده کنید. 

استفاده از HangFire

1. نصب :

  • برای نصب HangFire در یک پروژه‌ی Asp.Net Core لازم است ابتدا پکیج‌های مورد نیاز آن را نصب کنید؛ که شامل موارد زیر است:
Install-Package Hangfire.Core 
Install-Package Hangfire.SqlServer
Install-Package Hangfire.AspNetCore
  • پس از نصب پکیج‌ها باید تنظیمات مورد نیاز برای پیاده سازی HangFire را در برنامه، اعمال کنیم. این تنظیمات شامل افزودن سرویس‌ها و اینترفیس‌های HangFire به برنامه است که اینکار را با افزودن HangFire به متد ConfigureService کلاس Startup انجام خواهیم داد: 
public void ConfigureServices(IServiceCollection services)
{
    // Add Hangfire services.
    services.AddHangfire(configuration => configuration
        .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
        .UseSimpleAssemblyNameTypeSerializer()
        .UseRecommendedSerializerSettings()
        .UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions
        {
            CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
            SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
            QueuePollInterval = TimeSpan.Zero,
            UseRecommendedIsolationLevel = true,
            DisableGlobalLocks = true
        }));

    // Add the processing server as IHostedService
    services.AddHangfireServer();

    // Add framework services.
    services.AddMvc();
}
  • پکیج HangFire برای مدیریت کار و زمان ، Table هایی دارد که پس از نصب، بر روی دیتابیس برنامه‌ی شما قرار میگیرد. فقط باید دقت داشته باشید ConnectionString دیتابیس خود را در متد AddHangFire مقدار دهی کنید، تا از این طریق دیتابیس برنامه را شناخته و Table‌های مورد نظر را در Schema جدیدی با نام HangFire به آن اضافه کند. 

پ ن : HangFire بصورت پیش‌فرض با دیتابیس SqlServer ارتباط برقرار میکند.

  • این پکیج یک داشبورد اختصاصی دارد که در آن لیستی از انواع تسک‌های در صف انجام و گزارشی از انجام شده‌ها را در اختیار ما قرار میدهد. برای تنظیم این داشبورد باید Middleware مربوط به آن و endpoint جدیدی را برای شناسایی مسیر داشبورد HangFire در برنامه، در متد Configure کلاس Startup اضافه کنید : 
public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IHostingEnvironment env)
{
    // HangFire Dashboard
    app.UseHangfireDashboard();

     app.UseEndpoints(endpoints =>
     {
         endpoints.MapControllers();
         
         // HangFire Dashboard endpoint
         endpoints.MapHangfireDashboard();
     });
}

برای اینکه به داشبورد HangFire دسترسی داشته باشید، کافیست پس از نصب و انجام تنظیمات مذکور، برنامه را اجرا کنید و در انتهای Url برنامه، کلمه‌ی "hangfire" را وارد کنید. سپس وارد پنل داشبورد آن خواهید شد. 
http://localhost:50255/hangfire
البته میتوانید آدرس داشبورد HangFire را در برنامه، از کلمه‌ی "hangfire" به هر چیزی که میخواهید شخصی سازی کنید. برای اینکار کافیست درون Middleware تعریف شده بصورت ورودی string، آدرس جدیدی را برای HangFire تعریف کنید. 
app.UseHangfireDashboard("/mydashboard");
و به طبع در Url : 
http://localhost:50255/mydashboard

2. داشبورد :

داشبورد HangFire شامل چندین بخش و تب مختلف است که به اختصار هر یک را بررسی خواهیم کرد. 



تب Job :
همه‌ی تسک‌های تعریف شده، شامل Enqueued, Succeeded, Processing, Failed و... در این تب نشان داده میشوند. 


تب Retries :
این تب مربوط به تسک‌هایی است که در روال زمانبندی و اجرا، به دلایل مختلفی مثل Stop شدن برنامه توسط iis یا Down شدن سرور و یا هر عامل خارجی دیگری، شکست خوردند و در زمانبندی مشخص شده، اجرا نشدند. همچنین قابلیت دوباره‌ی به جریان انداختن job مورد نظر را در اختیار ما قرار میدهد که از این طریق میتوان تسک‌های از دست رفته را مدیریت کرد و دوباره انجام داد. 


تب Recurring Jobs :
وقتی شما یک تسک را مانند گرفتن بکاپ از دیتابیس، بصورت ماهانه تعریف میکنید و قرار است در هر ماه، این اتفاق رخ دهد، این مورد یک تسک تکراری تلقی شده و این تب مسئول نشان دادن اینگونه از تسک‌ها میباشد. 


تب Servers :
این بخش، سرویس‌هایی را که HangFire برای محاسبه‌ی زمانبندی از آن‌ها استفاده میکند، نشان میدهد. وقتی متد services.AddHangfireServer را به متد ConfigureService کلاس Startup اضافه میکنید، سرویس‌های HangFire جهت محاسبه‌ی زمانبندی‌ها فعال میشوند. 


3. امنیت داشبورد :

همانطور که دانستید، داشبورد، اطلاعات کاملی از نوع کارها و زمان اجرای آن‌ها و نام متدها را در اختیار ما قرار میدهد و همچنین اجازه‌ی تغییراتی را مثل حذف یک تسک، یا دوباره به اجرا در آوردن تسک‌ها و یا اجرای سریع تسک‌های به موعد نرسیده را به کاربر میدهد. گاهی ممکن است این اطلاعات، شامل محتوایی امنیتی و غیر عمومی باشد که هرکسی در برنامه حق دسترسی به آن‌ها را ندارد. برای مدیریت کردن این امر، میتوانید مراحل زیر را طی کنید :
مرحله اول : یک کلاس را ایجاد میکنیم (مثلا با نام MyAuthorizationFilter) که این کلاس از اینترفیسی با نام IDashboardAuthorizationFilter ارث بری خواهد کرد. 
public class MyAuthorizationFilter : IDashboardAuthorizationFilter
{
    public bool Authorize(DashboardContext context)
    {
        var httpContext = context.GetHttpContext();

        // Allow all authenticated users to see the Dashboard (potentially dangerous).
        return httpContext.User.Identity.IsAuthenticated;
    }
}
درون این کلاس، متدی با نام Authorize از اینترفیس مربوطه، پیاده سازی میشود که شروط احراز هویت و صدور یا عدم صدور دسترسی را کنترل میکند. این متد، یک خروجی Boolean دارد که اگر هر یک از شروط احراز هویت شما تایید نشد، خروجی false را بر میگرداند. در این مثال، ما برای دسترسی، محدودیت Login بودن را اعمال کرده‌ایم که این را از HttpContext میگیریم.

مرحله دوم : در این مرحله کلاسی را که بعنوان فیلتر احراز هویت برای کاربران ساخته‌ایم، در option‌های middleware پکیج HangFire اضافه میکنیم. 
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
    Authorization = new [] { new MyAuthorizationFilter() }
});
یکی دیگر از option‌های این middleware که میتوان برای کنترل دسترسی در HangFire استفاده کرد، گزینه‌ی Read-only view نام دارد. 
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
    IsReadOnlyFunc = (DashboardContext context) => true
});
این گزینه اجازه‌ی هرگونه تغییری را در روند تسک‌ها، از طریق صفحه‌ی داشبورد، از هر کاربری سلب میکند و داشبورد را صرفا به جهت نمایش کار‌ها استفاده میکند نه چیز دیگر.



انواع تسک‌ها در HangFire :

1. تسک‌های Fire-And-Forget :

تسک‌های Fire-And-Forget زمانبندی خاصی ندارند و بلافاصله بعد از فراخوانی، اجرا میشوند. برای مثال شرایطی را در نظر بگیرید که میخواهید پس از ثبت نام هر کاربر در وب‌سایت، یک ایمیل خوش آمد گویی ارسال کنید. این عمل یک تسک پس زمینه تلقی میشود، اما زمانبندی خاصی را نیز نمیخواهید برایش در نظر بگیرید. در چنین شرایطی میتوانید از متد Enqueue استفاده کنید و یک تسک Fire-And-Forget را ایجاد کنید تا این تسک صرفا در تسک‌های پس زمینه‌تان نام برده شود و قابل مشاهده باشد. 
public class HomeController : Controller
    {
        private readonly IBackgroundJobClient _backgroundJobClient;
        public HomeController(IBackgroundJobClient backgroundJobClient)
        {
            _backgroundJobClient = backgroundJobClient;
        }
    
         [HttpPost]
         [Route("welcome")]
         public IActionResult Welcome(string userName)
         {
             var jobId = _backgroundJobClient.Enqueue(() => SendWelcomeMail(userName));
             return Ok($"Job Id {jobId} Completed. Welcome Mail Sent!");
         }

         public void SendWelcomeMail(string userName)
         {
             //Logic to Mail the user
             Console.WriteLine($"Welcome to our application, {userName}");
         }    
    }
همانطور که میبینید در مثال بالا ابتدا برای استفاده از تسک‌های Fire-and-Forget در HangFire باید اینترفیس IBackgroundJobClient را تزریق کنیم و با استفاده از متد Enqueue در این اینترفیس، یک تسک پس زمینه را ایجاد میکنیم که کار آن، فراخوانی متد SendWelcomeMail خواهد بود.

2. تسک‌های Delayed :

همانطور که از اسم آن پیداست، تسک‌های Delayed، تسک‌هایی هستند که با یک تاخیر در زمان، اجرا خواهند شد. بطور کلی زمانبندی این تسک‌ها به دو دسته تقسیم میشود :

  • دسته اول : اجرا پس از تاخیر در زمانی مشخص.
همان شرایط ارسال ایمیل را به کاربرانی که در وبسایتتان ثبت نام میکنند، در نظر بگیرید؛ اما اینبار میخواهید نه بلافاصله، بلکه 10 دقیقه بعد از ثبت نام کاربر، ایمیل خوش آمد گویی را ارسال کنید. در این نوع شما یک تاخیر 10 دقیقه‌ای میخواهید که Delayed Job‌ها اینکار را برای ما انجام میدهند. 
public class HomeController : Controller
    {
        private readonly IBackgroundJobClient _backgroundJobClient;
        public HomeController(IBackgroundJobClient backgroundJobClient)
        {
            _backgroundJobClient = backgroundJobClient;
        }
    
         [HttpPost]
         [Route("welcome")]
         public IActionResult Welcome(string userName)
         {
             var jobId = BackgroundJob.Schedule(() => SendWelcomeMail(userName),TimeSpan.FromMinutes(10));
             return Ok($"Job Id {jobId} Completed. Welcome Mail Sent!");
         }

         public void SendWelcomeMail(string userName)
         {
             //Logic to Mail the user
             Console.WriteLine($"Welcome to our application, {userName}");
         }    
    }
در این مثال با استفاده از متد Schedule در اینترفیس IBackgroundJobClient توانستیم متد SendWelcomeMail را صدا بزنیم و با ورودی TimeSpan یک تاخیر 10 دقیقه‌ای را در متد HangFire اعمال کنیم.
همچنین میتوانید از ورودی‌های دیگر نوع TimeSpan شامل TimeSpan.FromMilliseconds و TimeSpan.FromSecondsو TimeSpan.FromMinutes و TimeSpan.FromDays برای تنظیم تاخیر در تسک‌های خود استفاده کنید.

  • دسته دوم : اجرا در زمانی مشخص.
نوع دیگر استفاده از متد Schedule، تنظیم یک تاریخ و زمان مشخصی برای اجرا شدن تسک‌های در آن تاریخ و زمان واحد میباشد. برای مثال سناریویی را در نظر بگیرید که دستور اجرا و زمانبندی آن، در اختیار کاربر باشد و کاربر بخواهد یک Reminder، در تاریخ مشخصی برایش ارسال شود که در اینصوررت میتوانید با استفاده از instance دیگری از متد Schedule که ورودی ای از جنس DateTimeOffset را دریافت میکند، تاریخ مشخصی را برای آن اجرا، انتخاب کنید. 
public class HomeController : Controller
    {
        private readonly IBackgroundJobClient _backgroundJobClient;
        public HomeController(IBackgroundJobClient backgroundJobClient)
        {
            _backgroundJobClient = IBackgroundJobClient;
        }
    
         [HttpPost]
         [Route("welcome")]
         public IActionResult Welcome(string userName , DateTime dateAndTime)
         {
             var jobId = BackgroundJob.Schedule(() => SendWelcomeMail(userName),DateTimeOffset(dateAndTime));
             return Ok($"Job Id {jobId} Completed. Welcome Mail Sent!");
         }

         public void SendWelcomeMail(string userName)
         {
             //Logic to Mail the user
             Console.WriteLine($"Welcome to our application, {userName}");
         }    
    }
در این مثال، تاریخ مشخصی را برای اجرای تسک‌های خود، از کاربر، در ورودی اکشن متد دریافت کرده‌ایم و به متد Schedule، در غالب DateTimeOffset تعریف شده، پاس میدهیم.

3. تسک‌های Recurring :

تسک‌های Recurring به تسک‌هایی گفته میشود که باید در یک بازه‌ی گردشی از زمان اجرا شوند. در یک مثال، بیشتر با آن آشنا خواهیم شد. فرض کنید میخواهید هر هفته، برنامه از اطلاعات دیتابیس موجود بکاپ بگیرد. در اینجا تسکی را دارید که قرار است هر هفته و هربار به تکرر اجرا شود. 
public class HomeController : Controller
    {
        private readonly IRecurringJobManager _recurringJobManager;
        public HomeController(IRecurringJobManager recurringJobManager)
        {
            _recurringJobManager = recurringJobManager;
        }
    
         [HttpPost]
         [Route("BackUp")]
         public IActionResult BackUp(string userName)
         {
             _recurringJobManager.AddOrUpdate("test", () => BackUpDataBase(), Cron.Weekly);
             return Ok();
         }

         public void BackUpDataBase()
         {
            // ...
         }    
    }
برای تنظیم یک Recurring Job باید اینترفیس دیگری را بنام IRecurringJobManager، تزریق کرده و متد AddOrUpdate را استفاده کنید. در ورودی این متد، یک نوع تعریف شده در HangFire بنام Cron دریافت میشود که بازه‌ی گردش در زمان را دریافت میکند که در اینجا بصورت هفتگی است.

انواع دیگر Cron شامل :

  • هر دقیقه (Cron.Minutely) :
این Cron هر دقیقه یکبار اجرا خواهد شد. 
 _recurringJobManager.AddOrUpdate("test", () => job , Cron.Minutely);
  • هر ساعت (Cron.Hourly) :
این Cron هر یک ساعت یکبار و بصورت پیش‌فرض در دقیقه اول هر ساعت اجرا میشود. 
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Hourly);
اما میتوانید یک ورودی دقیقه به آن بدهید که در اینصورت در N اُمین دقیقه از هر ساعت اجرا شود. 
 _recurringJobManager.AddOrUpdate("test", () => Job, Cron.Hourly(10));
  • هر روز (Cron.Daily) :
این Cron بصورت روزانه و در حالت پیشفرض، در اولین ساعت و اولین دقیقه‌ی هر روز اجرا خواهد شد. 
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Daily);
در حالتی دیگر میتوانید ورودی ساعت و دقیقه را به آن بدهید تا در ساعت و دقیقه‌ای مشخص، در هر روز اجرا شود. 
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Daily(3,10));
  • هر هفته (Cron.Weekly) :
این Cron هفتگی است. بصورت پیشفرض هر هفته، شنبه در اولین ساعت و در اولین دقیقه، اجرا میشود. 
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Weekly);
در حالتی دیگر چندمین روز هفته و ساعت و دقیقه مشخصی را در ورودی میگیرد و حول آن میچرخد. 
_recurringJobManager.AddOrUpdate("test", () => Job,Cron.Weekly(DayOfWeek.Monday,3,10));
  • هر ماه (Cron.Monthly) :
این Cron بصورت ماهانه اولین روز ماه در اولین ساعت روز و در اولین دقیقه ساعت، زمانبندی خود را اعمال میکند. 
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Monthly);
و در صورت دادن ورودی میتوانید زمانبندی آن در چندمین روز ماه، در چه ساعت و دقیقه‌ای را نیز تنظیم کنید. 
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Monthly(10,3,10));
  • هر سال (Cron.Yearly) :
و در نهایت این Cron بصورت سالانه و در اولین ماه، روز، ساعت و دقیقه هر سال، وظیفه خود را انجام خواهد داد. 
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Yearly);
که این‌هم مانند بقیه، ورودی‌هایی دریافت میکند که به ترتیب شامل ماه، روز، ساعت و دقیقه است. 
_recurringJobManager.AddOrUpdate("test", () => Job, Cron.Yearly(2,4,3,10));
در نهایت با استفاده از این Cron‌ها میتوانید انواع مختلفی از Recurring Job‌ها را بسازید.

4. تسک‌های Continuations :

این نوع از تسک‌ها، وابسته به تسک‌های دیگری هستند و بطور کلی وقتی استفاده میشوند که ما میخواهیم تسکی را پس از تسک دیگری، با یک زمانبندی، به نسبت زمان اجرای تسک اول، اجرا کنیم. برای مثال میخواهیم 10 دقیقه بعد از ثبت نام کاربر، برای او ایمیل احراز هویت ارسال شود که شبیه اینکار را در تسک‌های Delayed انجام داده بودیم. اما همچنین قصد داریم 5 دقیقه بعد از ارسال ایمیل احراز هویت، لینک فرستاده شده را منسوخ کنیم. در این سناریو ما دو زمانبندی داریم؛ اول 10 دقیقه بعد از ثبت نام کاربر و دوم 5 دقیقه بعد از اجرای متد اول. 
var stepOne = _backgroundJobClient.Schedule(() => SendAuthorizationEmail(), TimeSpan.FromMinute(10));
_backgroundJobClient.ContinueJobWith(stepOne, () => _backgroundJobClient.Schedule(() => ExpireAuthorizationEmail(), TimeSpan.FromHours(5)));
برای ایجاد یک Continuations Job باید از متد ContinueJobWith در اینترفیس IBackgroundJobClient استفاده کنیم و در ورودی اول این متد، آیدی تسک ایجاد شده قبلی را پاس دهیم.

برخی از نکات و ترفند‌های HangFire

1. استفاده از Cron Expression در Recurring Job‌ها :

بطور کلی، Cron‌، ساختاری تعریف شده برای تعیین بازه‌های زمانی است. Cron اختصار یافته‌ی کلمات Command Run On میباشد که به اجرا شدن یک دستور، در زمان مشخصی اشاره دارد. برای استفاده از آن، ابتدا به تعریف این ساختار میپردازیم : 
* * * * * command to be executed
- - - - -
| | | | |
| | | | ----- Day of week (0 - 7) (Sunday=0 or 7)
| | | ------- Month (1 - 12)
| | --------- Day of month (1 - 31)
| ----------- Hour (0 - 23)
------------- Minute (0 - 59)
این ساختار را از پایین به بالا در زیر برایتان تشریح میکنیم : 
* * * * *
  • فیلد اول (Minute) : در این فیلد باید دقیقه‌ای مشخص از یک ساعت را وارد کنید؛ مانند دقیقه 10 (میتوانید محدوده‌ای را هم تعیین کنید)
  • فیلد دوم (Hour) : در این فیلد باید زمان معلومی را با فرمت ساعت وارد کنید؛ مانند ساعت 7 (میتوانید محدوده‌ای  را هم تعیین کنید، مانند ساعات 12-7)
  • فیلد سوم (Day of Month) : در این فیلد باید یک روز از ماه را وارد کنید؛ مانند روز 15 ام از ماه (میتوانید محدوده‌ای را هم تعیین کنید)
  • فیلد چهارم (Month) : در این فیلد باید یک ماه از سال را وارد کنید؛ مثلا ماه 4 ام(آوریل) (میتوانید محدوده‌ای را هم تعیین کنید)
  • فیلد پنجم (Day of Week) : در این فیلد باید روزی از روز‌های هفته یا محدوده‌ای از آن روز‌ها را تعیین کنید. مانند صفرم هفته که در کشور‌های اروپایی و آمریکایی معادل روز یکشنبه است.
همانطور که میبینید، Cron‌ها دسترسی بهتری از تعیین بازه‌های زمانی مختلف را ارائه میدهند که میتوانید از آن در Recurring متد‌ها بجای ورودی‌های Yearly - Monthly - Weekly - Daily - Hourly - Minutely استفاده کنید. در واقع خود این ورودی‌ها نیز متدی تعریف شده در کلاس Cron هستند که با فراخوانی آن، خروجی Cron Expression را میسازند و در درون ورودی متد Recurring قرار میگیرند.

در ادامه مثالی را خواهیم زد تا نیازمندی به Cron Expression‌ها را بیشتر درک کنید. فرض کنید میخواهید یک زمانبندی داشته باشید که "هر ماه بین بازه 10 ام تا 15 ام، بطور روزانه در ساعت 4:00" اجرا شود. اعمال این زمانبندی با متد‌های معمول در کلاس Cron امکان پذیر نیست؛ اما میتوانید با Cron Expression آن‌را اعمال کنید که به این شکل خواهد بود: 
0 4 10-15 * *
برای ساخت Cron Expression‌ها وبسایت هایی وجود دارند که کمک میکنند انواع Cron Expression‌های پیچیده‌ای را طراحی کنیم و با استفاده از آن، زمانبندی‌های دقیق‌تر و جزئی‌تری را بسازیم. یکی از بهترین وبسایت‌ها برای اینکار crontab.guru است.

پ. ن. برای استفاده از Cron Expression در متد‌های Recurring کافی است بجای ورودی‌های Yearly - Monthly - Weekly - Daily - Hourly - Minutely ، خود Cron Expression را درون ورودی متد تعریف کنیم : 
 _recurringJobManager.AddOrUpdate("test", () => job , "0 4 10-15 * *" );


2. متد Trigger :

متد Trigger یک متد برای اجرای آنی تسک‌های Recurring است که به کمک آن میتوانید این نوع از تسک‌ها را بدون در نظر گرفتن زمانبندی آن‌ها، در لحظه اجرا کنید و البته تاثیری در دفعات بعدی تکرار نداشته باشد. 
RecurringJob.Trigger("some-id");


3. تعیین تاریخ انقضاء برای Recurring Job‌ها :

گاهی ممکن است در تسک‌های Recurring شرایطی پیش آید که برفرض میخواهید کاری را هر ماه انجام دهید، اما این تکرار در پایان همان سال تمام میشود. در اینصورت باید یک Expire Time برای متد Recurring خود تنظیم کنیم تا بعد از 12 ماه تکرار، در تاریخ 140X/12/30 به پایان برسد. HangFire برای متد‌های Recurring ورودی با عنوان ExpireTime تعریف نکرده، اما میتوان از طریق ایجاد یک زمانبندی، تاریخ مشخصی را برای حذف کردن متد Recurring تعریف کرد که همانند یک ExpireTime عمل میکند. 
_recurringJobManager.AddOrUpdate("test", () => Console.WriteLine("Recurring Job"), Cron.Monthly);
_backgroundJobClient.Schedule(() => _recurringJobManager.RemoveIfExists("test"), DateTimeOffset(dateAndTime));
با اجرای این متد، اول کاری برای تکرار در زمانبندی ماهیانه ایجاد میشود و در متد دوم، زمانی برای حذف متد اول مشخص میکند.

در آخر امیدوارم این مقاله برایتان مفید واقع شده باشد. میتوانید فیدبکتان را در قالب کامنت یا یک قهوه برایم ارسال کنید.

مطالب
Reflection در ES6
در زبان‌های برنامه‌نویسی مانند سی‌شارپ و یا جاوا می‌توانیم از Reflection جهت خواندن متادیتاها استفاده کنیم. به عنوان مثال امکان تعریف پراپرتی و یا متدها و حتی تایپ‌هایی در زمان اجرا را در اختیارمان قرار می‌دهد. اما از آنجائیکه جاوا اسکریپت یک زبان داینامیک است، این قابلیت کمتر مورد توجه قرار گرفته است. در جاوا اسکریپت حین کار با کلاس‌ها و اشیاء، ممکن است نیاز داشته باشید تا از اعضای یک کلاس کوئری بگیرید و یا اینکه یک سری پراپرتی و متدهایی را در زمان اجرا به اشیاء‌تان اضافه کنید و یا مواردی از این دست.
تا قبل از ES 6 می‌توانستیم به این صورت به اعضای یک کلاس دسترسی داشته باشیم:
var person = {
    name: 'Sirwan',
    family: 'Afifi',
    doWork: function () {
        //....
    }
};

for (var prop in person) {
    console.log(prop); 
}
همانطور که مشاهده می‌کنید، توسط سینتکس for...in می‌توانیم به اعضای person دسترسی داشته باشیم. همچنین برای دسترسی به مقادیر هر کدام از اعضای آن می‌توانستیم از bracket syntax استفاده کنیم:
for (var prop in person) {
    console.log(person[prop]); 
}
لازم به ذکر است می‌توانستیم از متدهایی همانند Array.isArray, Object.getOwnPropertyDescriptor و حتی Object.keys نیز جهت دسترسی به اعضای پنهان یک شیء استفاده کنیم. اکنون قابلیت توکاری با نام Reflect این امکان را به ES 6 اضافه کرده است تا تمام اعمال فوق را به راحتی انجام داد.

Reflect
همانطور که عنوان شد توسط API جدیدی با نام Reflect، می‌توانیم به سادگی اعمال مربوط به Reflection را انجام دهیم. به عنوان مثال برای دسترسی به اعضای شیء person با استفاده از این API می‌توانیم به این صورت عمل کنیم:
var person = {
    name: 'Sirwan',
    family: 'Afifi',
    doWork: function () {
        //....
    }
};

for (let prop of Reflect.enumerate(person)) {
    console.log(`the value for ${prop} is ${person[prop]}`); 
  
}
در کد فوق از متد enumerate استفاده شده است، این متد یک پارامتر target را از ورودی می‌پذیرد. در واقع target همان شیءایی است که می‌خواهید پراپرتی‌های آن را پیمایش کنید. همچنین مقدار بازگشتی این متد یک iterator می‌باشد. مفهوم iterator نیز خیلی ساده است. به زبان ساده، امکان پیمایش درون یک کالکشن را در اختیارمان قرار می‌دهد. نکته‌ی دیگری که در کد فوق وجود دارد، استفاده از سینتکس for..of است، این سینتکس شبیه به for...in عمل می‌کند، با این تفاوت که در اینجا به جای پیمایش درون یکسری keys، درون values پیمایش می‌کنیم. در نتیجه برای پیمایش iterators باید از این سینتکس استفاده شود.

متدهای شیء Reflect
در ادامه تعدادی از متدهای شیء Reflect را مشاهده می‌کنید:
Reflect.get(target, name, [receiver])

Reflect.set(target, name, value, [receiver])

Reflect.has(target, name)

Reflect.apply(target, receiver, args)

Reflect.construct(target, args)

Reflect.getOwnPropertyDescriptor(target, name)

Reflect.defineProperty(target, name, desc)

Reflect.getPrototypeOf(target)

Reflect.setPrototypeOf(target, newProto)

Reflect.deleteProperty(target, name)

Reflect.enumerate(target)

Reflect.preventExtensions(target)

Reflect.isExtensible(target)

Reflect.ownKeys(target)
تعدادی از متدهای فوق خیلی مشابه متدهای Object هستند؛ با این تفاوت که متدهای فوق اطلاعات بهتری را بر می‌گردانند. به عنوان مثال متد Reflect.defineProperty در صورت ایجاد شدن یک پراپرتی جدید، مقدار true را برمی‌گرداند:
var yay = Reflect.defineProperty(target, 'foo', { value: 'bar' })
if (yay) {
  // yay!
} else {
  // oops.
}
اگر همین کار را با استفاده از متد Object.defineProperty انجام می‌دادیم می‌بایست کد را درون بلاک try/catch می‌نوشتیم:
try {
  Object.defineProperty(target, 'foo', { value: 'bar' })
  // yay!
} catch (e) {
  // oops.
}
به عنوان یک مثال دیگر، قبلاً برای حذف یک پراپرتی از یک شیء از کلمه کلیدی delete به اینصورت استفاده می‌کردیم:
var target = { foo: 'bar', baz: 'wat' }
delete target.foo
console.log(target)
// <- { baz: 'wat' }
اما با کمک متد Reflect.deleteProperty به راحتی می‌توانیم یک پراپرتی را حذف کنیم:
var target = { foo: 'bar', baz: 'wat' }
let isDeleted = Reflect.deleteProperty(target, 'foo')
console.log(isDeleted );
// true
در این‌حالت همانند متد Reflect.defineProperty، در صورت موفقیت‌آمیز بودن عمل حذف، مقدار true برگردانده می‌شود.
برای مشاهده‌ی لیست کامل متدهای فوق می‌توانید به اینجا مراجعه کنید. همچنین می‌توان با مراجعه به قسمت Browser compatibility وضعیت پشتیبانی هر کدام را در مرورگرهای مختلف مشاهده کرد.

نکاتی که در حین کار کردن با Reflect باید در نظر بگیرید:
  • این شیء فاقد متد [[Construct]] می‌باشد. یعنی نمی‌تواند همراه با کلمه‌ی کلیدی new مورد استفاده قرار گیرد.
  • همچنین نمی‌توان آن را همانند یک تابع فراخوانی کرد.
  • تمام متدهای آن به صورت استاتیک تعریف شده‌اند (همانند شیء Math).
مطالب
پیاده سازی پروژه‌های React با TypeScript - قسمت هفتم - تعیین نوع هوک useContext
پیشتر در مطلب «React 16x - قسمت 33 - React Hooks - بخش 4 - useContext Hook» با هوک useContext آشنا شدیم. در این قسمت می‌خواهیم نکات تایپ‌اسکریپتی آن‌را بررسی کنیم.


ایجاد UserContext

فرض کنید برای انتقال داده‌های کاربر وارد شده‌ی به سیستم، از روش انتقال props از بالاترین کامپوننت موجود در component tree به پایین‌ترین کامپوننت آن، نیاز است از Context استفاده کنیم. به همین جهت فایل جدید src\contexts\userContext.tsx را با محتوای زیر ایجاد می‌کنیم:
import React from "react";

export type User = {
  name: string;
  isActive: boolean;
  logOut: () => void;
};

export const UserContext = React.createContext<User>({});
export const useUser = () => React.useContext(UserContext);
متد createContext در اصل یک متد جنریک است که بر اساس نوع آرگومان جنریک آن، مقدار شیء تامین کننده‌ی مقدار آن مشخص می‌شود. برای مثال اگر نوع User را تعریف کرده و به آن انتساب دهیم، یک چنین امضایی را پیدا می‌کند:
function React.createContext<User>(defaultValue: User,
 calculateChangedBits?: ((prev: User, next: User) => number) | undefined): React.Context<User>
اما ... قطعه کد فوق با خطای تایپ‌اسکریپتی زیر کامپایل نمی‌شود:
Argument of type '{}' is not assignable to parameter of type 'User'.
Type '{}' is missing the following properties from type 'User': name, isActive, logOutts(2345)
عنوان می‌کند که شیء خالی که به createContext ارسال کرده‌ایم، از نوع User نیست. برای رفع این مشکل می‌توان از مفهومی به نام Partial استفاده کرد:
export const UserContext = React.createContext<Partial<User>>({});
به این ترتیب تمام خواص نوع User به صورت اختیاری معرفی می‌شوند و در این حالت انتساب یک شیء خالی اولیه به آن، مشکلی را ایجاد نخواهد کرد.

نکته 1: البته می‌توان آرگومان جنریک آن‌را ذکر نکرد و createContext را با یک شیء آغازین از نوع User مقدار دهی کرد. در این حالت با استفاده از Type Inference، نوع آن، همان User درنظر گرفته می‌شود و دیگر نیازی به ذکر صریح این نوع نخواهد بود.
نکته 2: اگر از شیء مقدار دهی شده‌ی آغازین استفاده کنید، دیگر حتی نیازی به ذکر export type User هم نیست. نوع createContext بر اساس نوع خواص شیء آغازین ارسالی به آن در سراسر برنامه مشخص شده و قابل دسترسی می‌شود؛ با intellisense کامل و type checking دقیق.
نکته 3: useUser تعریف شده، در حقیقت یک هوک سفارشی است که می‌توان بجای React.useContext از آن در سایر قسمت‌های برنامه استفاده کرد.
نکته 4: اگر علاقمند به استفاده‌ی از نوع Partial نیستید، می‌توان از union types هم در اینجا استفاده کرد. در این حالت می‌توان مقدار اولیه را undefined تعریف کرد:
export const UserContext = React.createContext<User | undefined>(undefined);


معرفی UserContext به بالاترین کامپوننت موجود در component tree

برای این منظور به فایل src\App.tsx مراجعه کرده و آن‌را با UserContext.Provider محصور می‌کنیم:
import { User, UserContext } from "./contexts/userContext";
// ...

function App() {
  const user: User = {
    name: "User 1",
    isActive: true,
    logOut: () => {
      console.log("LogOut.");
    },
  };

  return (
    <UserContext.Provider value={user}>
    // ... 
    </UserContext.Provider>
  );
}

export default App;
ابتدا نوع User و سپس UserContext  را import کرده‌ایم و سپس کل محتوای موجود در کامپوننت App را با UserContext.Provider که دارای مقدار user ابتدایی تعریف شده‌است، محصور می‌کنیم.


خواندن شیء Context در کامپوننتی دیگر

برای این منظور به کامپوننت src\components\Head.tsx که در قسمت‌های قبل ایجاد کردیم، مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
import { UserContext } from "../contexts/userContext";

// ...

export const Head = ({ title, isActive = true }: Props) => {
  const context = React.useContext(UserContext); // or useUser();
// ...
};
در اینجا با استفاده از هوک useContext به UserContext دریافتی دسترسی یافته و سپس می‌توان با اطلاعات شیء User کار کرد.
اولین مزیت کار با TypeScript در اینجا، وجود intellisense کامل به همراه context است:


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



یک نکته: اگر UserContext را با استفاده از union types تعریف کنیم:
export const UserContext = React.createContext<User | undefined>(undefined);
هنگام استفاده‌ی از آن، خطای عدم وجود خاصیت‌های آن‌را در حین Object Destructuring دریافت می‌کنیم:


علت اینجا است که با فعال بودن بررسی نوع‌های نال‌نپذیر، چون user context اکنون می‌تواند undefined هم باشد، یا باید از روش context?.name استفاده کرد و یا یک علامت تعجب را پس از useContext قرار داد:
const context = React.useContext(UserContext)!; // or useUser()!;
const { name } = context;
ذکر ! به این معنا است که می‌دانیم خروجی این متد، نال و یا undefined نیست (راهنمایی کردن کامپایلر TypeScript). چون پیشتر آن‌را در کامپوننت App، در حین تعریف Provider، مقدار دهی اولیه کرده‌ایم.
مطالب
CoffeeScript #15

قسمت‌های اصلاح نشده

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

استفاده از parseInt

تابع ()parseInt در جاوااسکریپت، در صورتیکه یک مقدار رشته‌ای را به آن ارسال کنید و پایه‌ی مناسب آن را تعیین نکنید، نتایج غیره منتظره‌ای (unexpected) را باز می‌گرداند . برای مثال:

# Returns 8, not 10!
parseInt('010') is 8
البته ممکن است شما این کد را در مرورگر خود تست کنید و مقدار 10 را باز گرداند؛ اما این برای همه‌ی مرورگرها یکسان نیست. برای اطمینان از مقدار بازگشتی صحیح، همیشه پایه‌ی آن را تعیین کنید.
# Use base 10 for the correct result
parseInt('010', 10) is 10
دقت کنید این چیزی نیست که CoffeeScript بتواند برای شما انجام دهد؛ شما فقط یادتان باشد که همیشه پایه‌ی صحیح را در موقع استفاده‌ی از ()parseInt تعریف کنید.

Strict mode


Strict mode یکی از قابلیت‌های ECMAScript 5 است که به شما اجازه می‌دهد تا یک برنامه یا تابع جاوااسکریپت را در محیطی محدود اجرا کنید. این محدودیت موجب نمایش بیشتر خطاها و هشدارها نسبت به حالت نرمال می‌شود و به توسعه دهندگان این امکان را می‌دهد تا از نوشتن کدهای غیر قابل بهینه سازی برای اشتباهات رایج جلوگیری کنند.
به عبارت دیگر Strict mode باعث کاهش اشکالات، افزایش امنیت، بهبود عملکرد و حذف برخی از سختی‌های استفاده از ویژگی‌های زبان می‌شود.
در حال حاضر Strict mode، در مرورگرهای زیر پشتیبانی می‌شود:

  • Chrome >= 13.0
  • Safari >= 5.0
  • Opera >= 12.0
  • Firefox >= 4.0
  • IE >= 10.0

با این حال، Strict mode به طور کامل با مرورگرهای قدیمی سازگار است.


تغییرات Strict mode

بیشتر تغییرات Strict mode مربوط به syntax جاوااسکریپت بوده است:

  • خطا در پروپرتی‌ها و نام آرگومان‌های تابع تکراری
  • خطا در عدم استفاده‌ی صحیح از delete
  • خطا در زمان دسترسی به arguments.caller و arguments.callee (به دلایل عملکرد)
  • استفاده از عمگر with سبب بروز خطای نحوی می‌شود
  • متغیرهای خاص مانند undefined که قابل نوشتن نیستند
  • معرفی کلمات کلیدی رزرو شده مانند implements, interface, let, package, private, protected, public, static و yield.

با این حال، برخی از رفتارهای زمان اجرای Strict mode نیز تغییر کرده است:

  • متغییرهای سراسری به صورت صریح و روشن هستند (کلمه کلیدی var نیاز است). مقدار سراسری this نیز به صورت undefined است.
  • eval نمی‌تواند متغیر جدیدی را در حوزه‌ی محلی خود تعریف کند.
  • بدنه‌ی هر تابع باید قبل از استفاده تعریف شده باشد (قبلا گفتم که در جاوااسکریپت شما می‌توانید قبل از تعریف تابع آن را فراخوانی کنید).
  • آرگومان‌ها تغییر ناپذیر هستند.

CoffeeScript در حال حاضر بسیاری از الزامات Strict mode را پیاده سازی کرده‌است مانند: همیشه از کلمه کلیدی var برای تعریف متغیر استفاده می‌کند؛ اما فعال کردن Strict mode در برنامه‌های CoffeeScript نیز بسیار مفید خواهد بود. در واقع CoffeeScript بر روی انطباق برنامه‌ها با Strict mode در زمان کامپایل را، در برنامه‌های آینده خود دارد.


استفاده از Strict mode

برای فعال کردن بررسی محدودیت، کد و توابع خود را با این رشته شروع کنید:
->
  "use strict"

  # ... your code ...
فقط با استفاده از رشته "use strict". به مثال زیر توجه کنید:
do ->
  "use strict"
  console.log(arguments.callee)
اجرای قطعه کد بالا درحالت strict mode، سبب بروز خطای syntax می‌شود؛ در حالیکه در حالت معمول این کد به خوبی اجرا می‌شود.
Strict mode دسترسی به arguments.callee و arguments.caller، که تاثیر بدی را بر روی عملکرد کد شما دارند، حذف می‌کند و استفاده‌ی از آنها سبب بروز خطا می‌شود.

در مثال زیر در حالت strict mode سبب بروز خطای TypeError می‌شود، اما در حالت نرمال به خوبی اجرا شده و یک متغیر سراسری را ایجاد می‌کند.
do ->
  "use strict"
  class @Spine
دلیل این رفتار این است که در Strict mode متغیر this به صورت undefined است؛ در حالیکه در حالت نرمال، this به شیء window اشاره می‌کند. راه حل این مشکل تعریف متغیرهای سراسری به صورت صریح به شیء window است.
do ->
  "use strict"
  class window.Spine
در حالیکه توصیه می‌شود که همیشه Strict mode فعال باشد، اما Strict mode هیچ یک از ویژگی‌های جدید جاوااسکریپت را که هنوز آماده نیست، فعال نمی‌کند و در واقع به علت بررسی بیشتر کدهای شما در زمان اجرا، باعث کاهش سرعت می‌شود.
شما می‌توانید در زمان توسعه برنامه جاوااسکریپت خود Strict mode را فعال کنید و در زمان انتشار، بدون Strict mode برنامه‌ی خود را منتشر کنید.

JavaScript Lint 

JavaScript Lint یک ابزار بررسی کیفیت کدهای جاوااسکریپت است و اجرای برنامه‌ی شما از طریق این راه عالی باعث بهبود کیفیت و بهترین شیوه‌ی کد نویسی می‌شود. این پروژه براساس ابزار JSLint است. شما می‌توانید چک لیست سایت JSLint را که شامل موضوعاتی است که باید آن‌ها در نظر داشته باشید، مانند متغیرهای سراسری، فراموش کردن نوشتن سمی کالن، کیفیت ضعیف عمل مقایسه را نام برد.

خبر خوب این است که CoffeeScript تمام موارد گفته شده‌ی در چک لیست را انجام می‌دهد. بنابراین کد تولیدی CoffeeScript با JavaScript Lint کاملا سازگار است. در واقع ابزار coffee از lint ،option پشتیبانی می‌کند.

coffee --lint index.coffee
  index.coffee: 0 error(s), 0 warning(s)

مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 20 - بررسی تغییرات فیلترها
پیشنیازها

- فیلترها در MVC
- ASP.NET MVC #15


فیلترها در ASP.NET MVC، امکان اجرای کدهایی را پیش و یا پس از مرحله‌ی خاصی از طول اجرای pipeline آن فراهم می‌کنند. کلیات فیلترها در ASP.NET Core با نگارش‌های قبلی ASP.NET MVC (پیشنیازهای فوق) تفاوت چندانی را ندارد و بیشتر تغییراتی مانند نحوه‌ی معرفی سراسری آن‌ها، اکشن فیلترهای Async و یا تزریق وابستگی‌ها در آن‌ها، جدید هستند.


امکان تعریف فیلترهای Async در ASP.NET Core

حالت کلی تعریف یک فیلتر در ASP.NET MVC که در ASP.NET Core نیز همچنان معتبر است، پیاده سازی اینترفیس کلی IActionFilter می‌باشد که توسط آن می‌توان به مراحل پیش و پس از اجرای قطعه‌ای از کدهای برنامه دسترسی پیدا کرد:
namespace FiltersSample.Filters
{
    public class SampleActionFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context)
        {
            // انجام کاری پیش از اجرای اکشن متد
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            // انجام کاری پس از اجرای اکشن متد
        }
    }
}
در اینجا اینترفیس IAsyncActionFilter نیز معرفی شده‌است که توسط آن می‌توان فراخوانی‌های غیرهمزمان و async را نیز مدیریت کرد:
namespace FiltersSample.Filters
{
    public class SampleAsyncActionFilter : IAsyncActionFilter
    {
        public async Task OnActionExecutionAsync(
            ActionExecutingContext context,
            ActionExecutionDelegate next)
        {
            // انجام کاری پیش از اجرای اکشن متد
            await next();
            // انجام کاری پس از اجرای اکشن متد
        }
    }
}
به کامنت‌های نوشته شده‌ی در بدنه‌ی متد OnActionExecutionAsync دقت کنید. در اینجا کدهای پیش از await next معادل OnActionExecuting و کدهای پس از await next معادل OnActionExecuted حالت همزمان و یا همان حالت متداول هستند. بنابراین جایی که اکشن متد اجرا می‌شود، همان await next است.

یک نکته: توصیه شده‌است که تنها یکی از حالت‌های همزمان و یا غیرهمزمان را پیاده سازی کنید و نه هر دوی آن‌ها را. اگر هر دوی این‌ها را در طی یک کلاس پیاده سازی کنید (تک کلاسی که هر دوی اینترفیس‌های IActionFilter و IAsyncActionFilter را با هم پیاده سازی می‌کند)، تنها نگارش Async آن توسط ASP.NET Core فراخوانی و استفاده خواهد شد. همچنین مهم نیست که اکشن متد شما Async هست یا خیر؛ برای هر دو حالت می‌توان از فیلترهای async نیز استفاده کرد.


ساده سازی تعریف فیلترها

اگر مدتی با ASP.NET MVC کار کرده باشید، می‌دانید که عموما کسی از این اینترفیس‌های کلی برای پیاده سازی فیلترها استفاده نمی‌کند. روش کار با ارث بری از یکی از فیلترهای از پیش تعریف شده‌ی ASP.NET MVC صورت می‌گیرد؛ از این جهت که این فیلترها که در اصل همین اینترفیس‌ها را پیاده سازی کرده‌اند، یک سری جزئیات توکار protected را نیز به همراه دارند که با ارث بری از آن‌ها می‌توان به امکانات بیشتری دسترسی پیدا کرد و کدهای ساده‌تر و کم حجم‌تری را تولید نمود:
ActionFilterAttribute
ExceptionFilterAttribute
ResultFilterAttribute
FormatFilterAttribute
ServiceFilterAttribute
TypeFilterAttribute

برای مثال در اینجا فیلتری را مشاهده می‌کنید که با ارث بری از فیلتر توکار ResultFilterAttribute، سعی در تغییر Response برنامه و افزودن هدری به آن کرده‌است:
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
    public class AddHeaderAttribute : ResultFilterAttribute
    {
        private readonly string _name;
        private readonly string _value;
        public AddHeaderAttribute(string name, string value)
        {
            _name = name;
            _value = value;
        }

        public override void OnResultExecuting(ResultExecutingContext context)
        {
            context.HttpContext.Response.Headers.Add(
                _name, new string[] { _value });
            base.OnResultExecuting(context);
        }
    }
}
و برای استفاده‌ی از این فیلتر جدید خواهیم داشت:
[AddHeader("Author", "DNT")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("با فایرباگ هدر خروجی را بررسی کنید");
    }
}


نحوه‌ی تعریف میدان دید فیلترها

نحوه‌ی دید فیلترها در اینجا نیز همانند سابق، سه حالت را می‌تواند داشته باشد:
الف) اعمال شده‌ی به یک اکشن متد.
ب) اعمال شده‌ی به یک کنترلر که به تمام اکشن متدهای آن کنترلر اعمال خواهد شد.
ج) حالت تعریف سراسری و این مورد محل تعریف آن به کلاس آغازین برنامه منتقل شده‌است:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(SampleActionFilter)); // by type
        options.Filters.Add(new SampleGlobalActionFilter()); // an instance
    });
}
در اینجا دو روش معرفی فیلترهای سراسری را در متد ConfigureServices کلاس آغازین برنامه مشاهده می‌کنید:
الف) اگر توسط ارائه‌ی new ClassName معرفی شوند، یعنی وهله سازی را خودتان قرار است مدیریت کنید و در این حالت تزریق وابستگی‌هایی صورت نخواهند گرفت.
ب) اگر توسط typeof معرفی شوند، یعنی این وهله سازی توسط IoC Container توکار ASP.NET Core انجام خواهد شد و طول عمر آن Transient است. یعنی به ازای هربار نیاز به آن، یکبار وهله سازی خواهد شد.


ترتیب اجرای فیلترها

توسط خاصیت Order می‌توان ترتیب اجرای چندین فیلتر اجرا شده‌ی به یک اکشن متد را مشخص کرد. اگر این مقدار منفی وارد شود:
 [MyFilter(Name = "Method Level Attribute", Order=-1)]
این فیلتر پیش از فیلترهای سراسری و همچنین فیلترهای اعمال شده‌ی در سطح کلاس اجرا می‌شود.


تزریق وابستگی‌ها در فیلترها

فیلترهایی که به صورت ویژگی‌ها یا Attributes تعریف می‌شوند و قرار است به کنترلرها و یا اکشن متدها به صورت مستقیم اعمال شوند، نمی‌توانند دارای وابستگی‌های تزریق شده‌ی در سازنده‌ی خود باشند. این محدودیتی است که توسط زبان‌های برنامه نویسی اعمال می‌شود و نه ASP.NET Core. اگر ویژگی قرار است پارامتری در سازنده‌ی خود داشته باشد، هنگام تعریف و اعمال آن، این پارامترها باید مشخص بوده و تعریف شوند. به همین جهت آنچنان با تزریق وابستگی‌های از طریق سازنده‌ی کلاس قابل مدیریت نیستند. برای رفع این نقصیه، راه‌حل‌های متفاوتی در ASP.NET Core پیشنهاد و طراحی شده‌اند:
الف) استفاده‌ی از ServiceFilterAttribute
[ServiceFilter(typeof(AddHeaderFilterWithDi))]
public IActionResult Index()
{
   return View();
}
ویژگی جدید ServiceFilter، نوع کلاس فیلتر را دریافت می‌کند و سپس هر زمانیکه نیاز به اجرای این فیلتر خاص بود، کار وهله سازی‌های وابستگی‌های آن، در پشت صحنه توسط IoC Container توکار ASP.NET Core انجام خواهد شد.
همچنین باید دقت داشت که در این حالت ثبت کلاس فیلتر در متد ConfigureServices کلاس آغازین برنامه الزامی است.
 services.AddScoped<AddHeaderFilterWithDi>();
در غیراینصورت استثنای ذیل را دریافت خواهید کرد:
 System.InvalidOperationException: No service for type 'FiltersSample.Filters.AddHeaderFilterWithDI' has been registered.

ب) استفاده از TypeFilterAttribute
[TypeFilter(typeof(AddHeaderAttribute),  Arguments = new object[] { "Author", "DNT" })]
public IActionResult Hi(string name)
{
   return Content($"Hi {name}");
}
فیلتر و ویژگی TypeFilter بسیار شبیه است به عملکرد ServiceFilter، با این تفاوت که:
- نیازی نیست تا وابستگی آن‌را در متد ConfigureServices ثبت کرد (هرچند وابستگی‌های خود را از DI Container دریافت می‌کنند).
- امکان دریافت پارامترهای اضافی سازنده‌ی کلاس مدنظر را نیز دارند.


یک مثال تکمیلی: لاگ کردن تمام استثناءهای مدیریت نشده‌ی یک برنامه‌ی ASP.NET Core 1.0

می‌توان با سفارشی سازی فیلتر توکار ExceptionFilterAttribute، امکان ثبت وقایع را توسط فریم ورک توکار Logging اضافه کرد:
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
 
namespace Core1RtmEmptyTest.StartupCustomizations
{
    public class CustomExceptionLoggingFilterAttribute : ExceptionFilterAttribute
    {
        private readonly ILogger<CustomExceptionLoggingFilterAttribute> _logger;
        public CustomExceptionLoggingFilterAttribute(ILogger<CustomExceptionLoggingFilterAttribute> logger)
        {
            _logger = logger;
        }
 
        public override void OnException(ExceptionContext context)
        {
            _logger.LogInformation($"OnException: {context.Exception}");
            base.OnException(context);
        }
    }
}
و برای ثبت سراسری آن در کلاس آغازین برنامه خواهیم داشت:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(typeof(CustomExceptionLoggingFilterAttribute));
در اینجا از typeof استفاده شده‌است تا کار تزریق وابستگی‌های این فیلتر به صورت خودکار انجام شود.
در ادامه با این فرض که پیشتر تنظیمات ثبت وقایع صورت گرفته‌است:
public void Configure(ILoggerFactory loggerFactory)
{
   loggerFactory.AddDebug(minLevel: LogLevel.Debug);
اکنون اگر یک چنین اکشن متدی فراخوانی شود:
public IActionResult GetData()
{
  throw new Exception("throwing an exception!");
}
در پنجره‌ی دیباگ ویژوال استودیو، این استثناء قابل مشاهده خواهد بود:

مطالب
چک لیست امنیتی پروژه های نرم افزاری تحت وب
 مقدمه:

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

مهمترین و خطرناک‌ترین حملات سطح وب :

حمله XSS

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

حمله SQL Injection

این حمله معروفترین حمله است که تقریبا با قدرت می‌توانم بگویم که درتکنولوژی ASP.Net با امکانات فوق العاده‌ای که بصورت توکار در دات نت در نظر گرفته شده است، بصورت کامل به فراموشی سپرده شده است. فقط 2 تا نکته‌ی ریز هست که باید در کدهایتان رعایت کنید و تمام.
این حمله بدین صورت است که هکر یک سری دستورات SQL را در کوئری استرینگ، به صفحات تزریق می‌کند و بدین صورت می‌تواند در کدهای کوئری TSQL شما اختلال ایجاد کند و اطلاعات جداول شما را بدست بیاورد. در این نوع حمله، هکر از طریق باگ سطح کد نویسی کدهای نرم افزار، به دیتابیس حمله می‌کند و اطلاعاتی مثل نام کاربری و کلمه‌ی عبور ادمین یا کاربران را می‌دزد و بعد می‌رود داخل پنل و خرابکاری می‌کند.
 

حمله CSRF

این حمله یکی از جالب‌ترین و جذاب‌ترین نوع حملات است که هوش بالای دوستان هکر را نشون می‌دهد. عبارت CSRF مخفف Cross Site Request Forgery است (احتمالا دوستان ام وی سی کار، این عبارت برایشان آشناست).
در این نوع حمله هکر یک فایل برای کاربر شما از طریق ایمیل یا روش‌های دیگر ارسال می‌کند و کاربر را به این سمت سوق می‌دهد که فایل را باز کند. کاربر یک فایل به ظاهر معمولی مثل عکس یا ... را می‌بیند و فایل را باز می‌کند. وقتی فایل باز می‌شود دیتای خاصی دیده نمی‌شود و گاهی هم اروری مبنی بر ناقص بودن فایل یا ... به کاربر نمایش داده می‌شود و کاربر فکر می‌کند که فایل، ناقص برای ارسال شده ...
اما در حقیقت با کلیک بر روی فایل و باز کردن آن یک درخواست POST از کامپیوتر کاربر برای سایت شما ارسال می‌شود و در صورتیکه کاربر در آن زمان در سایت شما لاگین باشد، سایت درخواست را با روی باز می‌پذیرد و درخواست را اجرا می‌کند. بدین صورت هکر می‌تواند درخواست‌هایی را به سرویس‌های سایت شما که مثلا برای حذف یک سری داده است، ارسال کند و اطلاعات کاربر را حذف کند.
 

حمله Brute Force

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

حمله DDOS

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


حمله SHELL

شل فایلی است خطرناک که اگر بر روی سرور سایت شما آپلود و اجرا شود، هکر از طریق آن دسترسی کاملی به کل سرور سایت شما خواهد داشت. فایل‌های دیگری با نام بک‌دور [1] نیز وجود دارند که نویسنده تمایل دارد آنها را نیز از نوع حمله SHELL معرفی نماید. این نوع از فایل‌ها به مراتب بسیار خطرناک‌تر از فایل‌های شل می‌باشند؛ تا جایی که ممکن است سال‌ها هکر به سروی دسترسی داشته باشد و مدیر سرور کاملا از آن بی خبر باشد. اینجاست که باید شدیدا مراقب فایل‌هایی که روی سایت شما آپلود می‌شوند باشید. نویسنده به تمامی خوانندگان پیشنهاد می‌نماید، در صورتیکه نرم افزار حساسی دارند، حتما از سرور اختصاصی استفاده نمایند؛ چرا که در هاست‌های اشتراکی که در آنها فضا و امکانات یک سرور بصورت اشتراکی در اختیار چندین سایت قرار می‌گیرد، وجود باگ امنیتی در سایر سایت‌های موجود بر روی سرور اشتراکی می‌تواند امنیت سایت شما را نیز به مخاطره بیاندازد. نویسنده تهیه‌ی سرور اختصاصی را شدیدا به توسعه دهندگان سایت‌های دارای تراکنش‌های بانکی بالا (داخلی یا خارجی) پیشنهاد می‌نماید. زیرا درگاه تراکنش‌های بانکی بر روی آی پی هاست شما قفل می‌شوند و در صورتیکه سرور بصورت اختصاصی تهیه شده باشد، آی پی سرور شما فقط و فقط در اختیار شماست و هکر نمی‌تواند با تهیه هاستی بر روی سرور اشتراکی شما، به راحتی آی پی قفل شده در درگاه بانکی شما را در اختیار داشته باشد. بدیهی است تنها در اختیار داشتن آی پی سرور شما جهت انجام خرابکاری در درگاه بانکی شما کافی نیست. ولی به نظر نویسنده این مورد در بدترین حالت ممکن 30% کار هکر می‌باشد. البته بحث حمله شل به سطح مهارت متخصصان سرورها نیز بستگی دارد. نویسنده اظهار می‌دارد اطلاعات دقیقی از تنظیماتی که بتواند جلوی اجرای انواع شل و یا جلوی دسترسی فایل‌های شل را بگیرد، ندارد. بنابراین از متخصصان این حوزه دعویت می‌نماید اطلاعاتی درباره این موضوع ارائه نمایند.
 

حمله SNIFF

در این نوع حملات، هکر پکت‌های رد و بدل شده‌ی بین کاربران و سرور شما را شنود می‌نماید و به راحتی می‌تواند اطلاعات مهمی مثل نام کاربری و رمز عبور کاربران شما را بدست آورد.



چک لیست امنیتی پروژه‌های نرم افزاری تحت وب

- بررسی کامل ورودی‌های دریافتی از فرم‌های سایت؛ هم در سمت کلاینت و هم در سطح سرور .
- در تکنولوژی دات نت به منظور تمیز سازی ورودی‌ها و حذف تگهای خطرناکی همچون تگ script، کتابخانه‌ای با نام Microsoft.Security.Application وجود دارد. کتابخانه‌های سورس باز دیگری نیز وجود دارند که نمونه آن کتابخانه AntiXss [2] سایت نوگت [3] می‌باشد.
- بررسی کامل ورودی‌های دریافتی از کوئری استرینگ‌های [4] سایت. اگر از ASP.Net MVC استفاده می‌نمایید، تا حدی زیادی نیاز به نگرانی نخواهد داشت، زیرا تبدیلات [5] در سیستم Model Binding انجام می‌پذیرد و این موضوع تا حد زیادی شما را در برابر حملات SQL Injection مقاوم می‌نماید.
- حتما در فرم‌های عمومی سایتتان از تصویر کپچا با امنیت بالا استفاده نمایید. این موضوع جهت شناخت روبات‌ها از انسان‌ها می‌باشد و شما را در برابر حملات Brute Force مقاوم می‌نماید.
-  حتما سیستم شخصی سازی صفحات ارور را فعال نمایید و از نمایش صفحات ارور حاوی اطلاعات مهمی مانند صفحات ارور ASP.Net جلوگیری نمایید. این موضوع بسیار حساس می‌باشد و می‌تواند نقاط ضعف نرم افزار شما را برای هکر نمایان کند. حتی ممکن است اطلاعات حساسی مانند نام بانک اطلاعاتی، نام کاربری اتصال به بانک اطلاعاتی و نام جداول بانک اطلاعاتی شما را در اختیار هکر قرار دهد.
- استفاده از ORM ها یا استفاده از پروسیجرهای پارامتریک. این موضوع کاملا شما را در برابر حملات SQL Injection مقاوم می‌نماید. کما اینکه ORM ها، سطحی از کش را بصورت توکار دارا می‌باشند و این موضوع در سرعت دستیابی به داده‌ها نیز بسیار تاثیر گذار است. از طرف دیگر بانک اطلاعاتی SQL نیز امکانات توکاری جهت کش نمودن پرس و جو‌های [6] پارامتریک دارد.
- لاگ کردن ارورهای سطح کد و سطح روتینگ [7] . یکی از مهمترین خصیصه‌های پروژه‌های با کیفیت، لاگ شدن خطاهای سطح کد می‌باشد. این امر شما را با نقاط حساس و ضعف‌های نرم افزار آگاه می‌سازد و به شما اجازه می‌دهد به سرعت در جهت رفع آنها اقدام نمایید. لاگ نمودن خطاهای سطح روتینگ شما را از فعالیت‌های هکر‌ها جهت یافتن صفحات لاگین و صفحات مدیریتی پنل مدیریتی سایت اگاه می‌نماید، همچنین شما را از حملات SQL Injection نیز آگاه می‌نماید.
- جلوگیری از ایندکس شدن صفحات لاگین پنل مدیریت سایت در موتورهای جستجو. بخش مهمی از عملیات هکر ها، قرار دادن روبات‌های تشخیص رمز بر روی صفحات لاگین می‌باشد که به نوعی می‌توان این نوع حملات را در دسته حملات Brute Force قرار داد. موتورهای جستجو یکی از ابزارهای مهم هکرها می‌باشد. عملیات هایی مانند یافتن صفحات لاگین پنل مدیریتی یکی از کاربردهای موتورهای جستجو برای هکرها می‌باشد.
- لاگ کردن ورود و خروج افراد به همراه تاریخ، زمان، آی پی افراد و وضعیت لاگین. با کمک این موضوع شما می‌توانید ورود و خروج کاربران نرم افزار خود را کنترل نمایید و موارد غیر طبیعی و مشکوک را در سریعترین زمان مورد بررسی قرار دهید.
- استفاده از روال‌های استاندارد جهت بخش "فراموشی کلمه عبور". همیشه از استاندارهای نرم افزارهای بزرگ پیروی نمایید. بدیهی است استاندارهای استفاده شده در این نرم افزارها بارها و بارها تست شده و سپس بعنوان یک روال استاندارد در همه‌ی نرم افزارهای بزرگ بکار گرفته شده است. استاندارد جهانی بخش "فراموشی کلمه عبور" که در اغلب نرم افزارهای معروف جهان بکار گرفته شده است، عبارت است از دریافت آدرس ایمیل کاربر، احراز هویت ایمیل وارد شده، ارسال یک نامه‌ی الکترونیکی [8] حاوی نام کاربری و لینک تنظیم کلمه عبور جدید به ایمیل کاربر. بهتر است لینک ارسال شده به ایمیل کاربر بصورت یکبار مصرف باشد. کاربر پس از کلیک بر روی لینک تنظیم کلمه عبور جدید، وارد یکی از صفحات سایت شده و می‌تواند کلمه‌ی عبور جدیدی را برای خود ثبت نماید. در پایان، کاربر به صفحه‌ی ورود سایت هدایت شده و پیامی مبنی بر موفقیت آمیز بودن عملیات تغییر کلمه‌ی عبور به او نمایش داده می‌شود. البته روال ذکر شده حداقل رول استانداردی می‌باشد و می‌توان در کنار آن از روال‌های تکمیل کننده‌ای مانند پرسش‌های امنیتی و غیره نیز استفاده نمود.
- قراردادن امکاناتی جهت بلاک نمودن آی پی‌ها و غیر فعال نمودن حساب کاربری اعضای سایت. در نرم افزار باید این امکان وجود داشته باشد که آی پی هایی که بصورت غیر طبیعی در سایت فعالیت می‌نمایند و یا مکررا اقدام به ورود به پنل مدیریتی و پنل کاربران می‌نمایند را بلاک نماییم. همچنین در صورت تخلف کاربران باید بتوان حساب کاربری کاربر خاطی را مسدود نمود. این موضوع می‌تواند بسته به اندازه پروژه و یا سلیقه تیم توسعه بصورت خودکار، دستی و یا هر دو روش در نرم افزار در تعبیه شود.
- امن سازی سرویس‌های ای جکس و چک کردن ای جکس بودن درخواست ها. حتما جلوی اجرای سرویس‌های درون نرم افزاری از بیرون از نرم افزار را بگیرید. سرویس‌های ای جکس یکی از این نوع سرویس‌ها می‌باشند که در نرم افزار‌ها جهت استفاده‌های داخلی در نظر گرفته می‌شوند. در این نوع سرویس‌ها حتما نوع درخواست را بررسی نمایید و از پاسخگویی سرویس‌ها به درخواست‌های غیر ای جکسی جلوگیری نمایید. در ASP.Net MVC این امر توسط متد Request.IsAjaxRequest انجام می‌پذیرد .
- محدود کردن سرویس‌های حساس به درخواست‌های POST. حتما از دسترسی به سرویس هایی از نوع Insert,Update و Delete از طریق فعل GET جلوگیری نمایید. در ASP.Net MVC این سرویس‌ها را به فعل POST محدود نموده و در ASP.Net Web API این سرویس‌ها را به افعال POST,PUT و DELETE محدود نمایید.
- عدم استفاده از آی دی در پنل‌های کاربران بالاخص در آدرس صفحات (کوئری استرینگ) و استفاده از کد غیر قابل پیش بینی مثل GUID به جای آن. حتی الامکان بررسی مالکیت داده‌ها در همه بخش‌های پنل‌های کاربری سایت را جهت محکم کاری بیشتر انجام دهید تا خدای نکرده کاربر با تغییر اطلاعات کوئری استرینگ صفحات نتوانند به داده‌های یک کاربر دیگه دسترسی داشته باشند.
- حتی الامکان پنل مدیران را از کاربران بصورت فیزیکی جدا نمایید. این مورد جهت جلوگیری از خطاهایی است که ممکن است توسط توسعه دهنده در سطح سیستم مدیریت نقش رخ دهد و موجب دسترسی داشتن کاربران به بخش هایی از پنل مدیریتی شود.
- استفاده از الگوریتم‌های کدگذاری ترکیبی و کد کردن اطلاعات حساس قبل از ذخیره سازی در بانک اطلاعاتی. اطلاعات حساسی مانند کلمات عبور را حتما توسط چند الگوریتم کدگذاری، کدگذاری نمایید و سپس درون بانک اطلاعاتی ذخیره نمایید.
- تنظیمات حساس نرم افزار را درون فایل web.config قرار دهید و حتی الامکان آنها را نیز کدگذاری نمایید. بصورتی که اطلاعات قابلیت دیکد شدن را داشته باشند.
- ساخت پروژه بصورت چند لایه. این موضوع جهت جلوگیری از دستیابی هکر به ساختار لایه‌های پروژه‌های شما می‌باشد. به بیان دیگر اگر نهایتا هکر بتواند به اطلاعات FTP هاست شما دست یابد، استفاده از تکنولوژی چند لایه در بدترین حالت هکر را از دستیابی به اطاعات لایه‌های زیرین نرم افزار باز می‌دارد. البته این کار برای هکر‌ها غیر ممکن نیست، اما بسیار سخت و زمان بر می‌باشد.
- اشتراک گذاری اینترفیس در سرویس‌های خارج برنامه ای و عدم اشتراک گذاری کلاس اصلی. این موضوع از دستیابی هکر به بدنه سرویس‌ها و پیاده سازی‌های آنها جلوگیری می‌نماید.
- استفاده از تکنیک‌های مقابله با CSRF در همه سرویس‌های POST. در ASP.NET MVC اتریبیوتی با نام AntiForgery جهت مقاوم سازی سرویس‌ها از حملات CSRF وجود دارد. مکانیزم بدین صورت است که در تمامی فرم‌های سایت یک کد منحصر به فرد تولید می‌گردد که همراه درخواست GET به کامپیوتر کاربر ارسال می‌شود و در هنگام ارسال درخواست POST به سرور، صحت کد مورد نظر بررسی شده و در صورت صحت، اجازه‌ی اجرای سرویس به درخواست داده می‌شود. بدین صورت وقتی کاربر سایت شما فایل آلوده‌ای را باز می‌نماید، در خواست ارسالی هکر که توسط فایل باز شده، به سرور سایت ما ارسال می‌گردد، فاقد کد منحصر به فرد بوده و از اجرای سرویس جلو گیری می‌شود.
- استفاده از سیستم‌های مدیریت نقش امن مانند IDENTITY در ASP.Net MVC و یا استفاده از امکانات توکار دات نت در سیستم‌های مدیریت نقش شخصی سازی شده [9] . بدیهی است امنیت این سیستم‌ها بارها و بارها تست شده است.
- بررسی فرمت و پسوند فایل‌های آپلود شده. توجه نمایید که بررسی پسوند فایل‌ها کافی نبوده و فرمت فایل‌ها نیز می‌بایست بررسی شود. حتی نویسنده پیشنهاد می‌نماید فایل‌ها را به نوع‌های مرتبطشان تبدیل [10] نمایید. در حوزه هک بایند نمودن انواع ویروس، تروجان، شل و بک دور [11] به فایل‌های تصویری و متنی یک امر بسیار رایج است. بنابراین حساسیت زیادی روی این موضوع قرار دهید. نویسنده توصیه می‌نماید کتابخانه‌های کاملی برای این موضوع تدارک ببینید تا در تمامی پروژه‌ها نیاز به ایجاد مجدد آنها نداشته باشید و سعی نمایید در هر پروژه این کتابخانه‌ها را تکمیل‌تر و بهتر نمایید.
- تنظیم IIS  جهت جلوگیری از اجرای فایل‌های اجرایی در مسیر آپلود فایل‌ها. شاید جمله بیان شده به نظر ترسناک و یا سخت برسد، اما این کار با نوشتن چند تگ ساده در فایل Web.Config به راحتی قابل انجام است و نیاز به هیچ نوع کدنویسی ندارد.
- آپلود فایل‌ها در پوشه App_Data و دسترسی به فایل‌ها از طریق سرویس‌های خود شما. پوشه App_Data پوشه‌ای امن است و دسترسی مستقیم از طریق آدرس بار مرورگر به فایل‌های درون آن توسط IIS داده نمی‌شود و افراد فقط از طریق سرویس‌های خود شما می‌توانند به فایل‌های داخل این پوشه دسترسی داشته باشند. بدین صورت در سرویس‌های خود می‌توانید با تبدیل نمودن [12] فایل‌ها به نوع خودشان (تصویر. پی دی اف یا ...) هکر را نا امید نمایید. این موضوع شما را در مقابل حملات SHELL مقاوم می‌نماید.
- استفاده از تکنیک‌های لاگین چند سطحی برای پنل ادمین. در این روش شما حتی با داشتن نام کاربری و کلمه‌ی عبور ادمین، قادر نخواهید بود وارد پنل ادمین شوید. نویسنده ابزار می‌دارد که این روش، یک روش ابداعی می‌باشد که از ترکیبی از احرا هویت ساده توسط نام کاربری و کلمه‌ی عبور به همراه تکنیک‌های احراز هویت ایمیل و موبایل مدیریت سایت می‌باشد.
- استفاده از SSL بسیار اهمیت دارد. بالاخص اگر نرم افزار شما Service Oriented باشد و نرم افزار شما سرویس هایی جهت اتصال به اپلیکیشن‌های خارجی مثل اپلیکیشن اندروید دارد. این مورد در صفحات لاگین نیز بسیار مهم است و موجب می‌شود نام کاربری و کلمه عبور کاربران شما بصورت هش شده بین کامپیوتر کاربر و سرور شما رد و بدل شود و عملا شنود پکت‌ها فایده ای برای هکر نخواهد داشت، زیرا داده‌ها توسط الگوریتم‌های امنیتی که بین سرور و مرورگر کاربران توافق می‌شود کدگذاری شده و سپس رد و بدل می‌شوند.



[1] Back Door
[2] https://www.nuget.org/packages/AntiXss/
[3] www. Nuget.org
[4] Query String
[5] Casting
[6] Procedure
[7] Routing
[8] Email
[9] Custom Role Provider
[10] Cast
[11] Back Door
[12] Cast
مطالب
پیاده سازی Remote Validation در Blazor
فرم‌های Blazor به همراه پشتیبانی از ویژگی Remote که به همراه ASP.NET Core ارائه می‌شود، نیستند. هرچند می‌توان در حین ارسال فرم به سرور، نتیجه‌ی اعتبارسنجی از راه دور و سمت سرور را به کاربر نمایش داد، اما تجربه‌ی کاربری آن در حد Remote validation نیست. یعنی می‌خواهیم در حین ورود اطلاعات و یا انتقال focus به کنترل دیگری، اعتبارسنجی سمت سرور صورت گیرد و نه فقط در زمان ارسال کل اطلاعات به سرور، در پایان کار. در این مطلب روشی را جهت پیاده سازی این عملیات بررسی خواهیم کرد.


ایجاد یک پروژه‌ی ابتدایی Blazor WASM

پروژه‌ای را که در این مطلب تکمیل خواهیم کرد، از نوع Blazor WASM هاست شده‌است. بنابراین در پوشه‌ی فرضی BlazorAsyncValidation، دستور «dotnet new blazorwasm --hosted» را صادر می‌کنیم تا ساختار ابتدایی پروژه که به همراه یک کلاینت Blazor WASM و یک سرور ASP.NET Core Web API است، تشکیل شود. از قسمت Web API، برای پیاده سازی اعتبارسنجی سمت سرور استفاده خواهیم کرد.


مدل ثبت نام برنامه

مدل ثبت نام برنامه تنها از یک خاصیت نام تشکیل شده و در پروژه‌ی Shared قرار می‌گیرد تا هم در کلاینت و هم در سرور قابل استفاده باشد:
using System.ComponentModel.DataAnnotations;

namespace BlazorAsyncValidation.Shared;

public class UserDto
{
    [Required] public string Name { set; get; } = default!;
}
این نام است که می‌خواهیم عدم تکراری بودن آن‌را حین ثبت نام در سمت سرور، بررسی کنیم. به همین جهت کنترلر API زیر را برای آن تدارک خواهیم دید.


کنترلر API ثبت نام برنامه

کنترلر زیر که در پوشه‌ی BlazorAsyncValidation\Server\Controllers قرار می‌گیرد، منطق بررسی تکراری نبودن نام دریافتی از برنامه‌ی کلاینت را شبیه به منطق remote validation استاندارد MVC، پیاده سازی می‌کند که در نهایت یک true و یا false را باز می‌گرداند.
در اینجا خروجی بازگشت داده کاملا در اختیار شما است و نیازی نیست تا حتما ارتباطی با MVC داشته باشد؛ چون مدیریت سمت کلاینت بررسی آن‌را خودمان انجام خواهیم داد و نه یک کتابخانه‌ی از پیش نوشته شده و مشخص.
using BlazorAsyncValidation.Shared;
using Microsoft.AspNetCore.Mvc;

namespace BlazorAsyncValidation.Server.Controllers;

[ApiController, Route("api/[controller]/[action]")]
public class RegisterController : ControllerBase
{
    [HttpPost]
    public IActionResult IsUserNameUnique([FromBody] UserDto userModel)
    {
        if (string.Equals(userModel?.Name, "Vahid", StringComparison.OrdinalIgnoreCase))
        {
            return Ok(false);
        }

        return Ok(true);
    }
}

غنی سازی فرم استاندارد Blazor جهت انجام Remote validation



اگر بخواهیم از EditForm استاندارد Blazor در حالت متداول آن و بدون هیچ تغییری استفاده کنیم، مانند مثال زیر که InputText متصل به خاصیت Name مربوط به Dto برنامه را نمایش می‌دهد:
@page "/"

<PageTitle>Index</PageTitle>

<h2>Register</h2>

<EditForm EditContext="@EditContext" OnValidSubmit="DoSubmitAsync">
    <DataAnnotationsValidator/>
    <div class="row mb-2">
        <label class="col-form-label col-lg-2">Name:</label>
        <div class="col-lg-10">
            <InputText @bind-Value="Model.Name" class="form-control"/>
            <ValidationMessage For="@(() => Model.Name)"/>
        </div>
    </div>

    <button class="btn btn-secondary" type="submit">Submit</button>
</EditForm>
 تنها رخ‌دادی که در اختیار ما قرار می‌گیرد، رخ‌داد submit (در حالت موفقیت اعتبارسنجی سمت کلاینت و یا تنها submit معمولی) است. بنابراین برای دسترسی به رخ‌دادهای بیشتر EditForm، نیاز است با EditContext آن کار کنیم:
public partial class Index
{
    private const string UserValidationUrl = "/api/Register/IsUserNameUnique";

    private ValidationMessageStore? _messageStore;
    [Inject] private HttpClient HttpClient { set; get; } = default!;
    private EditContext? EditContext { set; get; }

    private UserDto Model { get; } = new();
به همین جهت EditContext را در سطح کامپوننت جاری تعریف کرده و همچنین سرویس HttpClient را جهت ارسال اطلاعات Name و دریافت پاسخ true/false از سرور و یک ValidationMessageStore را برای نگهداری تعاریف خطاهای سفارشی که قرار است در فرم نمایش داده شوند، معرفی می‌کنیم.
ValidationMessageStore به همراه متد Add است و اگر به آن نام فیلد مدنظر را به همراه پیامی، اضافه کنیم، این اطلاعات را به صورت خطای اعتبارسنجی توسط کامپوننت ValidationMessage نمایش می‌دهد.

محل مقدار دهی اولیه‌ی این اشیاء نیز در روال رویدادگردان OnInitialized به صورت زیر است:
    protected override void OnInitialized()
    {
        EditContext = new EditContext(Model);
        _messageStore = new ValidationMessageStore(EditContext);
        EditContext.OnFieldChanged += (sender, eventArgs) =>
        {
            var fieldIdentifier = eventArgs.FieldIdentifier;
            _messageStore?.Clear(fieldIdentifier);
            _ = InvokeAsync(async () =>
            {
                var errors = await OnValidateFieldAsync(fieldIdentifier.FieldName);
                if (errors?.Any() != true)
                {
                    return;
                }

                foreach (var error in errors)
                {
                    _messageStore?.Add(fieldIdentifier, error);
                }

                EditContext.NotifyValidationStateChanged();
            });
            StateHasChanged();
        };
        EditContext.OnValidationStateChanged += (sender, eventArgs) => StateHasChanged();
        EditContext.OnValidationRequested += (sender, eventArgs) => _messageStore?.Clear();
    }
در اینجا ابتدا یک نمونه‌ی جدید از EditContext، بر اساس Model فرم، تشکیل می‌شود. سپس ValidationMessageStore سفارشی که قرار است خطاهای اعتبارسنجی remote ما را نمایش دهد نیز نمونه سازی می‌شود. در ادامه امکان دسترسی به رخ‌دادهای OnFieldChanged ، OnValidationStateChanged و OnValidationRequested را خواهیم داشت که در حالت معمولی کار با EditForm میسر نیستند.
برای مثال اگر فیلدی تغییر کند، رویداد OnFieldChanged صادر می‌شود. در همینجا است که کار فراخوانی متد OnValidateFieldAsync که در ادامه معرفی می‌شود را انجام می‌دهیم تا کار اعتبارسنجی Async سمت سرور را انجام دهد. اگر نتیجه‌ای به همراه آن بود، توسط messageStore به صورت یک خطای اعتبارسنجی نمایش داده خواهد شد و همچنین EditContext نیز با فراخوانی متد NotifyValidationStateChanged، وادار به به‌روز رسانی وضعیت اعتبارسنجی خود می‌گردد.

متد سفارشی OnValidateFieldAsync که کار اعتبارسنجی سمت سرور را انجام می‌دهد، به صورت زیر تعریف شده‌است:
    private async Task<IList<string>?> OnValidateFieldAsync(string fieldName)
    {
        switch (fieldName)
        {
            case nameof(UserDto.Name):
                var response = await HttpClient.PostAsJsonAsync(
                    UserValidationUrl,
                    new UserDto { Name = Model.Name });
                var responseContent = await response.Content.ReadAsStringAsync();
                if (string.Equals(responseContent, "false", StringComparison.OrdinalIgnoreCase))
                {
                    return new List<string>
                    {
                        $"`{Model.Name}` is in use. Please choose another name."
                    };
                }

                // TIP: It's better to use the `DntDebounceInputText` component for this case to reduce the network round-trips.

                break;
        }

        return null;
    }
در اینجا درخواستی به سمت آدرس api/Register/IsUserNameUnique ارسال شده و سپس بر اساس مقدار true و یا false آن، پیامی را بازگشت می‌دهد. اگر نال را بازگشت دهد یعنی مشکلی نبوده.

یک نکته: InputText استاندارد در حالت معمول آن، پس از تغییر focus به یک کنترل دیگر، سبب بروز رویداد OnFieldChanged می‌شود و نه در حالت فشرده شدن کلیدها. به همین جهت اگر برنامه پیوستی را می‌خواهید آزمایش کنید، نیاز است فقط focus را تغییر دهید و یا یک کنترل سفارشی را برای اینکار توسعه دهید.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: BlazorAsyncValidation.zip