قرار دادن این محتوای تولید شده در سیستم Bundling MVC به شکل مستقیم امکان پذیر نیست؛ زیرا این سیستم با فایلهای استاتیک سر و کار دارد و افزودن یک url به آن مجاز نمیباشد. حال اگر در پروژهی خود محتوای پویایی را تولید کرده و میخواهید از مزایای فشرده سازی سیستم Bundling بهرهمند شوید، باید مراحل زیر را انجام دهید:
ابتدا متدی را برای دریافت محتوای تولید شده بنویسید. برای مثال برای دریافت محتوای تولیده شدهی فایل hubs.js میتوانید از متد زیر استفاده کنید:
public static string GetSignalRContent() { var resolver = new DefaultHubManager(new DefaultDependencyResolver()); var proxy = new DefaultJavaScriptProxyGenerator(resolver, new NullJavaScriptMinifier()); return proxy.GenerateProxy("/signalr"); }
همچنین در این سیستم مسیریابی سفارشی شما باید محتوای تولید شده را هم در اختیار داشته باشید. برای نمونه به کد زیر توجه کنید که ما از کلاس VirtualPathProvider، یک کلاس مشتق کرده و سیستم مسیریابی دلخواه خود را ایجاد میکنیم:
public class CustomVirtualPathProvider : VirtualPathProvider { public CustomActionVirtualPathProvider(VirtualPathProvider virtualPathProvider) { // Wrap an existing virtual path provider VirtualPathProvider = virtualPathProvider; } protected VirtualPathProvider VirtualPathProvider { get; set; } public override string CombineVirtualPaths(string basePath, string relativePath) { return VirtualPathProvider.CombineVirtualPaths(basePath, relativePath); } public override bool DirectoryExists(string virtualDir) { return VirtualPathProvider.DirectoryExists(virtualDir); } public override bool FileExists(string virtualPath) { if (virtualPath == "~/signalr/hubs") { return true; } return VirtualPathProvider.FileExists(virtualPath); } public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) { // BaseClass can't create a CacheDependency for your content, remove it // You could also add your own CacheDependency and aggregate it with the base dependency List<string> virtualPathDependenciesCopy = virtualPathDependencies.Cast<string>().ToList(); virtualPathDependenciesCopy.Remove("~/signalr/hubs"); return VirtualPathProvider.GetCacheDependency(virtualPath, virtualPathDependenciesCopy, utcStart); } public override string GetCacheKey(string virtualPath) { return VirtualPathProvider.GetCacheKey(virtualPath); } public override VirtualDirectory GetDirectory(string virtualDir) { return VirtualPathProvider.GetDirectory(virtualDir); } public override VirtualFile GetFile(string virtualPath) { if (virtualPath == "~/signalr/hubs") { return new CustomVirtualFile(virtualPath, new MemoryStream(Encoding.Default.GetBytes(GetSignalRContent()))); } return VirtualPathProvider.GetFile(virtualPath); } public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies) { return VirtualPathProvider.GetFileHash(virtualPath, virtualPathDependencies); } public override object InitializeLifetimeService() { return VirtualPathProvider.InitializeLifetimeService(); } } public class CustomVirtualFile : VirtualFile { public CustomVirtualFile (string virtualPath, Stream stream) : base(virtualPath) { Stream = stream; } public Stream Stream { get; private set; } public override Stream Open() { return Stream; } }
public override bool FileExists(string virtualPath) { if (virtualPath == "~/signalr/hubs") { return true; } return VirtualPathProvider.FileExists(virtualPath); }
همچنین باید توجه داشت که کلاس پایه، قادر به تولید CacheDependency برای محتوای تولیدی شده ما نیست. بنابراین باید متد GetCacheDependency کلاس پایهی ما بازنویسی و منطق مورد نظر را برای آن پیاده سازی کنیم. در این متد ابتدا مسیر مورد نظر خود را از لیست مسیرهایی که باید از CacheDependency استفاده کنند، حذف و سپس سناریوی خود را پیاده سازی میکنیم. در این مثال من از پیاده سازی آن خودداری کرده و فقط مسیر را از لیست مسیرها حذف کردهام:
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) { List<string> virtualPathDependenciesCopy = virtualPathDependencies.Cast<string>().ToList(); virtualPathDependenciesCopy.Remove("~/signalr/hubs"); return VirtualPathProvider.GetCacheDependency(virtualPath, virtualPathDependenciesCopy, utcStart); }
public override VirtualFile GetFile(string virtualPath) { if (virtualPath == "~/signalr/hubs") { return new CustomVirtualFile(virtualPath, new MemoryStream(Encoding.Default.GetBytes(GetSignalRContent()))); } return VirtualPathProvider.GetFile(virtualPath); }
در نهایت باید کلاس سفارشی شده را با سیستم مسیریابی پیش فرض سیستم Bundling جایگزین کنیم:
public static void RegisterBundles(BundleCollection bundles) { BundleTable.VirtualPathProvider = new CustomVirtualPathProvider(BundleTable.VirtualPathProvider); Bundle include = new Bundle("~/bundle") .Include("~/Content/static.js") .Include("~/signalr/hubs"); bundles.Add(include); }
ASP.NET Web API فریم ورکی برای ساختن APIهای وب بر روی فریم ورک دات نت است. در این مقاله با استفاده از این فریم ورک، API وبی خواهیم ساخت که لیستی از محصولات را بر میگرداند. صفحه وب کلاینت، با استفاده از jQuery نتایج را نمایش خواهد داد.
یک پروژه Web API بسازید
در ویژوال استودیو 2013 پروژه جدیدی از نوع ASP.NET Web Application بسازید و نام آن را "ProductsApp" انتخاب کنید.
در دیالوگ New ASP.NET Project قالب Empty را انتخاب کنید و در قسمت "Add folders and core references for" گزینه Web API را انتخاب نمایید.
می توانید از قالب Web API هم استفاده کنید. این قالب با استفاده از ASP.NET MVC صفحات راهنمای API را خواهد ساخت. در این مقاله از قالب Empty استفاده میکنیم تا تمرکز اصلی، روی خود فریم ورک Web API باشد. بطور کلی برای استفاده از این فریم ورک لازم نیست با ASP.NET MVC آشنایی داشته باشید.
افزودن یک مدل
یک مدل (model) آبجکتی است که داده اپلیکیشن شما را معرفی میکند. ASP.NET Web API میتواند بصورت خودکار مدل شما را به JSON, XML و برخی فرمتهای دیگر مرتب (serialize) کند، و سپس داده مرتب شده را در بدنه پیام HTTP Response بنویسد. تا وقتی که یک کلاینت بتواند فرمت مرتب سازی دادهها را بخواند، میتواند آبجکت شما را deserialize کند. اکثر کلاینتها میتوانند XML یا JSON را تفسیر کنند. بعلاوه کلاینتها میتوانند فرمت مورد نظرشان را با تنظیم Accept header در پیام HTTP Request مشخص کنند.
بگذارید تا با ساختن مدلی ساده که یک محصول (product) را معرفی میکند شروع کنیم.
کلاس جدیدی در پوشه Models ایجاد کنید.
نام کلاس را به "Product" تغییر دهید، و خواص زیر را به آن اضافه کنید.
namespace ProductsApp.Models { public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal Price { get; set; } } }
افزودن یک کنترلر
در Web API کنترلرها آبجکت هایی هستند که درخواستهای HTTP را مدیریت کرده و آنها را به اکشن متدها نگاشت میکنند. ما کنترلری خواهیم ساخت که میتواند لیستی از محصولات، یا محصولی بخصوص را بر اساس شناسه برگرداند. اگر از ASP.NET MVC استفاده کرده اید، با کنترلرها آشنا هستید. کنترلرهای Web API مشابه کنترلرهای MVC هستند، با این تفاوت که بجای ارث بری از کلاس Controller از کلاس ApiController مشتق میشوند.
کنترلر جدیدی در پوشه Controllers ایجاد کنید.
در دیالوگ Add Scaffold گزینه Web API Controller - Empty را انتخاب کرده و روی Add کلیک کنید.
در دیالوگ Add Controller نام کنترلر را به "ProductsController" تغییر دهید و روی Add کلیک کنید.
توجه کنید که ملزم به ساختن کنترلرهای خود در پوشه Controllers نیستید، و این روش صرفا قراردادی برای مرتب نگاه داشتن ساختار پروژهها است. کنترلر ساخته شده را باز کنید و کد زیر را به آن اضافه نمایید.
using ProductsApp.Models; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Web.Http; namespace ProductsApp.Controllers { public class ProductsController : ApiController { Product[] products = new Product[] { new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 }, new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M }, new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M } }; public IEnumerable<Product> GetAllProducts() { return products; } public IHttpActionResult GetProduct(int id) { var product = products.FirstOrDefault((p) => p.Id == id); if (product == null) { return NotFound(); } return Ok(product); } }
کنترلر ما دو متد برای دریافت محصولات تعریف میکند:
- متد GetAllProducts لیست تمام محصولات را در قالب یک <IEnumerable<Product بر میگرداند.
- متد GetProductById سعی میکند محصولی را بر اساس شناسه تعیین شده پیدا کند.
همین! حالا یک Web API ساده دارید. هر یک از متدهای این کنترلر، به یک یا چند URI پاسخ میدهند:
URI | Controller Method |
api/products/ | GetAllProducts |
api/products/id/ | GetProductById |
برای اطلاعات بیشتر درباره نحوه نگاشت درخواستهای HTTP به اکشن متدها توسط Web API به این لینک مراجعه کنید.
فراخوانی Web API با جاوا اسکریپت و jQuery
در این قسمت یک صفحه HTML خواهیم ساخت که با استفاده از AJAX متدهای Web API را فراخوانی میکند. برای ارسال درخواستهای آژاکسی و بروز رسانی صفحه بمنظور نمایش نتایج دریافتی از jQuery استفاده میکنیم.
در پنجره Solution Explorer روی نام پروژه کلیک راست کرده و گزینه Add, New Item را انتخاب کنید.
در دیالوگ Add New Item قالب HTML Page را انتخاب کنید و نام فایل را به "index.html" تغییر دهید.
حال محتوای این فایل را با لیست زیر جایگزین کنید.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Product App</title> </head> <body> <div> <h2>All Products</h2> <ul id="products" /> </div> <div> <h2>Search by ID</h2> <input type="text" id="prodId" size="5" /> <input type="button" value="Search" onclick="find();" /> <p id="product" /> </div> <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script> <script> var uri = 'api/products'; $(document).ready(function () { // Send an AJAX request $.getJSON(uri) .done(function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, item) { // Add a list item for the product. $('<li>', { text: formatItem(item) }).appendTo($('#products')); }); }); }); function formatItem(item) { return item.Name + ': $' + item.Price; } function find() { var id = $('#prodId').val(); $.getJSON(uri + '/' + id) .done(function (data) { $('#product').text(formatItem(data)); }) .fail(function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); } </script> </body> </html>
گرفتن لیستی از محصولات
برای گرفتن لیستی از محصولات، یک درخواست HTTP GET به آدرس "api/products/" ارسال کنید.
تابع getJSON یک درخواست آژاکسی ارسال میکند. پاسخ دریافتی هم آرایه ای از آبجکتهای JSON خواهد بود. تابع done در صورت موفقیت آمیز بودن درخواست، اجرا میشود. که در این صورت ما DOM را با اطلاعات محصولات بروز رسانی میکنیم.
$(document).ready(function () { // Send an AJAX request $.getJSON(apiUrl) .done(function (data) { // On success, 'data' contains a list of products. $.each(data, function (key, item) { // Add a list item for the product. $('<li>', { text: formatItem(item) }).appendTo($('#products')); }); }); });
گرفتن محصولی مشخص
برای گرفتن یک محصول توسط شناسه (ID) آن کافی است یک درخواست HTTP GET به آدرس "api/products/id/" ارسال کنید.
function find() { var id = $('#prodId').val(); $.getJSON(apiUrl + '/' + id) .done(function (data) { $('#product').text(formatItem(data)); }) .fail(function (jqXHR, textStatus, err) { $('#product').text('Error: ' + err); }); }
اجرای اپلیکیشن
اپلیکیشن را با F5 اجرا کنید. صفحه وب باز شده باید چیزی مشابه تصویر زیر باشد.
برای گرفتن محصولی مشخص، شناسه آن را وارد کنید و روی Search کلیک کنید.
اگر شناسه نامعتبری وارد کنید، سرور یک خطای HTTP بر میگرداند.
استفاده از F12 برای مشاهده درخواستها و پاسخ ها
هنگام کار با سرویسهای HTTP، مشاهدهی درخواستهای ارسال شده و پاسخهای دریافتی بسیار مفید است. برای اینکار میتوانید از ابزار توسعه دهندگان وب استفاده کنید، که اکثر مرورگرهای مدرن، پیاده سازی خودشان را دارند. در اینترنت اکسپلورر میتوانید با F12 به این ابزار دسترسی پیدا کنید. به برگه Network بروید و روی Start Capturing کلیک کنید. حالا صفحه وب را مجددا بارگذاری (reload) کنید. در این مرحله اینترنت اکسپلورر ترافیک HTTP بین مرورگر و سرور را تسخیر میکند. میتوانید تمام ترافیک HTTP روی صفحه جاری را مشاهده کنید.
به دنبال آدرس نسبی "api/products/" بگردید و آن را انتخاب کنید. سپس روی Go to detailed view کلیک کنید تا جزئیات ترافیک را مشاهده کنید. در نمای جزئیات، میتوانید headerها و بدنه درخواستها و پاسخها را ببینید. مثلا اگر روی برگه Request headers کلیک کنید، خواهید دید که اپلیکیشن ما در Accept header دادهها را با فرمت "application/json" درخواست کرده است.
اگر روی برگه Response body کلیک کنید، میتوانید ببینید چگونه لیست محصولات با فرمت JSON سریال شده است. همانطور که گفته شده مرورگرهای دیگر هم قابلیتهای مشابهی دارند. یک ابزار مفید دیگر Fiddler است. با استفاده از این ابزار میتوانید تمام ترافیک HTTP خود را مانیتور کرده، و همچنین درخواستهای جدیدی بسازید که این امر کنترل کاملی روی HTTP headers به شما میدهد.
قدمهای بعدی
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
System.Web.dll System.Web.ApplicationServices.dll
C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Pages\v2.0\Assemblies\WebMatrix.Data.dll C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Pages\v2.0\Assemblies\WebMatrix.WebData.dll
<?xml version="1.0" encoding="utf-8" ?> <configuration> <connectionStrings> <add name="DefaultConnection" connectionString="Data Source=.;Initial Catalog=SimpleMembershipProviderDB;Integrated Security=True;" providerName="System.Data.SqlClient"/> </connectionStrings> <system.web> <roleManager enabled="true" defaultProvider="SimpleRoleProvider"> <providers> <clear/> <add name="SimpleRoleProvider" type="WebMatrix.WebData.SimpleRoleProvider, WebMatrix.WebData"/> </providers> </roleManager> <membership defaultProvider="SimpleMembershipProvider"> <providers> <clear/> <add name="SimpleMembershipProvider" type="WebMatrix.WebData.SimpleMembershipProvider, WebMatrix.WebData"/> </providers> </membership> </system.web> </configuration>
using System; using System.Security.Principal; using System.Web.Security; using WebMatrix.WebData; namespace MemberShipConsoleApplication { class Program { static void Main(string[] args) { WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true); AddUserAndRolSample(); Login(); if (System.Threading.Thread.CurrentPrincipal.Identity.IsAuthenticated) RunApp(); } static void AddUserAndRolSample() { if (WebSecurity.UserExists("iman")) return; // No implements in SimpleMembershipProvider : // Membership.CreateUser("iman", "123"); WebSecurity.CreateUserAndAccount("iman", "123"); Roles.CreateRole("admin"); Roles.CreateRole("User"); Roles.AddUserToRole("iman", "admin"); } static void Login() { for (int i = 0; i < 3; i++) { Console.Write("UserName: "); var userName = Console.ReadLine(); Console.Write("Password: "); var password = Console.ReadLine(); if (Membership.ValidateUser(userName, password)) { var user = Membership.GetUser(userName); var identity = new GenericIdentity(user.UserName); var principal = new RolePrincipal(identity); System.Threading.Thread.CurrentPrincipal = principal; Console.Clear(); return; } Console.WriteLine("User Name Or Password Not Valid."); } } static void RunApp() { Console.WriteLine("Welcome To MemberShip. User Name: {0}", System.Threading.Thread.CurrentPrincipal.Identity.Name); if (System.Threading.Thread.CurrentPrincipal.IsInRole("admin")) Console.WriteLine("Hello Admin User!"); Console.Read(); } } }
InitializeDatabaseConnection(string connectionStringName, string userTableName, string userIdColumn, string userNameColumn, bool autoCreateTables)
ایجاد فیلتر برای هدایت همهی درخواستها به صفحهی «در حال بهروزرسانی» در برنامههای ASP.NET MVC
طراحی Url در Restful API
Url بخش اصلی و راه ارتباطی API شما با توسعه دهنده است .بنابراین طراحی یک ساختار مناسب و یکپارچه برای Url ها دارای اهمیت زیادی است .
Url پایه API خود را ساده و خوانا ، حفظ کنید . داشتن یک Url پایه ساده استفاده از API را آسان کرده و خوانایی آن را بالا میبرد و باعث میشود که توسعه دهنده برای استفاده از آن نیاز کمتری به مراجعه به مستندات داشته باشد. پیشنهاد میشود که برای هر منبع تنها دو Url پایه وجود داشته باشد . یکی برای مجموعه ای از منبع موردنظر و دیگری برای یک واحد مشخص از آن منبع . برای مثال اگر منبع موردنظر ما کتاب باشد ، خواهیم داشت :
.../books
.../books/1001
استفاده از این روش یک مزیت دیگر هم به همراه دارد و آن دور کردن افعال از Urlها است.
بسیاری در زمان طراحی Urlها و در نامگذاری از فعلها استفاده میکنند. برای هر منبعی که مدلسازی میکنید هیچ وقت نمیتوانید آن را به تنهایی و جداافتاده در نظر بگیرید. بلکه همیشه منابع مرتبطی وجود دارند که باید در نظر گرفته شوند. در مثال کتاب میتوان منابعی مثل نویسنده ، ناشر ، موضوع و ... را بیان کرد. حالا سعی کنید به تمام Url هایی که برای پوشش دادن تمام درخواستهای مربوط به منبع کتاب نیاز داریم فکر کنید . احتمالا به چیزی شبیه این میرسیم :
.../getAllBooks .../getBook .../newBook .../getNewBooksSince .../getComputerBooks .../BooksNotPublished .../UpdateBookPriceTo .../bookForPublisher .../GetLastBooks .../DeleteBook ….
پس حالا این درخواستهای متنوع را چطور با دو Url اصلی انجام دهیم ؟
1- از افعال Http برای کار کردن بر روی منابع استفاده کنید . با استفاده از افعال Http شامل POST ، GET ، PUT و DELETE و دو Url اصلی ، یک مجموعهی مناسب از عملیاتها در دسترس توسعه دهنده خواهد بود . به جدول زیر نگاه کنید .
توسعه دهندگان احتمالا نیازی به این جدول برای درک اینکه API چطور کار میکند نخواهند داشت.
2- با استفاده از نکته قبلی بخشی از Urlهای بالا حذف خواهند شد. اما هنوز با روابط بین منابع چکار کنیم؟ منابع تقریبا همیشه دارای روابطی با دیگر منابع هستند . یک روش ساده برای بیان این روابط در API چیست ؟ به مثال کتاب برمیگردیم. کتابها دارای نویسنده هستند. اگر بخواهیم کتابهای یک نویسنده را برگردانیم چه باید بکنیم؟ با استفاده از Urlهای پایه و افعال Http میتوان اینکار را انجام داد. یکی از ساختارهای ممکن این است :
GET .../authors/1001/books
POST .../authors/1001/books
3- بیشتر APIها دارای پیچیدگیهای بیشتری نسبت به Url اصلی یک منبع هستند . هر منبع مشخصات
و روابط متنوعی دارد که قابل جستجو کردن، مرتب سازی، بروزرسانی و تغییر هستند. Url
اصلی را ساده نگه دارید و این پیچیدگیها را به کوئری استرینگ منتقل کنید.
برای برگرداندن تمام کتابهای با قیمت پنچ هزار تومان با قطع جیبی که دارای امتیاز 8 به بالا هستند از کوئری زیر میشود استفاده کرد :
GET .../books?price=5000&size=pocket&score=8
و البته فراموش نکنید که لیستی از فیلدهای مجاز را در مستندات خود ارائه کنید.
4 - گفتیم که بهتر است افعال را از Url ها خارج کنیم . ولی در مواردی که درخواست ارسال شده در مورد یک منبع نیست چطور؟ مواردی مثل محاسبه مالیات پرداختی یا هزینه بیمه ، جستجو در کل منابع ، ترجمه یک عبارت یا تبدیل واحدها . هیچکدام از اینها ارتباطی با یک منبع خاص ندارند. در این موارد بهتر است از افعال استفاده شود. و حتما در مستندات خود ذکر کنید که در این موارد از افعال استفاده میشود..../convert?value=25&from=px&to=em .../translate?term=web&from=en&to=fa
5 - استفاده از اسامی جمع یا مفرد
با توجه به ساختاری که تا اینجا طراحی کرده ایم بکاربردن اسامی جمع بامعناتر و خواناتر است. اما مهمتر از روشی که بکار میبرید ، اجتناب از بکاربردن هر دو روش با هم است ، اینکه در مورد یک منبع از اسم منفرد و در مورد دیگری از اسم جمع استفاده کنید . یکدستی API را حفظ کنید و به توسعه دهنده کمک کنید راحتتر API شما را یاد بگیرد.
6- استفاده از نامهای عینی به جای نامهای کلی و انتزاعیAPI ی را در نظر بگیرید که محتواهایی را در فرمتهای مختلف ارائه میدهد. بلاگ ، ویدئو ، اخبار و .... حالا فرض کنیداین API منابع را در بالاتری سطح مدسازی کرده باشد مثل /items یا /assets . درک کردن محتوای این API و کاری که میتوان با این API انجام داد برای توسعه دهنده سخت است . خیلی راحتتر و مفیدتر است که منابع را در قالب بلاگ ، اخبار ، ویدئو مدلسازی کنیم .
آفیس
توسعه وب
دات نت فریم ورک
دبلیو سی اف
سی و مشتقات
کتابهای رایگان
متفرقه
مرورگرها
ویندوز
پی اچ پی