نظرات مطالب
سلام. میخواستم بدونم چطور میشه editor-label و editor-field رو در کنار هم (در یک خط) قرار داد؟ چون به صورت پیش فرض این دو زیر هم قرار میگیرند که در مواردی که پروپرتیها زیاد باشد باعث Scroll خوردن میشود.
ممنون.
سؤال: چه زمانی از متدهای async و چه زمانی از متدهای همزمان بهتر است استفاده شود؟
از متدهای همزمان متداول برای انجام امور ذیل استفاده نمائید:
- جهت پردازش اعمالی ساده و سریع
- اعمال مدنظر بیشتر قرار است بر روی CPU اجرا شوند و از مرزهای IO سیستم عبور نمیکنند.
و از متدهای غیرهمزمان برای پردازش موارد زیر کمک بگیرید:
- از وب سرویسهایی استفاده میکنید که متدهای نگارش async را نیز ارائه دادهاند.
- عمل مدنظر network-bound و یا I/O-bound است بجای CPU-bound. یعنی از مرزهای IO سیستم عبور میکند.
- نیاز است چندین عملیات را به موازات هم اجرا کرد.
- نیاز است مکانیزمی را جهت لغو یک عملیات طولانی ارائه دهید.
مزایای استفاده از متدهای async در ASP.NET
استفاده از await در ASP.NET، ساختار ذاتی پروتکل HTTP را که اساسا یک synchronous protocol، تغییر نمیدهد. کلاینت، درخواستی را ارسال میکند و باید تا زمان آماده شدن نتیجه و بازگشت آن از طرف سرور، صبر کند. نحوهی تهیهی این نتیجه، خواه async باشد و یا حتی همزمان، از دید مصرف کننده کاملا مخفی است. اکنون سؤال اینجا است که چرا باید از متدهای async استفاده کرد؟
- پردازش موازی: میتوان چند Task را مثلا توسط Task.WhenAll به صورت موازی با هم پردازش کرده و در نهایت نتیجه را سریعتر به مصرف کننده بازگشت داد. اما باید دقت داشت که این Taskها اگر I/O bound باشند، ارزش پردازش موازی را دارند و اگر compute bound باشند (اعمال محاسباتی)، صرفا یک سری ترد را ایجاد و مصرف کردهاید که میتوانستهاند به سایر درخواستهای رسیده پاسخ دهند.
- خالی کردن تردهای در حال انتظار: در اعمالی که disk I/O و یا network I/O دارند، پردازش موازی و اعمال async به شدت مقیاس پذیری سیستم را بالا میبرند. به این ترتیب worker thread جاری (که تعداد آنها محدود است)، سریعتر آزاد شده و به worker pool بازگشت داده میشود تا بتواند به یک درخواست دیگر رسیده سرویس دهد. در این حالت میتوان با منابع کمتری، درخواستهای بیشتری را پردازش کرد.
ایجاد Asynchronous HTTP Handlers در ASP.Net 4.5
در نگارشهای پیش از دات نت 4.5، برای نوشتن فایلهای ashx غیرهمزمان میبایستی اینترفیس IHttpAsynchHandler پیاده سازی میشد که نحوهی کار با آن از مدل APM پیروی میکرد؛ نیاز به استفاده از یک سری callback داشت و این عملیات باید طی دو متد پردازش میشد. اما در دات نت 4.5 و با معرفی امکانات async و await، نگارش سازگاری با پیاده سازی کلاس پایه HttpTaskAsyncHandler فراهم شده است.
برای آزمایش آن، یک برنامهی جدید ASP.NET Web forms نگارش 4.5 یا بالاتر را ایجاد کنید. سپس از منوی پروژه، گزینهی Add new item یک Generic handler به نام LogRequestHandler.ashx را به پروژه اضافه نمائید.
زمانیکه این فایل به پروژه اضافه میشود، یک چنین امضایی را دارد:
IHttpHandler آنرا اکنون به HttpTaskAsyncHandler تغییر دهید. سپس پیاده سازی ابتدایی آن به شکل زیر خواهد بود:
واژهی کلیدی async را نیز جهت استفاده از await به نسخهی غیرهمزمان آن اضافه کردهایم.
در این مثال آدرس یک فید RSS از طریق کوئری استرینگ rssfeedURL دریافت شده و سپس محتوای آن به کمک متد DownloadStringTaskAsync دریافت و بازگشت داده میشود.
برای آزمایش آن، مسیر ذیل را درخواست دهید:
کاربردهای فایلهای ashx برای مثال ارائه فیدهای XML ایی یک سایت، ارائه منبع نمایش تصاویر پویا از بانک اطلاعاتی، ارائه JSON برای افزونههای auto complete جیکوئری و امثال آن است. مزیت آنها سربار بسیار کم است؛ زیرا وارد چرخهی طول عمر یک صفحهی aspx معمولی نمیشوند.
صفحات async در ASP.NET 4.5
در قسمتهای قبل مشاهده کردیم که در برنامههای دسکتاپ، به سادگی میتوان امضای روالهای رخداد گردان را به async تغییر داد و ... برنامه کار میکند. به علاوه از مزیت استفاده از واژه کلیدی await نیز در آنها برخوردار خواهیم شد. اما ... هرچند این روش در وب فرمها نیز صادق است (مثلا public void Page_Load را به public async void Page_Load میتوان تبدیل کرد) اما اعضای تیم ASP.NET آنرا در مورد برنامههای وب فرم توصیه نمیکنند:
Async void event handlers تنها در مورد تعداد کمی از روالهای رخدادگردان ASP.NET Web forms کار میکنند و از آنها تنها برای تدارک پردازشهای ساده میتوان استفاده کرد. اگر کار در حال انجام اندکی پیچیدگی دارد، «باید» از PageAsyncTask استفاده نمائید. علت اینجا است که Async void یعنی fire and forget (کاری را شروع کرده و فراموشش کنید). این روش در برنامههای دسکتاپ کار میکند، زیرا این برنامهها مدل طول عمر متفاوتی داشته و تا زمانیکه برنامه از طرف OS خاتمه نیابد، مشکلی نخواهند داشت. اما برنامههای بدون حالت وب متفاوتند. اگر عملیات async پس از خاتمهی طول عمر صفحه پایان یابد، دیگر نمیتوان اطلاعات صحیحی را به کاربر ارائه داد. بنابراین تا حد ممکن از تعاریف async void در برنامههای وب خودداری کنید.
تبدیل روالهای رخدادگردان متداول وب فرمها به نسخهی async شامل دو مرحله است:
الف) از متد جدید RegisterAsyncTask که در کلاس پایه Page قرار دارد برای تعریف یک PageAsyncTask استفاده کنید:
با استفاده از System.Web.UI.PageAsyncTask میتوان یک async Task را در روالهای رخدادگردان ASP.NET مورد استفاده قرار داد.
ب) سپس در کدهای فایل aspx، نیاز است خاصیت async را نیز true نمائید:
تغییر تنظیمات IIS برای بهره بردن از پردازشهای Async
اگر از ویندوزهای 7، ویستا و یا 8 استفاده میکنید، IIS آنها به صورت پیش فرض به 10 درخواست همزمان محدود است.
بنابراین تنظیمات ذیل مرتبط است به یک ویندوز سرور و نه یک work station :
به IIS manager مراجعه کنید. سپس برگهی Application Pools آنرا باز کرده و بر روی Application pool برنامه خود کلیک راست نمائید. در اینجا گزینهی Advanced Settings را انتخاب کنید. در آن Queue Length را به مثلا عدد 5000 تغییر دهید. همچنین در دات نت 4.5 عدد 5000 برای MaxConcurrentRequestsPerCPU نیز مناسب است. به علاوه عدد connectionManagement/maxconnection را نیز به 12 برابر تعداد هستههای موجود تغییر دهید.
از متدهای همزمان متداول برای انجام امور ذیل استفاده نمائید:
- جهت پردازش اعمالی ساده و سریع
- اعمال مدنظر بیشتر قرار است بر روی CPU اجرا شوند و از مرزهای IO سیستم عبور نمیکنند.
و از متدهای غیرهمزمان برای پردازش موارد زیر کمک بگیرید:
- از وب سرویسهایی استفاده میکنید که متدهای نگارش async را نیز ارائه دادهاند.
- عمل مدنظر network-bound و یا I/O-bound است بجای CPU-bound. یعنی از مرزهای IO سیستم عبور میکند.
- نیاز است چندین عملیات را به موازات هم اجرا کرد.
- نیاز است مکانیزمی را جهت لغو یک عملیات طولانی ارائه دهید.
مزایای استفاده از متدهای async در ASP.NET
استفاده از await در ASP.NET، ساختار ذاتی پروتکل HTTP را که اساسا یک synchronous protocol، تغییر نمیدهد. کلاینت، درخواستی را ارسال میکند و باید تا زمان آماده شدن نتیجه و بازگشت آن از طرف سرور، صبر کند. نحوهی تهیهی این نتیجه، خواه async باشد و یا حتی همزمان، از دید مصرف کننده کاملا مخفی است. اکنون سؤال اینجا است که چرا باید از متدهای async استفاده کرد؟
- پردازش موازی: میتوان چند Task را مثلا توسط Task.WhenAll به صورت موازی با هم پردازش کرده و در نهایت نتیجه را سریعتر به مصرف کننده بازگشت داد. اما باید دقت داشت که این Taskها اگر I/O bound باشند، ارزش پردازش موازی را دارند و اگر compute bound باشند (اعمال محاسباتی)، صرفا یک سری ترد را ایجاد و مصرف کردهاید که میتوانستهاند به سایر درخواستهای رسیده پاسخ دهند.
- خالی کردن تردهای در حال انتظار: در اعمالی که disk I/O و یا network I/O دارند، پردازش موازی و اعمال async به شدت مقیاس پذیری سیستم را بالا میبرند. به این ترتیب worker thread جاری (که تعداد آنها محدود است)، سریعتر آزاد شده و به worker pool بازگشت داده میشود تا بتواند به یک درخواست دیگر رسیده سرویس دهد. در این حالت میتوان با منابع کمتری، درخواستهای بیشتری را پردازش کرد.
ایجاد Asynchronous HTTP Handlers در ASP.Net 4.5
در نگارشهای پیش از دات نت 4.5، برای نوشتن فایلهای ashx غیرهمزمان میبایستی اینترفیس IHttpAsynchHandler پیاده سازی میشد که نحوهی کار با آن از مدل APM پیروی میکرد؛ نیاز به استفاده از یک سری callback داشت و این عملیات باید طی دو متد پردازش میشد. اما در دات نت 4.5 و با معرفی امکانات async و await، نگارش سازگاری با پیاده سازی کلاس پایه HttpTaskAsyncHandler فراهم شده است.
برای آزمایش آن، یک برنامهی جدید ASP.NET Web forms نگارش 4.5 یا بالاتر را ایجاد کنید. سپس از منوی پروژه، گزینهی Add new item یک Generic handler به نام LogRequestHandler.ashx را به پروژه اضافه نمائید.
زمانیکه این فایل به پروژه اضافه میشود، یک چنین امضایی را دارد:
public class LogRequestHandler : IHttpHandler
using System; using System.Net; using System.Text; using System.Threading.Tasks; using System.Web; namespace Async14 { public class LogRequestHandler : HttpTaskAsyncHandler { public override async Task ProcessRequestAsync(HttpContext context) { string url = context.Request.QueryString["rssfeedURL"]; if (string.IsNullOrWhiteSpace(url)) { context.Response.Write("Rss feed URL is not provided"); } using (var webClient = new WebClient {Encoding = Encoding.UTF8}) { webClient.Headers.Add("User-Agent", "LogRequestHandler 1.0"); var rssfeed = await webClient.DownloadStringTaskAsync(url); context.Response.Write(rssfeed); } } public override bool IsReusable { get { return true; } } public override void ProcessRequest(HttpContext context) { throw new Exception("The ProcessRequest method has no implementation."); } } }
در این مثال آدرس یک فید RSS از طریق کوئری استرینگ rssfeedURL دریافت شده و سپس محتوای آن به کمک متد DownloadStringTaskAsync دریافت و بازگشت داده میشود.
برای آزمایش آن، مسیر ذیل را درخواست دهید:
http://localhost:4207/LogRequestHandler.ashx?rssfeedURL=https://www.dntips.ir/feed/latestchanges
صفحات async در ASP.NET 4.5
در قسمتهای قبل مشاهده کردیم که در برنامههای دسکتاپ، به سادگی میتوان امضای روالهای رخداد گردان را به async تغییر داد و ... برنامه کار میکند. به علاوه از مزیت استفاده از واژه کلیدی await نیز در آنها برخوردار خواهیم شد. اما ... هرچند این روش در وب فرمها نیز صادق است (مثلا public void Page_Load را به public async void Page_Load میتوان تبدیل کرد) اما اعضای تیم ASP.NET آنرا در مورد برنامههای وب فرم توصیه نمیکنند:
Async void event handlers تنها در مورد تعداد کمی از روالهای رخدادگردان ASP.NET Web forms کار میکنند و از آنها تنها برای تدارک پردازشهای ساده میتوان استفاده کرد. اگر کار در حال انجام اندکی پیچیدگی دارد، «باید» از PageAsyncTask استفاده نمائید. علت اینجا است که Async void یعنی fire and forget (کاری را شروع کرده و فراموشش کنید). این روش در برنامههای دسکتاپ کار میکند، زیرا این برنامهها مدل طول عمر متفاوتی داشته و تا زمانیکه برنامه از طرف OS خاتمه نیابد، مشکلی نخواهند داشت. اما برنامههای بدون حالت وب متفاوتند. اگر عملیات async پس از خاتمهی طول عمر صفحه پایان یابد، دیگر نمیتوان اطلاعات صحیحی را به کاربر ارائه داد. بنابراین تا حد ممکن از تعاریف async void در برنامههای وب خودداری کنید.
تبدیل روالهای رخدادگردان متداول وب فرمها به نسخهی async شامل دو مرحله است:
الف) از متد جدید RegisterAsyncTask که در کلاس پایه Page قرار دارد برای تعریف یک PageAsyncTask استفاده کنید:
using System; using System.Net; using System.Text; using System.Threading.Tasks; using System.Web.UI; namespace Async14 { public partial class _default : Page { protected void Page_Load(object sender, EventArgs e) { RegisterAsyncTask(new PageAsyncTask(LoadSomeData)); } public async Task LoadSomeData() { using (var webClient = new WebClient { Encoding = Encoding.UTF8 }) { webClient.Headers.Add("User-Agent", "LogRequest 1.0"); var rssfeed = await webClient.DownloadStringTaskAsync("url"); //listcontacts.DataSource = rssfeed; } } } }
ب) سپس در کدهای فایل aspx، نیاز است خاصیت async را نیز true نمائید:
<%@ Page Language="C#" AutoEventWireup="true" Async="true" CodeBehind="default.aspx.cs" Inherits="Async14._default" %>
تغییر تنظیمات IIS برای بهره بردن از پردازشهای Async
اگر از ویندوزهای 7، ویستا و یا 8 استفاده میکنید، IIS آنها به صورت پیش فرض به 10 درخواست همزمان محدود است.
بنابراین تنظیمات ذیل مرتبط است به یک ویندوز سرور و نه یک work station :
به IIS manager مراجعه کنید. سپس برگهی Application Pools آنرا باز کرده و بر روی Application pool برنامه خود کلیک راست نمائید. در اینجا گزینهی Advanced Settings را انتخاب کنید. در آن Queue Length را به مثلا عدد 5000 تغییر دهید. همچنین در دات نت 4.5 عدد 5000 برای MaxConcurrentRequestsPerCPU نیز مناسب است. به علاوه عدد connectionManagement/maxconnection را نیز به 12 برابر تعداد هستههای موجود تغییر دهید.
عموما تزریق وابستگیهای کلاسها، در برنامههای Angular صورت میگیرند. برای مثال در یک NgModule در قسمت providers آن نام کلاسی را معرفی میکنیم و سپس میتوان این کلاس را به سازندهی کامپوننتها تزریق کرد و از امکانات آن استفاده کرد. اما سیستم تزریق وابستگیهای Angular محدود به تزریق وهلههای کلاسها نیست و میتوان قسمت providers را با یک سری شیء تعریف شدهی با {} نیز مقدار دهی کرد. در اینجا میتوان یک token را به یک وابستگی انتساب داد.
انواع providers در Angular
سیستم تزریق وابستگیهای Angular، تامین کنندههای ذیل را نیز به همراه دارد:
- تامین کنندهی مقادیر که با useValue مشخص میشود.
- تامین کنندهی Factoryها که با useFactory تعریف خواهد شد.
- تامین کنندهی کلاسها که با useClass تعریف میشود.
- تامین کنندهی کلاسهایی با نامهای مستعار که توسط useExisting مشخص میشود.
یک تامین کننده مشخص میکند که سیستم تزریق کنندهی وابستگیها، با درخواست توکن/کلیدی مشخص، چه وابستگی را باید وهله سازی کند.
تزریق وابستگیهایی از نوع ثوابت در برنامههای Angular
فرض کنید برنامهی Angular شما در مسیر دیگری نسبت به Web API سمت سرور آن قرار دارد. به همین جهت در تمام سرویسهای برنامه نیاز به تعریف مسیر پایهی Web API مانند API_BASE_HREF را خواهید داشت. یک روش حل این مساله، تعریف این ثابت به صورت یک وابستگی و سپس تزریق آن به کلاسهای سرویسها و یا کامپوننتهای برنامه است:
- در اینجا چندین مثال از تکمیل قسمت providers یک ماژول را با شیءهای token دار provide مشاهده میکنید. هر provide یک token را مشخص میکند که از آن جهت دریافت مقدار وابستگی منتسب به آن استفاده خواهد شد.
- در این مثال، حالتهای مختلفی از تامین کنندهی useValue را نیز مشاهده میکنید. انتساب یک رشته، یک مقدار boolean و یا یک مقدار که در زمان انتساب محاسبه خواهد شد مانند Math.random.
- همچنین در اینجا میتوان در قسمت useValue مانند emailApiConfig، یک شیء را نیز تعریف کرد. علت استفادهی از Object.freeze، تعریف این شیء به صورت read only است.
- در حین تعریف provideها اگر کلید توکن بکار رفته یکی باشد، آخرین مقدار، مابقی را بازنویسی میکند؛ مانند حالت languages که در اینجا دوبار تعریف شدهاست. اما با ذکر خاصیت multi، میتوان به کلید languages به صورت یک آرایه دسترسی یافت و در این حالت مقادیر آن بازنویسی نمیشوند.
اکنون برای استفادهی از این توکنهای تعریف شده توسط سیستم تزریق وابستگیها، میتوان به صورت ذیل عمل کرد:
در اینجا هر توکن توسط ویژگی Inject به سازندهی کلاس تزریق شدهاست. از این جهت آنها را public تعریف کردهایم که بتوان در قالب این کامپوننت، به مقادیر تزریق شده، دسترسی یافت:
با این خروجی:
در اینجا همانطور که مشاهده میکنید، languages از نوع multi: true به یک آرایه تبدیل شدهاست و یا emailApiConfig نیز یک شیء است که توسط کلیدهای آن میتوان به مقادیر متناظر آن دسترسی یافت. Random نیز تنها یکبار دریافت شدهاست و مهم نیست که چندبار صدا زده شود؛ همواره مقدار آن مساوی اولین مقداری است که در زمان انتساب دریافت میکند.
تزریق تنظیمات برنامه توسط تامین کنندهی مقادیر
یک نمونه از تزریق شیء emailApiConfig: any را در مثال فوق ملاحظه کردید. روش بهتر و نوع دار آن به صورت ذیل است. ابتدا یک فایل جدید thismodule.config.ts یا app.config.ts را ایجاد میکنیم:
تاکنون توکنهای تعریف شده را توسط یک رشتهی ثابت مانند "API_BASE_HREF" تعریف کردیم. مشکل این روش، امکان تداخل آنها در یک برنامهی بزرگ است. به همین جهت روش توصیه شده، قرار دادن این کلید داخل یک InjectionToken است تا همواره بتوان به یک توکن منحصربفرد در طول عمر برنامه دست یافت که نمونهی آنرا در تعریف APP_CONFIG مشاهده میکنید. در برنامه اگر دو new InjectionToken، با یک سازندهی یکسان تعریف شوند، با هم مساوی نخواهند بود و توکن نهایی آن منحصربفرد است:
سپس نوع تنظیمات را توسط اینترفیس IThisModuleConfig تعریف کردهایم (که نسبت به استفادهی از any یک پیشرفت محسوب میشود). در آخر وهلهای از این اینترفیس را به نحوی که مشاهده میکنید export کردهایم.
اکنون نحوهی تعریف تزریق وابستگی از نوع IThisModuleConfig در یک NgModule به صورت ذیل است:
اینبار توکن تعریف شده توسط InjectionToken مشخص شدهاست و مقدار آن توسط ThisModuleConfig تامین خواهد شد.
در آخر، تزریق آن به سازندهی یک کامپوننت بر اساس توکن APP_CONFIG و از نوع مشخص اینترفیس آن خواهد بود:
تزریق وابستگیها توسط تامین کنندهی Factory ها
تا اینجا useValue را بررسی کردیم. نوع دیگر تامین کنندههای قابل تعریف، useFactory هستند:
در اینجا روش استفادهی از useFactory را مشاهده میکنید. کار کرد آن با useValue دقیقا یکی است؛ یک توکن را مشخص میکنیم و سپس مقداری به آن نسبت داده میشود. اما در اینجا میتوان یک متد را که بیانگر نحوهی تامین این مقدار است نیز مشخص کرد و نسبت به حالت useValue که تنها یک مقدار ثابت و مشخص را دریافت میکند، انعطاف پذیری بیشتر دارد و میتوان منطق سفارشی خاصی را نیز در اینجا پیاده سازی کرد.
روش استفادهی از آن نیز همانند توکنهای useValue است که توسط ویژگی Inject مشخص میشوند:
حالت useFactory علاوه بر امکان دریافت یک منطق سفارشی توسط یک function، امکان دریافت یک سری وابستگی را نیز دارد. فرض کنید کلاس سرویس خودرو به صورت زیر تعریف شدهاست که دارای وابستگی از نوع HttpClient تزریق شدهی در سازندهی آن است:
در این حالت useFactory آن جهت تامین پارامتر سازندهی new CarService، به همراه متدی خواهد بود که پارامتری از نوع HttpClient را دریافت میکند:
در اینجا برای تامین این پارامتر سازنده، خاصیت دیگری به نام deps قابل تعریف است که میتواند یک یا چند سرویس و وابستگی را تزریق و تامین کند. برای مثال سرویس HttpClient در اینجا توسط deps: [HttpClient] تزریق شدهاست.
تزریق وابستگیها توسط تامین کنندهی کلاسها
تا اینجا useValue و useFactory را بررسی کردیم. نوع دیگر تامین کنندههای قابل تعریف، useClass هستند. در حالت استفادهی useClass، نام یک نوع مشخص میشود و سپس Angular وهلهای از آنرا تامین خواهد کرد. در این حالت اگر این وابستگی دارای پارامترهای تزریق شدهای در سازندهی آن باشد، آنها نیز به صورت خودکار وهله سازی میشوند.
این حالت دقیقا معادل تعریف متداول سرویس ذیل است؛ با این تفاوت که توکن آن مساوی مقدار سفارشی Car_Service_Name1 است:
تزریق وابستگیها توسط تامین کنندهی کلاسهایی با نامهای مستعار
چگونه میتوان دو تامین کننده را برای کلاسی مشابه، با توکنهایی متفاوت ایجاد کرد؟ در این حالت از useExisting استفاده میشود:
در اینجا CarService توسط دو توکن مختلف در معرض دید قرار گرفتهاست. باید دقت داشت که درخواست "Car_Service_Token2" دقیقا همان وهلهی ایجاد شدهی توسط توکن "Car_Service_Name1" را بازگشت میدهد و وهلهی جدیدی در این حالت ایجاد نخواهد شد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
انواع providers در Angular
سیستم تزریق وابستگیهای Angular، تامین کنندههای ذیل را نیز به همراه دارد:
- تامین کنندهی مقادیر که با useValue مشخص میشود.
- تامین کنندهی Factoryها که با useFactory تعریف خواهد شد.
- تامین کنندهی کلاسها که با useClass تعریف میشود.
- تامین کنندهی کلاسهایی با نامهای مستعار که توسط useExisting مشخص میشود.
یک تامین کننده مشخص میکند که سیستم تزریق کنندهی وابستگیها، با درخواست توکن/کلیدی مشخص، چه وابستگی را باید وهله سازی کند.
تزریق وابستگیهایی از نوع ثوابت در برنامههای Angular
فرض کنید برنامهی Angular شما در مسیر دیگری نسبت به Web API سمت سرور آن قرار دارد. به همین جهت در تمام سرویسهای برنامه نیاز به تعریف مسیر پایهی Web API مانند API_BASE_HREF را خواهید داشت. یک روش حل این مساله، تعریف این ثابت به صورت یک وابستگی و سپس تزریق آن به کلاسهای سرویسها و یا کامپوننتهای برنامه است:
@NgModule({ imports: [ CommonModule, InjectionBeyondClassesRoutingModule ], declarations: [TestProvidersComponent], providers: [ { provide: "API_BASE_HREF", useValue: "http://localhost:5000" }, { provide: "APP_BASE_HREF", useValue: document.location.pathname }, { provide: "IS_PROD", useValue: true }, { provide: "APIKey", useValue: "XYZ1234ABC" }, { provide: "Random", useValue: Math.random() }, { provide: "emailApiConfig", useValue: Object.freeze({ apiKey: "email-key", context: "registration" }) }, { provide: "languages", useValue: "en", multi: true }, { provide: "languages", useValue: "fa", multi: true } ] }) export class InjectionBeyondClassesModule { }
- در این مثال، حالتهای مختلفی از تامین کنندهی useValue را نیز مشاهده میکنید. انتساب یک رشته، یک مقدار boolean و یا یک مقدار که در زمان انتساب محاسبه خواهد شد مانند Math.random.
- همچنین در اینجا میتوان در قسمت useValue مانند emailApiConfig، یک شیء را نیز تعریف کرد. علت استفادهی از Object.freeze، تعریف این شیء به صورت read only است.
- در حین تعریف provideها اگر کلید توکن بکار رفته یکی باشد، آخرین مقدار، مابقی را بازنویسی میکند؛ مانند حالت languages که در اینجا دوبار تعریف شدهاست. اما با ذکر خاصیت multi، میتوان به کلید languages به صورت یک آرایه دسترسی یافت و در این حالت مقادیر آن بازنویسی نمیشوند.
اکنون برای استفادهی از این توکنهای تعریف شده توسط سیستم تزریق وابستگیها، میتوان به صورت ذیل عمل کرد:
import { Component, OnInit, Inject } from "@angular/core"; import { inject } from "@angular/core/testing"; @Component({ selector: "app-test-providers", templateUrl: "./test-providers.component.html", styleUrls: ["./test-providers.component.css"] }) export class TestProvidersComponent implements OnInit { constructor( @Inject("API_BASE_HREF") public apiBaseHref: string, @Inject("APP_BASE_HREF") public appBaseHref: string, @Inject("IS_PROD") public isProd: boolean, @Inject("APIKey") public apiKey: string, @Inject("Random") public random: string, @Inject("emailApiConfig") public emailApiConfig: any, @Inject("languages") public languages: string[] ) { } ngOnInit() { } }
<h1> Injection Beyond Classes </h1> <div class="alert alert-info"> <ul> <li>API_BASE_HREF: {{apiBaseHref}}</li> <li>APP_BASE_HREF: {{appBaseHref}}</li> <li>IS_PROD: {{isProd}}</li> <li>APIKey: {{apiKey}}</li> <li>Random-1: {{random}}</li> <li>Random-2: {{random}}</li> <li>emailApiConfig {{emailApiConfig | json}}</li> <li>languages: {{languages | json}}</li> </ul> </div>
در اینجا همانطور که مشاهده میکنید، languages از نوع multi: true به یک آرایه تبدیل شدهاست و یا emailApiConfig نیز یک شیء است که توسط کلیدهای آن میتوان به مقادیر متناظر آن دسترسی یافت. Random نیز تنها یکبار دریافت شدهاست و مهم نیست که چندبار صدا زده شود؛ همواره مقدار آن مساوی اولین مقداری است که در زمان انتساب دریافت میکند.
تزریق تنظیمات برنامه توسط تامین کنندهی مقادیر
یک نمونه از تزریق شیء emailApiConfig: any را در مثال فوق ملاحظه کردید. روش بهتر و نوع دار آن به صورت ذیل است. ابتدا یک فایل جدید thismodule.config.ts یا app.config.ts را ایجاد میکنیم:
import { InjectionToken } from "@angular/core"; export let APP_CONFIG = new InjectionToken<string>("this.module.config"); export interface IThisModuleConfig { apiEndpoint: string; } export const ThisModuleConfig: IThisModuleConfig = { apiEndpoint: "http://localhost:45043/api/" };
import { InjectionToken } from '@angular/core'; export const EmailService1 = new InjectionToken<string>("EmailService"); export const EmailService2 = new InjectionToken<string>("EmailService"); console.log(EmailService1 === EmailService2); // false
سپس نوع تنظیمات را توسط اینترفیس IThisModuleConfig تعریف کردهایم (که نسبت به استفادهی از any یک پیشرفت محسوب میشود). در آخر وهلهای از این اینترفیس را به نحوی که مشاهده میکنید export کردهایم.
اکنون نحوهی تعریف تزریق وابستگی از نوع IThisModuleConfig در یک NgModule به صورت ذیل است:
import { ThisModuleConfig, APP_CONFIG } from "./thismodule.config"; @NgModule({ providers: [ { provide: APP_CONFIG, useValue: ThisModuleConfig } ] }) export class InjectionBeyondClassesModule { }
در آخر، تزریق آن به سازندهی یک کامپوننت بر اساس توکن APP_CONFIG و از نوع مشخص اینترفیس آن خواهد بود:
import { IThisModuleConfig, APP_CONFIG } from "./../thismodule.config"; @Component() export class TestProvidersComponent implements OnInit { constructor( @Inject(APP_CONFIG) public config: IThisModuleConfig ) { } ngOnInit() { } }
تزریق وابستگیها توسط تامین کنندهی Factory ها
تا اینجا useValue را بررسی کردیم. نوع دیگر تامین کنندههای قابل تعریف، useFactory هستند:
@NgModule({ providers: [ // ------ useFactory { provide: "BASE_URL", useFactory: getBaseUrl }, { provide: "RandomFactory", useFactory: randomFactory } ] }) export class InjectionBeyondClassesModule { } export function getBaseUrl() { return document.getElementsByTagName("base")[0].href; } export function randomFactory() { return Math.random(); }
روش استفادهی از آن نیز همانند توکنهای useValue است که توسط ویژگی Inject مشخص میشوند:
export class TestProvidersComponent implements OnInit { constructor( @Inject("BASE_URL") public baseUrl: string, @Inject("RandomFactory") public randomFactory: string ) { }
حالت useFactory علاوه بر امکان دریافت یک منطق سفارشی توسط یک function، امکان دریافت یک سری وابستگی را نیز دارد. فرض کنید کلاس سرویس خودرو به صورت زیر تعریف شدهاست که دارای وابستگی از نوع HttpClient تزریق شدهی در سازندهی آن است:
import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; @Injectable() export class CarService { constructor(private http: HttpClient) { } }
import { CarService } from "./car.service"; import { HttpClient } from "@angular/common/http"; @NgModule({ providers: [ // ------ useFactory { provide: "Car_Service", useFactory: carServiceFactory, deps: [HttpClient] } ] }) export class InjectionBeyondClassesModule { } export function carServiceFactory(http: HttpClient) { return new CarService(http); }
تزریق وابستگیها توسط تامین کنندهی کلاسها
تا اینجا useValue و useFactory را بررسی کردیم. نوع دیگر تامین کنندههای قابل تعریف، useClass هستند. در حالت استفادهی useClass، نام یک نوع مشخص میشود و سپس Angular وهلهای از آنرا تامین خواهد کرد. در این حالت اگر این وابستگی دارای پارامترهای تزریق شدهای در سازندهی آن باشد، آنها نیز به صورت خودکار وهله سازی میشوند.
import { CarService } from "./car.service"; @NgModule({ providers: [ // ------ useClass { provide: "Car_Service_Name1", useClass: CarService }, ] }) export class InjectionBeyondClassesModule { }
import { CarService } from "./car.service"; @NgModule({ providers: [ CarService ] }) export class InjectionBeyondClassesModule { }
تزریق وابستگیها توسط تامین کنندهی کلاسهایی با نامهای مستعار
چگونه میتوان دو تامین کننده را برای کلاسی مشابه، با توکنهایی متفاوت ایجاد کرد؟ در این حالت از useExisting استفاده میشود:
import { CarService } from "./car.service"; @NgModule({ providers: [ // ------ useClass { provide: "Car_Service_Name1", useClass: CarService }, // ------ useExisting { provide: "Car_Service_Token2", useExisting: "Car_Service_Name1" }, ] }) export class InjectionBeyondClassesModule { }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
React یک کتابخانهی جاوا اسکریپتی، برای ساخت رابطهای کاربری سریع و تعاملی است. توسعهی آن از سال 2011 در فیسبوک شروع شد و در حال حاضر محبوبترین کتابخانهی جاوا اسکریپتی در این ردهاست:
به همین جهت اگر میخواهید رزومهی غنیتری را ارائه دهید، فراگیری React میتواند موقعیتهای شغلی بیشتری را نصیب شما کند.
ساختار کلی یک برنامهی React
کامپوننتها (جزئی از یک رابط کاربری) قلب هر برنامهی React ای را تشکیل میدهند. برای ساخت یک برنامهی React، تعدادی کامپوننت مستقل را تهیه و با هم ترکیب میکنیم تا به رابط کاربری نهایی برسیم.
هر برنامهی React، حداقل از یک کامپوننت تشکیل میشود که به آن Root component هم میگویند. این کامپوننت بیانگر کل برنامهاست و دربرگیرندهی مابقی Child components برنامه است. بنابراین ساختار هر برنامهی React، شبیه به درختی از کامپوننتها است. اگر با Angular 2 به بعد کار کرده باشید، این مفهوم برای شما آشنا است.
یک مثال: فرض کنید میخواهیم UI برنامهای را به مانند رابط کاربری Twitter، ایجاد کنیم. هر قسمت یک صفحهی توئیتر، به کامپوننتهایی شکسته میشود؛ مانند منوی راهبری، نمایش پروفایل شخص، نمایش لیست آخرین اخبار مورد علاقهی شخص و نمایش فید. اگر بخواهیم این ساختار را توسط یک برنامهی React شبیه سازی کنیم، در بالاترین سطح، کامپوننت root را خواهیم داشت که کار ترکیب و نمایش سایر کامپوننتهای برنامه مانند nav bar ، trends ، profile و feed را انجام میدهد. اکنون در این ساختار ایجاد شده، برای مثال کامپوننت feed نیز میتواند از چندین کامپوننت مجزا تشکیل شود؛ مانند کامپوننتهای tweet و like.
بنابراین هر کامپوننت، قسمتی از UI را تشکیل میدهد. هر کدام از آنها به صورت مجزای از دیگری ساخته شده و سپس در کنار هم قرار میگیرند تا UI نهایی را شکل دهند:
هر کامپوننت در React به صورت یک کلاس ES6، با ساختاری که دارای یک شیء state و متد render است، تشکیل میشود:
state در اینجا همان اطلاعاتی است که قرار است در زمان نمایش این کامپوننت، رندر شود. کار متد render نیز همانطور که از نام آن نیز مشخص است، بیان نحوهی تشکیل و رندر UI است. خروجی این متد، یک React Element است که در حقیقت یک شیء جاوا اسکریپتی خالص است و در نهایت به المانهای DOM، نگاشت میشود. یک React Element، یک DOM Element واقعی نیست؛ بلکه تنها یک شیء جاوا اسکریپتی بیانگر DOM Element، در حافظهاست. بنابراین یک برنامهی React تشکیل شدهاست از لیستی از React Elementها در حافظه که به آن Virtual DOM هم گفته میشود.
مزیت کارکردن با Virtual DOM، سادگی ایجاد، تغییر و به روز رسانی آن در مقایسه با DOM واقعی است که در نهایت کار رندر عناصر UI را در مرورگر انجام میدهد. زمانیکه در state کامپوننتی تغییری رخ میدهد، یک React Element جدید تولید میشود. سپس React این شیء جدید را با نمونهی قبلی آن مقایسه کرده و تغییرات رخداده را محاسبه میکند. در آخر این تغییرات را به DOM واقعی اعمال میکند تا با Virtual DOM موجود هماهنگ شود.
بنابراین در حین کار با React، دیگر همانند کار با جاوا اسکریپت خالص و یا jQuery، مستقیما عناصر UI و DOM واقعی را تغییر نمیدهیم. در اینجا فقط state یک کامپوننت را تغییر میدهیم و سپس React، کار ایجاد شیء UI درون حافظهای متناظر با آن و سپس اعمال آنرا به UI نهایی قابل مشاهدهی در مرورگر، انجام میدهد. به همین جهت به این کتابخانه React میگویند! چون به تغییرات state کامپوننتها واکنش نشان میدهد و سپس DOM واقعی را به روز میکند.
Angular یا React؟!
هر دوی React و Angular از لحاظ طراحی کامپوننتها بسیار شبیه به هم هستند؛ اما Angular یک فریمورک است و React تنها یک کتابخانه. تنها کاری را که React انجام میدهد، رندر View است و هماهنگ نگه داشتن آن با state کامپوننتها. این تمام کاری است که React انجام میدهد؛ نه بیشتر و نه کمتر! بنابراین یادگیری React، بسیار سریعتر و سادهتر از Angular است. بدیهی است یک برنامهی تک صفحهای وب، از اجزای دیگری مانند مسیریابی و یا کار با سرویسهای HTTP نیز تشکیل میشود. در React شما مختار هستید که کتابخانههای جانبی فراهم شدهی برای آنرا خودتان انتخاب کرده و استفاده کنید؛ برخلاف روشی که در Angular مرسوم است و به صورت مشخص و ثابتی به همراه این فریمورک ارائه میشوند.
برپایی محیط توسعهی React
اولین برنامهای را که برای کار با React باید نصب کنید، node.js است. البته ما در این سری قرار نیست با node.js کار کنیم؛ اما از یکی از اجزای آن به نام node package manager یا npm، برای نصب کتابخانهی جاوا اسکریپتی ثالث، زیاد استفاده خواهیم کرد. پس از نصب آن، به خط فرمان مراجعه کرد و دستور زیر را صادر کنید:
این دستور npm قدیمی موجود بر روی سیستم را به روز رسانی میکند (اگر پیشتر یک node.js قدیمی را نصب و اکنون آنرا به روز رسانی کردهاید).
اگر هم خیلی پیشترها node.js را نصب کردهاید (برای مثال چند سال قبل!)، نصب نگارش جدید آن احتمالا کار نخواهد کرد. حتی عزل و نصب مجدد آن نیز کارساز نیست. در این حالت باید پس از عزل آن، پوشههای قدیمی آنرا یکی یکی یافته و دستی حذف کنید . سپس مجددا آنرا نصب کنید.
در ادامه در خط فرمان و توسط npm، قالب create-react-app را نصب خواهیم کرد:
در اینجا سوئیچ i به معنای install است و g یعنی نصب global و سراسری بستهی create-react-app. نصب سراسری یک بسته یعنی در هر پوشهای میتوان به امکانات آن دسترسی یافت و از آن استفاده کرد. اگر از سوئیچ g استفاده نمیشد، این بسته تنها در پوشهی جاری و با سطح دید مختص به آن، نصب و قابل استفاده میشد.
ابزار دیگری که در این سری از آن استفاده خواهیم کرد، ادیتور بسیار معروف و محبوب VSCode است. پس از دریافت و نصب آن، چند افزونهی زیر را نیز به آن اضافه خواهیم کرد:
برای نصب آنها، پنل extensions را در VSCode، از نوار ابزار کنار صفحهی آن، انتخاب کرده و نامهای فوق را در آن جستجو و سپس نصب کنید.
و یا میتوانید این فایل را اجرا کرده و تعدادی از افزونههای مفید VSCode را یکجا نصب کنید: install-addons.zip
همچنین قابلیت فرمتکردن پس از Save را نیز در VSCode فعال کنید تا پس از هربار Save، اعمال این افزونهها به صورت خودکار صورت گیرد. برای این منظور گزینهی file->preferences->settings را در VSCode انتخاب کرده و سپس save را جستجو کرده و Format On Save را انتخاب کنید:
علاوه بر اینها، جهت کار بهتر با VSCode، بهتر است بررسی کنندههای کدهای جاوا اسکریپتی (static code analyzers) را نیز با اجرای دستور زیر نصب کنید:
پس از این تغییرات، نیاز است یکبار VSCode را بسته و مجددا باز کنید. سپس مجددا گزینهی file->preferences->settings را در VSCode انتخاب کرده و ابتدا eslint را در اینجا جستجو کنید. در صفحهی نمایش تنظیمات آن، گزینهی Auto fix on save آنرا انتخاب نمائید. در آخر در همین قسمت settings، عبارت prettier را انتخاب کنید. در اینجا اگر گزینهی قدیمی یکپارچگی با eslint آن هنوز وجود دارد، آنرا از حالت انتخاب شده خارج کنید (به صورت قرمز و deprecated نمایش داده میشود) تا افزونهی prettier بدون مشکل و خطا کار کند (disable Prettier ESLint integration).
ایجاد قالب اولین برنامهی React
در ادامه برای ایجاد اولین برنامهی React، از بستهی create-react-app که پیشتر آنرا نصب کردیم، استفاده میکنیم. برای این منظور در خط فرمان دستور زیر را صادر کنید:
در اینجا sample-01 یک نام دلخواه است و در حین اجرای این دستور باید به اینترنت متصل باشید تا وابستگیهای مرتبط با پروژه را نیز دریافت کند. برای بار اول، اجرای آن ممکن است کمی طول بکشد. اما از دفعات آتی، چون بستههای مرتبط را در npm-cache سیستم نیز ذخیره میکند، اجرای آن بسیار سریع خواهد بود.
این قالب نه تنها React را نصب میکند، بلکه یک development server را برای اجرا و مشاهدهی سریع برنامه، webpack را برای یکی کردن فایلها (bundling & minification)، Babel را برای کامپایل کدهای فایلهای JSX و ... نیز نصب میکند. بنابراین به این ترتیب، یک پروژهی تنظیم شده و آمادهی استفاده و توسعه را شاهد خواهیم بود که نیازی به تنظیمات اولیهی آن نیست.
پس ایجاد برنامه، وارد پوشهی sample-01 شده و دستور npm start را صادر کنید:
به این ترتیب برنامه بر روی پورت 3000، قابل دسترسی و مشاهده میشود:
development server آن، تغییرات فایلهای برنامه را تحت نظر قرار میدهد و با هر تغییری، به صورت خودکار برنامه را در مرورگر بارگذاری مجدد خواهد کرد.
بررسی ساختار اولین پروژهی React ایجاد شده
ساختار پوشهها و فایلهای مثال اولیهی ایجاد شده توسط قالب create-react-app به صورت زیر است:
البته شما در این تصویر پوشهی node_modules را که در کنار این پوشهها قرار دارد، مشاهده نمیکنید. وجود یک چنین پوشهی سنگینی با هزاران فایل داخل آن، کار نمایشی IDEها را با مشکل مواجه میکند (مصرف حافظهی بالا، به همراه کند شدن شدید آن). اگر نمیخواهید این پوشه نمایش داده شود، در مسیر file->preferences->settings، عبارت npm را جستجو کرده و سپس در قسمت npm: exclude آن، بر روی لینک edit in settings.json کلیک کنید:
و سپس در فایل باز شده، یک چنین تنظیمی را میتوانید اضافه و یا ویرایش و تکمیل کنید:
در ادامه پوشهی public این پروژه را مشاهده میکنید. تمام فایلهایی که قرار است به صورت عمومی توسط برنامه ارائه شوند، مانند favicon.ico و غیره، در این پوشه قرار میگیرند.
در این پوشه بر روی فایل index.html آن کلیک کنید تا بتوان محتوای آنرا بهتر بررسی کرد. برای مثال در ابتدای آن، درج تعدادی متادیتا را که یکی از آنها ذکر manifest.json است، مشاهده میکنید. کار فایل manifest.json، ارائهی یک سری متادیتای خاص مخصوص دستگاههای موبایل است که در آنها بجای favicon.ico، میتوان از تصاویر و یا آیکنهای بزرگتری مانند فایلهای png موجود در پوشهی public، استفاده کرد. در ادامهی این فایل، به تنظیم زیر میرسیم:
div با id مساوی root، محل ارائهی کل برنامهی React ما است.
در پوشهی src و فایل App.js آن، شاهد یک کامپوننت ابتدایی هستید که کار رندر صفحهی مشکی پیشفرض این قالب را انجام میدهد. در این فایل، شاهد بازگشت یک چنین تگهایی هستیم:
احتمالا تابحال چنین return ای را در برنامههای جاوا اسکریپتی مشاهده نکردهاید؛ چون درج آنها در فایلهای js به این نحو، غیرمجاز است. این تگها نه رشتهای هستند و نه HTML خالص. به آن jsx گفته میشود که مخفف JavaScript XML میباشد. کار آن ارائهی ساختار UI ای است که قرار است رندر شود. یک چنین کدی برای اینکه قابل تفسیر و اجرا باشد، از درون کامپایلر ویژهای به نام Babel عبور میکند و تبدیل به کدهای جاوا اسکریپتی خالصی میشود که برای مرورگرها قابل درک و اجرا است.
برای درک بهتر آن به آدرس https://babeljs.io/repl مراجعه کنید. سپس در سمت چپ صفحه، یک قطعه کد jsx را به یک ثابت انتساب دهید:
همانطور که مشاهده میکنید، این قطعه کد jsx (که یک رشتهی معمولی نیست)، توسط Babel به یک قطعه کد کاملا جاوا اسکریپتی قابل درک برای مرورگر تبدیل شدهاست:
بدیهی است نوشتن کدهای jsx، سادهتر از نوشتن قطعه کد فوق است و درک آن نیز به علت شباهت آن به HTML، آسانتر است. به همین جهت در کدهای React، ما از jsx استفاده میکنیم و تفسیر آنرا به Babel واگذار خواهیم کرد.
در پوشهی src، فایل مهم دیگری که وجود دارد، index.js است. این فایل نقطهی آغازین برنامه را مشخص میکند. در قسمتهای بعدی، محتویات این فایل را بیشتر بررسی خواهیم کرد.
در اینجا فایل serviceWorker.js را نیز مشاهده میکنید. این فایل به صورت خودکار توسط قالب create-react-app ایجاد شدهاست و کار آن کمک به ارائهی محلی برنامه، توسط development server آن است. بنابراین ما کاری با این فایل نخواهیم داشت.
نوشتن اولین برنامهی React
به پوشهی src ایجاد شده مراجعه کرده و تمام فایلهای موجود و پیشفرض آنرا حذف کنید. در ادامه خودمان آنها را از صفر ایجاد خواهیم کرد. برای این منظور فایل جدید و خالی src\index.js را ایجاد میکنیم. در ابتدای کار نیاز است تعدادی ماژول React را import کنیم.
در اینجا شیء React از ماژول react دریافت شده و سپس یک ثابت را با یک عبارت jsx مقدار دهی کردهایم. چون از jsx استفاده میکنیم، ذکر import ابتدای فایل الزامی است؛ از این جهت که Babel به کمک آن است که میتواند معادل React.createElement را تولید کند.
اگر هنوز برنامه توسط دستور npm start در حال اجرا است، هر بار که فایل index.js را ذخیره میکنیم، خروجی نهایی را در مرورگر نمایش میدهد (اگر هم آنرا بستهاید، یکبار از طریق خط فرمان، دستور npm start را در ریشهی پروژه، صادر کنید). به این قابلیت hot module reloading هم گفته میشود.
در این حالت اگر به مرورگر مراجعه کنید، یک صفحهی سفید را مشاهده خواهید کرد. اکنون دکمهی F12 را فشرده (و یا ctrl+shift+i) و developer console مرورگر را باز کنید.
شیءای را که در اینجا مشاهده میکنید، همان حاصل console.log کدهای فوق است؛ به عبارتی Babel، عبارت jsx ما را تبدیل به یک شیء جاوا اسکریپتی قابل فهم برای مرورگر کردهاست که از دیدگاه React، جزئی از همان Virtual DOM ای است که پیشتر معرفی شد (نمایش درون حافظهای DOM مختص React، جهت محاسبهی تغییرات، با تغییر state هر کامپوننت و سپس اعمال آنها به DOM اصلی در مرورگر).
اکنون میخواهیم این المان را در DOM اصلی، رندر کرده و نمایش دهیم:
برای این منظور نیاز است از متد ReactDOM.render استفاده کرد. این شیء در ماژول react-dom قرار دارد؛ به همین جهت در ابتدای فایل import شدهاست. سپس در متد render آن، ابتدا المانی که قرار است رندر شود ذکر خواهد شد و سپس محل درج آنرا مشخص میکنیم که دقیقا به همان div با id مساوی root در فایل public\index.html اشاره میکند.
اکنون پس از ذخیره سازی فایل index.js، اگر به مرورگر مراجعه کنید، عبارت Hello World! را مشاهده خواهید کرد:
همانطور که در این تصویر نیز مشخص است، المان h1 ما را داخل div ای با id مساوی root، درج کردهاست.
هدف از این مثال ساده، نمایش نحوهی کارکرد React، در پشت صحنه بود. در یک برنامهی واقعی، بجای رندر یک المان ساده در DOM، کار رندر App component را انجام خواهیم داد. کامپوننت App، کامپوننت ریشهای برنامه بوده و میتواند شامل درختی از کامپوننتها که UI نهایی را تشکیل میدهند، شود.
نگاهی به تنظیمات پروژهی ایجاد شده
اگر فایل package.json پروژه را باز کنید، یک چنین بستههایی در آن درج شدهاست:
در اینجا صرفا سه بستهی react، react-dom و react-scripts را در قسمت dependencies مشاهده میکنید که کل Importهای ما را تشکیل میدهند.
بستهی react-scripts است که کار مدیریت چهار جزء قسمت scripts این فایل را انجام میدهد. برای نمونه دستور npm start ای که در اینجا تعریف شده، سبب اجرای react-scripts start میشود. در ادامه اگر دستور npm run build را اجرا کنیم، یک بستهی نهایی بهینه سازی شده را تولید میکند.
آخرین دستور آن eject است. اگر دستور npm run eject را اجرا کنید، امکان سفارشی سازی پشت صحنهی create-react-app را خواهید داشت؛ اما در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر میشوند). همچنین این عملیات نیز یک طرفهاست. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانیهای بعدی create-react-app را با مشکل مواجه میکند. این گزینه صرفا مختص توسعه دهندگان پیشرفتهی React است.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-01.zip
در قسمت بعد، پیشنیازهای جاوا اسکریپتی شروع به کار با React را بررسی میکنیم.
به همین جهت اگر میخواهید رزومهی غنیتری را ارائه دهید، فراگیری React میتواند موقعیتهای شغلی بیشتری را نصیب شما کند.
ساختار کلی یک برنامهی React
کامپوننتها (جزئی از یک رابط کاربری) قلب هر برنامهی React ای را تشکیل میدهند. برای ساخت یک برنامهی React، تعدادی کامپوننت مستقل را تهیه و با هم ترکیب میکنیم تا به رابط کاربری نهایی برسیم.
هر برنامهی React، حداقل از یک کامپوننت تشکیل میشود که به آن Root component هم میگویند. این کامپوننت بیانگر کل برنامهاست و دربرگیرندهی مابقی Child components برنامه است. بنابراین ساختار هر برنامهی React، شبیه به درختی از کامپوننتها است. اگر با Angular 2 به بعد کار کرده باشید، این مفهوم برای شما آشنا است.
یک مثال: فرض کنید میخواهیم UI برنامهای را به مانند رابط کاربری Twitter، ایجاد کنیم. هر قسمت یک صفحهی توئیتر، به کامپوننتهایی شکسته میشود؛ مانند منوی راهبری، نمایش پروفایل شخص، نمایش لیست آخرین اخبار مورد علاقهی شخص و نمایش فید. اگر بخواهیم این ساختار را توسط یک برنامهی React شبیه سازی کنیم، در بالاترین سطح، کامپوننت root را خواهیم داشت که کار ترکیب و نمایش سایر کامپوننتهای برنامه مانند nav bar ، trends ، profile و feed را انجام میدهد. اکنون در این ساختار ایجاد شده، برای مثال کامپوننت feed نیز میتواند از چندین کامپوننت مجزا تشکیل شود؛ مانند کامپوننتهای tweet و like.
بنابراین هر کامپوننت، قسمتی از UI را تشکیل میدهد. هر کدام از آنها به صورت مجزای از دیگری ساخته شده و سپس در کنار هم قرار میگیرند تا UI نهایی را شکل دهند:
هر کامپوننت در React به صورت یک کلاس ES6، با ساختاری که دارای یک شیء state و متد render است، تشکیل میشود:
class Tweet { state = {}; render() { } }
مزیت کارکردن با Virtual DOM، سادگی ایجاد، تغییر و به روز رسانی آن در مقایسه با DOM واقعی است که در نهایت کار رندر عناصر UI را در مرورگر انجام میدهد. زمانیکه در state کامپوننتی تغییری رخ میدهد، یک React Element جدید تولید میشود. سپس React این شیء جدید را با نمونهی قبلی آن مقایسه کرده و تغییرات رخداده را محاسبه میکند. در آخر این تغییرات را به DOM واقعی اعمال میکند تا با Virtual DOM موجود هماهنگ شود.
بنابراین در حین کار با React، دیگر همانند کار با جاوا اسکریپت خالص و یا jQuery، مستقیما عناصر UI و DOM واقعی را تغییر نمیدهیم. در اینجا فقط state یک کامپوننت را تغییر میدهیم و سپس React، کار ایجاد شیء UI درون حافظهای متناظر با آن و سپس اعمال آنرا به UI نهایی قابل مشاهدهی در مرورگر، انجام میدهد. به همین جهت به این کتابخانه React میگویند! چون به تغییرات state کامپوننتها واکنش نشان میدهد و سپس DOM واقعی را به روز میکند.
Angular یا React؟!
هر دوی React و Angular از لحاظ طراحی کامپوننتها بسیار شبیه به هم هستند؛ اما Angular یک فریمورک است و React تنها یک کتابخانه. تنها کاری را که React انجام میدهد، رندر View است و هماهنگ نگه داشتن آن با state کامپوننتها. این تمام کاری است که React انجام میدهد؛ نه بیشتر و نه کمتر! بنابراین یادگیری React، بسیار سریعتر و سادهتر از Angular است. بدیهی است یک برنامهی تک صفحهای وب، از اجزای دیگری مانند مسیریابی و یا کار با سرویسهای HTTP نیز تشکیل میشود. در React شما مختار هستید که کتابخانههای جانبی فراهم شدهی برای آنرا خودتان انتخاب کرده و استفاده کنید؛ برخلاف روشی که در Angular مرسوم است و به صورت مشخص و ثابتی به همراه این فریمورک ارائه میشوند.
برپایی محیط توسعهی React
اولین برنامهای را که برای کار با React باید نصب کنید، node.js است. البته ما در این سری قرار نیست با node.js کار کنیم؛ اما از یکی از اجزای آن به نام node package manager یا npm، برای نصب کتابخانهی جاوا اسکریپتی ثالث، زیاد استفاده خواهیم کرد. پس از نصب آن، به خط فرمان مراجعه کرد و دستور زیر را صادر کنید:
> npm install -g npm@latest
اگر هم خیلی پیشترها node.js را نصب کردهاید (برای مثال چند سال قبل!)، نصب نگارش جدید آن احتمالا کار نخواهد کرد. حتی عزل و نصب مجدد آن نیز کارساز نیست. در این حالت باید پس از عزل آن، پوشههای قدیمی آنرا یکی یکی یافته و دستی حذف کنید . سپس مجددا آنرا نصب کنید.
در ادامه در خط فرمان و توسط npm، قالب create-react-app را نصب خواهیم کرد:
> npm i -g create-react-app
ابزار دیگری که در این سری از آن استفاده خواهیم کرد، ادیتور بسیار معروف و محبوب VSCode است. پس از دریافت و نصب آن، چند افزونهی زیر را نیز به آن اضافه خواهیم کرد:
برای نصب آنها، پنل extensions را در VSCode، از نوار ابزار کنار صفحهی آن، انتخاب کرده و نامهای فوق را در آن جستجو و سپس نصب کنید.
و یا میتوانید این فایل را اجرا کرده و تعدادی از افزونههای مفید VSCode را یکجا نصب کنید: install-addons.zip
همچنین قابلیت فرمتکردن پس از Save را نیز در VSCode فعال کنید تا پس از هربار Save، اعمال این افزونهها به صورت خودکار صورت گیرد. برای این منظور گزینهی file->preferences->settings را در VSCode انتخاب کرده و سپس save را جستجو کرده و Format On Save را انتخاب کنید:
علاوه بر اینها، جهت کار بهتر با VSCode، بهتر است بررسی کنندههای کدهای جاوا اسکریپتی (static code analyzers) را نیز با اجرای دستور زیر نصب کنید:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks
پس از این تغییرات، نیاز است یکبار VSCode را بسته و مجددا باز کنید. سپس مجددا گزینهی file->preferences->settings را در VSCode انتخاب کرده و ابتدا eslint را در اینجا جستجو کنید. در صفحهی نمایش تنظیمات آن، گزینهی Auto fix on save آنرا انتخاب نمائید. در آخر در همین قسمت settings، عبارت prettier را انتخاب کنید. در اینجا اگر گزینهی قدیمی یکپارچگی با eslint آن هنوز وجود دارد، آنرا از حالت انتخاب شده خارج کنید (به صورت قرمز و deprecated نمایش داده میشود) تا افزونهی prettier بدون مشکل و خطا کار کند (disable Prettier ESLint integration).
ایجاد قالب اولین برنامهی React
در ادامه برای ایجاد اولین برنامهی React، از بستهی create-react-app که پیشتر آنرا نصب کردیم، استفاده میکنیم. برای این منظور در خط فرمان دستور زیر را صادر کنید:
> create-react-app sample-01
این قالب نه تنها React را نصب میکند، بلکه یک development server را برای اجرا و مشاهدهی سریع برنامه، webpack را برای یکی کردن فایلها (bundling & minification)، Babel را برای کامپایل کدهای فایلهای JSX و ... نیز نصب میکند. بنابراین به این ترتیب، یک پروژهی تنظیم شده و آمادهی استفاده و توسعه را شاهد خواهیم بود که نیازی به تنظیمات اولیهی آن نیست.
پس ایجاد برنامه، وارد پوشهی sample-01 شده و دستور npm start را صادر کنید:
> cd sample-01 > npm start
development server آن، تغییرات فایلهای برنامه را تحت نظر قرار میدهد و با هر تغییری، به صورت خودکار برنامه را در مرورگر بارگذاری مجدد خواهد کرد.
بررسی ساختار اولین پروژهی React ایجاد شده
ساختار پوشهها و فایلهای مثال اولیهی ایجاد شده توسط قالب create-react-app به صورت زیر است:
البته شما در این تصویر پوشهی node_modules را که در کنار این پوشهها قرار دارد، مشاهده نمیکنید. وجود یک چنین پوشهی سنگینی با هزاران فایل داخل آن، کار نمایشی IDEها را با مشکل مواجه میکند (مصرف حافظهی بالا، به همراه کند شدن شدید آن). اگر نمیخواهید این پوشه نمایش داده شود، در مسیر file->preferences->settings، عبارت npm را جستجو کرده و سپس در قسمت npm: exclude آن، بر روی لینک edit in settings.json کلیک کنید:
و سپس در فایل باز شده، یک چنین تنظیمی را میتوانید اضافه و یا ویرایش و تکمیل کنید:
"files.exclude": { "**/.git": true, "**/.svn": true, "**/.hg": true, "**/CVS": true, "**/.DS_Store": true, "**/node_modules": true, "**/wwwroot": true, "**/bower_components": true, "**/**/bin": true, "**/**/obj": true, "**/packages": true },
در ادامه پوشهی public این پروژه را مشاهده میکنید. تمام فایلهایی که قرار است به صورت عمومی توسط برنامه ارائه شوند، مانند favicon.ico و غیره، در این پوشه قرار میگیرند.
در این پوشه بر روی فایل index.html آن کلیک کنید تا بتوان محتوای آنرا بهتر بررسی کرد. برای مثال در ابتدای آن، درج تعدادی متادیتا را که یکی از آنها ذکر manifest.json است، مشاهده میکنید. کار فایل manifest.json، ارائهی یک سری متادیتای خاص مخصوص دستگاههای موبایل است که در آنها بجای favicon.ico، میتوان از تصاویر و یا آیکنهای بزرگتری مانند فایلهای png موجود در پوشهی public، استفاده کرد. در ادامهی این فایل، به تنظیم زیر میرسیم:
<body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div>
در پوشهی src و فایل App.js آن، شاهد یک کامپوننت ابتدایی هستید که کار رندر صفحهی مشکی پیشفرض این قالب را انجام میدهد. در این فایل، شاهد بازگشت یک چنین تگهایی هستیم:
return ( <div className="App"> <header className="App-header"> ... </header> </div> );
برای درک بهتر آن به آدرس https://babeljs.io/repl مراجعه کنید. سپس در سمت چپ صفحه، یک قطعه کد jsx را به یک ثابت انتساب دهید:
const element = <h1>Hello World!</h1>;
همانطور که مشاهده میکنید، این قطعه کد jsx (که یک رشتهی معمولی نیست)، توسط Babel به یک قطعه کد کاملا جاوا اسکریپتی قابل درک برای مرورگر تبدیل شدهاست:
"use strict"; var element = React.createElement("h1", null, "Hello World!");
بدیهی است نوشتن کدهای jsx، سادهتر از نوشتن قطعه کد فوق است و درک آن نیز به علت شباهت آن به HTML، آسانتر است. به همین جهت در کدهای React، ما از jsx استفاده میکنیم و تفسیر آنرا به Babel واگذار خواهیم کرد.
در پوشهی src، فایل مهم دیگری که وجود دارد، index.js است. این فایل نقطهی آغازین برنامه را مشخص میکند. در قسمتهای بعدی، محتویات این فایل را بیشتر بررسی خواهیم کرد.
در اینجا فایل serviceWorker.js را نیز مشاهده میکنید. این فایل به صورت خودکار توسط قالب create-react-app ایجاد شدهاست و کار آن کمک به ارائهی محلی برنامه، توسط development server آن است. بنابراین ما کاری با این فایل نخواهیم داشت.
نوشتن اولین برنامهی React
به پوشهی src ایجاد شده مراجعه کرده و تمام فایلهای موجود و پیشفرض آنرا حذف کنید. در ادامه خودمان آنها را از صفر ایجاد خواهیم کرد. برای این منظور فایل جدید و خالی src\index.js را ایجاد میکنیم. در ابتدای کار نیاز است تعدادی ماژول React را import کنیم.
import React from "react"; const element = <h1>Hello World!</h1>; console.log(element);
اگر هنوز برنامه توسط دستور npm start در حال اجرا است، هر بار که فایل index.js را ذخیره میکنیم، خروجی نهایی را در مرورگر نمایش میدهد (اگر هم آنرا بستهاید، یکبار از طریق خط فرمان، دستور npm start را در ریشهی پروژه، صادر کنید). به این قابلیت hot module reloading هم گفته میشود.
در این حالت اگر به مرورگر مراجعه کنید، یک صفحهی سفید را مشاهده خواهید کرد. اکنون دکمهی F12 را فشرده (و یا ctrl+shift+i) و developer console مرورگر را باز کنید.
شیءای را که در اینجا مشاهده میکنید، همان حاصل console.log کدهای فوق است؛ به عبارتی Babel، عبارت jsx ما را تبدیل به یک شیء جاوا اسکریپتی قابل فهم برای مرورگر کردهاست که از دیدگاه React، جزئی از همان Virtual DOM ای است که پیشتر معرفی شد (نمایش درون حافظهای DOM مختص React، جهت محاسبهی تغییرات، با تغییر state هر کامپوننت و سپس اعمال آنها به DOM اصلی در مرورگر).
اکنون میخواهیم این المان را در DOM اصلی، رندر کرده و نمایش دهیم:
import React from "react"; import ReactDOM from "react-dom"; const element = <h1>Hello World!</h1>; console.log(element); ReactDOM.render(element, document.getElementById("root"));
اکنون پس از ذخیره سازی فایل index.js، اگر به مرورگر مراجعه کنید، عبارت Hello World! را مشاهده خواهید کرد:
همانطور که در این تصویر نیز مشخص است، المان h1 ما را داخل div ای با id مساوی root، درج کردهاست.
هدف از این مثال ساده، نمایش نحوهی کارکرد React، در پشت صحنه بود. در یک برنامهی واقعی، بجای رندر یک المان ساده در DOM، کار رندر App component را انجام خواهیم داد. کامپوننت App، کامپوننت ریشهای برنامه بوده و میتواند شامل درختی از کامپوننتها که UI نهایی را تشکیل میدهند، شود.
نگاهی به تنظیمات پروژهی ایجاد شده
اگر فایل package.json پروژه را باز کنید، یک چنین بستههایی در آن درج شدهاست:
{ "name": "sample-01", "version": "0.1.0", "private": true, "dependencies": { "react": "^16.11.0", "react-dom": "^16.11.0", "react-scripts": "3.2.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } }
بستهی react-scripts است که کار مدیریت چهار جزء قسمت scripts این فایل را انجام میدهد. برای نمونه دستور npm start ای که در اینجا تعریف شده، سبب اجرای react-scripts start میشود. در ادامه اگر دستور npm run build را اجرا کنیم، یک بستهی نهایی بهینه سازی شده را تولید میکند.
آخرین دستور آن eject است. اگر دستور npm run eject را اجرا کنید، امکان سفارشی سازی پشت صحنهی create-react-app را خواهید داشت؛ اما در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر میشوند). همچنین این عملیات نیز یک طرفهاست. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانیهای بعدی create-react-app را با مشکل مواجه میکند. این گزینه صرفا مختص توسعه دهندگان پیشرفتهی React است.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-01.zip
در قسمت بعد، پیشنیازهای جاوا اسکریپتی شروع به کار با React را بررسی میکنیم.
اشتراکها
کتابخانه AspNetCoreRateLimit
AspNetCoreRateLimit is an ASP.NET Core rate limiting solution designed to control the rate of requests that clients can make to a Web API or MVC app based on IP address or client ID. The AspNetCoreRateLimit package contains an IpRateLimitMiddleware and a ClientRateLimitMiddleware, with each middleware you can set multiple limits for different scenarios like allowing an IP or Client to make a maximum number of calls in a time interval like per second, 15 minutes, etc. You can define these limits to address all requests made to an API or you can scope the limits to each API URL or HTTP verb and path.
اشتراکها
مسیر راه خودآموزی دوره علوم رایانه
مباحث پایهای اعتبارسنجی کاربران در ASP.NET Core با نگارشهای پیشین ASP.NET MVC، آنچنان تفاوت ساختاری مهمی ندارند و یکی هستند. عمدهی تفاوتها در نحوهی برپایی تنظیمات اولیهی اسکریپتهای آنها و همچنین یک سری Tag Helper جدید معادل با HTML Helperهای متداول اعتبارسنجی هستند.
دریافت وابستگیهای سمت کاربر مباحث اعتبارسنجی
زمانیکه گزینهی ایجاد یک پروژهی جدید ASP.NET Core را در VS.NET انتخاب میکنیم، علاوه بر قالب empty آن، قالب دیگری به نام web application نیز در آن موجود است. با انتخاب این قالب، فایلی را به نام bower.json نیز با این محتوا مشاهده میکنید:
این مداخل تمام پیشنیازهای مباحث اعتبارسنجی کاربران را به صورت خودکار به پروژه اضافه میکنند. بنابراین افزودن فایل جدید bower.json به پروژه با محتوای فوق و سپس مراجعه به solution explorer و کلیک راست بر روی گره dependencies و در آخر انتخاب restore packages، سبب دریافت خودکار این بستهها میشود.
این بستهها را پس از دریافت، در پوشهی bower_components خواهید یافت:
البته باید دقت داشت که استفاده از bower در اینجا الزامی نیست. اگر علاقمند بودید از npm و node.js استفاده کنید.
افزودن وابستگیهای سمت کاربر مباحث اعتبارسنجی و عمومی کردن آنها
پس از دریافت وابستگیهای مورد نیاز توسط bower، به فایل layout برنامه مراجعه کرده و سپس آنها را به ترتیب ذیل اضافه میکنیم:
هرچند این ترتیب درست است، اما ... کار نمیکند. علت را هم در مبحث «ارتقاء به ASP.NET Core 1.0 - قسمت 4 - فعال سازی پردازش فایلهای استاتیک» بررسی کردیم: تمام پوشههای موجود در ریشهی یک برنامهی ASP.NET Core، غیرعمومی هستند (و توسط کاربران قابل درخواست نیستند)، مگر اینکه آنهارا توسط میان افزار static files، عمومی کنیم. برای این منظور به کلاس آغازین برنامه مراجعه کرده و سپس به متد Configure آن، تنظیمات ذیل را اضافه کنید:
به این ترتیب فایلهای موجود در پوشهی bower_components به مسیر bower_components/ نگاشت میشوند. البته انتخاب مقدار RequestPath در اینجا اختیاری است و برای مثال میتوانید مسیر scripts/ را نیز معرفی کنید (نگاشت مسیر فیزیکی و واقعی فایلها به URL خاص دیگری که توسط وب سرور ارائه خواهد شد).
اگر RequestPath را به مسیر دیگری تنظیم کردید، نیاز است ابتدای سه مدخل ذکر شده را بر این اساس اصلاح کنید، تا فایلها توسط وب سرور قابل ارائه شوند.
استفاده از CDN برای توزیع اسکریپتهای اعتبارسنجی مورد نیاز
در مورد environment tag helper در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers» پیشتر بحث شد. در اینجا نیز میتوان برای مثال در حال توسعه، از اسکریپتهای محلی
و در حالت نهایی توزیع برنامه، از CDNهای ارائهی اسکریپتها، جهت کاهش بار سرور استفاده کرد:
در اینجا fallback به آدرس محلی دریافت اسکریپتهای مدنظر، در صورت در دسترس نبودن CDN، تنظیم شدهاست.
روش عملکرد fallback هم به این صورت است که بررسی میشود آیا عبارت ذکر شدهی در قسمت asp-fallback-test قابل اجرا است یا خیر؟ اگر خیر، یعنی CDN قابل دسترسی نیست و از نمونهی محلی استفاده میکند.
خلاصهای از Tag helpers اعتبارسنجی
در جدول «راهنمای تبدیل HTML Helpers به Tag Helpers» مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers»، معادلهای HTML Helpers مباحث اعتبارسنجی را نیز ملاحظه کردید. خلاصهی تکمیلی آن به صورت ذیل است:
ValidationSummary.All سبب نمایش خطاهای اعتبارسنجی خواص و همچنین کل مدل میشود:
معادل است با
ValidationSummary.ModelOnly صرفا خطاهای اعتبارسنجی در سطح مدل را نمایش میدهد:
معادل است با
و برای تعیین نمایش خطاهای اعتبارسنجی یک خاصیت از مدل:
معادل است با
دریافت وابستگیهای سمت کاربر مباحث اعتبارسنجی
زمانیکه گزینهی ایجاد یک پروژهی جدید ASP.NET Core را در VS.NET انتخاب میکنیم، علاوه بر قالب empty آن، قالب دیگری به نام web application نیز در آن موجود است. با انتخاب این قالب، فایلی را به نام bower.json نیز با این محتوا مشاهده میکنید:
{ "name": "asp.net", "private": true, "dependencies": { "bootstrap": "3.3.6", "jquery": "2.2.0", "jquery-validation": "1.14.0", "jquery-validation-unobtrusive": "3.2.6" } }
این بستهها را پس از دریافت، در پوشهی bower_components خواهید یافت:
البته باید دقت داشت که استفاده از bower در اینجا الزامی نیست. اگر علاقمند بودید از npm و node.js استفاده کنید.
افزودن وابستگیهای سمت کاربر مباحث اعتبارسنجی و عمومی کردن آنها
پس از دریافت وابستگیهای مورد نیاز توسط bower، به فایل layout برنامه مراجعه کرده و سپس آنها را به ترتیب ذیل اضافه میکنیم:
<script src="~/bower_components/jquery/dist/jquery.min.js"></script> <script src="~/bower_components/jquery-validation/dist/jquery.validate.min.js"></script> <script src="~/bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script> @RenderSection("scripts", required: false) </body> </html>
// Serve wwwroot as root app.UseFileServer(); // Serve /bower_components as a separate root app.UseFileServer(new FileServerOptions { // Set root of file server FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "bower_components")), // Only react to requests that match this path RequestPath = "/bower_components", // Don't expose file system EnableDirectoryBrowsing = false });
اگر RequestPath را به مسیر دیگری تنظیم کردید، نیاز است ابتدای سه مدخل ذکر شده را بر این اساس اصلاح کنید، تا فایلها توسط وب سرور قابل ارائه شوند.
استفاده از CDN برای توزیع اسکریپتهای اعتبارسنجی مورد نیاز
در مورد environment tag helper در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers» پیشتر بحث شد. در اینجا نیز میتوان برای مثال در حال توسعه، از اسکریپتهای محلی
<environment name="Development"> <script src="~/bower_components/jquery/dist/jquery.min.js"></script> <script src="~/bower_components/jquery-validation/dist/jquery.validate.min.js"></script> <script src="~/bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script> </environment>
<environment names="Staging, Production"> <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.4.min.js" asp-fallback-src="/bower_components/jquery/dist/jquery.min.js" asp-fallback-test="window.jQuery"> </script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js" asp-fallback-src="bower_components/jquery-validation/dist/jquery.validate.min.js" asp-fallback-test="window.jQuery && window.jQuery.validator"> </script> <script src="https://ajax.aspnetcdn.com/ajax/mvc/5.2.3/jquery.validate.unobtrusive.min.js" asp-fallback-src="/bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js" asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"> </script> </environment>
روش عملکرد fallback هم به این صورت است که بررسی میشود آیا عبارت ذکر شدهی در قسمت asp-fallback-test قابل اجرا است یا خیر؟ اگر خیر، یعنی CDN قابل دسترسی نیست و از نمونهی محلی استفاده میکند.
خلاصهای از Tag helpers اعتبارسنجی
در جدول «راهنمای تبدیل HTML Helpers به Tag Helpers» مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers»، معادلهای HTML Helpers مباحث اعتبارسنجی را نیز ملاحظه کردید. خلاصهی تکمیلی آن به صورت ذیل است:
ValidationSummary.All سبب نمایش خطاهای اعتبارسنجی خواص و همچنین کل مدل میشود:
@Html.ValidationSummary(false)
<div asp-validation-summary="All"></div>
ValidationSummary.ModelOnly صرفا خطاهای اعتبارسنجی در سطح مدل را نمایش میدهد:
@Html.ValidationSummary(true)
<div asp-validation-summary="ModelOnly"></div>
و برای تعیین نمایش خطاهای اعتبارسنجی یک خاصیت از مدل:
@Html.ValidationMessageFor(m => m.UserName, "", new { @class = "text-danger" })
<span asp-validation-for="UserName" class="text-danger"></span>