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

استفاده از محیط گرافیکی IIS جهت لیست کردن، اضافه و حذف ماژول‌ها

به بخش modules درIIS بروید. در پنل سمت راست همه امکانات جهت افزودن و ویرایش و حذف وجود دارند:


روش معرفی ماژول در خط فرمان با استفاده از دستور Appcmd

 Appcmd.exe install module   /name:MODULE_NAME /image:PATH_TO_DLL

قسمت name که نام ماژول است  و قسمت image هم مسیر قرار گرفتن فایل dll هست.

برای نمونه:

%windir%\system32\inetsrv\appcmd.exe install module /name:DefaultDocumentModule /image:%windir%\system32\inetsrv\defdoc.dll

در صورتیکه ماژولی که قبلا افزوده شده باشد را بخواهید اضافه کنید، خطای زیر را دریافت خواهید کرد:

ERROR ( message:Failed to add duplicate collection element "DefaultDocumentModule". )

جهت حذف ماژول دستور زیر را صادر کنید:

Appcmd.exe uninstall module MODULE_NAME

نمونه:

%windir%\system32\inetsrv\appcmd.exe uninstall module DefaultDocumentModule

گرفتن کوئری یا لیستی از ماژول‌های فعال برای یک اپلیکیشن یا عمومی:

Appcmd.exe list modules [/app.name:APPLICATION_NAME]

سوپپچ aap.name اختیاری است ولی اگر نام یک اپلیکیشن را به آن بدهید، فقط ماژول‌هایی را که روی این اپلیکیشن اجرا می‌شوند، لیست می‌کند.

نمونه:

%windir%\system32\inetsrv\appcmd.exe list modules /app.name:"Default Web Site"

کد زیر هم نمونه ای برای لیست کردن تمامی ماژول‌های عمومی که بر روی تمامی اپلیکیشن‌ها اجرا می‌شوند:

%windir%\system32\inetsrv\appcmd.exe list modules

خط زیر یک ماژول را برای همه اپلیکیشن‌ها یا اپلیکیشن خاصی فعال می‌کند که بستگی دارد سوییچ type چگونه مقداردهی شده باشد:

Appcmd.exe add module /name:MODULE_NAME /type:MGD_TYPE

برای مثال خط زیر باعث می‌شود ماژول Forms Authentication فقط برای وب اپلیکیشن default web site فعال شود:

%windir%\system32\inetsrv\appcmd.exe add module /name:FormsAuthentication /type:System.Web.Security.FormsAuthenticationModule /app.name:"Default Web Site"

یا در پایین آن را به صورت عمومی یا global فعال می‌کند:

%windir%\system32\inetsrv\appcmd.exe add module /name:FormsAuthentication /type:System.Web.Security.FormsAuthenticationModule

برای غیرفعال کردن یک ماژول از دستور زیر استفاده می‌شود:

Appcmd.exe delete module MODULE_NAME [/app.name:APPLICATION_NAME]

اگر غیر فعال کردن یک ماژول در یک اپلیکیشن خاص مدنظر شما باشد دستور زیر نمونه آن است:

%windir%\system32\inetsrv\appcmd.exe delete module FormsAuthentication /app.name:"Default Web Site"

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

%windir%\system32\inetsrv\appcmd.exe delete module FormsAuthentication

حفظ کردن یا به خاطر سپردن دستورات بالا ممکن است کار سخت و دشواری باشد، به همین جهت از help کمک بگیرید:

Appcmd.exe module /?

یا به شکل اختصاصی‌تر برای یک دستور

Appcmd.exe install module /?Appcmd add module /?
مطالب
بررسی کارآیی کوئری‌ها در SQL Server - قسمت سوم - جمع آوری اطلاعات آماری کوئری‌ها توسط DMO's
Extended events ای که در قسمت قبل بررسی شدند، جهت جمع آوری اطلاعات آماری تک کوئری‌ها مورد استفاده قرار می‌گیرند؛ اما Dynamic management objects یا به اختصار DMO's، تجمعی عمل می‌کنند (برای مثال جهت محاسبه‌ی میانگین logical reads چند کوئری مانند هم). متن یک کوئری و پلن آن، توسط DMO's مختلفی قابل استخراج هستند. متن یک کوئری توسط sys.dm_exec_sql_text قابل استخراج است و برای دسترسی به کوئری پلن‌ها از sys.dm_exec_query_plan، sys.dm_exec_cached_plans و sys.dm_exec_text_query_plan استفاده می‌شود. در این حالت برای دسترسی به اطلاعات آماری از sys.dm_exec_query_stats و sys.dm_exec_function_stats کمک گرفته خواهد شد.


استفاده از Dynamic management objects برای جمع آوری اطلاعات آماری کوئری‌ها

در ادامه در طی چند مثال، روش استخراج اطلاعات آماری کوئری‌ها را توسط DMO's بررسی می‌کنیم.

دریافت متن کوئری‌های در حال اجرا

توسط کوئری زیر که توسط تابع sys.dm_exec_sql_text اجرا می‌شود، می‌توان لیست کوئری‌های در حال اجرای بر روی بانک‌های اطلاعاتی جاری را بدست آورد:
SELECT
    [r].[session_id],
    DB_NAME([r].[database_id]) [DatabaseName],
    [t].[text]
FROM sys.dm_exec_requests [r]
CROSS APPLY sys.dm_exec_sql_text([r].sql_handle) [t];
GO
در اینجا text، همان متن کوئری است و هربار که این کوئری اجرا می‌شود، نتیجه‌ی متفاوتی را بر اساس کوئری‌هایی که در آن لحظه در حال اجرا هستند، دریافت خواهیم کرد.
تابع sys.dm_exec_sql_text برای اجرا نیاز به یک sql_handle دارد که آن‌را از طریق sys.dm_exec_requests می‌توان تامین کرد.


دریافت پلن کوئری‌های در حال اجرا

توسط کوئری زیر که توسط تابع sys.dm_exec_query_plan اجرا می‌شود، می‌توان لیست پلن کوئری‌های در حال اجرای بر روی بانک‌های اطلاعاتی جاری را بدست آورد:
SELECT
    [r].[session_id],
    DB_NAME([r].[database_id]) [DatabaseName],
    [t].[text],
    [p].[query_plan]
FROM sys.dm_exec_requests [r]
CROSS APPLY sys.dm_exec_sql_text([r].sql_handle) [t]
CROSS APPLY sys.dm_exec_query_plan([r].[plan_handle]) [p];
GO
تابع sys.dm_exec_query_plan برای اجرا نیاز به یک plan_handle دارد که آن‌را از طریق sys.dm_exec_requests می‌توان تامین کرد.
حاصل این کوئری، به همراه text یا اصل متن کوئری‌های در حال اجرا و همچنین query_plan، یا همان اطلاعات XML ای پلن که در قسمت اول، نمونه‌ای از آن‌را بررسی کردیم، می‌باشد که با کلیک بر روی هر کدام در management studio، نمایش گرافیکی آن‌ها ظاهر خواهد شد. البته این پلن‌ها، تنها تخمین‌ها را به همراه دارند؛ چون از کش خوانده می‌شوند.


دریافت لیست پلن‌های کش شده

توسط Viewای به نام sys.dm_exec_cached_plans می‌توان به لیست پلن‌های کش شده‌ی در سیستم دسترسی یافت:
SELECT *
FROM sys.dm_exec_cached_plans;
البته خروجی آن، آنچنان جالب نیست. چون یکی از ستون‌های آن، فقط حاوی همان plan_handle ای است که در مثال قبل بررسی کردیم و به خودی خود، حاوی اطلاعات قابل مشاهده‌ای نیست. به همین جهت اگر بخواهیم آن‌را با کوئری‌هایی که تاکنون نوشتیم، ترکیب کنیم به کوئری زیر خواهیم رسید:
SELECT
    [r].[session_id],
    DB_NAME([r].[database_id]) [DatabaseName],
    [cp].[objtype],
    [cp].[size_in_bytes],
    [t].[text],
    [p].[query_plan]
FROM sys.dm_exec_requests [r]
CROSS APPLY sys.dm_exec_sql_text([r].sql_handle) [t]
CROSS APPLY sys.dm_exec_query_plan([r].[plan_handle]) [p]
    JOIN sys.dm_exec_cached_plans [cp]
    ON [r].[plan_handle] = [cp].[plan_handle];
GO
مزیت این کوئری نسبت به موارد قبلی، وجود ستون‌های جدید objtype و size_in_bytes است که بیانگر نوع کوئری، مانند AdHoc و اندازه‌ی پلن در کش هستند.


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

با استفاده از تابع sys.dm_exec_text_query_plan می‌توان به متن پلن‌های عمیق دسترسی یافت. در این حالت خروجی کوئری در management studio به صورت یک لینک قابل کلیک ظاهر نمی‌شود و صرفا یک متن قابل کپی است که می‌توان آن‌را با پسوند sqlplan برای بررسی‌های بعدی، ذخیره کرد:
SELECT
    [r].[session_id],
    DB_NAME([r].[database_id]) [DatabaseName],
    [tq].[query_plan]
FROM sys.dm_exec_requests [r]
CROSS APPLY sys.dm_exec_text_query_plan([r].plan_handle, 0, -1) [tq];
GO
در اینجا اعداد 0 و 1- به معنای ابتدا و انتهای batch هستند.


دریافت اطلاعات آماری کوئری‌های درحال اجرا

توسط viewای به نام sys.dm_exec_query_stats می‌توان به اطلاعات آماری کوئری‌های در حال اجرا دسترسی یافت:
SELECT *
FROM sys.dm_exec_query_stats;
GO
این کوئری تعداد ستون‌های قابل توجهی را به همراه دارد مانند Physical reads، logical reads و .... به همین جهت نیاز است اطلاعات مفید آن‌را فیلتر کرد:
SELECT
    [qs].[last_execution_time],
    [qs].[execution_count],
    [qs].[total_logical_reads]/[qs].[execution_count] [AvgLogicalReads],
    [qs].[max_logical_reads],
    [t].[text],
    [p].[query_plan]
FROM sys.dm_exec_query_stats [qs]
CROSS APPLY sys.dm_exec_sql_text([qs].sql_handle) [t]
CROSS APPLY sys.dm_exec_query_plan([qs].[plan_handle]) [p]
WHERE [qs].[execution_count] > 25
    OR [qs].[total_logical_reads] > 10000
ORDER BY [qs].[total_logical_reads]/[qs].[execution_count] DESC;
GO
این کوئری در حقیقت ترکیبی است از کوئری‌هایی که تاکنون نوشتیم و در آن text و query_plan از sys.dm_exec_sql_text و sys.dm_exec_query_plan تامین شده‌اند، به همراه تعدادی ستون مفید sys.dm_exec_query_stats مانند last_execution_time و AvgLogicalReads. به علاوه در اینجا کوئری‌هایی که بیشتر از 25 بار اجرا شده‌اند و یا total_logical_reads آن‌ها بیش از 10 هزار بوده، در خروجی ظاهر خواهند شد (مفهوم تجمعی بودن DMO's).

از SQL Server 2016 به بعد، امکان دریافت اطلاعات آماری توابع نیز میسر شده‌است:
SELECT *
FROM sys.dm_exec_function_stats;
GO

یک نکته: قابلیت جدیدی تحت عنوان Query Store از زمان SQL Server 2016 معرفی شد‌ه‌است و کار آن دریافت تمام اطلاعاتی است که تاکنون بررسی کردیم و تفاوت آن، در ذخیره شده بودن آن است. یعنی این اطلاعات را داخل بانک اطلاعاتی در حال بررسی ذخیره می‌کند که شامل متن و پلن کوئری و همچنین اطلاعات آماری آن است که توسط DMO's تهیه می‌شود.
مطالب
Blazor 5x - قسمت دوم - بررسی ساختار اولیه‌ی پروژه‌های Blazor
پس از آشنایی با دو مدل هاستینگ برنامه‌های Blazor در قسمت قبل، اکنون می‌خواهیم ساختار ابتدایی قالب‌های این دو پروژه را بررسی کنیم.


ایجاد پروژه‌های خالی Blazor

در انتهای قسمت قبل، با روش ایجاد پروژه‌های خالی Blazor به کمک NET SDK 5x. آشنا شدیم. به همین جهت دو پوشه‌ی جدید BlazorWasmSample و BlazorServerSample را ایجاد کرده و از طریق خط فرمان و با کمک NET CLI.، در پوشه‌ی اولی دستور dotnet new blazorwasm و در پوشه‌ی دومی دستور dotnet new blazorserver را اجرا می‌کنیم.
البته اجرای این دو دستور، نیاز به اتصال به اینترنت را هم برای بار اول دارند؛ تا فایل‌های مورد نیاز و بسته‌های مرتبط را دریافت و restore کنند. بسته به سرعت اینترنت، حداقل یک ربعی را باید صبر کنید تا دریافت ابتدایی بسته‌های مرتبط به پایان برسد. برای دفعات بعدی، از کش محلی NuGet، برای restore بسته‌های blazor استفاده می‌شود که بسیار سریع است.


بررسی ساختار پروژه‌ی Blazor Server و اجرای آن

پس از اجرای دستور dotnet new blazorserver در یک پوشه‌ی خالی و ایجاد پروژه‌ی ابتدایی آن:


همانطور که مشاهده می‌کنید، ساختار این پروژه، بسیار شبیه به یک پروژه‌ی استاندارد ASP.NET Core از نوع Razor pages است.
- در پوشه‌ی properties آن، فایل launchSettings.json قرار دارد که برای نمونه، شماره پورت اجرایی برنامه را در حالت اجرای توسط دستور dotnet run و یا توسط IIS Express مشخص می‌کند.

- پوشه‌ی wwwroot آن، مخصوص ارائه‌ی فایل‌های ایستا مانند wwwroot\css\bootstrap است. در ابتدای کار، این پوشه به همراه فایل‌های CSS بوت استرپ است. در ادامه اگر نیاز باشد، فایل‌های جاوا اسکریپتی را نیز می‌توان به این قسمت اضافه کرد.

- در پوشه‌ی Data آن، سرویس تامین اطلاعاتی اتفاقی قرار دارد؛ به نام WeatherForecastService که هدف آن، تامین اطلاعات یک جدول نمایشی است که در ادامه در آدرس https://localhost:5001/fetchdata قابل مشاهده است.

- در پوشه‌ی Pages، تمام کامپوننت‌های Razor برنامه قرار می‌گیرند. یکی از مهم‌ترین صفحات آن، فایل Pages\_Host.cshtml است. کار این صفحه‌ی ریشه، افزودن تمام فایل‌های CSS و JS، به برنامه‌است. بنابراین در آینده نیز از همین صفحه برای افزودن فایل‌های CSS و JS استفاده خواهیم کرد. اگر به قسمت body این صفحه دقت کنیم، تگ جدید کامپوننت قابل مشاهده‌است:
<body>
   <component type="typeof(App)" render-mode="ServerPrerendered" />
کار آن، رندر کامپوننت App.razor واقع در ریشه‌ی پروژه‌است.
همچنین در همینجا، تگ‌های دیگری نیز قابل مشاهده هستند:
<body>
    <component type="typeof(App)" render-mode="ServerPrerendered" />

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.server.js"></script>
</body>
همانطور که در قسمت قبل نیز عنوان شد، اتصال برنامه‌ی در حال اجرای در مرورگر blazor server با backend، از طریق یک اتصال دائم SignalR صورت می‌گیرد. اگر در این بین خطای اتصالی رخ دهد، div با id مساوی blazor-error-ui فوق، یکی از پیام‌‌هایی را که مشاهده می‌کنید، بسته به اینکه برنامه در حالت توسعه در حال اجرا است و یا در حالت ارائه‌ی نهایی، نمایش می‌دهد. افزودن مدخل blazor.server.js نیز به همین منظور است. کار آن مدیریت اتصال دائم SignalR، به صورت خودکار است و از این لحاظ نیازی به کدنویسی خاصی از سمت ما ندارد. این اتصال، کار به روز رسانی UI و هدایت رخ‌دادها را به سمت سرور، انجام می‌دهد.

- در پوشه‌ی Shared، یکسری فایل‌های اشتراکی قرار دارند که قرار است در کامپوننت‌های واقع در پوشه‌ی Pages مورد استفاه قرار گیرند. برای نمونه فایل Shared\MainLayout.razor، شبیه به master page برنامه‌های web forms است که قالب کلی سایت را مشخص می‌کند. داخل آن Body@ را مشاهده می‌کنید که به معنای نمایش صفحات دیگر، دقیقا در همین محل است. همچنین در این پوشه فایل Shared\NavMenu.razor نیز قرار دارد که ارجاعی به آن در MainLayout.razor ذکر شده و کار آن نمایش منوی آبی‌رنگ سمت چپ صفحه‌است.

- در پوشه‌ی ریشه‌ی برنامه، فایل Imports.razor_ قابل مشاهده‌است. مزیت تعریف usingها در اینجا این است که از تکرار آن‌ها در کامپوننت‌های razor ای که در ادامه تهیه خواهیم کرد، جلوگیری می‌کند. هر using تعریف شده‌ی در اینجا، در تمام کامپوننت‌ها، قابل دسترسی است؛ به آن global imports هم گفته می‌شود.

- در پوشه‌ی ریشه‌ی برنامه، فایل App.razor نیز قابل مشاهده‌است. کار آن تعریف قالب پیش‌فرض برنامه‌است که برای مثال به Shared\MainLayout.razor اشاره می‌کند. همچنین کامپوننت مسیریابی نیز در اینجا ذکر شده‌است:
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>
اگر شخصی مسیر از پیش تعریف شده‌ای را وارد کند، به قسمت Found وارد می‌شود و در غیر اینصورت به قسمت NotFound. در قسمت Found است که از قالب MainLayout برای رندر یک کامپوننت توسط کامپوننت RouteView، استفاده خواهد شد و در قسمت NotFound، فقط پیام «Sorry, there's nothing at this address» به کاربر نمایش داده می‌شود. قالب‌های هر دو نیز قابل تغییر و سفارشی سازی هستند.

- فایل appsettings.json نیز همانند برنامه‌های استاندارد ASP.NET Core در اینجا مشاهده می‌شود.

- فایل Program.cs آن که نقطه‌ی آغازین برنامه‌است و کار فراخوانی Startup.cs را انجام می‌دهد، دقیقا با یک فایل Program.cs برنامه‌ی استاندارد ASP.NET Core یکی است.

- در فایل Startup.cs آن، همانند قبل دو متد تنظیم سرویس‌ها و تنظیم میان‌افزارها قابل مشاهده‌است.
public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<WeatherForecastService>();
}
در ConfigureServices آن، سرویس‌های صفحات razor و ServerSideBlazor اضافه شده‌اند. همچنین سرویس نمونه‌ی WeatherForecastService نیز در اینجا ثبت شده‌است.
قسمت‌های جدید متد Configure آن، ثبت مسیریابی توکار BlazorHub است که مرتبط است با اتصال دائم SignalR برنامه و اگر مسیری پیدا نشد، به Host_ هدایت می‌شود:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...

    app.UseEndpoints(endpoints =>
    {
       endpoints.MapBlazorHub();
       endpoints.MapFallbackToPage("/_Host");
    });
}
در ادامه در همان پوشه، دستور dotnet run را اجرا می‌کنیم تا پروژه، کامپایل و همچنین وب سرور آن نیز اجرا شده و برنامه در آدرس https://localhost:5001 قابل دسترسی شود.


که به همراه 13 درخواست و نزدیک به 600 KB دریافت اطلاعات از سمت سرور است.

این برنامه‌ی نمونه، به همراه سه صفحه‌ی نمایش Home، نمایش یک شمارشگر و نمایش اطلاعاتی از پیش آماده شده‌است. اگر صفحه‌ی شمارشگر آن‌را باز کنیم، با کلیک بر روی دکمه‌ی آن، هرچند مقدار current count افزایش می‌یابد، اما شاهد post-back متداولی به سمت سرور نیستیم و این صفحه بسیار شبیه به صفحات برنامه‌های SPA (تک صفحه‌ای وب) به نظر می‌رسد:


همانطور که عنوان شد، مدخل blazor.server.js فایل Pages\_Host.cshtml، کار به روز رسانی UI و هدایت رخ‌دادها را به سمت سرور به صورت خودکار انجام می‌دهد. به همین جهت است که post-back ای را مشاهده نمی‌کنیم و برنامه، شبیه به یک برنامه‌ی SPA به نظر می‌رسد؛ هر چند تمام رندرهای آن سمت سرور انجام می‌شوند و توسط SignalR به سمت کلاینت بازگشت داده خواهند شد.
برای نمونه اگر بر روی دکمه‌ی شمارشگر کلیک کنیم، در برگه‌ی network مرورگر، هیچ اثری از آن مشاهده نمی‌شود (هیچ رفت و برگشتی را مشاهده نمی‌کنیم). علت اینجا است که اطلاعات متناظر با این کلیک، از طریق web socket باز شده‌ی توسط SignalR، به سمت سرور ارسال شده و نتیجه‌ی واکنش به این کلیک‌ها و رندر HTML نهایی سمت سرور آن، از همین طریق به سمت کلاینت بازگشت داده می‌شود.


بررسی ساختار پروژه‌ی Blazor WASM و اجرای آن

پس از اجرای دستور dotnet new blazorwasm در یک پوشه‌ی خالی و ایجاد پروژه‌ی ابتدایی آن:


همان صفحات پروژه‌ی خالی Blazor Server در اینجا نیز قابل مشاهده هستند. این برنامه‌ی نمونه، به همراه سه صفحه‌ی نمایش Home، نمایش یک شمارشگر و نمایش اطلاعاتی از پیش آماده شده‌است. صفحات و کامپوننت‌های پوشه‌های Pages و Shared نیز دقیقا همانند پروژه‌ی Blazor Server قابل مشاهده هستند. مفاهیمی مانند فایل‌های Imports.razor_ و App.razor نیز مانند قبل هستند.

البته در اینجا فایل Startup ای مشاهده نمی‌شود و تمام تنظیمات آغازین برنامه، داخل فایل Program.cs انجام خواهند شد:
namespace BlazorWasmSample
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("#app");

            builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

            await builder.Build().RunAsync();
        }
    }
}
در اینجا ابتدا کامپوننت App.razor را به برنامه معرفی می‌کند که ساختار آن با نمونه‌ی مشابه Blazor Server دقیقا یکی است. سپس سرویسی را برای دسترسی به HttpClient، به سیستم تزریق وابستگی‌های برنامه معرفی می‌کند. هدف از آن، دسترسی ساده‌تر به endpoint‌های یک ASP.NET Core Web API است. از این جهت که در یک برنامه‌ی سمت کلاینت، دیگر دسترسی مستقیمی به سرویس‌های سمت سرور را نداریم و برای کار با آن‌ها همانند سایر برنامه‌های SPA که از Ajax استفاده می‌کنند، در اینجا از HttpClient برای کار با Web API‌های مختلف استفاده می‌شود.

تفاوت ساختاری دیگر این پروژه‌ی WASM، با نمونه‌ی Blazor Server، ساختار پوشه‌ی wwwroot آن است:


که به همراه فایل جدید نمونه‌ی wwwroot\sample-data\weather.json است؛ بجای سرویس متناظر سمت سرور آن در برنامه‌ی blazor server. همچنین فایل جدید wwwroot\index.html نیز قابل مشاهده‌است و محتوای تگ body آن به صورت زیر است:
<body>
    <div id="app">Loading...</div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
</body>
- چون این برنامه، یک برنامه‌ی سمت کلاینت است، اینبار بجای فایل Host_ سمت سرور، فایل index.html سمت کلاینت را برای ارائه‌ی آغازین برنامه داریم که وابستگی به دات نت ندارد و توسط مرورگر قابل درک است.
- در ابتدای بارگذاری برنامه، یک loading نمایش داده می‌شود که در اینجا نحوه‌ی تعریف آن مشخص است. همچنین اگر خطایی رخ دهد نیز توسط div ای با id مساوی blazor-error-ui اطلاع رسانی می‌شود.
- مدخل blazor.webassembly.js در اینجا، کار دریافت وب اسمبلی و فایل‌های NET runtime. را انجام می‌دهد؛ برخلاف برنامه‌های Blazor Server که توسط فایل blazor.server.js، یک ارتباط دائم SignalR را با سرور برقرار می‌کردند تا کدهای رندر شده‌ی سمت سرور را دریافت و نمایش دهند و یا اطلاعاتی را به سمت سرور ارسال کنند: برای مثال بر روی دکمه‌ای کلیک شده‌است، اطلاعات مربوطه را در سمت سرور پردازش کن و نتیجه‌ی نهایی رندر شده را بازگشت بده. اما در اینجا همه چیز داخل مرورگر اجرا می‌شود و برای این نوع اعمال، رفت و برگشتی به سمت سرور صورت نمی‌گیرد. به همین جهت تمام کدهای #C ما به سمت کلاینت ارسال شده و داخل مرورگر به کمک فناوری وب اسمبلی، اجرا می‌شوند. در اینجا از لحاظ ارسال تمام کدهای مرتبط با UI برنامه‌ی سمت کلاینت به مرورگر کاربر، تفاوتی با فریم‌ورک‌هایی مانند Angular و یا React نیست و آن‌ها هم تمام کدهای UI برنامه را کامپایل کرده و یکجا ارسال می‌کنند.

در ادامه در همان پوشه، دستور dotnet run را اجرا می‌کنیم تا پروژه کامپایل و همچنین وب سرور آن نیز اجرا شده و برنامه در آدرس https://localhost:5001 قابل دسترسی شود.


که به همراه 205 درخواست و نزدیک به 9.6 MB دریافت اطلاعات از سمت سرور است. البته اگر همین صفحه را refresh کنیم، دیگر شاهد دریافت مجدد فایل‌های DLL مربوط به NET Runtime. نخواهیم بود و اینبار از کش مرورگر خوانده می‌شوند:


در این برنامه‌ی سمت کلاینت، ابتدا تمام فایل‌های NET Runtime. و وب اسمبلی دریافت شده و سپس اجرای تغییرات UI، در همین سمت و بدون نیاز به اتصال دائم SignalR ای به سمت سرور، پردازش و نمایش داده می‌شوند. به همین جهت زمانیکه بر روی دکمه‌ی شمارشگر آن کلیک می‌کنیم، اتفاقی در برگه‌ی network مرورگر ثبت نمی‌شود و رفت و برگشتی به سمت سرور صورت نمی‌گیرد.

عدم وجود اتصال SignalR، مزیت امکان اجرای آفلاین برنامه‌ی WASM را نیز میسر می‌کند. برای مثال یکبار دیگر همان برنامه‌ی Blazor Server را به کمک دستور dotnet run اجرا کنید. سپس آن‌را در مرورگر در آدرس https://localhost:5001 باز کنید. اکنون پنجره‌ی کنسولی که dotnet run را اجرا کرده، خاتمه دهید (قسمت اجرای سمت سرور آن‌را ببندید).


بلافاصله تصویر «سعی در اتصال مجدد» فوق را مشاهده خواهیم کرد که به دلیل قطع اتصال SignalR رخ داده‌است. یعنی یک برنامه‌ی Blazor Server، بدون این اتصال دائم، قادر به ادامه‌ی فعالیت نیست. اما چنین محدودیتی با برنامه‌های Blazor WASM وجود ندارد.
البته بدیهی است اگر یک Web API سمت سرور برای ارائه‌ی اطلاعاتی به برنامه‌ی WASM نیاز باشد، API سمت سرور برنامه نیز باید جهت کار و نمایش اطلاعات در سمت کلاینت در دسترس باشد؛ وگرنه قسمت‌های متناظر با آن، قادر به نمایش و یا ثبت اطلاعات نخواهند بود.
مطالب
کار با شیوه‌نامه‌های تصاویر در بوت استرپ 4
بوت استرپ 4 به همراه کلاس‌های کمکی مفیدی برای کار با تصاویر نیز می‌باشد؛ مانند:
- کلاس img-fluid که سبب ایجاد تصاویر واکنشگرا می‌شود. به این معنا که اگر این کلاس به تصویری انتساب داده شد، عرض آن با عرض container آن، تطابق پیدا می‌کند.
- کلاس rounded-dir برای گرد کردن گوشه‌های تصاویر کاربرد دارد. در اینجا ذکر dir اختیاری است و می‌تواند مقادیر زیر را داشته باشد:
top, right, bottom, left, circle
همچنین اگر تصویری دارای گوشه‌های گرد است (توسط کلاس css دیگری تنظیم شده‌است)، می‌توان با اعمال کلاس rounded-0، آن‌را لغو کرد.
- کلاس img-thumbnail نیز تصویری را با گوشه‌های گرد و همچنین با حاشیه‌ای به ضخامت 1px، ایجاد می‌کند.
- کلاس‌های float-left و float-right حالت پیش‌فرض نمایش inline تصاویر را لغو کرده و آن‌ها را در جهت‌های دلخواهی نمایش می‌دهند.
- اگر حالت نمایش تصویر inline است، می‌توان مانند متون با استفاده از کلاس text-center، آن‌ها را در میانه‌ی container، نمایش داد.
- اگر تصویر به صورت block نمایش داده می‌شود (display:block)، کلاس mx-auto می‌تواند آن‌را در میانه‌ی container نمایش دهد.


مثالی از تصاویر واکنشگرا

<body>
    <div class="container">
        <section class="content" id="testimonials">
            <h2>Testimonials</h2>
            <figure>
                <img class="img-fluid" src="images/testimonial-johnb.jpg" alt="John B Photo">
                <figcaption>The staff at Wisdom worked tirelessly to determine
                    why our three-year old Golden Retriever, Roxie, started
                    going into sudden kidney failure. They stabilized her and
                    provided fluids until her kidneys were again functioning
                    normally.</figcaption>
            </figure>
        </section>
    </div>
</body>
در اینجا با اعمال کلاس img-fluid به یک تصویر، سبب خواهیم شد تا عرض آن با عرض container تطابق پیدا کند:


همانطور که مشاهده می‌کنید، این کلاس، یک تصویر بزرگ را متناسب با عرض دربرگیرنده‌ی آن، نمایش می‌دهد.


مثال تصویر با گوشه‌های گرد

در همین مثال اگر بخواهیم گوشه‌های تصویر را گرد کنیم، فقط کافی است کلاس rounded را نیز به آن اضافه کنیم:
<img class="img-fluid rounded" src="images/testimonial-johnb.jpg" alt="John B Photo">
با این خروجی:



مثال تصویر با گوشه‌های گرد در جهتی خاص

و یا اگر جهتی را نیز به آن اعمال کنیم:
<img class="img-fluid rounded-top" src="images/testimonial-johnb.jpg" alt="John B Photo">
برای مثال فقط  گوشه‌های بالای آن گرد می‌شوند:


و یا نتیجه‌ی اعمال جهت دایره‌ای به تصویر:
<img class="img-fluid rounded-circle" src="images/testimonial-johnb.jpg" alt="John B Photo">
بیضی شکلی به صورت زیر است:



مثال نمایش تصویر در گوشه‌ای خاص از صفحه

اگر بخواهیم تصویر را برای مثال در سمت چپ صفحه نمایش دهیم:
<img class="img-fluid rounded-circle float-left" width="200px"
                    src="images/testimonial-lorraines.jpg" alt="Lorraine Photo">
خروجی آن به صورت زیر خواهد بود:


برای نمایش تصویر در وسط صفحه:
<body>
    <div class="container">
        <section class="content" id="testimonials">
            <h2>Testimonials</h2>
            <figure class="text-center">
                <img class="img-fluid rounded-circle" src="images/testimonial-mcphersons.jpg"
                    alt="McPhersons Photo" width="200px">
                <figcaption>When Samantha, our Siamese cat, began sleeping all
                    the time and urinating excessively, we brought her to see
                    the specialists at Wisdom. Now, two years later, Samantha
                    is still free from any complications of diabetes, and her
                    blood sugar regularly tests normal.</figcaption>
            </figure>
        </section>
    </div>
</body>
در اینجا با استفاده از کلاس text-center، تصویر را به میانه‌ی صفحه منتقل کرده‌ایم.



کار با عناصر figure جهت نمایش بهتر تصاویر و متون ذیل آن‌ها

برای کار بهتر با عناصر figure در بوت استرپ، ابتدا کلاس figure را به آن‌ها انتساب می‌دهیم. سپس می‌توان کلاس figure-img را به تصاویر داخل آن‌ها افزود. این مورد سبب می‌شود تا تصاویر با اندکی فاصله نسبت به متن داخل آن نمایش داده شوند. در آخر می‌توان به figcaption، کلاس figure-caption را افزود تا رنگ و فاصله‌ی مناسبی را به آن اعمال کند:
<body>
    <div class="container">
        <section class="content" id="testimonials">
            <h2>Testimonials</h2>
            <figure class="figure">
                <img class="img-fluid figure-img rounded" src="images/testimonial-lorraines.jpg"
                    alt="Lorraine Photo">
                <figcaption class="figure-caption">Wisdom Pet Medicine is the
                    only clinic around that
                    will even book pet fish for appointments. When our 13-year
                    old Comet goldfish, McAllister, turned from silvery white
                    to an angry red, we called around, urgently trying to find
                    a veterinarian who could help. Wisdom not only got us in
                    the same day, but also was able to diagnose McAllister as
                    having a severe case of septicemia.</figcaption>
            </figure>
        </section>
    </div>
</body>
با این خروجی:


کلاس  figure-img سبب شده‌است تا تصویر، با متن، اندکی فاصله پیدا کند. همچنین کلاس figure-caption، متن ذیل تصویر را اندکی روشن‌تر نمایش می‌دهد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: Bootstrap4_03.zip
نظرات مطالب
حذف فضاهای خالی در خروجی صفحات ASP.NET MVC
با تشکر. من چندبار سعی کردم از روش حذف فواصل خالی استفاده کنم ولی هربار از خیرش گذشتم به این دلایل:
- در مرورگرهای قدیمی گاها باعث کرش و بسته شدن آنی برنامه می‌شد.
- کدهای جاوا اسکریپت یا CSS اگر داخل صفحه قرار داشتند، مشکل پیدا می‌کردند.
- گاهی از همین فضاهای خالی برای اندکی ایجاد فاصله بین عناصر ممکن است استفاده شود. این‌ها با حذف فواصل خالی به هم می‌ریزند.
- بعضی مرورگرها علاقمند هستند که doctype ابتدای یک فایل HTML، حتما در یک سطر مجزا ذکر شود.
- زمانیکه شما code‌ایی در صفحه تعریف می‌کنید، برای پردازش صحیح تگ PRE توسط مرورگر، مهم است که سطر جدیدی وجود داشته باشد، یا فاصله بین عناصر حفظ شود. در غیراینصورت کد نمایش داده شده به هم می‌ریزد.
- الگوریتم‌های فشرده سازی اطلاعات مانند GZIP یا Deflate، حداقل کاری را که به خوبی انجام می‌دهند، فشرده سازی فواصل خالی است.
مطالب
استفاده از EF7 با پایگاه داده SQLite تحت NET Core. به کمک Visual Studio Code
در این مقاله سعی داریم مراحل نوشتن و اجرای یک برنامه‌ی ساده را تحت NET Core. و با بهره گیری از دیتابیس SQLite و EF7، دنبال کنیم. همچنین از آنجایی‌که NET Core. به صورت چندسکویی طراحی شده‌است و تحت لینوکس و مکینتاش هم قابل اجراست، در نتیجه مناسب دیدم که ابزار نوشتن این پروژه‌ی ساده نیز قابلیت چندسکویی داشته و تحت لینوکس و مکینتاش نیز قابل اجرا باشد. در نتیجه به جای Visual Studio در این مقاله از Visual Studio Code استفاده شده است.

ابزارهای پیش نیاز:
  1. Visual Studio Code
  2. .NET Core
در اولین قدم، برنامه‌ی متن باز Visual Studio Code را از اینجا دانلود و نصب کنید. برنامه‌ی Visual Studio Code که در ادامه‌ی فعالیت‌های جدید متن باز مایکروسافت به بازار عرضه شده است، سریع، سبک و کاملا قابل توسعه و سفارشی سازی است و از اکثر زبان‌های معروف پشتیبانی می‌کند.
در قدم بعدی، شما باید NET Core. را از اینجا (64 بیتی) دانلود و نصب کنید.

 .NET Core چیست؟
NET Core. در واقع پیاده سازی بخشی از NET. اصلی است که به صورت متن باز در حال توسعه می‌باشد و بر روی لینوکس و مکینتاش هم قابل اجراست. موتور اجرای دات نت کامل CLR نام دارد و NET Core. نیز دارای موتور اجرایی CoreCLR است و شامل فریمورک CoreFX می‌باشد.

در حال حاضر شما می‌توانید با استفاده از NET Core. برنامه‌های کنسولی و تحت وب با ASP.NET 5 بنویسید و احتمالا در آینده می‌توان امیدوار بود که از ساختارهای پیچیده‌تری مثل WPF نیز پشتیبانی کند.

پس از آنکه NET Core. را دانلود و نصب کردید، جهت شروع پروژه، یک پوشه را در یکی از درایوها ساخته (در این مثال E:\Projects\EF7-SQLite-NETCore) و Command prompt را در آنجا باز کنید. سپس دستورات زیر را به ترتیب اجرا کنید:

dotnet new
dotnet restore
dotnet run

دستور dotnet new یک پروژه‌ی ساده‌ی Hello World را در پوشه‌ی جاری ایجاد می‌کند که حاوی فایل‌های زیر است:
  • NuGet.Config (این فایل، تنظیمات مربوط به نیوگت را جهت کشف و دریافت وابستگی‌های پروژه، شامل می‌شود)
  •  Program.cs (این فایل سی شارپ حاوی کد برنامه است)
  • project.json (این فایل حاوی اطلاعات پلتفرم هدف و لیست وابستگی‌های پروژه است)

دستور dotnet restore بر اساس لیست وابستگی‌ها و پلتفرم هدف، وابستگی‌های لازم را از مخزن نیوگت دریافت می‌کند. (در صورتی که در هنگام اجرای این دستور با خطای NullReferenceException مواجه شدید از دستور dnu restore استفاده کنید. این خطا در گیت هاب در حال بررسی است)

دستور dotnet run هم سورس برنامه را کامپایل و اجرا می‌کند. در صورتی که پیام Hello World را مشاهده کردید، یعنی برنامه‌ی شما تحت NET Core. با موفقیت اجرا شده است.

توسعه‌ی پروژه با Visual Studio Code

در ادامه، قصد داریم پروژه‌ی HelloWorld را تحت Visual Studio Code باز کرده و تغییرات بعدی را در آنجا اعمال کنیم. پس از باز کردن Visual Studio Code از منوی File گزینه‌ی Open Folder را انتخاب کنید و پوشه‌ی حاوی پروژه (EF7-SQLite-NETCore) را انتخاب کنید. اکنون پروژه‌ی شما تحت VS Code باز شده و قابل ویرایش است.

سپس از لیست فایل‌های پروژه، فایل project.json را باز کرده و در بخش "dependencies" یک ردیف را برای EntityFramework.SQLite به صورت زیر اضافه کنید. به محض افزودن این خط در project.json و ذخیره‌ی آن، در صورتیکه قبلا این وابستگی دریافت نشده باشد، Visual Studio Code با نمایش یک هشدار در بالای برنامه به شما امکان دریافت اتوماتیک این وابستگی را می‌دهد. در نتیجه کافیست دکمه‌ی Restore را زده و منتظر شوید تا وابستگی EntityFramework.SQLite از مخزن ناگت دانلود و برای پروژه‌ی شما تنظیم شود.

"EntityFramework.SQLite": "7.0.0-rc1-final"

دریافت اتوماتیک وابستگی‌ها توسط Visual Studio Code


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

اکنون همه‌ی موارد، جهت توسعه‌ی پروژه آماده است. ماوس خود را بر روی ریشه‌ی پروژه در VS Code قرار داده و New Folder را انتخاب کنید و نام Models را برای آن تایپ کنید. این پوشه قرار است مدل کلاس‌های پروژه را شامل شود. در اینجا ما یک مدل به نام Book داریم و نام کانتکست اصلی پروژه را هم LibraryContext گذاشته‌ایم.

بر روی پوشه‌ی Models راست کلیک کرده و گزینه‌ی New File را انتخاب کنید. سپس فایل‌های Book.cs و LibraryContext.cs را ایجاد کرده و کدهای زیر را برای مدل و کانتکست، در درون این دو فایل قرار دهید.

Book.cs

namespace Models
{
    public class Book
    {
        public int ID { get; set; }
        public string Title { get; set; }
        public string Author{get;set;}
        public int PublishYear { get; set; }
    }
}
LibraryContext.cs
using Microsoft.Data.Entity;
using Microsoft.Data.Sqlite;

namespace Models
{
    public class LibraryContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = "test.db" };
            var connectionString = connectionStringBuilder.ToString();
            var connection = new SqliteConnection(connectionString);
            optionsBuilder.UseSqlite(connection);
        }
        public DbSet<Book> Books { get; set; }
    }
}
در فایل Book.cs یک مدل ساده به نام Book داریم که حاوی اطلاعات یک کتاب است. فایل LibraryContext.cs نیز حاوی کلاس LibraryContext است که یک مجموعه از کتاب‌ها را با نام Books نگهداری می‌کند. در متد OnConfiguring تنظیمات لازم را جهت استفاده از دیتابیس SQLite با نام test.db، قرار داده‌ایم که البته این کد را در پروژه‌های کامل می‌توان در خارج از LibraryContext قرار داد تا بتوان مسیر ذخیره سازی دیتابیس را کنترل و قابل تنظیم کرد.

در قدم آخر هم کافیست که فایل Program.cs را تغییر دهید و مقادیری را در دیتابیس ذخیره و بازخوانی کنید.
Program.cs
using System;
using Models;

namespace ConsoleApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("EF7 + Sqlite with the taste of .NET Core");

            try
            {
                using (var context = new LibraryContext())
                {
                    context.Database.EnsureCreated();

                    var book1 = new Book()
                    {
                        Title = "Adaptive Code via C#: Agile coding with design patterns and SOLID principles ",
                        Author = "Gary McLean Hall",
                        PublishYear = 2014
                    };

                    var book2 = new Book()
                    {
                        Title = "CLR via C# (4th Edition)",
                        Author = "Jefrey Ritcher",
                        PublishYear = 2012
                    };

                    context.Books.Add(book1);
                    context.Books.Add(book2);

                    context.SaveChanges();

                    ReadData(context);
                }

                Console.WriteLine("Press any key to exit ...");
                Console.ReadKey();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An exception occured: {ex.Message}\n{ex.StackTrace}");
            }
        }

        private static void ReadData(LibraryContext context)
        {
            Console.WriteLine("Books in database:");
            foreach (var b in context.Books)
            {
                Console.WriteLine($"Book {b.ID}");
                Console.WriteLine($"\tName: {b.Title}");
                Console.WriteLine($"\tAuthor: {b.Author}");
                Console.WriteLine($"\tPublish Year: {b.PublishYear}");
                Console.WriteLine();
            }
        }
    }
}
در فایل Program.cs جهت تست پروژه، از روی کلاس Book دو نمونه ساخته و آن‌ها را به دیتابیس افزوده و ذخیره می‌کنیم و در انتها، اطلاعات تمامی کتاب‌های موجود در دیتابیس را با جزییات نمایش می‌دهیم.

جهت اجرای برنامه کافیست Command prompt را در آدرس پروژه باز کرده و دستور dotnet run را اجرا کنید. پروژه‌ی شما کامپایل و اجرا می‌شود و خروجی مشابه زیر را مشاهده خواهید کرد. اگر برنامه را مجددا اجرا کنید، به جای دو کتاب اطلاعات چهار کتاب نمایش داده خواهد شد؛ چرا که در هر مرحله اطلاعات دو کتاب در دیتابیس درج می‌شود.

.NET Core + EF7 + SQLite

اگر به پوشه‌ی bin که در پوشه‌ی پروژه ایجاد شده است، نگاهی بیندازید، خبری از فایل باینری نیست. چرا که در لحظه‌، تولید و اجرا شده است. جهت build کردن پروژه و تولید فایل باینری کافیست دستور dotnet build را اجرا کنید، تا فایل باینری در پوشه‌ی bin ایجاد شود.

جهت انتشار برنامه می‌توانید دستور dotnet publish را اجرا کنید. این دستور نه تنها برنامه، که تمام وابستگی‌های مورد نیاز آن را برای اجرای در یک پلتفرم خاص تولید می‌کند. برای مثال بعد از اجرای این دستور یک پوشه‌ی win7-x64 حاوی 211 فایل در مجموع تولید شده است که تمامی وابستگی‌های این پروژه را شامل می‌شود.

Publishing .NET Core app

در واقع این پوشه تمام وابستگی‌های مورد نیاز پروژه را همراه خود دارد و در نتیجه جهت اجرای این برنامه برخلاف برنامه‌های معمولی دات نت، دیگر نیازی به نصب هیچ وابستگی مجزایی نیست و حتی پروژه‌های نوشته شده تحت NET Core. را می‌توانید در سیستم‌های عامل‌های دیگری مثل لینوکس و مکینتاش و یا  Windows IoT بر روی سخت افزار Raspberry Pi 2 هم اجرا کنید.

جهت مطالعه‌ی بیشتر:
نظرات مطالب
شروع به کار با بوت استرپ 4
یک نکته‌ی تکمیلی: بررسی نگارش‌های ثالث راست به چپ بوت استرپ 4


1) MahdiMajidzadeh/bootstrap-v4-rtl
متاسفانه اصلا برای یک کار رسمی مناسب نیست و منوهای آن به هم ریخته‌است. list-group آن در حالت flush، کل عرض یک card را پر نمی‌کند و جداول آن نیز به همین صورت است. کامپوننت bread-crumb آن محل قرارگیری /‌های نامناسبی دارد. همچنین با آخرین نگارش بوت استرپ 4.1.3 سازگار نیست و از آن کمی عقب است و برای کار با آن، باید دقیقا همین بسته‌ی ثالث را دریافت و اضافه کنید و مستقل از خود بوت استرپ اصلی نیست. اما به همراه یک بسته‌ی npm مخصوص به خود است که یک مزیت به شمار می‌رود. مجوز آن، در مخزن کد Github آن ذکر نشده، اما در صفحه‌ی npm آن MIT ذکر شده‌است.
یک نمونه خروجی آن:


2) DediData/Bootstrap-RTL
به نظر یک پروژه‌ی خاتمه یافته‌است. با نگارش بوت استرپ 4.1.3 سازگار نیست و برای نگارش بتای آن تهیه شده‌است.


3) GhalamborM/bootstrap4-rtl
این پروژه، روش بهتری را نسبت به بسته‌های راست به چپ موجود، انتخاب کرده‌است. در اینجا شما بوت استرپ اصلی را با آخرین نگارش آن به صورت مستقل دریافت، نصب و تنظیم می‌کنید. سپس ذیل آن کلاس‌های راست به چپ این بسته‌ی ثالث را اضافه می‌کنید.
مجوز GPL، برای اینکار انتخاب شده‌است. متاسفانه یک چنین مجوزی در تضاد با مجوز MIT بوت استرپ اصلی است. مجوز GPL یعنی کار مشتق شده‌ی از آن نیز باید سورس باز شود و قابل استفاده‌ی در پروژه‌های تجاری غیر سورس باز نیست.
همچنین متاسفانه به صورت یک بسته‌ی npm نیز ارائه نشده‌است و باید آخرین نگارش آن‌را از GitHub به صورت مستقیم دریافت کنید.

با تمام این اوصاف، مشکلات ذکر شده‌ی مورد اولی که بررسی شد، در این نگارش وجود ندارند و بهترین خروجی را دریافت خواهید کرد:

 



4) PerseusTheGreat/bootstrap-4-rtl
روش راست به چپ سازی این نگارش نیز مانند حالت اولی است که بررسی شد و باید بسته‌ی مستقل آن‌را دریافت و استفاده کنید و به عنوان یک مکمل مطرح نیست. همچنین به همراه بسته‌ی npm نیز ارائه نشده‌است و تا این تاریخ، باید آخرین به روز رسانی‌های آن‌را از همان آدرس GitHub آن مستقیما دریافت کنید. البته مزیت آن، به روز رسانی هفتگی آن است. همچنین مجوز MIT این بسته را نیز تغییر نداده‌است.
خروجی آن با خروجی بسته‌ی سومی که معرفی شد، تقریبا یکی و مناسب است:



در کل اگر از مجوز GPL مورد سوم صرف نظر کنیم، به علت استقلال فایل CSS راست به چپ کننده‌ی آن از بسته‌ی اصلی بوت استرپ، انتخاب مناسب‌تری به نظر می‌رسد و خروجی قابل قبولی را نیز به همراه دارد. فقط ایکاش بسته‌ی npm ای نیز به پروژه‌ی آن اضافه شود.
به روز رسانی: تغییر مجوز به MIT و همچنین افزوده شدن بسته‌ی npm به مورد سوم صورت گرفته‌است:
npm install @ghalamborm/bootstrap4-rtl


پ.ن.
این روزها ارائه‌ی یک کتابخانه‌ی جاوا اسکریپتی و یا CSS ای بدون بسته‌ی npm متناظر با آن، ناقص به شمار می‌رود.
نظرات مطالب
نحوه شبیه سازی سرعت‌های اتصال پایین جهت آزمایش یک وب سایت
به روز رسانی
در developer tools نگارش‌های اخیر فایرفاکس و کروم، قابلیت توکار throttling وجود دارد و برای اینکار، نیازی به نصب هیچ افزونه‌ی جداگانه‌ای نیست:
در فایرفاکس:


در کروم:

نظرات مطالب
تنظیمات CORS در ASP.NET Core
یک نکته‌ی تکمیلی: دریافت خطای CORS پس از نصب تازه‌ی NET Core SDK.
پس از نصب تازه‌ی NET Core SDK. و ارسال پیامی از طرف برنامه، ممکن است خطاهای زیر را مشاهده کنید که از طرف مرورگر به اشتباه، خطای CORS گزارش می‌شوند:
در فایرفاکس:

در کروم:

برای رفع این مشکل فقط کافی است دو دستور زیر را با دسترسی مدیریتی اجرا کنید:

dotnet dev-certs https --clean
dotnet dev-certs https --trust
تا مجوز SSL آزمایشی NET Core.، به صورت امنی به سیستم معرفی شود.
این تنظیمات، نیاز به ری‌استارت کامل سیستم را هم دارند و تا آن زمان، باز هم خطاهای یاد شده را در مرورگرها مشاهده خواهید کرد.
مطالب
ایجاد کوکی با jcookie
همانطور که از نامش پیداست jcookie یک پلاگین jquery است. این پلاگین به شما این اجازه را می‌دهد تا هر نوع داده ای را که مایل هستید از قبیل رشته‌ها، آرایه‌ها و object را در قالب json با رمزگذاری base 64 ذخیره نمایید. استفاده از این رمزگذاری باعث کوچکتر شدن حجم کوکی تا 70 درصد می‌شود. در این مقاله شما یاد می‌گیرید که چطور برای ذخیره و بازیابی کوکی از آن استفاده کرده و چگونه در یک زبان سمت سرور، مثل سی شارپ نیز کوکی مورد نظر را با همان فرمت بخوانید.
جهت دانلود فایل jcookie به  اینجا  مراجعه کنید.
ذخیره کوکی
برای ساخت یک کوکی به روش زیر اقدام می‌کنیم. استفاده از jCookies.$ دو خاصیت به نام‌های نام کوکی و مقدار کوکی را name & Value در دسترس ما می‌گذارد:
var d = new Date();         
             
                $.jCookies({
                    name: 'dotnettips.info',
                    value: { Title: 'ساخت کوکی با jcookie', Author: 'علی یگانه مقدم', Seen: d.getDate(), Favorite: true }
                });
همانطور که می‌بینید ذخیره اطلاعات توسط jcookie بسیار ساده و راحت بوده و هر نوع داده ای در آن به راحتی قابل ذخیره سازیست. برای مثال می‌توانید اطلاعات یک کلاس را خیلی راحت و سریع با آن ذخیره کنید. به طور پیش فرض تاریخ انقضای کوکی 27 روز بعد از ایجاد آن می‌باشد. در صورتی که تمایل دارید این تاریخ را تغییر دهید یکی از خاصیت‌های seconds,minutes,hours و days در دسترس شماست و مقادیری که جلوی آن‌ها به کارگرفته می‌شود باید نوع صحیح بوده و در صورتی که مقدار نامعتبر وارد شود خاصیت مورد نظر نادیده گرفته می‌شود.
$.jCookies ({ name : 'User', value : { username : 'Bob' , level : 5 }, minutes : 60 });
برای تغییر پیش فرض‌های ساخت کوکی مانند انقضای 27 روز به عدد پیش فرض خودتان فایل jcookies.js را باز کرده و تنظیمات پیش فرض آن را تغییر بدهید. برای تغییر دنبال کد زیر بگردید:
$.jCookies.defaults =
{
name : '',
value : '',
days : 27
}

بازیابی کوکی
برای بازیابی کوکی مجددا از jCookies.$ استفاده می‌شود ولی تنها باید یک خاصیت get که نام کوکی هست را بنویسید:
var values = $.jCookies ({ get: 'dotnettips.info' });
در صورتی که نام کوکی‌ای که درخواست کرده اید وجود نداشته باشد یا اینکه تاریخ انقضای آن سر رسیده است و از سیستم کلاینت حذف شده است یا اینکه هنگام درخواست کوکی با خطایی مواجه شده باشد، مقدا برگشتی false خواهد بود و اگر نیاز دارید که بدانید آیا نوع برگشتی false به خاطر خطا بوده است یا خیر یک خاصیت نوع بول به نام error هم اضافه می‌شود:
var values = $.jCookies({ get: 'Rutabaga', error: true });
در صورتی که خطایی داده شود response مقدار values در مرورگر کروم به شکل زیر خواهد بود. در هر مرورگر نحوه نمایش خطا می‌تواند متفاوت باشد.
Error : {
            arguments : undefined,
            message : "Invalid base64 data",
            stack : "—",
            type : undefined
        }
بازیابی همه کوکی ها
در صورتی که به خاصیت get مقدار * را بدهید تمامی کوکی‌ها برگشت داده خواهند شد و به صورت آرایه ای از نام کوکی‌ها در دسترسی خواهند بود:
 var values = $.jCookies({ get: '*' });
                alert(values["dotnettips.info"].Title);
                alert(values["data2"].Title);

حذف کوکی
نحوه کدنویسی حذف کوکی هم دقیقا مشابه خواندن کوکی است؛ با این تفاوت که به جای استفاده از خاصیت get از خاصیت erase استفاده می‌کنیم و با دادن نام کوکی به این خاصیت، کوکی حذف خواهد شد:
var value = $.jCookies({ erase: 'dotnettips.info' });
در صورتی که کوکی وجود داشته باشد، آن را حذف کرده و مقدار true را برگشت خواهد زد و در صورتی که کوکی وجود نداشته باشد مقدار false را بر میگرداند.

بازیابی کوکی در سمت سرور با سی شارپ
در این روش ما ابتدا با همان دستور معمولی دات نت یعنی page.request.cookie درخواست دریافت کوکی را می‌دهیم ولی از آنجا که در jcookie دو عمل روی داده‌ها صورت گرفته است باید دو کار اضافه‌تر را انجام داد:
  1. برگشت داده‌ها از حالت رمزگذاری base64
  2. داده‌ها در فرمت json هستند و باید به حالتی قابل استفاده در محیط شی گرا تبدیل شوند.
برای بازگردانی از حالت base64 از کلاس و متد Convert.FromBase64String در فضای نام system.convert استفاده می‌کنیم که آرایه ای از نوع بایت را بر میگرداند و از Encoding.UTF8.GetString هم برای decode کردن آرایه به نوع رشته استفاده می‌کنیم. تا به اینجای کار داده‌های ما به صورت یک json خوانا با فرمت string درآمده است. برای دسترسی به داده‌های موجود در این فرمت باید آن‌ها را Deserialize کنیم که این کار را از طریق کلاس JavaScriptSerializer  در فضای نام System.Web.Script.Serialization انجام می‌دهیم و از کلاس دیکشنری برای ذخیره داده‌های برگشتی استفاده می‌کنیم که نوع string برای نام خاصیت و نوع آبجکت برای ذخیره مقدار خاصیت خواهد بود.  یعنی برای بازگردانی اولین مثال بالا باید داده‌های در نوع دیکشنری به صورت زیر لیست شوند
 Title ایجاد کوکی با jcookie 
 Author  علی یگانه مقدم
 Seen  2015/1/14
 Favorite  true
byte[] from64 = Convert.FromBase64String(Page.Request.Cookies["dotnettips.info"].Value);
            string json = Encoding.UTF8.GetString(from64);
            Dictionary<string, object> article =new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(json);
            Page.Response.Write("Title: "+ (string)article["Title"]);
پشتیبانی از یونیکد
موقعی که من اولین مثال بالا را نوشتم و مقادیر را به صورت فارسی وارد کردم متوجه شدم که رشته‌های یونیکد را انکود نمی‌کند و در نتیجه زبان فارسی در آن پشتیبانی نمی‌شود. برای همین تغییراتی در فایل js ایجاد کرده و عبارت value قبل از تبدیل به base64 را به صورت utf-16 انکود کردم و در هنگام خواندن کوکی هم به صورت utf-16 دیکود کردم و مشکل زبان فارسی هم در این حالت حل شد. البته کدی که اضافه کردم قابلیت‌های انکودینگ بیشتری هم دارد.
فقط تنها مورد این هست که برای خواندن کوکی در سمت سرور باید یک تغییر کوچک یک کلمه ای بدهیم؛ باید کلمه UTF8 را به Unicode که می‌شود همان UTF-16 در کد تغییر دهیم، که به کد زیر تغییر خواهد یافت:
byte[] from64 = Convert.FromBase64String(Page.Request.Cookies["dotnettips.info"].Value);
            string json = Encoding.Unicode.GetString(from64);
            Dictionary<string, object> article =new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(json);
            Page.Response.Write("Title: "+ (string)article["Title"]);
برای دریافت jcookie با پشتیبانی از زبان فارسی به اینجا مراجعه کنید.
کدهای بالا در فایل زیر قرار گرفته اند.