میدانید که در پروژه و نرم افزارهایی که توسعه داده میشوند بعضی مواقع مشاهده میکنیم قسمتی از برنامه نیاز به زمان یا پردازش بیشتری دارد تا عملیات خود را به اتمام برساند و رابط کاربری (User Interface) برنامه در این حین منتظر میماند و یا به اصطلاح (Freeze) میشود تا یک پردازش طولانی به اتمام برسد و بعد رابط کاربری به کار خود ادامه میدهد و بعضی مواقع پنجره Windows explorer is not responding را مشاهده کردهاید که با کلیک بر روی Close the program از آن گذر میکنیم. در حالی که برنامه ما باید حالت Responsive داشته باشد و نباید برنامه با یکچنین مواردی روبه رو شود.
ساختمان Thread :
تمامی اشیاء (Objects) در این مدل به دو گروه تقسیم بندی میشوند:
STA شامل فقط یک Thread میباشد و تمامی اشیاء در این ساختمان فقط میتوانند متدی را که در این Thread صدا زده میشود دریافت کنند؛ یا به عبارتی دیگر هنگامیکه شیء به Thread ای متصل میشود دیگر شما قادر نخواهید بود اشیاء UI را به صورت مستقیم و یا از طریق Threadهای دیگر تغییر دهید. لازم به ذکر است WPF از مدل STA پشتیبانی میکند که شامل نکات زیر میباشد:
1. یک Thread در کل برنامه اجرا میشود و شامل همه Objectهای WPF میباشد.
2. عناصر یا المانهای WPF، قابلیت تنظیم Thread Affinity را دارند. منظور این میباشدکه Threadها نمیتوانند با یکدیگر ارتباط برقرار کنند.
3. اشیاء WPF که قابلیت Thread Affinityرا دارند، از یک Object Dispatcher مشتق میشوند.
4. اشیاء WPF متعلق به Thread ای میباشند که توسط آن ایجاد شده است و Thread دیگر قادر به دسترسی مستقیم به این اشیاء نمیباشد.
ساختمان Multi-Threaded Apartment (MTA) :
MTA شامل یک یا چندین Thread میباشد. تمامی اشیاء در این مدل میتوانند از هر Thread ای فراخوانی شوند. در حقیقت رجیسترهای CPU و Ram که در اختیار برنامه قرار داده شده تکه تکه میشوند و برنامه ما تعداد Threadهای مورد نظر را اجرا میکند و یکی از راه حلهای بر طرف کردن اینکه رابط کاربری ما در حالت انتظار باقی نماند استفاده از پردازش کارها بصورت غیر همزمان (Asynchronous) میباشد که در اصطلاح Multithreading نامیده میشود.
WPF Dispatcher :
WPF از مدل STA پشتیبانی میکند. زمانیکه برنامه WPF اجرا میشود، به طور خودکار یک Dispatcher Object ساخته میشود و متد Run صدا زده میشود و از آن برای آماده سازی صف پیامها استفاده میشود که مدیریت یک صف از کارها بر عهده آن است و کارهای UI را در یک صف FIFO اجرا میکند. WPF یک Dispatcher را برای UI Thread ایجاد میکند. بنابراین شما نمیتوانید یک dispatcher دیگر برای آن تعریف کنید.
نکته : دیاگرام زیر نمایش میهد که تمامی اشیاء WPF
از DispatcherObject مشتق شدهاند.
نکته : زمانیکه برنامه WPF اجرا میشود دو Thread ساخته میشود:
1. UI Thread (Main Thread)
2. Render Thread
UI Thread : مسئولیت تمامی ورودیهای کاربر، handle events, paints screen و اجرای کدهای برنامه را بر عهده دارد.
Render Thread : در Background اجرا میشود و برای Render صفحه نمایش WPF استفاده میشود.
نکته: همانطور که آشنا شدیم WPF نمیتواند UI Thread را از طریق یک Thread دیگر به روز رسانی کند و یا به عبارتی دیگر یک Thread نمیتواند بصورت مستقیم به اشیایی که توسط Thread دیگر ایجاد شده، دسترسی داشته باشد.
Dispatcher برای این کار دو متد را در اختیار ما قرار میدهد :
متد Invoke : یک Action یا Delegate را میگیرد و متد آنرا به صورت همزمان اجرا میکند. این مورد به این معنا است که تا زمانی که اجرای متد کامل نگردد، عملیاتی صورت نخواهد گرفت و یا به عبارتی دیگر فراخوان را تا زمانیکه زمانبندی به پایان برسد، در حالت مسدود نگهداری خواهد کرد.
مثال :
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Task.Factory.StartNew(() => { InvokeMethodExample(); }); } private void InvokeMethodExample() { Thread.Sleep(2000); Dispatcher.Invoke(() => { btn1.Content = "By Invoke"; }); } }
مثال :
public MainWindow() { InitializeComponent(); Task.Factory.StartNew(() => { BeginInvokeExample(); }); } private void BeginInvokeExample() { DispatcherOperation op = Dispatcher.BeginInvoke((Action)(() => { btn1.Content = "By BeginInvoke"; })); }
فعالسازی بررسی مشکلات قالبهای کامپوننتها
برای فعالسازی بررسی مشکلات قالبهای کامپوننتها، نیاز است به فایل تنظیمات کامپایلر TypeScript و یا همان tsconfig.json مراجعه کرد و سپس قسمت جدیدی را به آن به نام angularCompilerOptions، افزود:
{ "compilerOptions": { "experimentalDecorators": true, ... }, "angularCompilerOptions": { "fullTemplateTypeCheck": true, "preserveWhiteSpace": false, ... } }
- البته این خاصیت در حین استفادهی از یکی از دستورات ng serve --aot و یا ng build --prod انتخاب میشود.
- مقدار این پرچم در نگارشهای 5x به صورت پیشفرض به false تنظیم شدهاست؛ اما در نگارش 6 آن به true تنظیم خواهد شد. بنابراین بهتر است از هم اکنون کار با آنرا شروع کنید.
یک مثال: بررسی خاصیت fullTemplateTypeCheck
فرض کنید اینترفیس یک مدل را به صورت زیر تعریف کردهاید که فقط دارای خاصیت name است:
export interface PonyModel { name: string; }
import { PonyModel } from "./pony"; @Component({ selector: "app-detect-common-errors-test", templateUrl: "./detect-common-errors-test.component.html", styleUrls: ["./detect-common-errors-test.component.css"] }) export class DetectCommonErrorsTestComponent implements OnInit { ponyModel: PonyModel = { name: "Pony1" };
<p>Hello {{ponyModel.age}}
در این حالت اگر fullTemplateTypeCheck فعال شده باشد و دستور ng build --prod را صادر کنیم، به خروجی ذیل خواهیم رسید:
\detect-common-errors-test.component.html(5,4): : Property 'age' does not exist on type 'PonyModel'.
برای اینکه بتوانید به حداکثر کارآیی این قابلیت برسید، بهتر است گزینهی strict را در تنظیمات کامپایلر TypeScript روشن کنید و خودتان را به کار با نوعهای نال نپذیر عادت دهید. به این ترتیب میتوانید تعداد خطاهای احتمالی بیشتری را پیش از موعد و پیش از وقوع آنها در زمان اجرا، در زمان کامپایل، پیدا و رفع کنید.
یک نکتهی تکمیلی
افزونهی Angular Language service نیز یک چنین قابلیتی را به همراه دارد (و حتی در نگارشهای پیش از 5 نیز قابل استفاده است).
- ثبت کننده اطلاعات
- کاربر عادی
- مدیر منطقه یک
- کاربر عادی
- تنظیم کننده گردش کار اداری
افزودن دکمهی ویرایش، به رکوردهای لیست اتاقها و نمایش جزئیات رکورد در حال ویرایش
تا اینجا کامپوننت Pages\HotelRoom\HotelRoomUpsert.razor دارای یک چنین مسیریابی است:
@page "/hotel-room/create"
@page "/hotel-room/create" @page "/hotel-room/edit/{Id:int}"
پس از تعریف مسیریابی دریافت پارامتر Id رکورد در حال ویرایش، نحوهی واکنش نشان دادن به آن در کامپوننت HotelRoomUpsert.razor به صورت زیر است:
@code { // ... [Parameter] public int? Id { get; set; } protected override async Task OnInitializedAsync() { if (Id.HasValue) { // Update Mode Title = "Update"; HotelRoomModel = await HotelRoomService.GetHotelRoomAsync(Id.Value); } else { // Create Mode HotelRoomModel = new HotelRoomDTO(); } } // ... }
- سپس میخواهیم در زمان بارگذاری کامپوننت جاری، اگر مقدار Id، تنظیم شده بود، تمام فیلدهای فرم متصل به شیء HotelRoomModel را به صورت خودکار بر اساس اطلاعات رکورد متناظر با آن در بانک اطلاعاتی، مقدار دهی اولیه کنیم.
<EditForm Model="HotelRoomModel" OnValidSubmit="HandleHotelRoomUpsert">
کار مقدار دهی اولیهی فیلدهای یک کامپوننت نیز باید در روال رویداد گردان OnInitializedAsync انجام شود که نمونهای از آن را در کدهای فوق مشاهده میکنید. در این مثال اگر Id مقدار داشته باشد، مقدار آنرا به متد GetHotelRoomAsync ارسال کرده و سپس شیء DTO دریافتی از آنرا به مدل فرم انتساب میدهیم تا فرم ویرایشی، قابل استفاده شود:
در ادامه برای ساده سازی رسیدن به مسیرهایی مانند hotel-room/edit/1، به کامپوننت Pages\HotelRoom\HotelRoomList.razor مراجعه کرده و در همان ردیفی که اطلاعات رکورد یک اتاق را نمایش میدهیم، لینکی را نیز به صفحهی ویرایش آن، اضافه میکنیم:
<tr> <td>@room.Name</td> <td>@room.Occupancy</td> <td>@room.RegularRate.ToString("c")</td> <td>@room.SqFt</td> <td> <NavLink href="@($"hotel-room/edit/{room.Id}")" class="btn btn-primary">Edit</NavLink> </td> </tr>
مدیریت ثبت اطلاعات ویرایش شدهی یک اتاق، در بانک اطلاعاتی
در حین تکمیل این قسمت میخواهیم پیامهایی مانند موفقیت آمیز بودن عملیات را نیز به کاربر نمایش دهیم. به همین جهت مراحل «Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت» را برای افزودن کتابخانهی معروف جاوا اسکریپتی Toastr طی میکنیم که شامل این قسمتها است:
- دریافت و نصب بستههای jquery و toastr
- اصلاح فایل Pages\_Host.cshtml برای افزودن مداخل فایلهای CSS و JS بستههای نصب شده
- تعریف فایل جدید wwwroot\js\common.js برای سادگی کار با توابع جاوا اسکریپتی toastr و افزودن مدخل آن به فایل Pages\_Host.cshtml
- تعریف متدهای الحاقی JSRuntimeExtensions ، جهت کاهش کدهای تکراری فراخوانی متدهای toastr و افزودن فضای نام آن به فایل Imports.razor_
جزئیات این موارد را در قسمت یازدهم این سری میتوانید مطالعه کنید و از تکرار آنها در اینجا صرفنظر میشود. همچنین کدهای تکمیل شدهی این قسمت را از انتهای مطلب جاری نیز میتوانید دریافت کنید.
همچنین پیش از تکمیل ادامهی کدهای ویرایش اطلاعات، نیاز است متد IsRoomUniqueAsync را به صورت زیر اصلاح کنیم:
namespace BlazorServer.Services { public interface IHotelRoomService { Task<bool> IsRoomUniqueAsync(string name, int roomId); // ... } } namespace BlazorServer.Services { public class HotelRoomService : IHotelRoomService { // ... public Task<bool> IsRoomUniqueAsync(string name, int roomId) { if (roomId == 0) { // Create Mode return _dbContext.HotelRooms .ProjectTo<HotelRoomDTO>(_mapperConfiguration) .AnyAsync(x => x.Name != name); } else { // Edit Mode return _dbContext.HotelRooms .ProjectTo<HotelRoomDTO>(_mapperConfiguration) .AnyAsync(x => x.Name != name && x.Id != roomId); } } } }
اکنون متد HandleHotelRoomUpsert کامپوننت Pages\HotelRoom\HotelRoomUpsert.razor جهت مدیریت ثبت و ویرایش اطلاعات به صورت زیر تغییر میکند:
// ... @inject IJSRuntime JsRuntime @code { // ... private async Task HandleHotelRoomUpsert() { var isRoomUnique = await HotelRoomService.IsRoomUniqueAsync(HotelRoomModel.Name, HotelRoomModel.Id); if (!isRoomUnique) { await JsRuntime.ToastrError($"The room name: `{HotelRoomModel.Name}` already exists."); return; } if (HotelRoomModel.Id != 0 && Title == "Update") { // Update Mode var updateResult = await HotelRoomService.UpdateHotelRoomAsync(HotelRoomModel.Id, HotelRoomModel); await JsRuntime.ToastrSuccess($"The `{HotelRoomModel.Name}` updated successfully."); } else { // Create Mode var createResult = await HotelRoomService.CreateHotelRoomAsync(HotelRoomModel); await JsRuntime.ToastrSuccess($"The `{HotelRoomModel.Name}` created successfully."); } NavigationManager.NavigateTo("hotel-room"); } }
- در ابتدا عدم تکراری بودن نام اتاق بررسی میشود:
- در آخر بر اساس Id مدل فرم، حالت ویرایش و یا ثبت اطلاعات را تشخیص میدهیم. البته Id مدل فرم، در حالت ثبت اطلاعات نیز صفر است؛ به علت فراخوانی ()HotelRoomModel = new HotelRoomDTO که سبب مقدار دهی Id آن با عدد پیشفرض صفر میشود. بنابراین صرفا بررسی Id مدل، کافی نیست و برای مثال میتوان عنوان تنظیم شدهی در متد OnInitializedAsync را نیز بررسی کرد.
- پس از تشخیص حالت ویرایش و یا ثبت، یکی از متدهای متناظر HotelRoom Service را مانند UpdateHotelRoomAsync و یا CreateHotelRoomAsync فراخوانی کرده و سپس پیامی را به کاربر نمایش داده و او را به صفحهی نمایش لیست اتاقها، هدایت میکنیم:
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-15.zip
خودکار کردن تعاریف DbSetها در EF Code first
اگر مدلها در چندین پروژه بودن چطور؟
مثلا به صورت ماژول هر ماژول مدل خود را دارد