در صورت کار نکردن edit & continue:
از منوی Tools در ویژوال استدیو، Options را باز کنید و در قسمت جستجوی آن، عبارت Intellitrace را بنویسید و اگر چیزی پیدا شد، تیک Enable Intellitrace را بردارید تا غیر فعال شود. همچنین مجدد Suppress JIT optimization را جستجو کنید و تیک آن را بزنید تا فعال شود. همچنین اگه مشکل پابرجا موند، پروژههای Android/iOS را un load کرده و ویژوال استدیو را ببندید و باز کنید و مجدد تست کنید.
همه ما با ابزار تزریق وابستگی مایکروسافت کار کردهایم و میدانیم برای معرفی وابستگیها میبایست آنها را به IServiceCollection معرفی کنیم. اما راه حل سادهتری هم وجود دارد. چیزی که در ابزارهای java-spring به صورت خودکار و با استفاده از annotationهایی که در component models تعریف میشوند اینکار را تسهیل میکند و دیگر خبری از یک فایل شلوغ برای معرفی وابستگیها به موتور تزریق وابستگی نیست. برای اینکار در c-sharp با استفاده از موتور تزریق وابستگی مایکروسافت، Reguto را آماده کردم و بسیار ساده با استفاده از attributeهایی که به صورت پیشفرض تعریف شده است میتوانید وابستگیهای خود را معرفی کنید.
در قسمت قبل، پایهی Web API و سرویسهای سمت سرور برنامهی کلاینت Blazor WASM این سری را آماده کردیم. این برنامهی سمت کلاینت، قرار است توسط عموم کاربران آن جهت رزرو کردن اتاقهای هتل فرضی مثال این سری، مورد استفاده قرار گیرد. پیش از این نیز یک برنامهی Blazor Server را تهیه کردیم که کار آن صرفا محدود است به مسائل مدیریتی هتل؛ مانند تعریف اتاقها و امکانات رفاهی آن.
ایجاد یک پروژهی جدید Blazor WASM
برای تکمیل پیاده سازی قسمت سمت کلاینت پروژهی این سری، نیاز به یک پروژهی جدید Blazor WASM را داریم که میتوان آنرا با اجرای دستور dotnet new blazorwasm در یک پوشهی خالی، ایجاد کرد. کدهای این پروژه را میتوانید در پوشهی HotelManagement\BlazorWasm\BlazorWasm.Client فایل پیوستی انتهای بحث مشاهده کنید.
افزودن فایلهای جاوااسکریپتی مورد نیاز
شبیه به کاری که در مطلب «Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت» انجام دادیم، در اینجا هم قصد افزودن یکسری کتابخانهی جاوااسکریپتی و CSS ای را داریم که توسط LibMan آنها را مدیریت خواهیم کرد.
- بنابراین در ابتدا به پوشهی BlazorWasm.Client\wwwroot\css وارد شده و پوشههای پیشفرض bootstrap و open-iconic آنرا حذف میکنیم؛ چون تحت مدیریت هیچ package manager ای نیستند و در این حالت، مدیریت به روز رسانی و یا بازیابی آنها به صورت خودکار میسر نیست.
- سپس فایل wwwroot\css\app.css را هم ویرایش کرده و سطر زیر را از ابتدای آن حذف میکنیم:
- اکنون دستورات زیر را در ریشهی پروژهی WASM، اجرا میکنیم تا کتابخانههای مدنظر ما، تحت مدیریت libman، در پوشهی wwwroot/lib نصب شوند:
این دستورات همچنین فایل libman.json متناظری را نیز جهت اجرای دستور libman restore برای دفعات آتی، تولید میکند.
- بعد از نصب بستههای ذکر شده، فایل wwwroot\index.html را به صورت زیر به روز رسانی میکنیم تا به مسیرهای جدید بستههای CSS و JS نصب شده، اشاره کند:
مداخل فایلهای css را در قسمت head و فایلهای js را پیش از بسته شدن تگ body تعریف میکنیم. در اینجا نیازی به ذکر پوشهی آغازین wwwroot نیست؛ چون base href تعریف شده، به این پوشه اشاره میکند.
- محتویات فایل wwwroot\css\app.css را هم به صورت زیر تغییر میدهیم تا یک spinner و شیوه نامههای نمایش تصاویر، به آن اضافه شوند:
- همچنین فایل جدید wwwroot\js\common.js را که در قسمت 11 این سری ایجاد کردیم، به پروژهی جاری نیز با محتوای زیر اضافه میکنیم تا سبب سهولت دسترسی به toastr شود:
- در قسمت 11، در بخش «کاهش کدهای تکراری فراخوانی متدهای جاوا اسکریپتی با تعریف متدهای الحاقی» آن، کلاس JSRuntimeExtensions را تعریف کردیم که سبب کاهش تکرار کدهای استفاده از تابع ShowToastr میشود. این فایلرا در پروژهی BlazorServer.App\Utils\JSRuntimeExtensions.cs این سری نیز استفاده کردیم. یا میتوان مجددا آنرا به پروژهی جاری کپی کرد؛ یا آنرا در یک پروژهی اشتراکی قرار داد. برای مثال اگر آنرا به پوشهی BlazorWasm.Client\Utils کپی کردیم، نیاز است فضای نام آنرا اصلاح کرده و سپس آنرا به انتهای فایل BlazorWasm.Client\_Imports.razor نیز اضافه کنیم تا در تمام کامپوننتهای برنامه قابل استفاده شود:
تغییر و ساده سازی منوی برنامهی کلاینت
در برنامهی کلاینت جاری دیگر نمیخواهیم منوی پیشفرض سمت چپ صفحه را شاهد باشیم. به همین جهت ابتدا فایل Shared\MainLayout.razor را به صورت زیر ساده میکنیم:
سپس محتوای فایل Shared\NavMenu.razor را نیز حذف کرده و با تعاریف زیر جایگزین میکنیم:
تا اینجا اگر برنامهی سمت کلاینت را اجرا کنیم، شکل زیر را پیدا کرده که به همراه یک navbar افقی قرار گرفتهی در بالای صفحه است؛ به همراه دو لینک به قسمتهای ثبتنام و لاگین:
تغییر محتوای صفحهی آغازین برنامه
صفحهی ابتدایی برنامه، یعنی کامپوننت Pages\Index.razor را نیز به صورت زیر تغییر میدهیم:
در اینجا فرمی تعریف شده که تاریخ ورود و رزرو اتاقی را مشخص میکند؛ به همراه دراپداونی برای انتخاب تعداد شبهای اقامت مدنظر.
تعریف View Model رابط کاربری Pages\Index.razor
پس از تعریف محتوای ثابت برنامه، اکنون نوبت به پویا سازی آن است. به همین جهت نیاز است مدلی را برای صفحهی آغازین برنامه تعریف کرد تا بتوان فرم آنرا به این مدل متصل کرد. این مدل چون مختص به برنامهی کلاینت است، آنرا در پوشهی جدید Models\ViewModels ایجاد میکنیم:
در اینجا EndDate، یک خاصیت محاسباتی است که بر اساس تاریخ شروع و تعداد شبهای انتخابی، قابل محاسبهاست.
پس از این تعریف، بهتر است فضای نام آنرا نیز به فایل BlazorWasm.Client\_Imports.razor افزود، تا کار با آن در کامپوننتهای برنامه، سادهتر شود:
اکنون میتوان فرم Pages\Index.razor را به مدل فوق متصل کرد که شامل این تغییرات است:
- ابتدا فیلدی که ارائه کنندهی شیء ViewModel فرم است را تعریف میکنیم:
- سپس بجای یک form ساده، از EditForm اشاره کنندهی به این فیلد، استفاده خواهیم کرد:
- در آخر بجای input معمولی، از کامپوننت InputDate متصل به HomeModel.StartDate :
و بجای select معمولی، از نمونهی متصل شدهی به HomeModel.NoOfNights استفاده میکنیم:
تعریف Local Storage سمت کلاینت
در ادامه میخواهیم اگر کاربری زمان شروع رزرو اتاقی را به همراه تعداد شب مدنظر، انتخاب کرد، با کلیک بر روی دکمهی Go، به یک صفحهی مشاهدهی جزئیات منتقل شود. بنابراین نیاز داریم تا اطلاعات انتخابی کاربر را به نحوی ذخیره سازی کنیم. برای یک چنین سناریوی سمت کلاینتی، میتوان از local storage استاندارد مرورگرها استفاده کرد که امکان کار آفلاین با برنامه را نیز فراهم میکند.
برای این منظور کتابخانهای به نام Blazored.LocalStorage طراحی شدهاست که پس از نصب آن توسط دستور زیر:
نیاز است سرویسهای آنرا به سیستم تزریق وابستگیهای برنامه اضافه کرد. در برنامههای Blazor Server، اینکار را در فایل Startup برنامه انجام میدادیم؛ اما در اینجا، سرویسها در فایل Program.cs تعریف میشوند:
پس از این تعاریف میتوان از سرویس ILocalStorageService آن در کامپوننتهای برنامه استفاده کرد. البته جهت سهولت استفادهی از این سرویس بهتر است فضای نام آنرا به فایل BlazorWasm.Client\_Imports.razor افزود:
اکنون برای استفاده از آن به کامپوننت Pages\Index.razor مراجعه کرده و سرویسهای ILocalStorageService و IJSRuntime را به کامپوننت تزریق میکنیم:
همچنین متدی را هم برای مدیریت رویداد OnValidSubmit تعریف خواهیم کرد:
در اینجا با استفاده از متد SetItemAsync و ذکر یک کلید دلخواه، اطلاعات مدل فرم را در local storage مرورگر ذخیره کردهایم. همچنین اگر خطایی هم رخ دهد توسط ToastrError نمایش داده خواهد شد.
برای مثال اگر تاریخ و عددی را انتخاب کنیم، نتیجهی حاصل از کلیک بر روی دکمهی Go را میتوان در قسمت Local storage مرورگر جاری مشاهده کرد:
البته با توجه به اینکه میخواهیم از کلید InitialRoomBookingInfo در سایر کامپوننتهای برنامه نیز استفاده کنیم، بهتر است آنرا به یک پروژهی مشترک مانند BlazorServer.Common که پیشتر نام نقشهایی مانند Admin را در آن تعریف کردیم، منتقل کنیم:
سپس باید ارجاعی به آن پروژه را افزوده:
همچنین فضای نام آنرا نیز به فایل BlazorWasm.Client\_Imports.razor اضافه میکنیم:
اکنون میتوان از کلید ثابت تعریف شدهی مشترک، استفاده کرد:
در آخر قصد داریم با کلیک بر روی Go، به یک صفحهی جدید مانند نمایش لیست اتاقها هدایت شویم. به همین جهت کامپوننت جدید Pages\HotelRooms\HotelRooms.razor را ایجاد میکنیم:
سپس در کامپوننت Pages\Index.razor با استفاده از سرویس NavigationManager، کار هدایت خودکار کاربر را به این کامپوننت جدید انجام خواهیم داد:
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-26.zip
ایجاد یک پروژهی جدید Blazor WASM
برای تکمیل پیاده سازی قسمت سمت کلاینت پروژهی این سری، نیاز به یک پروژهی جدید Blazor WASM را داریم که میتوان آنرا با اجرای دستور dotnet new blazorwasm در یک پوشهی خالی، ایجاد کرد. کدهای این پروژه را میتوانید در پوشهی HotelManagement\BlazorWasm\BlazorWasm.Client فایل پیوستی انتهای بحث مشاهده کنید.
افزودن فایلهای جاوااسکریپتی مورد نیاز
شبیه به کاری که در مطلب «Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت» انجام دادیم، در اینجا هم قصد افزودن یکسری کتابخانهی جاوااسکریپتی و CSS ای را داریم که توسط LibMan آنها را مدیریت خواهیم کرد.
- بنابراین در ابتدا به پوشهی BlazorWasm.Client\wwwroot\css وارد شده و پوشههای پیشفرض bootstrap و open-iconic آنرا حذف میکنیم؛ چون تحت مدیریت هیچ package manager ای نیستند و در این حالت، مدیریت به روز رسانی و یا بازیابی آنها به صورت خودکار میسر نیست.
- سپس فایل wwwroot\css\app.css را هم ویرایش کرده و سطر زیر را از ابتدای آن حذف میکنیم:
@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
dotnet tool update -g Microsoft.Web.LibraryManager.Cli libman init libman install bootstrap --provider unpkg --destination wwwroot/lib/bootstrap libman install open-iconic --provider unpkg --destination wwwroot/lib/open-iconic libman install jquery --provider unpkg --destination wwwroot/lib/jquery libman install toastr --provider unpkg --destination wwwroot/lib/toastr
- بعد از نصب بستههای ذکر شده، فایل wwwroot\index.html را به صورت زیر به روز رسانی میکنیم تا به مسیرهای جدید بستههای CSS و JS نصب شده، اشاره کند:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>BlazorWasm.Client</title> <base href="/" /> <link href="lib/toastr/build/toastr.min.css" rel="stylesheet" /> <link href="lib/open-iconic/font/css/open-iconic-bootstrap.min.css" rel="stylesheet" /> <link href="lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" /> <link href="css/app.css" rel="stylesheet" /> <link href="BlazorWasm.Client.styles.css" rel="stylesheet" /> </head> <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="lib/jquery/dist/jquery.min.js"></script> <script src="lib/toastr/build/toastr.min.js"></script> <script src="js/common.js"></script> <script src="_framework/blazor.webassembly.js"></script> </body> </html>
- محتویات فایل wwwroot\css\app.css را هم به صورت زیر تغییر میدهیم تا یک spinner و شیوه نامههای نمایش تصاویر، به آن اضافه شوند:
.valid.modified:not([type="checkbox"]) { outline: 1px solid #26b050; } .invalid { outline: 1px solid red; } .validation-message { color: red; } #blazor-error-ui { background: lightyellow; bottom: 0; box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); display: none; left: 0; padding: 0.6rem 1.25rem 0.7rem 1.25rem; position: fixed; width: 100%; z-index: 1000; } #blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.5rem; } .spinner { border: 16px solid silver !important; border-top: 16px solid #337ab7 !important; border-radius: 50% !important; width: 80px !important; height: 80px !important; animation: spin 700ms linear infinite !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%); position: absolute !important; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .room-image { display: block; width: 100%; height: 150px; background-size: cover !important; border: 3px solid green; position: relative; } .room-image-title { position: absolute; top: 0; right: 0; background-color: green; color: white; padding: 0px 6px; display: inline-block; }
window.ShowToastr = (type, message) => { if (type === "success") { toastr.success(message, "Operation Successful", { timeOut: 10000 }); } if (type === "error") { toastr.error(message, "Operation Failed", { timeOut: 10000 }); } };
- در قسمت 11، در بخش «کاهش کدهای تکراری فراخوانی متدهای جاوا اسکریپتی با تعریف متدهای الحاقی» آن، کلاس JSRuntimeExtensions را تعریف کردیم که سبب کاهش تکرار کدهای استفاده از تابع ShowToastr میشود. این فایلرا در پروژهی BlazorServer.App\Utils\JSRuntimeExtensions.cs این سری نیز استفاده کردیم. یا میتوان مجددا آنرا به پروژهی جاری کپی کرد؛ یا آنرا در یک پروژهی اشتراکی قرار داد. برای مثال اگر آنرا به پوشهی BlazorWasm.Client\Utils کپی کردیم، نیاز است فضای نام آنرا اصلاح کرده و سپس آنرا به انتهای فایل BlazorWasm.Client\_Imports.razor نیز اضافه کنیم تا در تمام کامپوننتهای برنامه قابل استفاده شود:
@using BlazorWasm.Client.Utils
تغییر و ساده سازی منوی برنامهی کلاینت
در برنامهی کلاینت جاری دیگر نمیخواهیم منوی پیشفرض سمت چپ صفحه را شاهد باشیم. به همین جهت ابتدا فایل Shared\MainLayout.razor را به صورت زیر ساده میکنیم:
@inherits LayoutComponentBase <NavMenu /> <div> @Body </div>
<nav class="navbar navbar-expand-sm navbar-dark bg-dark p-0"> <a class="navbar-brand mx-4" href="#">Navbar</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse pr-2" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"></ul> <ul class="my-0 navbar-nav"> <li class="nav-item p-0"> <NavLink class="nav-link" href="registration"> <span class="p-2"> Register </span> </NavLink> </li> <li class="nav-item p-0"> <NavLink class="nav-link" href="login"> <span class="p-2"> Login </span> </NavLink> </li> </ul> </div> </nav>
تغییر محتوای صفحهی آغازین برنامه
صفحهی ابتدایی برنامه، یعنی کامپوننت Pages\Index.razor را نیز به صورت زیر تغییر میدهیم:
@page "/" <form> <div class="row p-0 mx-0 mt-4"> <div class="col-12 col-md-5 offset-md-1 pl-2 pr-2 pr-md-0"> <div class="form-group"> <label>Check In Date</label> <input type="text" class="form-control" /> </div> </div> <div class="col-8 col-md-3 pl-2 pr-2"> <div class="form-group"> <label>No. of nights</label> <select class="form-control"> @for (var i = 1; i <= 10; i++) { <option value="@i">@i</option> } </select> </div> </div> <div class="col-4 col-md-2 p-0 pr-2"> <div class="form-group"> <label> </label> <input type="submit" value="Go" class="btn btn-success btn-block" /> </div> </div> </div> </form>
تعریف View Model رابط کاربری Pages\Index.razor
پس از تعریف محتوای ثابت برنامه، اکنون نوبت به پویا سازی آن است. به همین جهت نیاز است مدلی را برای صفحهی آغازین برنامه تعریف کرد تا بتوان فرم آنرا به این مدل متصل کرد. این مدل چون مختص به برنامهی کلاینت است، آنرا در پوشهی جدید Models\ViewModels ایجاد میکنیم:
using System; namespace BlazorWasm.Client.Models.ViewModels { public class HomeVM { public DateTime StartDate { get; set; } = DateTime.Now; public DateTime EndDate { get; set; } public int NoOfNights { get; set; } = 1; } }
پس از این تعریف، بهتر است فضای نام آنرا نیز به فایل BlazorWasm.Client\_Imports.razor افزود، تا کار با آن در کامپوننتهای برنامه، سادهتر شود:
using BlazorWasm.Client.Models.ViewModels
- ابتدا فیلدی که ارائه کنندهی شیء ViewModel فرم است را تعریف میکنیم:
@code{ HomeVM HomeModel = new HomeVM(); }
<EditForm Model="HomeModel"> // ... </EditForm>
<InputDate min="@DateTime.Now.ToString("yyyy-MM-dd")" @bind-Value="HomeModel.StartDate" type="text" class="form-control" />
<select @bind="HomeModel.NoOfNights">
تعریف Local Storage سمت کلاینت
در ادامه میخواهیم اگر کاربری زمان شروع رزرو اتاقی را به همراه تعداد شب مدنظر، انتخاب کرد، با کلیک بر روی دکمهی Go، به یک صفحهی مشاهدهی جزئیات منتقل شود. بنابراین نیاز داریم تا اطلاعات انتخابی کاربر را به نحوی ذخیره سازی کنیم. برای یک چنین سناریوی سمت کلاینتی، میتوان از local storage استاندارد مرورگرها استفاده کرد که امکان کار آفلاین با برنامه را نیز فراهم میکند.
برای این منظور کتابخانهای به نام Blazored.LocalStorage طراحی شدهاست که پس از نصب آن توسط دستور زیر:
dotnet add package Blazored.LocalStorage
namespace BlazorWasm.Client { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); // ... builder.Services.AddBlazoredLocalStorage(); // ... } } }
@using Blazored.LocalStorage
@page "/" @inject ILocalStorageService LocalStorage @inject IJSRuntime JsRuntime <EditForm Model="HomeModel" OnValidSubmit="SaveInitialData">
@code{ HomeVM HomeModel = new HomeVM(); private async Task SaveInitialData() { try { HomeModel.EndDate = HomeModel.StartDate.AddDays(HomeModel.NoOfNights); await LocalStorage.SetItemAsync("InitialRoomBookingInfo", HomeModel); } catch (Exception e) { await JsRuntime.ToastrError(e.Message); } } }
برای مثال اگر تاریخ و عددی را انتخاب کنیم، نتیجهی حاصل از کلیک بر روی دکمهی Go را میتوان در قسمت Local storage مرورگر جاری مشاهده کرد:
البته با توجه به اینکه میخواهیم از کلید InitialRoomBookingInfo در سایر کامپوننتهای برنامه نیز استفاده کنیم، بهتر است آنرا به یک پروژهی مشترک مانند BlazorServer.Common که پیشتر نام نقشهایی مانند Admin را در آن تعریف کردیم، منتقل کنیم:
namespace BlazorServer.Common { public static class ConstantKeys { public const string LocalInitialBooking = "InitialRoomBookingInfo"; } }
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> <ItemGroup> <ProjectReference Include="..\..\BlazorServer\BlazorServer.Common\BlazorServer.Common.csproj" /> </ItemGroup> </Project>
@using BlazorServer.Common
await LocalStorage.SetItemAsync(ConstantKeys.LocalInitialBooking, HomeModel);
در آخر قصد داریم با کلیک بر روی Go، به یک صفحهی جدید مانند نمایش لیست اتاقها هدایت شویم. به همین جهت کامپوننت جدید Pages\HotelRooms\HotelRooms.razor را ایجاد میکنیم:
@page "/hotel/rooms" <h3>HotelRooms</h3> @code { }
@page "/" @inject ILocalStorageService LocalStorage @inject IJSRuntime JsRuntime @inject NavigationManager NavigationManager @code{ HomeVM HomeModel = new HomeVM(); private async Task SaveInitialData() { try { HomeModel.EndDate = HomeModel.StartDate.AddDays(HomeModel.NoOfNights); await LocalStorage.SetItemAsync(ConstantKeys.LocalInitialBooking, HomeModel); NavigationManager.NavigateTo("hotel/rooms"); } catch (Exception e) { await JsRuntime.ToastrError(e.Message); } } }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-26.zip
مطالب دورهها
آشنایی با مدل برنامه نویسی TAP
تاریخچهی اعمال غیر همزمان در دات نت فریم ورک
دات نت فریم ورک، از زمان ارائه نگارش یک آن، از اعمال غیرهمزمان و API خاص آن پشتیبانی میکردهاست. همچنین این مورد یکی از ویژگیهای Win32 نیز میباشد. نوشتن کدهای همزمان متداول بسیار ساده است. در این نوع کدها هر عملیات خاص، پس از پایان عملیات قبلی انجام میشود.
در این مثال متداول، متد DownloadString به صورت همزمان یا synchronous عمل میکند. به این معنا که تا پایان عملیات دریافت اطلاعات از وب، منتظر مانده و ترد جاری را قفل میکند. مشکل از جایی آغاز میشود که مدت زمان دریافت اطلاعات، طولانی باشد. چون این عملیات در ترد UI در حال انجام است، کل رابط کاربری برنامه تا پایان عملیات نیز قفل شده و دیگر پاسخگوی سایر اعمال رسیده نخواهد بود. در این حالت عموما ویندوز در نوار عنوان برنامه، واژههای Not responding را نمایش میدهد.
این مورد همچنین در برنامههای سمت سرور نیز حائز اهمیت است. با قفل شدن تعداد زیادی ترد در حال اجرا، عملا قدرت پاسخدهی سرور نیز کاهش مییابد. بنابراین در این نوع موارد، برنامههای چند ریسمانی هرچند در سمت کلاینت ممکن است مفید واقع شوند و برای مثال ترد UI را آزاد کنند، اما اثر آنچنانی بر روی برنامههای سمت سرور ندارند. زیرا در آنها میتوان هزاران ترد را ایجاد کرد که همگی دارای کدهای اصطلاحا blocking باشند. برای حل این مساله استفاده از API غیرهمزمان توصیه میشود.
برای نمونه کلاس WebClient توکار دات نت، دارای متدی به نام DownloadStringAsync نیز میباشد. این متد به محض فراخوانی، ترد جاری را آزاد میکند. به این معنا که فراخوانی آن سبب توقف ترد جاری برای دریافت نتیجهی دریافت اطلاعات از وب نمیشود. به این نوع API، یک Asynchronous API گفته میشود؛ زیرا با سایر کدهای نوشته شده، هماهنگ و همزمان اجرا نمیشود.
هر چند این کد جدید مشکل عدم پاسخ دهی برنامه را برطرف میکند، اما مشکل دیگری را به همراه دارد؛ چگونه باید حاصل عملیات آنرا پس از پایان کار دریافت کرد؟ چگونه باید خطاها و مشکلات احتمالی را مدیریت کرد؟
برای مدیریت این مساله، رخدادی به نام DownloadStringCompleted تعریف شدهاست. روال رویدادگردان آن پس از پایان کار دریافت اطلاعات از وب، فراخوانی میگردد.
در اینجا همچنین توسط آرگومان DownloadStringCompletedEventArgs، موفقیت یا شکست عملیات نیز گزارش میشود و مقدار e.Result حاصل عملیات است.
مشکل! ما سادگی یک عملیات همزمان را از دست دادیم. متد TestNoneAsync از لحاظ پیاده سازی و همچنین خواندن و نگهداری آن در طول زمان، بسیار سادهتر است از نمونهی TestAsync نوشته شده. در کدهای غیرهمزمان فوق، یک متد ساده، به دو متد مجزا خرد شدهاست و نتیجهی نهایی، درون یک روال رخدادگردان بدست میآید.
به این مدل، EAP یا Event based asynchronous pattern نیز گفته میشود. EAP در دات نت 2 معرفی شد. روالهای رخدادگردان در این حالت، در ترد اصلی برنامه اجرا میشوند. اما اگر به حالت اصلی اعمال غیرهمزمان موجود از دات نت یک کوچ کنیم، اینطور نیست. در WinForms و WPF برای به روز رسانی رابط کاربری نیاز است اطلاعات دریافت شده در همان تردی که رابط کاربری ایجاد شده است، تحویل گرفته شده و استفاده شوند. در غیراینصورت استثنایی صادر شده و برنامه خاتمه مییابد.
آشنایی با Synchronization Context
ابتدا یک برنامهی WinForms ساده را آغاز کرده و یک دکمهی جدید را به نام btnGetInfo و یک تکست باکس را به نام txtResults، به آن اضافه کنید. سپس کدهای فرم اصلی آنرا به نحو ذیل تغییر دهید:
در اینجا از روش دیگری برای دریافت اطلاعات از وب استفاده کردهایم. با استفاده از امکانات HttpWebRequest، کوئریهای پیشرفتهتری را میتوان تهیه کرد. برای مثال میتوان نوع متد را به HEAD تنظیم نمود؛ تا صرفا مقادیر هدر آدرس درخواستی از سرور، دریافت شوند.
همچنین در این مثال از متد غیرهمزمان BeginGetResponse نیز استفاده شدهاست. در این نوع API خاص، کار با BeginGetResponse آغاز شده و سپس در callback نهایی توسط EndGetResponse، نتیجهی عملیات به دست میآید.
اگر برنامه را اجرا کنید، با استثنای زیر مواجه خواهید شد:
علت اینجا است که asyncResult دریافتی، در تردی دیگر نسبت به ترد اصلی برنامه که UI را اداره میکند، اجرا میشود. یکی از راه حلهای این مشکل و انتقال اطلاعات به ترد اصلی برنامه، استفاده از Synchronization Context است:
SynchronizationContext.Current در اینجا چون در ابتدای متد دریافت اطلاعات اجرا میشود، به ترد UI، یا ترد اصلی برنامه اشاره میکند. به همین جهت این زمینه را نباید داخل Async callback دریافت کرد؛ زیرا ترد جاری آن، ترد UI مدنظر ما نیست. سپس همانطور که ملاحظه میکنید، توسط متد Post آن میتوان اطلاعات را در زمینهی تردی که SynchronizationContext به آن اشاره میکند اجرا کرد.
برای درک بهتر آن، سه break point را پیش از متد BeginGetResponse، داخل Async calback و داخل delegate متد Post قرار دهید. پس از اجرای برنامه، از منوی دیباگ در VS.NET گزینهی Windows و سپس Threads را انتخاب کنید.
در اینجا همانطور که مشخص است، کد داخل delegate تعریف شده، در ترد اصلی برنامه اجرا میشود و نه یکی از Worker threadهای ثانویه.
هر چند استفاده از متدهای تو در تو و lambda syntax، نیاز به تعریف چندین متد جداگانه را برطرف کردهاست، اما باز هم کد سادهای به نظر نمیرسد. در سی شارپ 5، برای مدیریت بهتر تمام مشکلات یاد شده، پشتیبانی توکاری از اعمال غیرهمزمان، به هستهی زبان اضافه شدهاست.
Syntax ابتدایی یک متد Async
در ابتدا کلاس و متد Async زیر را در نظر بگیرید:
شیوهی نگارش آن بر اساس راهنمای نوشتن برنامههای Async یا Task asynchronous programming model یا به اختصار TAP است:
- در مدل برنامه نویسی TAP، متدهای غیرهمزمان باید یک Task را بازگشت دهند؛ یا نمونهی جنریک آنرا. البته کامپایلر، async void را نیز پشتیبانی میکند ولی در قسمتهای بعدی بررسی خواهیم کرد که چرا استفاده از آن مشکلزا است و باید از آن پرهیز شود.
- همچنین مطابق TAP، اینگونه متدها باید به پسوند Async ختم شوند تا استفاده کننده در حین کار با Intellisense، بتواند آنها را از متدهای معمولی سریعتر تشخیص دهد.
- از واژهی کلیدی async نیز استفاده میگردد تا کامپایلر از وجود اعمال غیر همزمان مطلع گردد.
- await به کامپایلر میگوید، عبارت پس از من، یک وظیفهی غیرهمزمان است و ادامهی کدهای نوشته شده، تنها زمانی باید اجرا شوند که عملیات غیرهمزمان معرفی شده، تکمیل گردد.
در متد DoWorkAsync، ابتدا به اندازهای مشخص توقف حاصل شده و سپس سطر بعدی یعنی Console.WriteLine اجرا میشود.
یک اشتباه عمومی! استفاده از واژههای کلیدی async و await متد شما را async نمیکنند.
برخلاف تصور ابتدایی از بکارگیری واژههای کلیدی async و await، این کلمات نحوهی اجرای متد شما را async نمیکنند. این کلمات صرفا برای تشکیل متدهایی که هم اکنون غیرهمزمان هستند، مفید میباشند. برای توضیح بیشتر آن به مثال ذیل دقت کنید:
در این متد با استفاده از Task.Delay، انجام یک عملیات طولانی شبیه سازی شدهاست؛ مثلا دریافت یک عدد یا نتیجه از یک وب سرویس. سپس در نهایت، عددی را بازگشت داده است. برای بازگشت یک خروجی double، در اینجا از نمونهی جنریک Task استفاده شدهاست.
در ادامه برای استفاده از آن خواهیم داشت:
خروجی این متد تنها زمانی بازگشت داده میشود که نتایج leftOperand و rightOperand از وب سرویس فرضی، دریافت شده باشند و در اختیار مصرف کننده قرارگیرند. بنابراین همانطور که ملاحظه میکنید از واژهی کلیدی await جهت تشکیل یک عملیات غیرهمزمان و مدیریت سادهتر کدهای نهایی، شبیه به کدهای معمولی همزمان استفاده شدهاست.
در کدهای همزمان متداول، سطر اول ابتدا انجام میشود و بعد سطر دوم و الی آخر. با استفاده از واژهی کلیدی await یک چنین عملکردی را با اعمال غیرهمزمان خواهیم داشت. پیش از این برای مدیریت اینگونه اعمال از یک سری callback و یا رخداد استفاده میشد. برای مثال ابتدا عملیات همزمانی شروع شده و سپس نتیجهی آن در یک روال رخداد گردان جایی در کدهای برنامه دریافت میشد (مانند مثال ابتدای بحث). اکنون تصور کنید که قصد داشتید جمع نهایی حاصل دو عملیات غیرهمزمان را از دو روال رخدادگردان جدا از هم، جمع آوری کرده و بازگشت دهید. هرچند اینکار غیرممکن نیست، اما حاصل کار به طور قطع آنچنان زیبا نبوده و قابلیت نگهداری پایینی دارد. واژهی کلیدی await، انجام اینگونه امور غیرهمزمان را طبیعی و همزمان جلوه میدهد. به این ترتیب بهتر میتوان بر روی منطق و الگوریتمهای مورد استفاده تمرکز داشت، تا اینکه مدام درگیر مکانیک اعمال غیرهمزمان بود.
امکان استفاده از واژهی کلیدی await در هر جایی از کدها وجود دارد. برای نمونه در مثال زیر، برای ترکیب دو عملیات غیرهمزمان، از await در حین تشکیل عملیات ضرب نهایی، دقیقا در جایی که مقدار متد باید بازگشت داده شود، استفاده شدهاست:
اگر await را از این مثال حذف کنیم، خطای کامپایل زیر را دریافت خواهیم کرد:
خروجی متد GetSumAsync صرفا یک Task است و نه یک عدد. پس از استفاده از await، عملیات آن انجام شده و بازگشت داده میشود.
اگر متد DownloadString همزمان ابتدای بحث را نیز بخواهیم تبدیل به نمونهی async سیشارپ 5 کنیم، میتوان از متد الحاقی جدید آن به نام DownloadStringTaskAsync کمک گرفت:
نکتهی مهم این کد علاوه بر ساده سازی اعمال غیر همزمان، برای استفاده از نتیجهی نهایی آن، نیازی به SynchronizationContext معرفی شده در تاریخچهی ابتدای بحث نیست. نتیجهی دریافتی از آن در ترد اصلی برنامه تحویل داده شده و به سادگی قابل استفاده است.
سؤال: آیا استفاده از await نیز ترد جاری را قفل میکند؟
اگر به کدها دقت کنید، استفاده از await به معنای صبر کردن تا پایان عملیات async است. پس اینطور به نظر میرسد که در اینجا نیز ترد اصلی، همانند قبل قفل شدهاست.
اگر این متد را اجرا کنید (در آن await بکار نرفته)، بلافاصله خروجی ذیل را مشاهده خواهید کرد:
به این معنا که در اصل، همانند سایر روشهای async موجود از دات نت یک، در اینجا نیز فراخوانی متد async ترد اصلی را بلافاصله آزاد میکند و ترد آنرا قفل نخواهد کرد. استفاده از await نیز عملکرد کدها را تغییر نمیدهد. تنها کامپایلر در پشت صحنه همان کدهای لازم جهت مدیریت روالهای رخدادگردان و callbackها را تولید میکند، به نحوی که صرفا نحوهی کدنویسی ما همزمان به نظر میرسد، اما در پشت صحنه، نحوهی اجرای آن غیرهمزمان است.
برنامههای Async و نگارشهای مختلف دات نت
شاید در ابتدا به نظر برسد که قابلیتهای جدید async و await صرفا متعلق هستند به دات نت 4.5 به بعد؛ اما خیر. اگر کامپایلری را داشته باشید که از این واژههای کلیدی را پشتیبانی کند، امکان استفاده از آنها را با دات نت 4 نیز خواهید داشت. برای این منظور تنها کافی است از VS 2012 به بعد استفاده نمائید. سپس در کنسول پاورشل نیوگت دستور ذیل را اجرا نمائید (فقط برای برنامههای دات نت 4 البته):
این روال متداول VS.NET بوده است تا به امروز. برای مثال اگر VS 2010 را نصب کنید و سپس یک برنامهی دات نت 3.5 را ایجاد کنید، امکان استفادهی کامل از تمام امکانات سیشارپ 4، مانند آرگومانهای نامدار و یا مقادیر پیش فرض آرگومانها را در یک برنامهی دات نت 3.5 نیز خواهید داشت. همین نکته در مورد async نیز صادق است. VS 2012 (یا نگارشهای جدیدتر) را نصب کنید و سپس یک پروژهی دات نت 4 را آغاز کنید. امکان استفاده از async و await را خواهید داشت. البته در این حالت دسترسی به متدهای الحاقی جدید را مانند DownloadStringTaskAsync نخواهید داشت. برای رفع این مشکل باید بستهی Microsoft.Bcl.Async را نیز توسط نیوگت نصب کنید.
دات نت فریم ورک، از زمان ارائه نگارش یک آن، از اعمال غیرهمزمان و API خاص آن پشتیبانی میکردهاست. همچنین این مورد یکی از ویژگیهای Win32 نیز میباشد. نوشتن کدهای همزمان متداول بسیار ساده است. در این نوع کدها هر عملیات خاص، پس از پایان عملیات قبلی انجام میشود.
public string TestNoneAsync() { var webClient = new WebClient(); return webClient.DownloadString("http://www.google.com"); }
این مورد همچنین در برنامههای سمت سرور نیز حائز اهمیت است. با قفل شدن تعداد زیادی ترد در حال اجرا، عملا قدرت پاسخدهی سرور نیز کاهش مییابد. بنابراین در این نوع موارد، برنامههای چند ریسمانی هرچند در سمت کلاینت ممکن است مفید واقع شوند و برای مثال ترد UI را آزاد کنند، اما اثر آنچنانی بر روی برنامههای سمت سرور ندارند. زیرا در آنها میتوان هزاران ترد را ایجاد کرد که همگی دارای کدهای اصطلاحا blocking باشند. برای حل این مساله استفاده از API غیرهمزمان توصیه میشود.
برای نمونه کلاس WebClient توکار دات نت، دارای متدی به نام DownloadStringAsync نیز میباشد. این متد به محض فراخوانی، ترد جاری را آزاد میکند. به این معنا که فراخوانی آن سبب توقف ترد جاری برای دریافت نتیجهی دریافت اطلاعات از وب نمیشود. به این نوع API، یک Asynchronous API گفته میشود؛ زیرا با سایر کدهای نوشته شده، هماهنگ و همزمان اجرا نمیشود.
هر چند این کد جدید مشکل عدم پاسخ دهی برنامه را برطرف میکند، اما مشکل دیگری را به همراه دارد؛ چگونه باید حاصل عملیات آنرا پس از پایان کار دریافت کرد؟ چگونه باید خطاها و مشکلات احتمالی را مدیریت کرد؟
برای مدیریت این مساله، رخدادی به نام DownloadStringCompleted تعریف شدهاست. روال رویدادگردان آن پس از پایان کار دریافت اطلاعات از وب، فراخوانی میگردد.
public void TestAsync() { var webClient = new WebClient(); webClient.DownloadStringAsync(new Uri("http://www.google.com")); webClient.DownloadStringCompleted += webClientDownloadStringCompleted; } void webClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { // use e.Result }
مشکل! ما سادگی یک عملیات همزمان را از دست دادیم. متد TestNoneAsync از لحاظ پیاده سازی و همچنین خواندن و نگهداری آن در طول زمان، بسیار سادهتر است از نمونهی TestAsync نوشته شده. در کدهای غیرهمزمان فوق، یک متد ساده، به دو متد مجزا خرد شدهاست و نتیجهی نهایی، درون یک روال رخدادگردان بدست میآید.
به این مدل، EAP یا Event based asynchronous pattern نیز گفته میشود. EAP در دات نت 2 معرفی شد. روالهای رخدادگردان در این حالت، در ترد اصلی برنامه اجرا میشوند. اما اگر به حالت اصلی اعمال غیرهمزمان موجود از دات نت یک کوچ کنیم، اینطور نیست. در WinForms و WPF برای به روز رسانی رابط کاربری نیاز است اطلاعات دریافت شده در همان تردی که رابط کاربری ایجاد شده است، تحویل گرفته شده و استفاده شوند. در غیراینصورت استثنایی صادر شده و برنامه خاتمه مییابد.
آشنایی با Synchronization Context
ابتدا یک برنامهی WinForms ساده را آغاز کرده و یک دکمهی جدید را به نام btnGetInfo و یک تکست باکس را به نام txtResults، به آن اضافه کنید. سپس کدهای فرم اصلی آنرا به نحو ذیل تغییر دهید:
using System; using System.Linq; using System.Net; using System.Windows.Forms; namespace Async02 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void btnGetInfo_Click(object sender, EventArgs e) { var req = (HttpWebRequest)WebRequest.Create("http://www.google.com"); req.Method = "HEAD"; req.BeginGetResponse( asyncResult => { var resp = (HttpWebResponse)req.EndGetResponse(asyncResult); var headersText = formatHeaders(resp.Headers); txtResults.Text = headersText; }, null); } private string formatHeaders(WebHeaderCollection headers) { var headerString = headers.Keys.Cast<string>() .Select(header => string.Format("{0}:{1}", header, headers[header])); return string.Join(Environment.NewLine, headerString.ToArray()); } } }
همچنین در این مثال از متد غیرهمزمان BeginGetResponse نیز استفاده شدهاست. در این نوع API خاص، کار با BeginGetResponse آغاز شده و سپس در callback نهایی توسط EndGetResponse، نتیجهی عملیات به دست میآید.
اگر برنامه را اجرا کنید، با استثنای زیر مواجه خواهید شد:
An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code Additional information: Cross-thread operation not valid: Control 'txtResults' accessed from a thread other than the thread it was created on.
private void btnGetInfo_Click(object sender, EventArgs e) { var sync = SynchronizationContext.Current; var req = (HttpWebRequest)WebRequest.Create("http://www.google.com"); req.Method = "HEAD"; req.BeginGetResponse( asyncResult => { var resp = (HttpWebResponse)req.EndGetResponse(asyncResult); var headersText = formatHeaders(resp.Headers); sync.Post(delegate { txtResults.Text = headersText; }, null); }, null); }
برای درک بهتر آن، سه break point را پیش از متد BeginGetResponse، داخل Async calback و داخل delegate متد Post قرار دهید. پس از اجرای برنامه، از منوی دیباگ در VS.NET گزینهی Windows و سپس Threads را انتخاب کنید.
در اینجا همانطور که مشخص است، کد داخل delegate تعریف شده، در ترد اصلی برنامه اجرا میشود و نه یکی از Worker threadهای ثانویه.
هر چند استفاده از متدهای تو در تو و lambda syntax، نیاز به تعریف چندین متد جداگانه را برطرف کردهاست، اما باز هم کد سادهای به نظر نمیرسد. در سی شارپ 5، برای مدیریت بهتر تمام مشکلات یاد شده، پشتیبانی توکاری از اعمال غیرهمزمان، به هستهی زبان اضافه شدهاست.
Syntax ابتدایی یک متد Async
در ابتدا کلاس و متد Async زیر را در نظر بگیرید:
using System; using System.Threading.Tasks; namespace Async01 { public class AsyncExample { public async Task DoWorkAsync(int parameter) { await Task.Delay(parameter); Console.WriteLine(parameter); } } }
- در مدل برنامه نویسی TAP، متدهای غیرهمزمان باید یک Task را بازگشت دهند؛ یا نمونهی جنریک آنرا. البته کامپایلر، async void را نیز پشتیبانی میکند ولی در قسمتهای بعدی بررسی خواهیم کرد که چرا استفاده از آن مشکلزا است و باید از آن پرهیز شود.
- همچنین مطابق TAP، اینگونه متدها باید به پسوند Async ختم شوند تا استفاده کننده در حین کار با Intellisense، بتواند آنها را از متدهای معمولی سریعتر تشخیص دهد.
- از واژهی کلیدی async نیز استفاده میگردد تا کامپایلر از وجود اعمال غیر همزمان مطلع گردد.
- await به کامپایلر میگوید، عبارت پس از من، یک وظیفهی غیرهمزمان است و ادامهی کدهای نوشته شده، تنها زمانی باید اجرا شوند که عملیات غیرهمزمان معرفی شده، تکمیل گردد.
در متد DoWorkAsync، ابتدا به اندازهای مشخص توقف حاصل شده و سپس سطر بعدی یعنی Console.WriteLine اجرا میشود.
یک اشتباه عمومی! استفاده از واژههای کلیدی async و await متد شما را async نمیکنند.
برخلاف تصور ابتدایی از بکارگیری واژههای کلیدی async و await، این کلمات نحوهی اجرای متد شما را async نمیکنند. این کلمات صرفا برای تشکیل متدهایی که هم اکنون غیرهمزمان هستند، مفید میباشند. برای توضیح بیشتر آن به مثال ذیل دقت کنید:
public async Task<double> GetNumberAsync() { var generator = new Random(); await Task.Delay(generator.Next(1000)); return generator.NextDouble(); }
در ادامه برای استفاده از آن خواهیم داشت:
public async Task<double> GetSumAsync() { var leftOperand = await GetNumberAsync(); var rightOperand = await GetNumberAsync(); return leftOperand + rightOperand; }
در کدهای همزمان متداول، سطر اول ابتدا انجام میشود و بعد سطر دوم و الی آخر. با استفاده از واژهی کلیدی await یک چنین عملکردی را با اعمال غیرهمزمان خواهیم داشت. پیش از این برای مدیریت اینگونه اعمال از یک سری callback و یا رخداد استفاده میشد. برای مثال ابتدا عملیات همزمانی شروع شده و سپس نتیجهی آن در یک روال رخداد گردان جایی در کدهای برنامه دریافت میشد (مانند مثال ابتدای بحث). اکنون تصور کنید که قصد داشتید جمع نهایی حاصل دو عملیات غیرهمزمان را از دو روال رخدادگردان جدا از هم، جمع آوری کرده و بازگشت دهید. هرچند اینکار غیرممکن نیست، اما حاصل کار به طور قطع آنچنان زیبا نبوده و قابلیت نگهداری پایینی دارد. واژهی کلیدی await، انجام اینگونه امور غیرهمزمان را طبیعی و همزمان جلوه میدهد. به این ترتیب بهتر میتوان بر روی منطق و الگوریتمهای مورد استفاده تمرکز داشت، تا اینکه مدام درگیر مکانیک اعمال غیرهمزمان بود.
امکان استفاده از واژهی کلیدی await در هر جایی از کدها وجود دارد. برای نمونه در مثال زیر، برای ترکیب دو عملیات غیرهمزمان، از await در حین تشکیل عملیات ضرب نهایی، دقیقا در جایی که مقدار متد باید بازگشت داده شود، استفاده شدهاست:
public async Task<double> GetProductOfSumAsync() { var leftOperand = GetSumAsync(); var rightOperand = GetSumAsync(); return await leftOperand * await rightOperand; }
Operator '*' cannot be applied to operands of type 'System.Threading.Tasks.Task<double>' and 'System.Threading.Tasks.Task<double>'
اگر متد DownloadString همزمان ابتدای بحث را نیز بخواهیم تبدیل به نمونهی async سیشارپ 5 کنیم، میتوان از متد الحاقی جدید آن به نام DownloadStringTaskAsync کمک گرفت:
public async Task<string> DownloadAsync() { var webClient = new WebClient(); return await webClient.DownloadStringTaskAsync("http://www.google.com"); }
سؤال: آیا استفاده از await نیز ترد جاری را قفل میکند؟
اگر به کدها دقت کنید، استفاده از await به معنای صبر کردن تا پایان عملیات async است. پس اینطور به نظر میرسد که در اینجا نیز ترد اصلی، همانند قبل قفل شدهاست.
public void TestDownloadAsync() { Debug.WriteLine("Before DownloadAsync"); DownloadAsync(); Debug.WriteLine("After DownloadAsync"); }
Before DownloadAsync After DownloadAsync
برنامههای Async و نگارشهای مختلف دات نت
شاید در ابتدا به نظر برسد که قابلیتهای جدید async و await صرفا متعلق هستند به دات نت 4.5 به بعد؛ اما خیر. اگر کامپایلری را داشته باشید که از این واژههای کلیدی را پشتیبانی کند، امکان استفاده از آنها را با دات نت 4 نیز خواهید داشت. برای این منظور تنها کافی است از VS 2012 به بعد استفاده نمائید. سپس در کنسول پاورشل نیوگت دستور ذیل را اجرا نمائید (فقط برای برنامههای دات نت 4 البته):
PM> Install-Package Microsoft.Bcl.Async
اشتراکها
موتور شبیه سازی فیزیک در محیط 2 بعدی
box2d یک کتاب خانه رایگان جهت شبیه سازی فیزیک 2 بعدی تحت مجوز zlib میباشد.
نسخهی اصلی آن به زبان c++ توسعه داده شده، اما پورت هایی برای زبانهای دیگر از جمله JavaScript,C#,Java,.... ارائه شده است.
یک مثال آنلاین که از نسخهی JavaScript استفاده میکند در اینجا
نظرات مطالب
خلاصهای کوتاه در مورد WinRT
معنای WinRT برای برنامه نویسهای دات نت چیست؟
WinRT ، دات نت نیست. برای درک این موضوع باید CLR و دات نت فریم ورک را از هم جدا کرد. برنامههای دات نت نوشته شده برای WinRT برفراز CLR اجرا میشوند اما از دات نت فریم ورک استفاده نمیکنند. بجای آن از توانمندیهای مشابه موجود در WinRT ، در پشت صحنه استفاده خواهند کرد.
در اینجا برنامههای C++ + XAML بدون دخالت CLR و مستقیما برفراز WinRT کار خواهند کرد. برنامههای سبک مترو HTML/CSS/JavaScript هم به همین صورت متکی به CLR نیستند و مستقیما برفراز WinRT اجرا میشوند.
WinRT فقط قادر است برنامههای سبک مترو ویندوز 8 را هاست کند. اگر نیاز دارید سیستم عاملهای قدیمی را پشتیبانی کنید یا اینکه اصلا کارتان ساخت برنامههای سمت کاربر و دسکتاپ نیست، اصلا این تغییرات به کار شما مرتبط نخواهند شد.
الگوی اصلی تعاملی برنامههای سبک مترو با تمرکز بر برنامههای لمسی (touch focused) و مبتنی بر محتوا (content-oriented) است. به این معنا که تمام برنامههای تجاری موجود که دارای 10 ها و صدها صفحهی ورود اطلاعات هستند، اصلا برای این نوع سبک ارائه محتوا طراحی نشدهاند و نخواهند شد. کاربردهای مهم این سبک، استفاده از آن در برنامههای مخصوص تمام صفحه tablets است یا حداکثر در حد داشبردهای ارائه خلاصه گزارشات یک برنامه میتوانند اهمیت داشته باشند. بنابراین اگر کارتان در حیطهی ساخت برنامههای مخصوص tablets و برنامههای لمسی قرار نمیگیرد، کماکان به ساخت برنامههای دسکتاپ نوشته شده با WPF/WinForms میتوانید مشغول باشید.
انتقال قسمت عمدهای از برنامههای موجودWPF و یا Silverlight مبتنی بر الگوهایی مانند MVVM و امثال آن با توجه به عدم گره خوردگی آنها به لایه نمایشی ، به WinRT میسر است.
در سمت سرور، تمرکز اصلی هنوز همان دات نت فریم ورک است. قرار نیست ASP.NET یا WCF و سایر مؤلفههای اصلی دات نت به WinRT منتقل شوند. حتی اگر WinRT به سرورهای بعدی هم راه پیدا کند در حد همان لایه نمایشی مترو است و تاثیری بر روی سرویسهای NT با دسترسی بالای ویندوز ، نخواهد داشت. مثلا قرار نیست SQL Server را با WinRT پیاده سازی کنند.
WinRT ، دات نت نیست. برای درک این موضوع باید CLR و دات نت فریم ورک را از هم جدا کرد. برنامههای دات نت نوشته شده برای WinRT برفراز CLR اجرا میشوند اما از دات نت فریم ورک استفاده نمیکنند. بجای آن از توانمندیهای مشابه موجود در WinRT ، در پشت صحنه استفاده خواهند کرد.
در اینجا برنامههای C++ + XAML بدون دخالت CLR و مستقیما برفراز WinRT کار خواهند کرد. برنامههای سبک مترو HTML/CSS/JavaScript هم به همین صورت متکی به CLR نیستند و مستقیما برفراز WinRT اجرا میشوند.
WinRT فقط قادر است برنامههای سبک مترو ویندوز 8 را هاست کند. اگر نیاز دارید سیستم عاملهای قدیمی را پشتیبانی کنید یا اینکه اصلا کارتان ساخت برنامههای سمت کاربر و دسکتاپ نیست، اصلا این تغییرات به کار شما مرتبط نخواهند شد.
الگوی اصلی تعاملی برنامههای سبک مترو با تمرکز بر برنامههای لمسی (touch focused) و مبتنی بر محتوا (content-oriented) است. به این معنا که تمام برنامههای تجاری موجود که دارای 10 ها و صدها صفحهی ورود اطلاعات هستند، اصلا برای این نوع سبک ارائه محتوا طراحی نشدهاند و نخواهند شد. کاربردهای مهم این سبک، استفاده از آن در برنامههای مخصوص تمام صفحه tablets است یا حداکثر در حد داشبردهای ارائه خلاصه گزارشات یک برنامه میتوانند اهمیت داشته باشند. بنابراین اگر کارتان در حیطهی ساخت برنامههای مخصوص tablets و برنامههای لمسی قرار نمیگیرد، کماکان به ساخت برنامههای دسکتاپ نوشته شده با WPF/WinForms میتوانید مشغول باشید.
انتقال قسمت عمدهای از برنامههای موجودWPF و یا Silverlight مبتنی بر الگوهایی مانند MVVM و امثال آن با توجه به عدم گره خوردگی آنها به لایه نمایشی ، به WinRT میسر است.
در سمت سرور، تمرکز اصلی هنوز همان دات نت فریم ورک است. قرار نیست ASP.NET یا WCF و سایر مؤلفههای اصلی دات نت به WinRT منتقل شوند. حتی اگر WinRT به سرورهای بعدی هم راه پیدا کند در حد همان لایه نمایشی مترو است و تاثیری بر روی سرویسهای NT با دسترسی بالای ویندوز ، نخواهد داشت. مثلا قرار نیست SQL Server را با WinRT پیاده سازی کنند.
a habit tracker app that is free to use on 6 platforms: Web browser, Windows, Linux, Android, iOS and macOS. Approximately 98% of the programming code is the same for all 6 platforms. There are 3 different projects that use the shared code: - a Blazor WASM project for the PWA - a MAUI Blazor project for Windows, Android, iOS and macOS - a Photino Blazor project for Linux
You’ve built a React web app and would love to bring it to iOS and Android. That means you have to learn React Native first, right? As it turns out, there’s an easier way to deploy to mobile. With Capacitor, a new native runtime for web apps, you can deploy any React-based web app as a PWA, iOS, or Android app - all from the same codebase.
مطالب
بازسازی LocalDb
LocalDb یک نسخهی کوچک شده از SQL Express 2012 است که نیازی به سرویس ندارد که سبب سهولت استفاده از آن را در پروژههای Visual Studio فراهم میآورد و یک ویژگی بسیار خوب برای برنامههایی است که نیاز به استفاده از کلیهی امکانات مهیای SQL Server را ندارند. یکی دیگر از ویژگیهای آن استفاده در محیط هایی است که اطمینانی از نصب بودن SQL Express در آنها نداریم.
در صورتیکه خطای زیر نمایش داده شد، مراحل بعد را انجام میدهیم.
هنگامیکه خطای فوق نمایش داده شده، باید ابتدا LocalDb را حذف و مجددا آن را ساخت.
دستور فوق عملیات حذف را انجام میدهد.
تا اینجا کلیهی اقدامات لازم جهت راه اندازی مجدد LocalDb انجام شد و هم اکنون میتوانید از آن استفاده نمایید.
در حین توسعهی نرم افزار ممکن است استفاده از LocalDb امکان پذیر نباشد و خللی در کارکرد آن ایجاد شده باشد و عملا استفاده از آن میسر نشود. اتفاقی که برای من روز گذشته رخ داد و مجبور شدم LocalDb را مجددا نصب نمایم ولی همچنان مشکل وجود داشت. بعد از جستجو به این لینک و این لینک رسیدم که جمع بندی آن بشرح زیر است:
ابتدا command prompt را اجرا مینماییم (ترجیحا بصورت Administrator اجرا شود) سپس به محلی که Instanceهای LocalDb قرار دارد
(localappdata\Microsoft\Microsoft SQL Server Local DB\Instances\v11.0) رفته و دستور زیر را صادر مینماییم:
sqllocaldb create "v11.0"
Creation of LocalDB instance "v11.0" with version 11.0 failed because of the following error: LocalDB instance is corrupted. See the Windows Application event log for event details.
sqllocaldb delete "v11.0"
و دستور زیر مجددا یک وهلهی نسخه 11.0 بنام"v11.0" را از LocalDb ایجاد میکند.
sqllocaldb create "v11.0"