اندازهی قلم متن
تخمین مدت زمان مطالعهی مطلب:
ده دقیقه
در ادامه قصد داریم از سرویس زیر که در قسمت قبل تکمیل شد، در یک برنامهی Blazor Server استفاده کنیم:
تعریف کامپوننتهای ابتدایی نمایش لیست اتاقها و ثبت و ویرایش آنها
در ابتدا کامپوننتهای خالی نمایش لیست اتاقها و همچنین فرم خالی ثبت و ویرایش آنها را به همراه مسیریابیهای مرتبط، ایجاد میکنیم. به همین جهت ابتدا داخل پوشهی Pages، پوشهی جدید HotelRoom را ایجاد کرده و فایل جدید HotelRoomList.razor را با محتوای ابتدایی زیر، به آن اضافه میکنیم.
این کامپوننت در مسیر hotel-room/ قابل دسترسی خواهد بود. بر این اساس، به کامپوننت Shared\NavMenu.razor مراجعه کرده و مدخل منوی آنرا تعریف میکنیم:
تا اینجا صفحهی ابتدایی نمایش لیست اتاقها، به همراه یک دکمهی افزودن اتاق جدید نیز هست. به همین جهت فایل جدید Pages\HotelRoom\HotelRoomUpsert.razor را به همراه مسیریابی hotel-room/create/ برای تعریف کامپوننت ابتدایی ثبت و ویرایش اطلاعات اتاقها، اضافه میکنیم:
- واژهی Upsert در مورد فرمی بکاربرده میشود که هم برای ثبت اطلاعات و هم برای ویرایش اطلاعات از آن استفاده میشود.
- NavLink تعریف شدهی در کامپوننت نمایش لیست اتاقها، به مسیریابی کامپوننت فوق اشاره میکند.
ایجاد فرم ثبت یک اتاق جدید
برای ثبت یک اتاق جدید نیاز است به مدل UI آن که همان HotelRoomDTO تعریف شدهی در قسمت قبل است، دسترسی داشت. به همین جهت در پروژهی BlazorServer.App، ارجاعی را به پروژهی BlazorServer.Models.csproj اضافه میکنیم:
سپس جهت سراسری اعلام کردن فضای نام آن، یک سطر زیر را به انتهای فایل BlazorServer.App\_Imports.razor اضافه میکنیم:
اکنون میتوانیم کامپوننت Pages\HotelRoom\HotelRoomUpsert.razor را به صورت زیر تکمیل کنیم:
توضیحات:
- در برنامههای Blazor، کامپوننت ویژهی EditForm را بجای تگ استاندارد form، مورد استفاده قرار میدهیم.
- این کامپوننت، مدل فرم را از فیلد HotelRoomModel که در قسمت کدها تعریف کردیم، دریافت میکند. کار آن تامین اطلاعات فیلدهای فرم است.
- سپس در EditForm تعریف شده، بجای المان استاندارد input، از کامپوننت InputText برای دریافت اطلاعات متنی استفاده میشود. با bind-value@ در قسمت چهارم این سری بیشتر آشنا شدیم و کار آن two-way data binding است. در اینجا هر اطلاعاتی که وارد میشود، سبب به روز رسانی خودکار مقدار خاصیت HotelRoomModel.Name میشود و برعکس.
یک نکته: در قسمت قبل، مدل UI را از نوع رکورد C# 9.0 و init only تعریف کردیم. رکوردها، با EditForm و two-way databinding آن سازگاری ندارند (bind-value@ در اینجا) و بیشتر برای کنترلرهای برنامههای Web API که یکبار قرار است کار وهله سازی آنها در زمان دریافت اطلاعات از کاربر صورت گیرد، مناسب هستند و نه با فرمهای پویای Blazor. به همین جهت به پروژهی BlazorServer.Models مراجعه کرده و نوع آنها را به کلاس و initها را به set معمولی تغییر میدهیم تا در فرمهای Blazor هم قابل استفاده شوند.
تا اینجا کامپوننت ثبت اطلاعات یک اتاق جدید، چنین شکلی را پیدا کردهاست:
تکمیل سایر فیلدهای فرم ورود اطلاعات اتاق
پس از تعریف فیلد ورود اطلاعات نام اتاق، سایر فیلدهای متناظر با HotelRoomDTO را نیز به صورت زیر به EditForm تعریف شده اضافه میکنیم که در اینجا از InputNumber برای دریافت اطلاعات عددی و از InputTextArea، برای دریافت اطلاعات متنی چندسطری استفاده شدهاست:
با این خروجی:
تعریف اعتبارسنجیهای فیلدهای یک فرم Blazor
در حین تعریف یک فرم، برای واکنش نشان دادن به دکمهی submit، میتوان رویداد OnSubmit را به کامپوننت EditForm اضافه کرد که سبب فراخوانی متدی در قسمت کدهای کامپوننت جاری خواهد شد؛ مانند فراخوانی متد HandleHotelRoomUpsert در مثال زیر:
هرچند HotelRoomDTO تعریف شده به همراه تعریف اعتبارسنجیهایی مانند Required است، اما اگر بر روی دکمهی submit کلیک کنیم، متد HandleHotelRoomUpsert فراخوانی میشود. یعنی روال رویدادگردان OnSubmit، صرفنظر از وضعیت اعتبارسنجی مدل فرم، همواره با submit فرم، اجرا میشود.
اگر این مورد، مدنظر نیست، میتوان بجای OnSubmit، از رویداد OnValidSubmit استفاده کرد. در این حالت اگر اعتبارسنجی مدل فرم با شکست مواجه شود، دیگر متد HandleHotelRoomUpsert فراخوانی نخواهد شد. همچنین در این حالت میتوان خطاهای اعتبارسنجی را نیز در فرم نمایش داد:
- در اینجا قسمتهای تغییر کرده را مشاهده میکنید که به همراه درج DataAnnotationsValidator و ValidationMessageها است.
- کامپوننت DataAnnotationsValidator، اعتبارسنجی مبتنی بر data annotations را مانند [Required]، در دامنهی دید یک EditForm فعال میکند.
- اگر خواستیم تمام خطاهای اعتبارسنجی را به صورت خلاصهای در بالای فرم نمایش دهیم، میتوان از کامپوننت ValidationSummary استفاده کرد.
- و یا اگر خواستیم خطاها را به صورت اختصاصیتری ذیل هر تکستباکس نمایش دهیم، میتوان از کامپوننت ValidationMessage کمک گرفت. خاصیت For آن از نوع <Expression<System.Func تعریف شدهاست که اجازهی تعریف strongly typed نام خاصیت در حال اعتبارسنجی را به صورتی که مشاهده میکنید، میسر میکند.
ثبت اولین اتاق هتل
در ادامه میخواهیم روال رویدادگردان HandleHotelRoomUpsert را مدیریت کنیم. به همین جهت نیاز به کار با سرویس IHotelRoomService ابتدای بحث خواهد بود. بنابراین در ابتدا به فایل BlazorServer.App\_Imports.razor مراجعه کرده و فضای نام سرویسهای برنامه را اضافه میکنیم:
اکنون امکان تزریق IHotelRoomService را که در قسمت قبل پیاده سازی و به سیستم تزریق وابستگیهای برنامه معرفی کردیم، پیدا میکنیم:
در اینجا در ابتدا، سرویس IHotelRoomService به کامپوننت جاری تزریق شده و سپس از متدهای IsRoomUniqueAsync و CreateHotelRoomAsync آن، جهت بررسی منحصربفرد بودن نام اتاق و ثبت نهایی اطلاعات مدل برنامه که به فرم جاری به صورت دو طرفهای متصل است، استفاده کردهایم. در نهایت پس از ثبت اطلاعات، کاربر به صفحهی نمایش لیست اتاقها، توسط سرویس توکار NavigationManager، هدایت میشود.
اگر پیشتر با ASP.NET Web Forms کار کرده باشید (اولین روش توسعهی برنامههای وب در دنیای دات نت)، مدل برنامه نویسی Blazor Server، بسیار شبیه به کار با وب فرمها است؛ البته بر اساس آخرین تغییرات دنیای دانت نت مانند برنامه نویسی async، کار با سرویسها، تزریق وابستگیهای توکار و غیره.
نمایش لیست اتاقهای ثبت شده
تا اینجا موفق شدیم اطلاعات یک مدل اعتبارسنجی شده را در بانک اطلاعاتی ثبت کنیم. مرحلهی بعد، نمایش لیست اطلاعات ثبت شدهی در بانک اطلاعاتی است. بنابراین به کامپوننت HotelRoomList.razor مراجعه کرده و آنرا به صورت زیر تکمیل میکنیم:
توضیحات:
- متد GetAllHotelRoomsAsync، لیست اتاقهای ثبت شده را بازگشت میدهد. البته خروجی آن از نوع <IAsyncEnumerable<HotelRoomDTO است که از زمان C# 8.0 ارائه شد و روش کار با آن اندکی متفاوت است. IAsyncEnumerableها را باید توسط await foreach پردازش کرد.
- همانطور که در مطلب بررسی چرخهی حیات کامپوننتها نیز عنوان شد، متدهای رویدادگران OnInitialized و نمونهی async آن برای دریافت اطلاعات از سرویسها طراحی شدهاند که در اینجا نمونهای از آنرا مشاهده میکنید.
- پس از تشکیل لیست اتاقها، حلقهی foreach (var room in HotelRooms) تعریف شده، ردیفهای آنرا در UI نمایش میدهد.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-14.zip
namespace BlazorServer.Services { public interface IHotelRoomService { Task<HotelRoomDTO> CreateHotelRoomAsync(HotelRoomDTO hotelRoomDTO); Task<int> DeleteHotelRoomAsync(int roomId); IAsyncEnumerable<HotelRoomDTO> GetAllHotelRoomsAsync(); Task<HotelRoomDTO> GetHotelRoomAsync(int roomId); Task<HotelRoomDTO> IsRoomUniqueAsync(string name); Task<HotelRoomDTO> UpdateHotelRoomAsync(int roomId, HotelRoomDTO hotelRoomDTO); } }
تعریف کامپوننتهای ابتدایی نمایش لیست اتاقها و ثبت و ویرایش آنها
در ابتدا کامپوننتهای خالی نمایش لیست اتاقها و همچنین فرم خالی ثبت و ویرایش آنها را به همراه مسیریابیهای مرتبط، ایجاد میکنیم. به همین جهت ابتدا داخل پوشهی Pages، پوشهی جدید HotelRoom را ایجاد کرده و فایل جدید HotelRoomList.razor را با محتوای ابتدایی زیر، به آن اضافه میکنیم.
@page "/hotel-room" <div class="row mt-4"> <div class="col-8"> <h4 class="card-title text-info">Hotel Rooms</h4> </div> <div class="col-3 offset-1"> <NavLink href="hotel-room/create" class="btn btn-info">Add New Room</NavLink> </div> </div> @code { }
<li class="nav-item px-3"> <NavLink class="nav-link" href="hotel-room"> <span class="oi oi-list-rich" aria-hidden="true"></span> Hotel Rooms </NavLink> </li>
تا اینجا صفحهی ابتدایی نمایش لیست اتاقها، به همراه یک دکمهی افزودن اتاق جدید نیز هست. به همین جهت فایل جدید Pages\HotelRoom\HotelRoomUpsert.razor را به همراه مسیریابی hotel-room/create/ برای تعریف کامپوننت ابتدایی ثبت و ویرایش اطلاعات اتاقها، اضافه میکنیم:
@page "/hotel-room/create" <h3>HotelRoomUpsert</h3> @code { }
- NavLink تعریف شدهی در کامپوننت نمایش لیست اتاقها، به مسیریابی کامپوننت فوق اشاره میکند.
ایجاد فرم ثبت یک اتاق جدید
برای ثبت یک اتاق جدید نیاز است به مدل UI آن که همان HotelRoomDTO تعریف شدهی در قسمت قبل است، دسترسی داشت. به همین جهت در پروژهی BlazorServer.App، ارجاعی را به پروژهی BlazorServer.Models.csproj اضافه میکنیم:
<Project Sdk="Microsoft.NET.Sdk.Web"> <ItemGroup> <ProjectReference Include="..\BlazorServer.Models\BlazorServer.Models.csproj" /> </ItemGroup> </Project>
@using BlazorServer.Models
@page "/hotel-room/create" <div class="row mt-2 mb-5"> <h3 class="card-title text-info mb-3 ml-3">@Title Hotel Room</h3> <div class="col-md-12"> <div class="card"> <div class="card-body"> <EditForm Model="HotelRoomModel"> <div class="form-group"> <label>Name</label> <InputText @bind-Value="HotelRoomModel.Name" class="form-control"></InputText> </div> </EditForm> </div> </div> </div> </div> @code { private HotelRoomDTO HotelRoomModel = new HotelRoomDTO(); private string Title = "Create"; }
- در برنامههای Blazor، کامپوننت ویژهی EditForm را بجای تگ استاندارد form، مورد استفاده قرار میدهیم.
- این کامپوننت، مدل فرم را از فیلد HotelRoomModel که در قسمت کدها تعریف کردیم، دریافت میکند. کار آن تامین اطلاعات فیلدهای فرم است.
- سپس در EditForm تعریف شده، بجای المان استاندارد input، از کامپوننت InputText برای دریافت اطلاعات متنی استفاده میشود. با bind-value@ در قسمت چهارم این سری بیشتر آشنا شدیم و کار آن two-way data binding است. در اینجا هر اطلاعاتی که وارد میشود، سبب به روز رسانی خودکار مقدار خاصیت HotelRoomModel.Name میشود و برعکس.
یک نکته: در قسمت قبل، مدل UI را از نوع رکورد C# 9.0 و init only تعریف کردیم. رکوردها، با EditForm و two-way databinding آن سازگاری ندارند (bind-value@ در اینجا) و بیشتر برای کنترلرهای برنامههای Web API که یکبار قرار است کار وهله سازی آنها در زمان دریافت اطلاعات از کاربر صورت گیرد، مناسب هستند و نه با فرمهای پویای Blazor. به همین جهت به پروژهی BlazorServer.Models مراجعه کرده و نوع آنها را به کلاس و initها را به set معمولی تغییر میدهیم تا در فرمهای Blazor هم قابل استفاده شوند.
تا اینجا کامپوننت ثبت اطلاعات یک اتاق جدید، چنین شکلی را پیدا کردهاست:
تکمیل سایر فیلدهای فرم ورود اطلاعات اتاق
پس از تعریف فیلد ورود اطلاعات نام اتاق، سایر فیلدهای متناظر با HotelRoomDTO را نیز به صورت زیر به EditForm تعریف شده اضافه میکنیم که در اینجا از InputNumber برای دریافت اطلاعات عددی و از InputTextArea، برای دریافت اطلاعات متنی چندسطری استفاده شدهاست:
<EditForm Model="HotelRoomModel"> <div class="form-group"> <label>Name</label> <InputText @bind-Value="HotelRoomModel.Name" class="form-control"></InputText> </div> <div class="form-group"> <label>Occupancy</label> <InputNumber @bind-Value="HotelRoomModel.Occupancy" class="form-control"></InputNumber> </div> <div class="form-group"> <label>Rate</label> <InputNumber @bind-Value="HotelRoomModel.RegularRate" class="form-control"></InputNumber> </div> <div class="form-group"> <label>Sq ft.</label> <InputText @bind-Value="HotelRoomModel.SqFt" class="form-control"></InputText> </div> <div class="form-group"> <label>Details</label> <InputTextArea @bind-Value="HotelRoomModel.Details" class="form-control"></InputTextArea> </div> <div class="form-group"> <button class="btn btn-primary">@Title Room</button> <NavLink href="hotel-room" class="btn btn-secondary">Back to Index</NavLink> </div> </EditForm>
تعریف اعتبارسنجیهای فیلدهای یک فرم Blazor
در حین تعریف یک فرم، برای واکنش نشان دادن به دکمهی submit، میتوان رویداد OnSubmit را به کامپوننت EditForm اضافه کرد که سبب فراخوانی متدی در قسمت کدهای کامپوننت جاری خواهد شد؛ مانند فراخوانی متد HandleHotelRoomUpsert در مثال زیر:
<EditForm Model="HotelRoomModel" OnSubmit="HandleHotelRoomUpsert"> </EditForm> @code { private HotelRoomDTO HotelRoomModel = new HotelRoomDTO(); private async Task HandleHotelRoomUpsert() { } }
اگر این مورد، مدنظر نیست، میتوان بجای OnSubmit، از رویداد OnValidSubmit استفاده کرد. در این حالت اگر اعتبارسنجی مدل فرم با شکست مواجه شود، دیگر متد HandleHotelRoomUpsert فراخوانی نخواهد شد. همچنین در این حالت میتوان خطاهای اعتبارسنجی را نیز در فرم نمایش داد:
<EditForm Model="HotelRoomModel" OnValidSubmit="HandleHotelRoomUpsert"> <DataAnnotationsValidator /> @*<ValidationSummary />*@ <div class="form-group"> <label>Name</label> <InputText @bind-Value="HotelRoomModel.Name" class="form-control"></InputText> <ValidationMessage For="()=>HotelRoomModel.Name"></ValidationMessage> </div> <div class="form-group"> <label>Occupancy</label> <InputNumber @bind-Value="HotelRoomModel.Occupancy" class="form-control"></InputNumber> <ValidationMessage For="()=>HotelRoomModel.Occupancy"></ValidationMessage> </div> <div class="form-group"> <label>Rate</label> <InputNumber @bind-Value="HotelRoomModel.RegularRate" class="form-control"></InputNumber> <ValidationMessage For="()=>HotelRoomModel.RegularRate"></ValidationMessage> </div>
- کامپوننت DataAnnotationsValidator، اعتبارسنجی مبتنی بر data annotations را مانند [Required]، در دامنهی دید یک EditForm فعال میکند.
- اگر خواستیم تمام خطاهای اعتبارسنجی را به صورت خلاصهای در بالای فرم نمایش دهیم، میتوان از کامپوننت ValidationSummary استفاده کرد.
- و یا اگر خواستیم خطاها را به صورت اختصاصیتری ذیل هر تکستباکس نمایش دهیم، میتوان از کامپوننت ValidationMessage کمک گرفت. خاصیت For آن از نوع <Expression<System.Func تعریف شدهاست که اجازهی تعریف strongly typed نام خاصیت در حال اعتبارسنجی را به صورتی که مشاهده میکنید، میسر میکند.
ثبت اولین اتاق هتل
در ادامه میخواهیم روال رویدادگردان HandleHotelRoomUpsert را مدیریت کنیم. به همین جهت نیاز به کار با سرویس IHotelRoomService ابتدای بحث خواهد بود. بنابراین در ابتدا به فایل BlazorServer.App\_Imports.razor مراجعه کرده و فضای نام سرویسهای برنامه را اضافه میکنیم:
@using BlazorServer.Services
@page "/hotel-room/create" @inject IHotelRoomService HotelRoomService @inject NavigationManager NavigationManager @code { private HotelRoomDTO HotelRoomModel = new HotelRoomDTO(); private string Title = "Create"; private async Task HandleHotelRoomUpsert() { var roomDetailsByName = await HotelRoomService.IsRoomUniqueAsync(HotelRoomModel.Name); if (roomDetailsByName != null) { //there is a duplicate room. show an error msg. return; } var createdResult = await HotelRoomService.CreateHotelRoomAsync(HotelRoomModel); NavigationManager.NavigateTo("hotel-room"); } }
اگر پیشتر با ASP.NET Web Forms کار کرده باشید (اولین روش توسعهی برنامههای وب در دنیای دات نت)، مدل برنامه نویسی Blazor Server، بسیار شبیه به کار با وب فرمها است؛ البته بر اساس آخرین تغییرات دنیای دانت نت مانند برنامه نویسی async، کار با سرویسها، تزریق وابستگیهای توکار و غیره.
نمایش لیست اتاقهای ثبت شده
تا اینجا موفق شدیم اطلاعات یک مدل اعتبارسنجی شده را در بانک اطلاعاتی ثبت کنیم. مرحلهی بعد، نمایش لیست اطلاعات ثبت شدهی در بانک اطلاعاتی است. بنابراین به کامپوننت HotelRoomList.razor مراجعه کرده و آنرا به صورت زیر تکمیل میکنیم:
@page "/hotel-room" @inject IHotelRoomService HotelRoomService <div class="row mt-4"> <div class="col-8"> <h4 class="card-title text-info">Hotel Rooms</h4> </div> <div class="col-3 offset-1"> <NavLink href="hotel-room/create" class="btn btn-info">Add New Room</NavLink> </div> </div> <div class="row mt-4"> <div class="col-12"> <table class="table table-bordered table-hover"> <thead> <tr> <th>Name</th> <th>Occupancy</th> <th>Rate</th> <th> Sqft </th> <th> </th> </tr> </thead> <tbody> @if (HotelRooms.Any()) { foreach (var room in HotelRooms) { <tr> <td>@room.Name</td> <td>@room.Occupancy</td> <td>@room.RegularRate.ToString("c")</td> <td>@room.SqFt</td> <td></td> </tr> } } else { <tr> <td colspan="5">No records found</td> </tr> } </tbody> </table> </div> </div> @code { private List<HotelRoomDTO> HotelRooms = new List<HotelRoomDTO>(); protected override async Task OnInitializedAsync() { await foreach(var room in HotelRoomService.GetAllHotelRoomsAsync()) { HotelRooms.Add(room); } } }
- متد GetAllHotelRoomsAsync، لیست اتاقهای ثبت شده را بازگشت میدهد. البته خروجی آن از نوع <IAsyncEnumerable<HotelRoomDTO است که از زمان C# 8.0 ارائه شد و روش کار با آن اندکی متفاوت است. IAsyncEnumerableها را باید توسط await foreach پردازش کرد.
- همانطور که در مطلب بررسی چرخهی حیات کامپوننتها نیز عنوان شد، متدهای رویدادگران OnInitialized و نمونهی async آن برای دریافت اطلاعات از سرویسها طراحی شدهاند که در اینجا نمونهای از آنرا مشاهده میکنید.
- پس از تشکیل لیست اتاقها، حلقهی foreach (var room in HotelRooms) تعریف شده، ردیفهای آنرا در UI نمایش میدهد.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-14.zip