مطالب دوره‌ها
دریافت قالب WpfFramework.vsix و نحوه نصب و راه اندازی آن
فایل قالب پروژه دوره جاری را از آدرس ذیل می‌توانید دریافت کنید:

نصب ابتدایی آن بسیار ساده است و نکته خاصی ندارد.
پس از نصب، یکبار VS.NET را بسته و سپس باز کنید. این قالب جدید، ذیل قسمت پروژه‌های ویندوز مرتبط با ویژوال سی شارپ، ظاهر خواهد شد:


در این حال اگر یک پروژه جدید را آغاز کنید، این قالب تدارک دیده شده، لایه‌ها و قسمت‌های مختلف را به صورت خودکار اضافه خواهد کرد:



نکته مهم! برنامه کامپایل نمی‌شود!

به عمد، جهت کاهش حجم قالب دریافتی فوق، فایل‌های باینری آن پیوست نشده‌اند. وگرنه باید بالای 30 مگابایت را دریافت می‌کردید که واقعا نیازی نیست.
اما اگر به هر پروژه داخل Solution دقت کنید، فایل متنی packages.config آن نیز پیوست شده است. به کمک این فایل‌ها به سادگی می‌توان تمام وابستگی‌ها را از طریق NuGet بازیابی کرد.
برای این منظور ابتدا به اینترنت متصل شده (مهم) و سپس بر روی Solution کلیک راست کرده و گزینه فعال سازی Restore کلیه بسته‌های نیوگت را انتخاب کنید.


پس از اینکار، آخرین نگارش NuGet.exe از اینترنت دریافت و به پروژه اضافه می‌شود:


اکنون اگر Solution را Build کنید، اولین کاری که صورت خواهد گرفت، دریافت کلیه وابستگی‌ها از سایت NuGet است. اندکی تامل کنید تا اینکار تمام شود.
پس از پایان کار دریافت، پوشه packages در کنار فایل sln پروژه ایجاد شده، تشکیل می‌شود.
یکبار وجود آن‌را بررسی کنید.


اکنون اگر برنامه را Build کنید احتمالا پیغام می‌دهد که Fody را نمی‌تواند پیدا کند با اینکه دریافت شده و در پوشه packages موجود است. هر زمان، هر پیغام خطایی در مورد Fody مشاهده کردید، فقط یکبار VS.NET را بسته و باز کنید. مشکل حل می‌شود!

تنها کاری که پس از بازسازی پوشه packages بهتر است صورت گیرد (اختیاری است البته)، صدور دستور ذیل در خط فرمان پاور شل است:
 PM> Update-Package
با این دستور، دریافت کامل مجددی از اینترنت صورت نمی‌گیرد؛ مگر اینکه به روز رسانی جدیدی را یافت کند. در سایر حالات از کش داخل سیستم اطلاعات را دریافت می‌کند.
پس از اینکار، نیاز است یکبار VS.NET را بسته و مجددا باز کنید (مهم) تا تمام وابستگی‌ها به درستی بارگذاری شوند. خصوصا بسته Fody که کار AOP را انجام می‌دهد. در غیر اینصورت موفق به Build پروژه نخواهید شد.

بنابراین به صورت خلاصه:
الف) یکبار گزینه فعال سازی Restore کلیه بسته‌های نیوگت را انتخاب کنید.
ب) پروژه را Build کنید تا وابستگی‌ها را از سایت NuGet دریافت کند.
ج) دستور Update-Package را اجرا نمائید (اختیاری).
ج) VS.NET را پس از سه مرحله فوق، یکبار بسته و باز کنید.

در کل بسته‌های مورد استفاده به این شرح هستند:
PM> Install-Package MahApps.Metro -Pre 
PM> Install-Package MahApps.Metro.Resources 
PM> Install-Package EntityFramework
PM> Install-Package structuremap
PM> Install-Package PropertyChanged.Fody
PM> Install-Package MvvmLight 
PM> Install-Package Microsoft.SqlServer.Compact

یک نکته جانبی
فید NuGet در VS.NET به Https تنظیم شده است. اگر دسترسی به Https برای شما به کندی صورت می‌گیرد فقط کافی است مسیر فید آن‌را در منوی Tools، گزینه‌ی Options، ذیل قسمت Package manager یافته و به http://nuget.org/api/v2 تغییر دهید؛ یعنی به Http خالی، بجای Https؛ تا سرعت دریافت بسته‌های NuGet مورد نظر افزایش یابند.



اجرای پروژه و برنامه

پس از این مراحل و Build موفقیت آمیز پروژه، برنامه را اجرا کنید. در اولین بار اجرای برنامه به صورت خودکار بانک اطلاعاتی به همراه ساختار جداول تشکیل می‌شوند. اما ... با خطای زیر مواجه خواهید شد:
 The path is not valid. Check the directory for the database. [ Path = D:\...\bin\Debug\Db\db.sdf ]
علت این است که در فایل app.config پروژه ریشه برنامه :
<connectionStrings>
    <clear/>
    <add name="MyWpfFrameworkContext"
         connectionString="Data Source=|DataDirectory|\Db\db.sdf;Max Buffer Size=30720;File Mode=Read Write;"
         providerName="System.Data.SqlServerCE.4.0" />
  </connectionStrings>
مسیر تشکیل بانک اطلاعاتی به پوشه db کنار فایل اجرایی برنامه تنظیم شده است. این پوشه db را در پوشه bin\debug ایجاد کرده و سپس برنامه را اجرا کنید.


برای ورود به برنامه از نام کاربری Admin و کلمه عبور 123456 استفاده نمائید.
این کاربر پیش فرض در کلاس MyWpfFrameworkMigrations لایه داده‌های برنامه، توسط متد addRolesAndAdmin اضافه شده است.


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




به روز رسانی 1.1
جهت سازگاری با EF 6 ، StructureMap 3 و همچنین VS 2013، پروژه به روز شد: WpfFrmwork_1.1.zip

به روز رسانی نهایی
به روز شده‌ی این پروژه را بر اساس آخرین وابستگی‌های آن از اینجا دریافت کنید.
مطالب
مروری بر کاربرد DoEvents

چند روز قبل هنگام استفاده از DoEvents در یک برنامه windows forms ، ناگهان پیغام stack overflow ظاهر شد! برای علت یابی و رفع آن کمی جستجو کردم که خلاصه‌ی آن به شرح زیر است:


DoEvents چیست؟

DoEvents یکی از متدهای کلاس Application در فضای نام System.Windows.Forms است.
ویندوز جهت مدیریت رخدادهای مختلف از یک صف استفاده می‌کند. رخدادهایی مانند کلیک ماوس، تغییر اندازه‌ی یک فرم و مواردی شبیه به آن ابتدا در یک صف قرار می‌گیرند و سپس پردازش می‌شوند. زمانیکه کنترلی مشغول پاسخ دهی به یک رخ‌داد می‌گردد، سایر رخ‌دادها هنوز در صف هستند و پردازش نخواهند شد. بنابراین اگر برنامه‌ی شما در یک روال رخ‌دادگردان کلیک، عملیاتی طولانی را در حال انجام باشد، بدلیل عدم پردازش سایر رخ‌دادها اینطور به نظر خواهد رسید که هنگ کرده است.
روش صحیح پردازش یک عملیات طولانی استفاده از یک ترد دیگر می‌باشد تا ترد اصلی برنامه که کار مدیریت رابط کاربر برنامه را به عهده دارد، درگیر این عملیات طولانی نشده و پاسخگوی رخ‌دادهای رسیده باشد.
راه میان‌بر و ساده‌ای که اینجا وجود دارد استفاده از DoEvents می‌باشد (بدون ایجاد یک ترد جدید). برای مثال اگر در روال رخ دادگردان کلیک یک برنامه، حلقه‌ای طولانی در حال پردازش است، هر از چندگاهی این متد فراخوانی شود، رخ‌دادهای در صف قرار گرفته فرصت ارسال به ترد اصلی برنامه را یافته و برنامه در حالت هنگ به نظر نخواهد رسید.
برای نمونه مثال زیر را در دو حالت با Application.DoEvents و بدون آن اجرا کنید:

private void btnProcessWithDoEvents_Click(object sender, EventArgs e)
{
for (int i = 0; i < 100000; i++)
{
TextBox1.Text = "Processing " + i.ToString();
Application.DoEvents();
}
}

در حالت بدون استفاده از Application.DoEvents ، تنها آخرین عبارت پردازش شده را در TextBox1 مشاهده خواهید کرد و همچنین در این حین، برنامه در حالت هنگ به نظر می‌رسد و برعکس.

مشکلات احتمالی حاصل از استفاده از Application.DoEvents :

الف) حس غلط پایان یافتن عملیات پیش از موعد
در مثال فوق در حین استفاده از Application.DoEvents ، دکمه‌ی btnProcessWithDoEvents مجددا فعال شده و قابل کلیک کردن می‌شود ولی آیا این بدین معنا است که پردازش قبلی به پایان رسیده است؟ به یک سری از کاربرها هم click-happy user گفته می‌شود! یعنی از کلیک کردن مجدد لذت می‌برند! در این حالت حتما باید دکمه‌ی btnProcessWithDoEvents را در ابتدای پردازش غیرفعال کرد و سپس در انتهای آن باید مجددا فعال شود.
مورد مشکل کلیک مجدد حتی می‌تواند منجر به تخریب اطلاعات در حال پردازش شود. فرض کنید برنامه در حال ذخیره‌ی اطلاعات در یک فایل است و کاربر مرتبا بر روی دکمه‌ی پردازش مربوطه کلیک کنید. فایل نهایی از یک سری اطلاعات ناهماهنگ و بی‌ربط پر خواهد شد.

ب) مشکل stack overflow
اگر علاقمند باشید، این مورد را می‌توان به صورت زیر شبیه سازی کرد:

یک تایمر را به برنامه اضافه کنید و یک دکمه. در روال رخ‌دادگردان کلیک مربوط به دکمه، دستورات زیر را اضافه کنید:

private void btnStartTimer_Click(object sender, EventArgs e)
{
this.timer1.Enabled = true;
this.timer1.Start();
this.timer1.Interval = 20;

}
و در روال tick مربوط به تایمر، دستورات زیر را اضافه کنید:

private void timer1_Tick(object sender, EventArgs e)
{
Thread.Sleep(50);
Application.DoEvents();
}
برنامه را اجرا کرده و یکی دو دقیقه صبر کنید، حتما با پیغام خطای stack overflow مواجه خواهید شد. چرا؟
فواصل زمانی اجرای تایمر به 20 میلی ثانیه تنظیم شده است اما در روال رخ‌داد گردان tick آن، نیاز به 50 میلی ثانیه (بیش از 20 میلی ثانیه) یا بیشتر برای اجرا دارد. با رسیدن به Application.DoEvents ، رخ‌داد در صف قرار گرفته‌ی دیگر tick بلافاصله اجرا می‌شود و همینطور الی آخر، تا بالاخره stack overflow حاصل خواهد شد.


پس چه باید کرد؟

الف) هنگام استفاده از Application.DoEvents به موارد فوق حتما دقت داشته باشید.
ب) بجای استفاده از این روش که در بیشتر موارد یک ضعف برنامه نویسی محسوب می‌شود، شروع به استفاده از روش‌های غیرهمزمان نمائید. برای مثال استفاده از :
BackgroundWorker
Asynchronous delegates
Threads

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

جهت مطالعه بیشتر
Keeping your UI Responsive and the Dangers of Application.DoEvents


مطالب
ایجاد و نگهداری Stored Procedure و View با استفاده از Migration
اگر از Entity Framework به روش Code-First استفاده میکنید و بر حسب نیاز، لازم است Stored procedure, View و یا Function ی ایجاد و استفاده نمایید، بهتر است برای ایجاد و نگهداری آنها از Migration‌ها استفاده نمایید. مزیت این روش این است که بر روی سورس کنترل قرار دارد و افراد تیم با گرفتن پروژه و اجرا کردن دستور Update-Database آخرین تغییرات را خواهند داشت.

فرض کنید میخواهید یک Procedure را بر روی دیتابیس ایجاد نمایید که وظیفه‌ی درج یک نویسنده را در جدول Authors، برعهده دارد. برای این منظور SP زیر لازم است نوشته شود:
CREATE PROCEDURE CreateNewAuthor
@name nvarchar(50)
AS
BEGIN
INSERT INTO Authors values(@name)
END
GO

برای ایجاد خودکار این SP میخواهیم از Migration استفاده نماییم. برای این کار ابتدا یک Migration جدید را با دستور زیر ایجاد میکنیم:
Add-Migration StoredProcedureForCreateNewAuthor
اگر مشکلی در بیلد پروژه وجود نداشته باشد، خروجی زیر حاصل می‌شود که یک فایل خالی می‌باشد:
 public partial class StoredProcedureForCreateNewAuthor : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {

        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {

        }
    }
در اینجا کاری که باید انجام شود این است که در متد Up که در زمان به روزرسانی دیتابیس اجرا می‌گردد، SP (View یا Function) را ایجاد نماییم. برای اینکار از متد Sql بر روی migrationBuilder، همانند کدهای زیر استفاده می‌کنیم:
protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql(@"CREATE PROCEDURE CreateNewAuthor
                                    @name nvarchar(50)
                                    AS
                                    BEGIN
                                        INSERT INTO Authors values(@name)
                                    END
                                    GO");
        }
با اجرای دستور Update-Database پروسیجر فوق در دیتابیس ایجاد می‌شود.
لازم به ذکر است، اگر با طرز کار Migration‌ها آشنایی داشته باشید، متد down، عکس عملکرد متد Up می‌باشد و وظیفه‌ی حذف تمامی تغییرات مربوط به Migration فعلی را دارد. با این اوصاف، نیاز هست متد Down را به صورت زیر تغییر دهیم:
 protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql("DROP PROCEDURE CreateNewAuthor");
        }

- در این مقاله فقط در مورد Stored Procedure‌ها صحبت شد؛ همین کار را میتوانید برای View‌ها هم انجام دهید.
پاسخ به بازخورد‌های پروژه‌ها
راهنمایی برای دستورات for .use.جهت Initialize کردن ObjectFactory
اینجا هم مثل کد بالا هست. فقط قسمت «تنظیمات خودکار اولیه در StructureMap» مقاله‌ای را که لینک دادید مورد استفاده قرار گرفته (انتهای مقاله).
ضمنا تزریق وابستگی‌ها هیچ ربطی به روابط یک به چند ندارند. در مورد معکوس سازی وابستگی‌ها و تنظیم سطوح دسترسی لایه‌ها است. اصلا ارتباطی به EF ندارد. بهتر است همان دوره‌ای را که لینک دادید تمام قسمت‌هایش را یکبار کامل مطالعه کنید و نه صرفا نتیجه‌ی یک جستجو.
مطالب
پردازش فایل‌های XML با استفاده از jQuery

فرض کنید مثال دریافت اطلاعات API فیدبرنر را بخواهیم با استفاده از jQuery پیاده سازی کنیم، یعنی امکان برنامه نویسی سمت سرور را نداریم و می‌خواهیم با استفاده از جاوا اسکریپت، تعداد مشترکین فید یک سایت را نمایش دهیم.
برای مثال آدرس دریافت اطلاعات مربوط به API فیدبرنر وبلاگ جاری به صورت زیر است:

و در حالت کلی :
http://api.feedburner.com/awareness/1.0/GetFeedData?uri=<feeduri>

که حاصل آن برای مثال یک فایل XML با فرمت زیر می‌باشد:

<rsp stat="ok">
<feed id="fhphjt61bueu08k93ehujpu234" uri="vahidnasiri">
<entry date="2009-01-23" circulation="153" hits="276" reach="10"/>
</feed>
</rsp>

همانطور که مطلع هستید چند روزی است که jQuery 1.3.1 ارائه شده است. جهت استفاده از آخرین نگارش موجود آن تا این زمان، می‌توان از گوگل به عنوان هاست این کتابخانه به صورت زیر استفاده کرد:

<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js' type='text/javascript'></script>

نحوه خواندن مقدار circulation فایل xml ذخیره شده بر روی کامپیوتر:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>FeedBurner API</title>
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js' type='text/javascript'>
</script>
<script type="text/javascript">
function parseXml(xml){
//find every entry and print the circulation
$(xml).find("entry").each(function(){
$("#output").append($(this).attr("circulation"));
});
}

$(document).ready(function(){
$.ajax({
type: "GET",
url: "GetFeedData_local.xml",
dataType: "xml",
success: parseXml
});
});
</script>
</head>
<body>
<div dir="rtl" style="font-family:tahoma; font-size:12px;">
تعداد مشترکین تغذیه خبری سایت:
<div id="output">
</div>
</div>
</body>
</html>

با استفاده از قابلیت Ajax کتابخانه jQuery ، اطلاعات فایل محلی GetFeedData_local.xml دریافت شده و محتوای آن به تابع parseXml پاس می‌شود (توسط قسمت success). سپس در این تابع تمام تگ‌های entry یافت شده و مقدار circulation آن‌ها به یک div با ID معادل output اضافه می‌شود.
مثال فوق در مورد خواندن اطلاعات از یک فایل xml می‌تواند برای مثال این کاربرد را در یک سایت داشته باشد:
نمایش اتفاقی سخن روز یا سخن بزرگان و امثال آن بدون برنامه نویسی سمت سرور جهت انجام این کار از یک فایل xml تهیه شده، بدون نیاز به استفاده از دیتابیس خاصی.

تا اینجای کار مشکلی نیست. اما همانطور که در مطلب مقابله با حملات CSRF نیز ذکر شد، مرورگرهای جدید امکان ارسال یا دریافت اطلاعات به صورت Ajax را بین سایت‌ها ممنوع کرده‌اند (ماجرا هم از آنجا شروع شد که یکبار جی‌میل این باگ امنیتی را داشته است). بنابراین اگر شما بجای url قسمت Ajax فوق، آدرس سایت فید برنر را قرار دهید با خطای زیر متوقف خواهید شد:

Access to restricted URI denied

تمام موارد دیگری هم که در jQuery برای دریافت اطلاعات از یک فایل یا url موجود است (مثلا تابع load یا get و امثال آن) فقط به سایت جاری و دومین جاری باید ختم شوند در غیر اینصورت توسط مرورگرهای جدید متوقف خواهند شد.

مطالب
آموزش فایرباگ - #5 - HTML Panel
در این سری از مقالات آموزش FireBug ، به صورت ترتیبی پیش رفتیم و ابتدا توضیحات تقریبا مفصلی در مورد پنل Console دادیم.
اکنون به پرکاربردترین بخش آن ، یعنی پنل HTML می‌رسیم.

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

نگاهی به کدهای ارسال شده به مرورگر در آدرس Google.com و نحوه‌ی نمایش آن در فایرباگ را ملاحظه بفرمایید.
( کدهای ارسال شده سمت چپ - نمایش در فایرباگ سمت راست )


این پنل به سه بخش اصلی تقسیم می‌شود :
  • بخش اصلی یا NodeView که محتوای صفحه را بصورت درختی و مرتب و رنگی نمایش می‌دهد.
  •  Panel Toolbar که در بالای پنل قرار دارد.
  •  Side Panels که شامل پنل‌های Style , Computed , Layout , DOM می‌شود.
    که به ترتیب برای نمایش و ویرایش استایل‌ها ، مشاهده استایل‌های محاسبه شده ، مشاهده Layout یا آرایش و نمایش اطلاعات DOM تگ انتخاب شده در NodeView است.

در این مقاله با دو بخش NodeView و Panel Toolbar ، و در مقاله‌ی بعد با پنل‌های سمت راست یعنی Side Panels آشنا می‌شویم.

ویرایش HTML

هر تگ HTML از یک سری Attribute تشکیل می‌شود که در فایرباگ نام ویژگی بصورت آبی تیره و مقدار آن با رنگ قرمز مشخص شده است. برای ویرایش هریک از آن‌ها کافیست برویش کلیک کنید تا آن مقدار در یک باکس ویرایش به نمایش دربیاید.
با ویرایش مقدار ، این تغییر در لحظه بروی صفحه اعمال می‌شود.


برای اضافه کردن یک Attribute جدید به تگ هم بروی تگ مورد نظر راست کلیک می‌کنید و سپس گزینه‌ی New Attribute را انتخاب می‌کنید. ابتدا نام ویژگی ، یک Enter ، سپس مقدار را وارد می‌کنید. با زدن کلیدهای Enter متوالی ، می‌توانید به وارد کردن ویژگی‌ها ادامه دهید.
برای ویرایش کردن یک تگ و تگ‌های فرزندش ، بروی تگ موردنظر کلیک کنید تا به حالت انتخاب دربیاید ، سپس بروی دکمه‌ی Edit در بالای پنل کلیک کنید. در نهایت بعد از انجام ویرایش ، مجددا بروی دکمه‌ی Edit کلیک کنید.


 Node View
NodeView نام بخش اصلی پنل HTML است که محتویات صفحه را بصورت مرتب شده و درختی نمایش می‌دهد.
تگ (Node) هایی که دارای فرزند می‌باشند ، یک علامت + یا - در کنارشان وجود دارد که با کلیک بروی آن می‌توانید آن تگ را باز/بسته کنید. همچنین این کار با کلید‌های + و - یا Right Arrow و Left Arrow از روی کیبورد هم قابل انجام است.
برای باز کردن یک لینک در این قسمت هم از ترکیب Ctrl و کلیک موس کمک بگیرید.
در نهایت زمانی که موس را بروی یک تگ قرار می‌دهید ، محیطی که توسط آن تگ در صفحه اشغال شده است بصورت رنگی مشخص می‌شود.
که رنگ زرد به معنی محیط margin ، رنگ آبی تیره به معنی محیط padding و رنگ آبی روشن هم به معنی محیط محتوا است.

 Panel Toolbar
این قسمت در بالای پنل HTML قرار دارد که گزینه‌های زیر را دارا می‌باشد:



  •  Break On Mutate
    این دکمه امکان توقف کد JavaScript ای که سعی در ویرایش محتوای صفحه را دارد ، می‌دهد.
    زمانی که FireBug تشخیص دهد که کدی سعی در ویرایش محتوا دارد ، شما را به خط مورد نظر از کد ، در پنل Script منتقل می‌کند.

  • Edit
    این دکمه برای ویرایش مستقیم محتوای یک تگ بکار می‌رود
    نکته‌ی جالب در ویرایش محتوا در فایرباگ این است که تغییرات در لحظه اعمال می‌شوند و نیاز به عمل بروزرسانیِ جداگانه نیست. برای مثال در همین قسمت Edit ، با هر ویرایش محتوا ، تغییرات در لحظه اعمال می‌شوند.

  •  Element Path
    زمانی که یک تگ را در FireBug انتخاب می‌کنید ، لیستی از تگ‌ها که از تگ جاری شروع و به تگ ریشه ختم می‌شود ، نمایش داده می‌شود که با کلیک بروی هرکدام ، همان به عنوان تگ فعلی یا انتخاب شده تعیین می‌شود.
    نتیجه‌ی عملیاتی که بروی این تگ‌های انجام می‌دهید (حرکت موس و راست کلیک کردن) معادل همان عملیات بروی تگ‌های نمایش داده شده در قسمت اصلی (NodeView) است.



Options Menu

هر تب یا پنل در فایرباگ دارای یک سری تنظیمات است که Options Menu نام دارد. تب HTML هم دارای یک سری تنظیمات است که دانشتن آنها بسیار به شما کمک خواهد کرد.
این منو با کلیک کردن بروی فلش تب () یا راست کلیک کردن بروی تب ظاهر می‌شود.


  • Show Full Text
    در صورت فعال بودن ، متون بصورت کامل نمایش داده می‌شوند ، در غیراینصورت بصورت خلاصه شده نمایش داده می‌شوند.

  • Show White Space
    در صورت فعال بودن ، فضاهای خالی ، کرکترهای خط جدید و ... را نمایش می‌دهد.


  • Show Comments
    در صورت فعال بودن ، کامنت‌ها را هم نمایش می‌دهد


  • سه گزینه ی Show Entities As Symbols ، Show Entities As Names و Show Entities As Unicode ، نوع نمایش کرکترهای ویژه را تعیین می‌کنند.

  • Highlight Changes
    در صورت فعال بودن ، تگ تغییر یافته توسط JavaScript (یا تگ والدی که قابل مشاهده باشد) Highlight می‌شود

  • Expand Changes
    در صورت فعال بودن ، زمان تغییر دادن یک تگ توسط JavaScript ، والدهای آن تگ باز (Expand) می‌شوند

  • Scroll Changes Into View
    در صورت فعال بودن این گزینه ، هنگام تغییر یک تگ در صفحه توسط JavaScript ، قسمت NodeView به قسمت تغییر بافته حرکت می‌کند.
    (فعال بودن این گزینه هنگام بررسی سایت هایی که اسلایدر یا سیستم هایی مشابه دارند ، باعث میشه که نتوانید بروی قسمت‌های سایت تمرکز کنید و مدام اسکرول به قسمت تغییرات منتقل می‌شود.)

  • Shade Box Model
    در صورت فعال بودن ، فضای margin , padding و content را به شکلی که در بالا گفته شد نمایش می‌دهد ، در غیر اینصورت فقط یک خط دور تگ نشان می‌دهد.

  • Show Quick Info Box
    در صورت فعال بودن ، یک پاپ‌آپ به همراه اطلاعات مختصری از تگ در صفحه نمایش می‌دهد.




Context Menu
اگر بروی یک تگ راست کلیک کنید ، یک منو نمایش داده می‌شود ، در این قسمت با گزینه‌های این منو آشنا می‌شویم.

  • Copy HTML
    خود تگ و محتوایش را بصورت HTML در حافظه کپی می‌کند.

  • Copy innerHTML
    محتوای تگ را در حافظه کپی می‌کند.

  • Copy XPath
    آدرس XPath تگ را در حافظه کپی می‌کند.

  • Copy CSS Path
    CSS Selector تگ را در حافظه کپی می‌کند.

  • Log Events
    رویدادهای تگ را در پنل Console ثبت می‌کند. (کلیک موس ، فشردن کلید ، ...)
    برای لغو لاگ کردن ، مجددا بروی تگ راست کلیک کرده و این گزینه را از حالت انتخاب خارج کنید.

  • Scroll Into View
    صفحه را به جایی که تگ قابل نمایش است منتقل می‌کند.

  • New Attribute...
    یک attribute جدید به تگ اضافه می‌کند.
    برای لغو عملیات ، دکمه‌ی Esc را بزنید.

  • Edit Attribute "<attribute name>"...
    اگر بروی یک attribute راست کلیک کرده باشید ، این گزینه و گزینه‌ی بعدی را مشاهده خواهید کرد.
    معادل کلیک بروی نام attribute است ، نام را به حالت ویرایش درمی آورد.

  • Delete Attribute "<attribute name>"
    attribute را حذف می‌کند.

  • Edit HTML...
    تگ را به حالت ویرایش می‌برد.
    معادل انتخاب تگ ، زدن کلید Edit است.

  • Delete Element
    تگ را حذف می‌کند.
    راه دیگر حذف یک تگ ، انتخاب تگ و فشردن کلید Del از کیبورد است.

  • Expand/Contract All
    تگ و Childهایش را باز/بسته می‌کند. (بجز تگ های <script> , <style> , <link>)
    می‌توان با ترکیب کلیدShift + * هم این کار را انجام داد که در این حالت تگ‌های فوق هم شامل باز/بسته شدن می‌شوند.

  • Break On Attribute Change
    مانع اجرای کد JavaScript ای که attribute تگ را تغییر می‌دهد می‌شود و فایرباگ به خطی که باعث این عمل شده است در پنل Script منتقل می‌شود.
    به عبارتی دیگر یک Break Point در خط JavaScript ای که باعث ویرایش attribute می‌شود قرار می‌دهد.

  • Break On Child Addition or Removal
    مشابه توضیح قبل ، Break Point را در خطی که باعث اضافه/حذف شدن تگِ Child شده است قرار می‌دهد.

  • Break On Element Removal
    مشابه توضیح قبل ، Break Point را در خطی که باعث حذف شدن تگ شده است قرار می‌دهد.

  • Inspect in DOM Tab
    تگ فعلی را در پنل DOM ، برای بررسی باز می‌کند.


در قسمت بعدی با پنل‌های سمت راست (Side Panels) آشنا می‌شویم.
مطالب
محدود سازی نرخ دسترسی به منابع در برنامه‌های ASP.NET Core - قسمت دوم - پیاده سازی
در قسمت قبل با مفاهیم، اصطلاحات و الگوریتم‌های مرتبط با میان‌افزار جدید Rate limiting مخصوص ASP.NET Core 7 آشنا شدیم که در پشت صحنه از امکانات موجود در فضای نام System.Threading.RateLimiting استفاده می‌کند. در این قسمت نحوه‌ی استفاده‌ی از آن‌را مرور خواهیم کرد.


روش افزودن میان‌افزار RateLimiter به برنامه‌های ASP.NET Core

شبیه به سایر میان‌افزارها، جهت فعالسازی میان‌افزار RateLimiter، ابتدا باید سرویس‌های متناظر با آن‌را به برنامه معرفی کرد و پس از فعالسازی میان‌افزار مسیریابی، آن‌‌را به زنجیره‌ی مدیریت یک درخواست معرفی نمود. برای نمونه در مثال زیر، امکان دسترسی به تمام درخواست‌ها، به 10 درخواست در دقیقه، محدود می‌شود که پارتیشن بندی آن (در مورد پارتیشن بندی در قسمت قبل بیشتر بحث شد)، بر اساس username کاربر اعتبارسنجی شده و یا hostname یک کاربر غیراعتبارسنجی شده‌است:
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRateLimiter(options =>
{
    options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: httpContext.User.Identity?.Name ?? httpContext.Request.Headers.Host.ToString(),
            factory: partition => new FixedWindowRateLimiterOptions
            {
                AutoReplenishment = true,
                PermitLimit = 10,
                QueueLimit = 0,
                Window = TimeSpan.FromMinutes(1)
            }));
});

// ...

var app = builder.Build();

// ...

app.UseRouting();
app.UseRateLimiter();

app.MapGet("/", () => "Hello World!");

app.Run();
توضیحات:
- فراخوانی builder.Services.AddRateLimiter، سبب معرفی سرویس‌های میان‌افزار rate limiter به سیستم تزریق وابستگی‌های ASP.NET Core می‌شود.
- در اینجا می‌توان برای مثال خاصیت options.GlobalLimiter تنظیمات آن‌را نیز مقدار دهی کرد. GlobalLimiter، سبب تنظیم یک محدود کننده‌ی سراسری نرخ، برای تمام درخواست‌های رسیده‌ی به برنامه می‌شود.
- GlobalLimiter را می‌توان با هر نوع PartitionedRateLimiter مقدار دهی کرد که در اینجا از نوع FixedWindowLimiter انتخاب شده‌است تا بتوان «الگوریتم‌های بازه‌ی زمانی مشخص» را به برنامه اعمال نمود تا برای مثال فقط امکان پردازش 10 درخواست در هر دقیقه برای هر کاربر، وجود داشته باشد.
- در پایان کار، فراخوانی app.UseRateLimiter را نیز مشاهده می‌‌کنید که سبب فعالسازی میان‌افزار، بر اساس تنظیمات صورت گرفته می‌شود.

برای آزمایش برنامه، آن‌را  اجرا کرده و سپس به سرعت شروع به refresh کردن صفحه‌ی اصلی آن کنید. پس از 10 بار ریفرش، پیام  503 Service Unavailable را مشاهده خواهید کرد که به معنای مسدود شدن دسترسی به برنامه توسط میان‌افزار rate limiter است.


بررسی تنظیمات رد درخواست‌ها توسط میان‌افزار rate limiter

اگر پس از محدود شدن دسترسی به برنامه توسط میان افزار rate limiter از status code = 503 دریافتی راضی نیستید، می‌توان آن‌را هم تغییر داد:
builder.Services.AddRateLimiter(options =>
{
    options.RejectionStatusCode = 429;

    // ...
});
برای مثال بسیاری از سرویس‌ها بجای 503، از status code دیگری مانند 429 Too Many Requests استفاده می‌کنند که نحوه‌ی تنظیم آن‌را در مثال فوق مشاهده می‌کنید.
علاوه بر آن در اینجا گزینه‌ی OnRejected نیز پیش بینی شده‌است تا بتوان response ارائه شده را در حالت رد درخواست، سفارشی سازی کرد تا بتوان پیام بهتری را به کاربری که هم اکنون دسترسی او محدود شده‌است، ارائه داد:
builder.Services.AddRateLimiter(options =>
{
    options.OnRejected = async (context, token) =>
    {
        context.HttpContext.Response.StatusCode = 429;
        if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
        {
            await context.HttpContext.Response.WriteAsync(
                $"Too many requests. Please try again after {retryAfter.TotalMinutes} minute(s). " +
                $"Read more about our rate limits at https://example.org/docs/ratelimiting.", cancellationToken: token);
        }
        else
        {
            await context.HttpContext.Response.WriteAsync(
                "Too many requests. Please try again later. " +
                "Read more about our rate limits at https://example.org/docs/ratelimiting.", cancellationToken: token);
        }
    };

    // ...
});
برای نمونه در مثال فوق ابتدا status code، به 429 تنظیم می‌شود و سپس یک response با معنا به سمت کاربر ارسال می‌گردد که دقیقا مشخص می‌کند آن کاربر چه زمانی می‌تواند مجددا سعی کند و همچنین لینکی را به مستندات محدود سازی برنامه جهت توضیحات بیشتر ارائه می‌دهد.

یک نکته: باتوجه به اینکه در اینجا به HttpContext دسترسی داریم، یعنی به context.HttpContext.RequestServices نیز دسترسی خواهیم داشت که توسط آن می‌توان برای مثال سرویس ILogger را از آن درخواست کرد و رخ‌داد واقع شده را برای بررسی بیشتر لاگ نمود؛ برای مثال چه کاربری مشکل پیدا کرده‌است؟
context.HttpContext.RequestServices.GetService<ILoggerFactory>()?
                .CreateLogger("Microsoft.AspNetCore.RateLimitingMiddleware")
                .LogWarning("OnRejected: {RequestPath}", context.HttpContext.Request.Path);
همچنین باید دقت داشت که اگر در اینجا از بانک اطلاعاتی استفاده کرده‌اید، تعداد کوئری‌های آن‌را محدود کنید؛ وگرنه واقعا rate limiter از لحاظ محدود کردن دسترسی به منابع، کمک زیادی را به شما نخواهد کرد.

طراحی فعلی میان‌افزار rate limiter، کمی محدود است. برای مثال «retry after»، تنها metadata مفیدی است که جهت بازگشت ارائه می‌دهد و همچنین مانند GitHub مشخص نمی‌کند که در لحظه‌ی جاری چند درخواست دیگر را می‌توان ارسال کرد و امکان دسترسی به اطلاعات آماری درونی آن وجود ندارد. اگر نیاز به یک چنین اطلاعاتی دارید شاید استفاده از میان‌افزار ثالث دیگری به نام AspNetCoreRateLimit برای شما مفیدتر باشد!


الگوریتم‌های پشتیبانی شده‌ی توسط میان‌افزار rate limiter

در قسمت قبل با چند الگوریتم استاندارد طراحی میان‌افزارهای rate limiter آشنا شدیم که میان‌افزار توکار rate limiter موجود در ASP.NET Core 7x، اکثر آن‌ها را پشتیبانی می‌کند:
- Concurrency limit: ساده‌ترین نوع محدود سازی نرخ درخواست‌ها است و کاری به زمان ندارد و فقط برای آن، تعداد درخواست‌های همزمان مهم است. برای مثال پیاده سازی «مجاز بودن تنها 10 درخواست همزمان».
- Fixed window limit: توسط آن می‌توان محدودیت‌هایی مانند «مجاز بودن تنها 60 درخواست در دقیقه» را اعمال کرد که به معنای امکان ارسال یک درخواست در هر ثانیه در هر دقیقه و یا حتی ارسال یکجای 60 درخواست در یک ثانیه است.
- Sliding window limit: این محدودیت بسیار شبیه به حالت قبل است اما به همراه قطعاتی که کنترل بیشتری را بر روی محدودیت‌ها میسر می‌کند؛ مانند مجاز بودن 60 درخواست در هر دقیقه که فقط در این حالت یک درخواست در هر ثانیه مجاز باشد.
- Token bucket limit: امکان کنترل نرخ سیلان را میسر کرده و همچنین از درخواست‌های انفجاری نیز پشتیبانی می‌کند (این مفاهیم در قسمت قبل بررسی شدند).

علاوه بر این‌ها امکان ترکیب گزینه‌های فوق توسط متد کمکی PartitionedRateLimiter.CreateChained نیز میسر است:
builder.Services.AddRateLimiter(options =>
{
    options.GlobalLimiter = PartitionedRateLimiter.CreateChained(
        PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
            RateLimitPartition.GetFixedWindowLimiter(httpContext.ResolveClientIpAddress(), partition =>
                new FixedWindowRateLimiterOptions
                {
                    AutoReplenishment = true,
                    PermitLimit = 600,
                    Window = TimeSpan.FromMinutes(1)
                })),
        PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
            RateLimitPartition.GetFixedWindowLimiter(httpContext.ResolveClientIpAddress(), partition =>
                new FixedWindowRateLimiterOptions
                {
                    AutoReplenishment = true,
                    PermitLimit = 6000,
                    Window = TimeSpan.FromHours(1)
                })));

    // ...
});
برای نمونه در مثال فوق به ازای یک آدرس IP مشخص، تنها می‌توان 600 درخواست را در دقیقه ارسال کرد؛ با این محدودیت که جمع آن‌ها در ساعت، بیشتر از 6000 مورد نباشد.
در این مثال فرضی، متد الحاقی ResolveClientIpAddress اهمیتی ندارد. بهتر است برای برنامه‌ی خود از کلید پارتیشن بندی بهتر و معقول‌تری استفاده کنید.


امکان در صف قرار دادن درخواست‌ها بجای رد کردن آن‌ها

در تنظیمات مثال‌های فوق، در کنار PermitLimit، می‌توان QueueLimit را نیز مشخص کرد. به این ترتیب با رسیدن به PermitLimit، به تعداد QueueLimit، درخواست‌ها در صف قرار می‌گیرند، بجای اینکه کاملا رد شوند:
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
    RateLimitPartition.GetFixedWindowLimiter(httpContext.ResolveClientIpAddress(), partition =>
        new FixedWindowRateLimiterOptions
        {
            AutoReplenishment = true,
            PermitLimit = 10,
            QueueLimit = 6,
            QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
            Window = TimeSpan.FromSeconds(1)
        })));
در این مثال هر کلاینت می‌تواند 10 درخواست در ثانیه را ارسال کند. در صورت رسیدن به این محدودیت، تا 6 عدد از درخواست‌های جدید رسیده، بجای رد شدن، در صف قرار می‌گیرند تا در ثانیه‌ی بعدی که این بازه‌ی مشخص به پایان می‌رسد، پردازش شوند.
این تنظیم، تجربه‌ی کاربری بهتری را برای استفاده کنندگان از برنامه‌ی شما به همراه خواهد داشت؛ بجای رد قاطع درخواست‌های ارسالی توسط آن‌ها.

یک نکته: بهتر است QueueLimitهای بزرگی را انتخاب نکنید؛ خصوصا برای بازه‌های زمانی طولانی. چون یک مصرف کننده نیاز دارد تا سریع، پاسخی را دریافت کند و اگر این‌طور نباشد، دوباره سعی خواهد کرد. تنها چند ثانیه‌ی کوتاه در صف بودن برای کاربران معنا دارد.


امکان ایجاد سیاست‌های محدود سازی سفارشی

اگر الگوریتم‌های توکار میان‌افزار rate limiter برای کار شما مناسب نیستند، می‌توانید با پیاده سازی <IRateLimiterPolicy<TPartitionKey، یک نمونه‌ی سفارشی را ایجاد کنید. پیاده سازی این اینترفیس، نیاز به دو متد را دارد:
الف) متد GetPartition که بر اساس HttpContext جاری، یک rate limiter مخصوص را باز می‌گرداند.
ب) متد OnRejected که امکان سفارشی سازی response رد درخواست‌ها را میسر می‌کند.

در مثال زیر پیاده سازی یک rate limiter سفارشی را مشاهده می‌کنید که نحوه‌ی پارتیشن بندی آن بر اساس user-name کاربر اعتبارسنجی شده و یا host-name کاربر وارد نشده‌ی به سیستم است. در اینجا کاربر وارد شده‌ی به سیستم، محدودیت بیشتری دارد:
public class ExampleRateLimiterPolicy : IRateLimiterPolicy<string>
{
    public RateLimitPartition<string> GetPartition(HttpContext httpContext)
    {
        if (httpContext.User.Identity?.IsAuthenticated == true)
        {
            return RateLimitPartition.GetFixedWindowLimiter(httpContext.User.Identity.Name!,
                partition => new FixedWindowRateLimiterOptions
                {
                    AutoReplenishment = true,
                    PermitLimit = 1_000,
                    Window = TimeSpan.FromMinutes(1),
                });
        }

        return RateLimitPartition.GetFixedWindowLimiter(httpContext.Request.Headers.Host.ToString(),
            partition => new FixedWindowRateLimiterOptions
            {
                AutoReplenishment = true,
                PermitLimit = 100,
                Window = TimeSpan.FromMinutes(1),
            });
    }

    public Func<OnRejectedContext, CancellationToken, ValueTask>? OnRejected { get; } =
        (context, _) =>
        {
            context.HttpContext.Response.StatusCode = 418; // I'm a 🫖
            return new ValueTask();
        };
}
و نحوه‌ی معرفی آن به سیستم به صورت زیر است:
options.AddPolicy<string, ExampleRateLimiterPolicy>("myPolicy");


امکان تعریف سیاست‌های محدود سازی نرخ دسترسی به گروهی از endpoints

تا اینجا روش‌های سراسری محدود سازی دسترسی به منابع برنامه را بررسی کردیم؛ اما ممکن است در برنامه‌ای بخواهیم محدودیت‌های متفاوتی را به گروه‌های خاصی از endpoints اعمال کنیم و یا شاید اصلا نخواهیم تعدادی از آن‌ها را محدود کنیم:
builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("Api", options =>
    {
        options.AutoReplenishment = true;
        options.PermitLimit = 10;
        options.Window = TimeSpan.FromMinutes(1);
    });

    options.AddFixedWindowLimiter("Web", options =>
    {
        options.AutoReplenishment = true;
        options.PermitLimit = 10;
        options.Window = TimeSpan.FromMinutes(1);
    });

    // ...
});
در این مثال روش تعریف دو سیاست مختلف محدودسازی را مشاهده می‌کنید که اینبار «نامدار» هستند؛ نام یکی Api است و نام دیگری Web.
البته باید درنظر داشت که متدهای الحاقی Add داری را که در اینجا ملاحظه می‌کنید، محدود سازی را بر اساس نام درنظر گرفته شده انجام می‌دهند. یعنی درحقیقت یک محدودسازی سراسری بر اساس گروهی از endpoints هستند و امکان تعریف پارتیشنی را به ازای یک کاربر یا آدرس IP خاص، ندارند. اگر نیاز به اعمال این نوع پارتیشن بندی را دارید، باید از متدهای AddPolicy استفاده کنید:
options.AddPolicy("Api", httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(httpContext.ResolveClientIpAddress(),
        partition => new FixedWindowRateLimiterOptions
        {
            AutoReplenishment = true,
            PermitLimit = 10,
            Window = TimeSpan.FromSeconds(1)
        }));
متدهای AddPolicy دار، هم امکان دسترسی به httpContext جاری را میسر می‌کنند و هم نامدار هستند که قابلیت اعمال آن‌ها را به گروهی از endpoints ممکن می‌کند.


محدود سازی نرخ دسترسی به منابع در ASP.NET Core Minimal API

پس از تعریف نامی برای سیاست‌های دسترسی، اکنون می‌توان از آن‌ها به صورت زیر جهت محدود سازی یک endpoint و یا گروهی از آن‌ها استفاده کرد:
// Endpoint
app.MapGet("/api/hello", () => "Hello World!").RequireRateLimiting("Api");

// Group
app.MapGroup("/api/orders").RequireRateLimiting("Api");
و یا حتی می‌توان بطور کامل محدود سازی نرخ دسترسی را برای یک endpoint و یا گروهی از آن‌ها غیرفعال کرد:
// Endpoint
app.MapGet("/api/hello", () => "Hello World!").DisableRateLimiting();

// Group
app.MapGroup("/api/orders").DisableRateLimiting();


محدود سازی نرخ دسترسی به منابع در ASP.NET Core MVC

می‌توان سیاست‌های نرخ دسترسی تعریف شده را بر اساس نام آن‌ها به کنترلرها و یا اکشن متدها اعمال نمود:
[EnableRateLimiting("Api")]
public class Orders : Controller
{
    [DisableRateLimiting]
    public IActionResult Index()
    {
        return View();
    }

    [EnableRateLimiting("ApiListing")]
    public IActionResult List()
    {
        return View();
    }
}
در اینجا سیاست نرخ دسترسی با نام Api، به کل کنترلر و اکشن متدهای آن اعمال شده، اما اکشن متد Index آن با بکارگیری ویژگی DisableRateLimiting، از این محدودیت خارج و اکشن متد List، از سیاست نام دار دیگری استفاده کرده‌است.
و یا حتی می‌توان این سیاست‌های محدود سازی نرخ دسترسی را به تمام کنترلرها و صفحات razor نیز به صورت زیر اعمال کرد:
app.UseConfiguredEndpoints(endpoints =>
{
    endpoints.MapRazorPages()
        .DisableRateLimiting();

    endpoints.MapControllers()
        .RequireRateLimiting("UserBasedRateLimiting");
});
نظرات مطالب
معرفی کتابخانه PdfReport
- لطفا برای رفع مشکلات مرتبط با PdfReport از این قسمت سایت استفاده نمائید.
- برای مشاهده خطای واقعی بر روی لینک view details (ذیل قسمت Actions تصویر فوق) کلیک کنید. در اینجا بهتر می‌توان بررسی کرد که مشکل اصلی چه چیزی بوده است. (ممکن است فونت مورد استفاده در مسیر برنامه شما نباشد، یا دسترسی write نداشته باشید، یا پوشه خروجی pdf در مسیر و ریشه برنامه شما ایجاد نشده (مطابق تنظیمات AppPath انتهای گزارش)، یا هر خطای دیگری که ریز آن در قسمت view details یاد شده، ذکر می‌شود)
 
مسیرراه‌ها
NHibernate
      مطالب
      Blazor 5x - قسمت 34 - توزیع برنامه‌های Blazor بر روی IIS
      زمانیکه صحبت از توزیع برنامه‌های Blazor بر روی IIS است، عموما تنظیمات مرتبط با برنامه‌های Blazor Server و یا Hosted Blazor Apps که همان ترکیب WASM+Web API هستند، مطرح است؛ در غیراینصورت اگر برنامه‌ای صرفا از فایل‌های Blazor WASM تشکیل شده باشد، توزیع آن حتی بر روی صفحات static مربوط به GitHub هم میسر است و وابستگی خاصی به سروری ندارند. بنابراین در اینجا بیشتر هدف تنظیمات IIS مرتبط با قسمت ASP.NET Core این برنامه‌ها است و این مورد را پیشتر در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 22 - توزیع برنامه توسط IIS» بررسی کرده‌ایم و نکته‌ی اضافه‌تری را به همراه ندارد.


      مروری بر نحوه‌ی توزیع برنامه‌های Blazor بر روی IIS

      1- پیش از هر کاری باید مطابق نگارش ASP.NET Core در حال استفاده (که به عنوان هاست Blazor Server و یا ارائه دهنده‌ی قسمت Web API برنامه‌ی سمت کلاینت WASM مطرح است)، بسته‌ی NET Core hosting bundle. را نصب کرد که عموما تحت عنوان «Hosting Bundle Installer» قابل دریافت است.
      نکته‌ی مهم: همانند تمام نگارش‌های دات نت، در اینجا نیز باید Hosting Bundle را پس از نصب IIS، بر روی سیستم نصب کرد. اگر این ترتیب تغییر کند، یکبار دیگر نصاب آن‌را اجرا کرده و گزینه‌ی ترمیم نصب را انتخاب کنید تا یکپارچگی آن با IIS صورت گیرد.

      2- نیاز است برنامه‌ی خود را اصطلاحا publish کرد تا به همراه فایل‌های نهایی قابل کپی باشد که در پوشه‌ای توسط IIS هاست خواهند شد. برای اینکار اگر از نگارش کامل ویژوال استودیو استفاده می‌کنید، فقط کافی است بر روی پروژه‌ی مدنظر کلیک راست کرده و از منوی باز شده، گزینه‌ی publish را انتخاب کنید و مراحل آن‌را طی نمائید و یا این مراحل را می‌توان توسط دستور خط فرمان زیر نیز خلاصه کرد که وابستگی خاصی، به IDE ویژه‌ای ندارد و چند سکویی است:
      dotnet publish -o "c:\dir1\dir2" -c Release
      این دستور، کار Build و توزیع پروژه را در پوشه‌ای که مشخص شده انجام می‌دهد. همچنین برای حالت build، نوع release را انتخاب می‌کند که به همراه بهینه سازی‌های بسیاری به همراه کاهش حجم نهایی فایل‌ها نیز هست.
      و یا اگر فقط دستور dotnet publish -c Release را در ریشه‌ی پروژه اجرا کنیم، خروجی نهایی را در پوشه‌ی bin\Release\net5.0\publish می‌توان مشاهده کرد که به همراه یک web.config مخصوص برنامه‌های blazor هم هست و در آن mime typeهای متناظری، به همراه URL rewriting مناسب برنامه‌های تک صفحه‌ای وب از پیش تنظیم شده‌است. بنابراین در اینجا نصب ماژول URL rewrite بر روی IIS نیز الزامی است.

      3- در اینجا نیز همانند تنظیمات برنامه‌های ASP.NET Core، باید application pool منتسب به برنامه را ویرایش کرده و NET Clr Version. آن‌را بر روی No Managed Code قرار داد.


      روش فعالسازی توزیع مبتنی بر فشرده سازی Brotli در IIS

      در حین عملیات publish استاندارد، به صورت پیش‌فرض از تمام فایل‌ها، سه نسخه‌ی اصلی، gz شده (gzip) و یا br شده (فشرده سازی Brotli که فایل‌های کم حجم‌تری را نسبت به gz ارائه می‌دهد) نیز تهیه می‌شوند که بسته به نوع مرورگر و پشتیبانی آن از روش‌های مختلف فشرده سازی، یکی از آن‌ها در اختیار کلاینت قرار خواهد گرفت که به این صورت کاربران، تجربه‌ی دریافت کم حجم‌تر و سریعتری را خواهند داشت.


      باید دقت داشت Web.config ای که به همراه دستور dotnet publish ایجاد می‌شود، روش توزیع پیش‌فرض فایل‌های br. تولیدی را ندارد. برای اینکار نیاز است تنظیمات این فایل web.config توصیه شده‌ی توسط تیم Blazor را به web.config خود اضافه کرد تا در نهایت حجم دریافتی از سرور به شدت کاهش یابد.

      یک نکته: اگر می‌خواهید فایل web.config سفارشی خودتان را داشته باشید، نمونه‌ای از آن‌را در ریشه‌ی پروژه قرار داده و سپس فایل csproj را به نحو زیر ویرایش کنید تا از آن در حین publish استفاده کند:
      <PropertyGroup>
          <PublishIISAssets>true</PublishIISAssets>
      </PropertyGroup>


      در حین publish برنامه‌های Blazor WASM کار IL trimming نیز انجام می‌شود

      برای کاهش حجم نهایی برنامه‌های Blazor WASM، در حین publish در حالت release، کار IL Trimming نیز به صورت خودکار انجام می‌شود تا کدهای IL ای که در برنامه نقش نداشته‌اند و مستقیما در جائی استفاده نشده‌اند، به صورت خودکار حذف شوند و به این ترتیب حجم ارائه‌ی نهایی به شدت کاهش یابد.
      فقط باید دقت داشت که در این حالت اگر عملیات پویایی مانند reflection در کدهای شما صورت می‌گیرد، به علت نداشتن ارجاع استاتیکی به منابع مورد استفاده، در زمان اجرا با مشکل مواجه خواهد شد. اگر می‌خواهید اخطارهایی را در این زمینه مشاهده کنید، گزینه‌ی زیر را به فایل csproj اضافه نمائید:
      <SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>


      حذف مباحث بومی سازی در صورت عدم نیاز


      اگر در برنامه‌ی خود از مباحث time-zones استفاده نمی‌کنید، می‌توانید با غیرفعال کردن آن در فایل csproj، حداقل 100 کیلوبایت از حجم برنامه‌ی نهایی را کاهش دهید:
      <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
      و یا اگر نمی‌خواهید خروجی‌های تاریخ و یا مقادیر، بر اساس فرهنگ بومی کاربر تطابق پیدا کنند، می‌توانید invariant culture را فعال کنید:
      <InvariantGlobalization>true</InvariantGlobalization>