Partial Views and Child Actions are one the most used features of ASP.NET MVC. Partial Views provides us a way to create a reusable component that can be used in multiple Views. There are Actions which can be marked as Child Actions and these cannot be invoked via URL but inside views or partial views. Child Actions are no more available with ASP.NET Core. View Components are new way to implement this feature in ASP.NET Core.
services.AddDNTScheduler(options => { // DNTScheduler needs a ping service to keep it alive. Set it to false if you don't need it. options.AddPingTask = true; options.AddScheduledTask<DoFillDataTask>( runAt: utcNow => { var schedulerTask = _config.GetSection("SchedulerTask"); var now = utcNow.AddHours(4.5); return now.Hour == 10 && now.Minute == 0 && now.Second == 0; }, order: 1); });
ایجاد یک پروژهی جدید 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
Closure ها در JavaScript چیست ؟
CoffeeScript #4
Syntax
Loops
for name in ["Vahid", "Hamid", "Saeid"] alert "Hi #{name}"
var i, len, name, ref; ref = ["Vahid", "Hamid", "Saeid"]; for (i = 0, len = ref.length; i < len; i++) { name = ref[i]; alert("Hi " + name); }
for name, i in ["Vahid", "Hamid", "Saeid"] alert "#{i} - Hi #{name}"
alert name for name in ["Vahid", "Hamid", "Saeid"]
names = ["Vahid", "Hamid", "Saeid"] alert name for name in names when name[0] is "V"
var i, len, name, names; names = ["Vahid", "Hamid", "Saeid"]; for (i = 0, len = names.length; i < len; i++) { name = names[i]; if (name[0] === "V") { alert(name); } }
names = "Vahid": "Mohammad Taheri", "Ali": "Ahmadi" alert("#{first} #{last}") for first, last of names
var first, last, names; names = { "Vahid": "Mohammad Taheri", "Ali": "Ahmadi" }; for (first in names) { last = names[first]; alert(first + " " + last); }
num = 6 minstrel = while num -= 1 num + " Hi"
var minstrel, num; num = 6; minstrel = (function() { var _results; _results = []; while (num -= 1) { _results.push(num + " Hi"); } return _results; })();
Arrays
CoffeeScript با الهام گرفتن از Ruby، به وسیله تعیین محدوده، آرایه را ایجاد میکند. محدوده آرایه به وسیله دو عدد تعیین میشوند که با .. یا ... از هم جدا میشوند.range = [1..5]
var range; range = [1, 2, 3, 4, 5];
firstTwo = ["one", "two", "three"][0..1]
var firstTwo; firstTwo = ["one", "two", "three"].slice(0, 2);
numbers = [0..9] numbers[3..5] = [-3, -4, -5]
my = "my string"[0..2]
words = ["Vahid", "Hamid", "Saeid", "Ali"] alert "Stop" if "Hamid" in words
- در صورت تعریف محدوده آرایه به صورت [..3]numbers (که آرایه numbers از قبل تعریف شده باشد)، خروجی، آرایهای از مقادیر موجود در numbers را از خانه شماره 4 تا انتهای آن برمی گرداند.
- در صورت تعریف محدوده آرایه به صورت [..3-]numbers (که آرایه numbers از قبل تعریف شده باشد)، خروجی، آرایهای از مقادیر موجود در numbers را از خانه انتهایی به میزان 3 خانه به سمت ابتدای آرایه برمیگرداند.
- در صورت عدم تعریف محدوده آرایه و فقط استفاده از [..] یا [...] (یک شکل عمل میکنند)، کل مقادیر آرایه اصلی (که از قبل تعریف شده باشد)، برگردانده میشود.
- تفاوت .. و ... در حالتی که دو عدد برای محدوده تعریف شود، در این است که ... آرایه به صورت عدد انتهایی - 1 تعریف میشود. مثلا [3...0] یعنی خانههای آرایه از 0 تا 2 را به عنوان خروجی برگردان.
Aliases
CoffeeScript شامل یک سری نامهای مستعار است که برای خلاصه نویسی بیشتر بسیار مفید هستند. یکی از آن نام ها، @ است که به جای نوشتن this به کار میرود.@name = "Vahid"
this.name = "Vahid";
User::first = -> @records[0]
User.prototype.first = function() { return this.records[0]; };
alert "OK" if name?
if (typeof name !== "undefined" && name !== null) { alert("OK"); }
name = myName ? "-"
var name; name = typeof myName !== "undefined" && myName !== null ? myName : "-";
user.getAddress()?.getStreetName()
var ref; if ((ref = user.getAddress()) != null) { ref.getStreetName(); }
user.getAddress().getStreetName?()
var base; if (typeof (base = user.getAddress()).getStreetName === "function") { base.getStreetName(); }
We finished week 2 of the 9-week boot camp. This week was AngularJS week. We covered building the front-end of a Single Page App with the AngularJS framework. In particular, we covered topics such as client-side routing, making Ajax calls using the $http service and the $route factory, building custom AngularJS services, working with Google Maps, using Angular UI Bootstrap, and uploading files to services such as FilePicker.io.
private static void Query10() { using (var context = new StoreDbContext()) { var customers = context.Customers; foreach (var customer in customers) { Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family); foreach (var order in customer.Orders) { Console.WriteLine("\t Order Date: {0}", order.Date); } } } }
- Lazy Loading
- Eager Loading
- Explicit Loading
private static void Query11() { using (var context = new StoreDbContext()) { var customer = context.Customers.First(); Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family); foreach (var order in customer.Orders) { Console.WriteLine("\t Order Date: {0}", order.Date); } } }
context.Configuration.LazyLoadingEnabled = false;
private static void Query12() { using (var context = new StoreDbContext()) { var customers = context.Customers; foreach (var customer in customers) { Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family); foreach (var order in customer.Orders) { Console.WriteLine("\t Order Date: {0}", order.Date); } } } }
private static void Query13() { using (var context = new StoreDbContext()) { var customers = context.Customers.Include(c => c.Orders); //var customers = context.Customers.Include("Orders"); foreach (var customer in customers) { Console.WriteLine("Customer Name: {0}, Customer Family: {1}", customer.Name, customer.Family); foreach (var order in customer.Orders) { Console.WriteLine("\t Order Date: {0}", order.Date); } } }
context.OrderDetails.Include(o => o.Order.Customer)
context.Orders.Include(o => o.OrderDetail.Select(od => od.Product))
context.Orders.Include(o => o.Customer).Include(o => o.OrderDetail)
روش Explicit Loading: این روش مانند Lazy Loading میباشد که میتوان دادههای مرتبط را جداگانه فراخوانی کرد اما نه به صورت اتوماتیک توسط EF بلکه به صورت صریح توسط خودمان انجام میشود. این روش حتی اگر navigation propertyهای ما virtual نباشند نیز قابل انجام است. برای انجام این روش از متد DbContext.Entry استفاده میشود.
private static void Query14() { using (var context = new StoreDbContext()) { var customer = context.Customers.First(c => c.Family == "Jamshidi"); context.Entry(customer).Collection(c => c.Orders).Load(); foreach (var order in customer.Orders) { Console.WriteLine(order.Date); } } }
private static void Query15() { using (var context = new StoreDbContext()) { var order = context.Orders.First(); context.Entry(order).Reference(o => o.Customer).Load(); Console.WriteLine(order.Customer.FullName); } }
if (context.Entry(order).Reference(o => o.Customer).IsLoaded) context.Entry(order).Reference(o => o.Customer).Load();
private static void Query16() { using (var context = new StoreDbContext()) { var customer = context.Customers.First(c => c.Family == "Jamshidi"); IQueryable<Order> query = context.Entry(customer).Collection(c => c.Orders).Query(); var order = query.First(); } }
The view 'Index' or its master was not found or no view engine supports the searched locations. The following locations were searched: ~/Areas/Biography/Views/My/Index.cshtml ~/Areas/Biography/Views/Shared/Index.cshtml ~/Views/My/Index.cshtml ~/Views/Shared/Index.cshtml ~/Areas/Biography/Views/My/Index.vbhtml ~/Areas/Biography/Views/Shared/Index.vbhtml ~/Views/My/Index.vbhtml ~/Views/Shared/Index.vbhtml
Come learn the top 7 reasons to use Visual Studio 2017 if you are a C++ developer. Whether you are using the product and are looking to upgrade, or you never used it before but you are considering it now, join us to discover how Visual Studio 2017 raises the bar in terms of C++ productivity. From C++ standards conformance to coding productivity, cross-platform development for Windows, Linux, Android, and iOS and CMake support, these are only a few of the topics we discuss in this demo-packed session.