ایجاد سرویس سمت کلاینت دریافت اطلاعات اتاقها از Web API
در قسمت 24، HotelRoomController را تکمیل کردیم که کار آن، بازگشت اطلاعات تمام اتاقها و یا یک اتاق مشخص به کلاینت است. اکنون میخواهیم در ادامهی قسمت قبل، اگر کاربری بر روی دکمهی Go صفحهی اول رزرو اتاقی کلیک کرد، لیست تمام اتاقهای تعریف شده را به او نمایش دهیم. به همین جهت نیاز به سرویس سمت کلاینتی داریم که بتواند با این Web API endpoint کار کند:
namespace BlazorWasm.Client.Services { public interface IClientHotelRoomService { public Task<IEnumerable<HotelRoomDTO>> GetHotelRoomsAsync(DateTime checkInDate, DateTime checkOutDate); public Task<HotelRoomDTO> GetHotelRoomDetailsAsync(int roomId, DateTime checkInDate, DateTime checkOutDate); } }
در ادامه اینترفیس فوق را به صورت زیر پیاده سازی میکنیم:
namespace BlazorWasm.Client.Services { public class ClientHotelRoomService : IClientHotelRoomService { private readonly HttpClient _httpClient; public ClientHotelRoomService(HttpClient httpClient) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); } public Task<HotelRoomDTO> GetHotelRoomDetailsAsync(int roomId, DateTime checkInDate, DateTime checkOutDate) { throw new NotImplementedException(); } public Task<IEnumerable<HotelRoomDTO>> GetHotelRoomsAsync(DateTime checkInDate, DateTime checkOutDate) { // How to url-encode query-string parameters properly var uri = new UriBuilderExt(new Uri(_httpClient.BaseAddress, "/api/hotelroom")) .AddParameter("checkInDate", $"{checkInDate:yyyy'-'MM'-'dd}") .AddParameter("checkOutDate", $"{checkOutDate:yyyy'-'MM'-'dd}") .Uri; return _httpClient.GetFromJsonAsync<IEnumerable<HotelRoomDTO>>(uri); } } }
- HttpClient یکی از سرویسهای تنظیم شدهی در فایل Program.cs پروژههای سمت کلاینت است. بنابراین میتوان آنرا از طریق تزریق به سازندهی این سرویس، به دست آورد.
- در اینجا برای دریافت اطلاعات JSON دریافتی از سمت سرور و سپس Deserialize خودکار آن به لیستی از DTO تعریف شده، از متد جدید GetFromJsonAsync استفاده شدهاست. این مورد جزو تازههای NET 5x. است.
- در اینجا استفاده از کلاس UriBuilderExt را نیز جهت تشکیل یک URL دارای کوئری استرینگ، مشاهده میکنید. هیچگاه نباید URL نهایی را از طریق جمع زدن اجزای آن به سمت سرور ارسال کرد؛ از این جهت که اجزای آن باید URL-encoded شوند؛ وگرنه در سمت سرور قابلیت پردازش نخواهند داشت. در ادامه تعریف کلاس جدید UriBuilderExt را مشاهده میکنید:
using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; using BlazorServer.Models; using BlazorWasm.Client.Utils; using System; using System.Collections.Specialized; using System.Web; namespace BlazorWasm.Client.Utils { public class UriBuilderExt { private readonly NameValueCollection _collection; private readonly UriBuilder _builder; public UriBuilderExt(Uri uri) { _builder = new UriBuilder(uri); _collection = HttpUtility.ParseQueryString(string.Empty); } public UriBuilderExt AddParameter(string key, string value) { _collection.Add(key, value); return this; } public Uri Uri { get { _builder.Query = _collection.ToString(); return _builder.Uri; } } } }
- تاریخهای ارسالی به سمت سرور را با فرمت yyyy'-'MM'-'dd تبدیل رشته کردیم. این قالب، یکی از قالبهای پذیرفته شدهاست.
- جهت سهولت استفادهی از سرویس فوق و همچنین مدلهای برنامه، فضای نام آنها را به فایل BlazorWasm.Client\_Imports.razor اضافه میکنیم تا در تمام کامپوننتهای برنامهی سمت کلاینت، قابل دسترسی شوند:
@using BlazorWasm.Client.Services @using BlazorServer.Models
namespace BlazorWasm.Client { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); // ... builder.Services.AddScoped<IClientHotelRoomService, ClientHotelRoomService>(); // ... } } }
چند اصلاح جزئی در کنترلرها و سرویسهای سمت سرور
در Url نهایی فوق، دو پارامتر جدید checkInDate و checkOutDate هم وجود دارند. به همین جهت این دو را به اکشن متدهای کنترلر HotelRoom:
namespace BlazorWasm.WebApi.Controllers { [Route("api/[controller]")] [ApiController] public class HotelRoomController : ControllerBase { // ... [HttpGet] public async Task<IActionResult> GetHotelRooms(DateTime? checkInDate, DateTime? checkOutDate) { // ... } [HttpGet("{roomId}")] public async Task<IActionResult> GetHotelRoom(int? roomId, DateTime? checkInDate, DateTime? checkOutDate) { // ... } } }
namespace BlazorServer.Services { public interface IHotelRoomService : IDisposable { Task<List<HotelRoomDTO>> GetAllHotelRoomsAsync(DateTime? checkInDate, DateTime? checkOutDate); Task<HotelRoomDTO> GetHotelRoomAsync(int roomId, DateTime? checkInDate, DateTime? checkOutDate); // ... } }
تنظیمات ویژهی HttpClient برنامهی سمت کلاینت
سرویس ClientHotelRoomService فوق، از HttpClient تزریق شدهی در سازندهی خود استفاده میکند که BaseAddress خود را مطابق تنظیمات ابتدایی برنامه، از HostEnvironment دریافت میکند. در اینجا علاقمندیم تا بجای این تنظیم پیشفرض، فایل جدید appsettings.json را به پوشهی BlazorWasm.Client\wwwroot\appsettings.json کلاینت اضافه کرده (محل قرارگیری آن در برنامههای سمت کلاینت، داخل پوشهی wwwroot است و نه در داخل پوشهی ریشهی اصلی پروژه):
{ "BaseAPIUrl": "https://localhost:5001/" }
namespace BlazorWasm.Client { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); // ... // builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseAPIUrl")) }); // ... } } }
تکمیل کامپوننت دریافت لیست تمام اتاقها
در قسمت قبل، کامپوننت خالی HotelRooms.razor را تعریف کردیم. کاربران پس از کلیک بر روی دکمهی Go صفحهی اول، به این کامپوننت هدایت میشوند. اکنون میخواهیم، لیست تمام اتاقها را در این کامپوننت، از Web API برنامه دریافت کنیم:
@page "/hotel/rooms" @inject ILocalStorageService LocalStorage @inject IJSRuntime JsRuntime @inject IClientHotelRoomService HotelRoomService <h3>HotelRooms</h3> @code { HomeVM HomeModel = new HomeVM(); IEnumerable<HotelRoomDTO> Rooms = new List<HotelRoomDTO>(); protected override async Task OnInitializedAsync() { try { var model = await LocalStorage.GetItemAsync<HomeVM>(ConstantKeys.LocalInitialBooking); if (model is not null) { HomeModel = model; } else { HomeModel.NoOfNights = 1; } await LoadRooms(); } catch (Exception e) { await JsRuntime.ToastrError(e.Message); } } private async Task LoadRooms() { Rooms = await HotelRoomService.GetHotelRoomsAsync(HomeModel.StartDate, HomeModel.EndDate); } }
روش اجرای پروژههای Blazor WASM
تا اینجا اگر برنامهی سمت کلاینت را توسط دستور dotnet watch run اجرا کنیم، هرچند صفحهی خالی نمایش لیست اتاقها ظاهر میشود، اما یک خطای fetch error را هم دریافت خواهیم کرد؛ چون نیاز است ابتدا پروژهی Web API را اجرا کرد و سپس پروژهی WASM را.
برای ساده سازی اجرای همزمان این دو پروژه، اگر از ویژوال استودیوی کامل استفاده میکنید، بر روی نام Solution کلیک راست کرده و از منوی ظاهر شده، گزینهی «Set Startup projects» را انتخاب کنید. در صفحه دیالوگ ظاهر شده، گزینهی «multiple startup projects» را انتخاب کرده و از لیست پروژههای موجود، دو پروژهی Web API و WASM را انتخاب کنید و Action مقابل آنها را به Start تنظیم کنید. در اینجا حتی میتوان ترتیب اجرای این پروژهها را هم تغییر داد. در این حالت زمانیکه بر روی دکمهی Run، در ویژوال استودیو کلیک میکنید، هر دو پروژه را با هم برای شما اجرا خواهد کرد.
نکتهی مهم! در این حالت هم برنامهی کلاینت نمیتواند با برنامهی Web API ارتباط برقرار کند! چون شماره پورت iisExpress درج شدهی در فایل appsettings.json آن، باید به شماره sslPort مندرج در فایل Properties\launchSettings.json پروژهی Web API تغییر داده شود که برای نمونه در اینجا این عدد 44314 است:
{ "iisSettings": { "iisExpress": { "applicationUrl": "http://localhost:62930", "sslPort": 44314 } } }
{ "BlazorWasm.Client": { "applicationUrl": "https://localhost:5002;http://localhost:5003", } }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-27.zip