مسیریابی (Routing) در ASP.NET MVC 5.x
// نصب لودرهای مورد نظر npm install css-loader style-loader -D
//index.html file <html> <head> <title>webpack part 4</title> </head> <body> <h1>webpack is awesome</h1> <p>part 4 of tutorial</p> <div>i have a background</div> <h1>تست فونت !</h1> <script src="/assets/js/bundle.js"> </script> </body> </html>
//webpack.config.js var path = require("path"); var webpack = require("webpack"); module.exports = { context: path.resolve("js"), entry: ['./main.js'] , output: { path: path.resolve("build/js"), publicPath: "assets/js", filename: 'bundle.js' }, devServer: { contentBase: "assets" } , watch: true , module: { loaders: [ { test: /\.css$/ , exclude: /node_modules/ , loader: 'style-loader!css-loader' } ] } }
loader:'style-loader!css-loader'
// main.js file require("./../assets/main.css"); console.log(`i'm bundled by webpack`);
// main.css body{ background-color: #DAA520; }
در تصویر بالا مشخص است که در تگ Head صفحه، یک تگ جدید style، توسط وبپک ایجاد شده و استایل ما به صفحه تزریق شدهاست. همچنین اگر وبپک را به حالت Minify کردن باندل ببریم (در مطلب قبلی نحوهی این کار ذکر شد)، باندل نهایی برای فایلهای css نیز Minify خواهد شد.
استفاده از Sass با کمک وبپک
روش استفاده از Sass نیز تفاوتی با css نخواهد داشت و فقط کافی است Loader آن را در پروژه نصب کنیم و در نهایت آن را در فایل پیکربندی، به وبپک معرفی کنیم. با دستور زیر لودر Sass را در پروژه وارد میکنیم:
// نصب لودر sass npm install -D sass-loader node-sass
( node-sass به عنوان وابستگی لودر sass، در کنار آن نصب شده است)
حال به فایل پیکربندی میرویم و لودر جدید را به قسمت لودرها اضافه میکنیم:
// webpack.config.js module: { loaders: [ { test: /\.css$/ , exclude: /node_modules/ , loader: 'style-loader!css-loader' } ,{ test:/\.scss$/ ,exclude:/node_modules/ ,loader:'style-loader!css-loader!sass-loader' } ] }
در پوشهی assets نیز فایل جدیدی را با عنوان main.scss ساخته و محتوای زیر را در آن وارد میکنیم:
// main.scss $background-color:#DAA520; body{ background-color: $background-color; }
سپس در فایل main.js به جای وارد کردن فایل css قبلی، فایل scss جدید را با کمک require وارد میکنیم و در ادامه وبپک را اجرا میکنیم. خواهیم دید که مانند قبل بدون مشکلی وبپک اجرا شده، فایل scss را به css ترجمه کرده و سپس به کمک بقیه لودرها، به باندل اضافه میکند. استفاده از بقیهی فریمورکهای css مانند Less و ... نیز با کمک لودر آنها به همین صورت قابل انجام است.
استفاده از Autoprefixer
همان طور که تمامی قابلیتهای نسخهی جدید جاوااسکریپت در همهی مرورگرها به صورت سراسری پشتیبانی نمیشود، برای css نیز چنین مشکل مشابهی وجود دارد و برای استفادهی بهینهی از برخی قابلیتها نیاز داریم تا prefixهای مورد نیاز مرورگرهای مختلف را به فایلهای css مان اضافه کنیم. میتوانیم این روند را با کمک یک لودر وبپک، ساده و به صورت خودکار کرد. برای نصب این لودر دستور زیر را وارد میکنیم:
npm install -D autoprefixer-loader
و بعد از نصب شدن آن، در فایل پیکربندی وبپک به لودرهایی که برای فایلهای css و scss اضافه کرده بودیم، این لودر را نیز به صورت زنجیر وار اضافه میکنیم:
//webpack.config.js module: { loaders: [ { test: /\.css$/ , exclude: /node_modules/ , loader: 'style-loader!css-loader!autoprefixer-loader' } ,{ test:/\.scss$/ ,exclude:/node_modules/ ,loader:'style-loader!css-loader!autoprefixer-loader!sass-loader' } ] }
در هر دو لودری که برای css و scss ساخته بودیم، از لودر autoprefixer استفاده کردیم. برای تست اینکه این لودر بدون مشکل کار میکند، در فایل main.scss تغییر زیر را ایجاد میکنیم:
//main.scss $background-color:#DAA520; body{ background-color: $background-color; display: flex; }
حال با اجرای وبپک خواهیم دید که prefixهای مورد نیاز توسط لودر اضافه شده اند ( این لودر از کتابخانهی postcss کمک میگیرد).
باندل کردن تصاویر و فونتها با کمک وبپک
تا اینجا با نحوهی وارد کردن فایلهای استایل، مانند css و ... به باندل آشنا شدیم. در ادامه قصد داریم که تصاویر و فونتها را نیز وارد باندل کنیم. روند کار شبیه به گذشته است و این کار نیز به کمک لودرهای وبپک انجام خواهد شد.
جهت باندل کردن تصاویر و فونتها، به لودر جدیدی با نام url-loader احتیاج داریم. قبل از هر چیزی این لودر را در پروژه با کمک npm نصب میکنیم:
npm install -D url-loader file-loader
(لودر file-loader به عنوان وابستگی مورد نیاز است)
روند همچنان مثل گذشته است و پس از نصب لودر، وارد فایل پیکربندی شده و لودر جدید را به وبپک معرفی میکنیم:
//webpack.config.js file module: { loaders: [ { test: /\.css$/ , exclude: /node_modules/ , loader: 'style-loader!css-loader!autoprefixer-loader' } ,{ test:/\.scss$/ ,exclude:/node_modules/ ,loader:'style-loader!css-loader!autoprefixer-loader!sass-loader' },{ test:/\.(png|jpg|ttf|eot)$/ ,exclude:/node_modules/ ,loader:'url-loader?limit=100000' } ] }
در لودر اضافه شده، پسوند فایلهایی را که قصد داریم به باندل وارد شوند، معرفی میکنیم. در اینجا فرمتهای png , jpg ,ttf, eot ذکر شدهاند.
تنها نکتهی جدید، در مشخص کردن نام لودر وجود دارد و آن نیز قسمت پس از علامت ؟ میباشد. هنگام مشخص کردن اینکه از چه لودری قصد استفاده داریم، میتوانیم با استفاده از ؟ پارامترهایی را به لودر مورد نظر ارسال کنیم. در اینجا به پارامتر limit، مقدار 100000 را دادهایم که برای این لودر به این معناست که اگر حجم فایل در حال پردازش، حجمی بیشتر از این مقدار را داشت، این فایل را به صورت یک لینک جدا از باندل قرار بده. ولی اگر حجمی کمتر از این مقدار داشت، لودر به صورت خودکار فایل را به فرمت Base64 انکود میکند و در درون باندل قرار میدهد.
برای تست اینکه آیا این لودر به درستی کار میکند یا نه، یک تصویر نمونه را در فولدر assets قرار میدهیم و سپس در فایل main.scss تغییرات زیر را انجام میدهیم.
حجم عکس قرار داده شده نزدیک به 400 کیلوبایت است و با مقدار محدودیت مشخص شده، تصویر مورد نظر از باندل توسط وبپک خارج میشود و به صورت جداگانه در بیلد نهایی قرار میگیرد. در تصویر زیر مشخص است که مرورگر درخواست جداگانه ای برای تصویر ارسال کرده است:
حال محدودیت حجم فایل را بالا میبریم و میتوان دید که تصویر در باندل نهایی به صورت انکود شده قرار گرفته است .
قطعا انجام این کار برای تصاویری با حجم بالا مناسب نخواهد بود و برنامه نویس بسته به نیاز بایستی مقدار محدودیت حجم را برای لودر مشخص کند.
در تعریف بالا دیدیم که فرمتهای مورد نیاز برای وارد کردن فونت را نیز علاوه بر تصاویر، برای وبپک مشخص کردهایم. روند وارد کردن فونتها به باندل نیز تفاوتی با تصاویر ندارد و کافی است تعاریف مورد نیاز را در فایلهای css داشته باشیم.
برای مثال فونت ساحل در پوشهی assets قرار داده شده و در فایل main.scss تغییرات زیر انجام شدهاند:
// main.scss $background-color:#DAA520; div{ background-image: url("galaxy.jpg"); } @font-face { font-family: Sahel; src: url('Sahel.eot'); src: url('Sahel.eot?#iefix') format('embedded-opentype'), url('Sahel.woff') format('woff'), url('Sahel.ttf') format('truetype'); font-weight: normal; } @font-face { font-family: Sahel; src: url('Sahel-Bold.eot'); src: url('Sahel-Bold.eot?#iefix') format('embedded-opentype'), url('Sahel-Bold.woff') format('woff'), url('Sahel-Bold.ttf') format('truetype'); font-weight: bold; } @font-face { font-family: Sahel; src: url('Sahel-Black.eot'); src: url('Sahel-Black.eot?#iefix') format('embedded-opentype'), url('Sahel-Black.woff') format('woff'); font-weight: 900; } body{ background-color: $background-color; font-family: 'Sahel'; display: flex; }
تصویر زیر، نتیجهی اجرای وبپک برای تولید باندل است. در تصویر میتوان دید که هم فونتها و هم فایلهای تصاویر، توسط وبپک شناسایی شده و وارد باندل شدهاند:
روش دیگری برای وارد کردن تصاویر نیز موجود است؛ به این صورت که به فرض مثال یک تگ img در اسکریپت ساخته و سپس پروپرتی src آن را با کمک require برابر با آدرس تصویر مورد نظر قرار میدهیم. این روش نیز برای وبپک قابل فهم بوده و فایل وارد باندل میشود. در ادامه مثالی از این روش آورده شده است:
var img = document.createElement("img"); img.width="200px"; img.height="200px"; img.src= require("path to some image");
چند نکتهی پایانی :
1. در فایل پیکربندی همیشه پسوند فایلهایی را که در کلید entry قرار داشتند، مشخص کردیم:
entry:['./main.js','./shared.ts']
با کلیدی با نام resolve در فایل پیکربندی میتوان مشخص کرد در صورتیکه پسوند فایلی مشخص نبود، به ترتیب مشخص شده به دنبال آن بگردد. به طور مثال:
// webpack.config.js resolve:{ extensions:['','.js','.ts'] }
در تعریف بالا ذکر میشود در صورتیکه پسوند فایل ورودی مشخص نبود، ابتدا به دنبال فایل بدون پسوند، سپس فایلهایی با پسوند js و در نهایت به دنبال فایلهایی با پسوند ts بگرد. توجه داشته باشید که ترتیب مشخص کردن پسوند فایلها مهم است و وبپک بر اساس این ترتیب به دنبال فایل مورد نظر خواهد گشت.
حال میتوان مقدار کلید entry را اینطور تعریف کرد:
entry:['./main','./shared']
2.استفاده از فایلهای css ی که در درونشان فونتهای مورد نیاز لینک شدهاند تنها با استفاده از لودر css قابل انجام نیست. به طور مثال استفاده از کتابخانهی بوت استرپ تنها با این لودر ممکن نیست و بایستی لودر url-loader نیز در پروژه نصب شده باشد تا در هنگامیکه وبپک به فونتها برخورد کرد، بتواند آنها را وابسته به شرایط، وارد باندل نهایی کند.
فایلهای پروژه: dntwebpack-part4.zip
معرفی Bit Platform
وب اسمبلی چیست؟
<BlazorMode> ... </BlazorMode> <WebAppDeploymentType> ... </WebAppDeploymentType>
- وجود سیستم Exception handling در سرور و کلاینت (این موضوع به گونه ای بر اساس Best Practiceها پیاده سازی شده که اپلیکیشن را از بروز هر خطایی که بخواهد موجب Crash کردن برنامه شود ایزوله کرده)
- وجود سیستم User Authentication بر اساس JWT که شما در همان ابتدا که از این تمپلیت پروژه جدیدی میسازید صفحات SignIn ، SignUp را خواهید داشت.
- پکیج Bit Blazor UI که بالاتر درمورد آن صحبت کرده ایم از همان ابتدا در TodoTemplate نصب و تنظیم شده تا بتوانید به راحتی صفحات جدید با استفاده از آن بسازید.
- کانفیگ استاندارد Swagger در سمت سرور.
- ارسال ایمیل در روند SignUp.
- وجود خاصیت AutoInject برای سادهسازی تزریق وابستگی ها.
- و بسیاری موراد دیگر که در داکیومنتهای پروژه میتوانید آنهارا ببینید.
- شما میتوانید این پروژه را در گیتهاب مشاهده کنید.
- برای اشکالات یا قابلیت هایی که میخواهید برطرف شود Issue ثبت کنید.
- پروژه را Fork کنید و Star دهید.
- ایشوهایی که وجود دارد را برطرف کنید و Pull Request ارسال کنید.
- برای در جریان بودن از روند توسعه در جلسات برنامه ریزی (Planning Meeting) و گزارشات هفتگی (Standup Meeting ) که همه اینها در Microsoft Teams برگزار میشود شرکت کنید.
Url Routing در ASP.Net WebForms
Lazy Loading در AngularJS
.state('state2', { url: '/state2', template: '<div>{{st2Ctrl.msg}}</div>', controller: 'state2Controller as st2Ctrl',
var deps = ['app/messageService.js', 'app/state2Controller.js'];
C# 7 - Binary literals and digit separators
در C# 7.2، جهت بهبود خوانایی، جداکنندهی ارقام را درست پس از پیشوندهای 0b و 0x نیز میتوان قرار داد:
class ClassCS72 { const int intLiteral = 100_000; const int binaryLiteral = 0b_0101_0101; const int hexLiteral = 0x_FF_FF; }
در پروژهی iOS، در فایل AppDelegate.cs، بعد از Forms.Init، کد زیر را کپی کنید:
SfListViewRenderer.Init();
همین کد را در MainPage.xaml.cs در پروژه UWP، قبل از LoadApplication قرار دهید. نیازی به انجام کاری در Android نیست.
سپس Product Key این محصول را به دست آورده و در پروژه XamApp، فولدر Views در فایل SyncfusionLicense قرار دهید.
حال برای نمایش لیستی از محصولات، ابتدا کلاس Product را ایجاد میکنیم. چه در زمانیکه یک Rest api را در سمت سرور فراخوانی میکنیم و چه زمانیکه با دیتابیس بر روی گوشی یعنی Sqlite کار میکنیم، در نهایت لیستی از یک کلاس را داریم (در اینجا Product).
public class Product : Bindable { public int Id { get; set; } public string Name { get; set; } public bool IsActive { get; set; } public decimal Price { get; set; } }
در یک View Model جدید با نام ProductsViewModel، در OnNavigatedToAsync، دیتا را از سرور یا دیتابیس، بر روی گوشی دریافت میکنیم؛ اما در این مثال، برای راحتی بیشتر یک List را New میکنیم:
public class ProductsViewModel : BitViewModelBase { public List<Product> Products { get; set; } public async override Task OnNavigatedToAsync(INavigationParameters parameters) { Products = new List<Product> // getting products from server or sqlite database { new Product { Id = 1, IsActive = true, Name = "Product1" , Price = 12.2m /* m => decimal */ }, new Product { Id = 2, IsActive = false, Name = "Product2" , Price = 14 }, new Product { Id = 3, IsActive = true, Name = "Product3" , Price = 11 }, }; await base.OnNavigatedToAsync(parameters); } }
حال نوبت به دادن یک Template میرسد. مثلا فرض کنید میخواهیم نام را درون یک Label نمایش دهیم و بر اساس فعال یا غیر فعال بودن Product، یک Checkbox را تغییر داده، تیک بزنیم یا نزنیم و در نهایت نمایش قیمت را در یک Label دیگر خواهیم داشت.
<sfListView:SfListView ItemsSource="{Binding Products}"> <sfListView:SfListView.ItemTemplate> <DataTemplate> <FlexLayout x:DataType="model:Product" Direction="Row"> <Label FlexLayout.Basis="50%" Text="{Binding Name}" VerticalTextAlignment="Center" /> <bitControls:BitCheckbox InputTransparent="True" FlexLayout.Basis="25%" IsChecked="{Binding IsActive}" /> <Label FlexLayout.Basis="25%" Text="{Binding Price}" VerticalTextAlignment="Center" /> </FlexLayout> </DataTemplate> </sfListView:SfListView.ItemTemplate> </sfListView:SfListView>
همانطور که میبینید، در DataTemplate از Flex Layout استفاده شده است. Flex Layout در کنار Grid, Stack, Relative, Absolute و سایر Layoutهای Xamarin Forms در پروژه قابلیت استفاده دارد و مزیتهای خاص خود را دارد.
این Data Template توسط List View، حداکثر سه بار ساخته میشود؛ چون View Model در لیست مثال خود، سه Product دارد. خود List View تکنیکهای Virtualization و Cell Reuse را بدون نیاز به هیچ کد اضافهای هندل میکند و Performance خوبی دارد. در View مربوطه یعنی ProductsView.xaml، هر Binding ای (مثل Binding Products) به View Model اشاره میکند، اما درون Data Template، هر Binding به Product ای اشاره میکند که آن ردیف List View، دارد نمایشاش میدهد. برای همین x:DataType را روی Flex Layout درون Data Template به Product وصل کردهایم. در این صورت اگر بنویسیم Binding N_ame، به ما خطا داده میشود که کلاس Product هیچ Property با نام N_ame ندارد که خطای درستی است.
روی BitCheckbox مقدار InputTransparent را برابر با True دادهایم که باعث میشود کلیک روی Checkbox عملا در نظر گرفته نشود. این منطقی است، زیرا عوض کردن مقدار Checkbox در این مثال ما ذخیره نمیشود و کاربرد نمایشی دارد و فقط باعث گیج شدن کاربر میشود.
کنترل BitCheckbox از مجموعه کنترلهای Bit است که اخیرا با BitDatePicker آن آشنا شدهاید. برای آشنایی با نحوه افزودن این کنترلها به یک پروژه، به مستندات Bit Framework مراجعه کنید. خود Syncfusion نیز Checkbox دارد.
حال فرض کنید که قرار است دکمهای برای هر ردیف List View داشته باشیم که با زدن روی آن، اطلاعات Product به سرور ارسال شود و جزئیات بیشتری دریافت و در قالب یک Alert نمایش داده شود. برای این کار، ابتدا به Data Template که Flex Layout است، یک دکمه اضافه میکنیم. سپس Command آن دکمه را به View Model بایند میکنیم. در آن Command البته احتیاج داریم بدانیم درخواست نمایش جزئیات بیشتر، برای کدام Product داده شده. این مهم با Command Parameter شدنی است.
برای پیاده سازی این مثال، در سمت View Model داریم:
public BitDelegateCommand<Product> ShowProductDetailsCommand { get; set; }public IUserDialogs UserDialogs { get; set; } async Task ShowProductDetails(Product product) { string productDetail = $"Product: {product.Name}'s more info: ..."; // get more info from server. await UserDialogs.AlertAsync(productDetail, "Product Detail"); }
کامند ShowProductDetailCommand یک پارامتر را از جنس Product میگیرد و آن Product ای است که روی دکمه آن کلیک شدهاست. با Clone کردن آخرین نسخه XamApp و درخواست نمایش صفحهی Products در App.xaml.cs به صورت زیر و اجرای برنامه، میتوانید درک بهتری از عملکرد آن داشته باشید:
await NavigationService.NavigateAsync("/Nav/Products", animated: false);
سپس در View مربوطه داریم:
...<Button Command="{Binding ShowProductDetailsCommand}" CommandParameter="{Binding .}" Text="Detail..." /> </FlexLayout> </DataTemplate>
CommandParameter اگر برابر با Binding Id میبود، به Command در سمت View Model، بجای کل Product، فقط Id آن ارسال میشد. ولی Show Product Detail Command منتظر یک Product کامل است، نه فقط Id آن. با نوشتن
CommandParameter="{Binding .}"
کل Product با کلیک روی دکمه به Command ارسال میشود.
اکنون اگر پروژه را Build کنید، خطایی را از x:DataType خواهید گرفت که منطقی است. اگر Binding Name و Binding Price دو Property با نامهای Name و Price را از کلاس Product جستجو میکنند، پس قاعدتا ShowProductDetailCommand نیز در همان کلاس مدل، یعنی Product جستجو میشود! ولی میدانیم که این Command در View Model ما یعنی ProductsViewModel است. برای حل این مشکل، به جای Binding از bit:ViewModelBinding استفاده میکنیم:
Command="{bit:ViewModelBinding ShowProductDetailsCommand}"
در این صورت، بجای جستجو کردن ShowProductDetailCommand در کلاس Product، این را در ProductsViewModel جستجو میکند که منجر به خروجی درست میشود.
این List View دارای امکاناتی چون Infinite loading، Pull to refresh و Grouping-Sorting-Filtering و ... است که میتوانید از روی مستندات خوب Syncfusion، آنها را راه اندازی کنید و اگر به مشکلی برخوردید نیز اینجا بپرسید. همچنین نگاهی به لیست 129 کنترل دیگر بیاندازید و ببینید که در برنامههای خود از کدام یک از آنها میتوانید استفاده کنید.
چگونه میتوان DateTime را به عنوان پارامتر مسیریابی ارسال کرد؟
Blazor از پارامترهایی از نوع DateTime، در حین مسیریابی پشتیبانی میکند؛ با این شرط که قید datetime در حین مسیریابی صریحا ذکر شود:
@page "/route/{parameter:datetime}"
@page "/" @inject NavigationManager NavManager <button @onclick="CurrentTime">Current Time</button> @code { public void CurrentTime() { NavManager.NavigateTo("/time/" + DateTime.Now); } }
@page "/time/{param:datetime}" <h3>Time</h3> <p>@Param</p> @code { [Parameter] public DateTime Param { get; set; } }
چگونه میتوان به عنوان صفحهی جاری دسترسی یافت؟
برای اینکار نیاز است از JavaScript interop استفاده کرد. ابتدا برای مثال تابع عمومی getTitle را که بر اساس DOM API مرورگر کار میکند، تهیه میکنیم:
window.getTitle = () => { return document.title; };
@page "/" @inject IJSRuntime jsRuntime <h2>Page Title: @Title</h2> <button class="btn btn-primary" @onclick="@GetTitle">Get Title</button> @code { public string Title = ""; public async void GetTitle() { Title = await jsRuntime.InvokeAsync<string>("getTitle"); } }
چگونه میتوان مسیری را از طریق کدهای برنامه در یک برگهی مجزای مرورگر باز کرد؟
در اینجا نیز میتوان با استفاده از JavaScript interop، متد استاندارد open مرورگر را فراخوانی کرد و پارامتر اول آنرا به url مدنظر و پارامتر بعدی آنرا به blank_ تنظیم کرد تا مرورگر آدرس درخواستی را در یک برگهی جدید باز کند:
@inject IJSRuntime jsRuntime <button @onclick="NavigateToNewTab">New Tab Navigation</button> @code { public async Task NavigateToNewTab() { string url = "/counter"; await jsRuntime.InvokeAsync<object>("open", url, "_blank"); } }