Three weeks ago, we released Visual Studio Tools for Apache Cordova Update 2. Last week, we introduced you to a new TACO CLI.
Now we’re back with Update 3, which is automatically included when you
install Visual Studio 2015. If you’re an existing user of Visual Studio
2015, you’ll receive a notification to download and install today.
MIX09 | Web Design and Development Conference
- Day One Keynote (Slides)
- Day Two Keynote
- What's New for Microsoft Silverlight and Microsoft Windows Presentation
Foundation (WPF) Developers in Microsoft Visual Studio 2010 - Semantic HTML and Unobtrusive JavaScript
- Design Fundamentals for Developers
- Microsoft Silverlight and Windows Presentation Foundation (WPF): Sharing
Skills and Code - Going Inside Microsoft Silverlight: Exploring the Core CLR
- Delivering Media with Internet Information Services 7 (IIS) Media
Services and Microsoft Silverlight - Shio O Totte: Using What You Know
- Building Data-Driven Scalable AJAX Web Pages
- Microsoft ASP.NET: Taking AJAX to the Next Level
- A Shot of Windows Live Messenger and a Pint of Microsoft Silverlight
- Automated User Interface (UI) Testing with Microsoft Visual Studio
Team System 2010 - There's a Little Scripter in All of Us: Building a Web App for the
Masses - Microsoft ASP.NET 4.0 Data Access: Patterns for Success with Web Forms
- Cascading Stylesheets
- Creating a "Next Generation" E-Commerce Experience
- Cloud Computing: What's in It for Me?
- Developing for Experience with 3 Heads
- Developing and Deploying Applications on Internet Information Services
(IIS) - Hiking Mt. Avalon
- Advance Your Design with UX Design Patterns
- Love the New Windows Live Messenger Web Toolkit for Social Websites
- Ten Ways to Ensure RIA Failure
- Using Dynamic Languages to Develop Microsoft Silverlight Applications
- Miss March and Other Distractions
- Caching REST with Windows Communication Foundation
- Designing the Windows 7 Desktop Experience
- Microsoft Silverlight Media End-to-End
- Optimizing Performance for Microsoft Expression Encoder
- Scaling a Rich Client to Half a Billion Users
- User Experience Design for Non-Designers
- Build Applications on the Microsoft Platform Using Eclipse, Java,
Ruby and PHP! - Customized Live Search for Web and Client Applications
- Building Microsoft Silverlight Controls
- Working across the Client Continuum
- Building a Rich Social Network Application
- Adding Microsoft Silverlight to Your Company's Skill Set
- Principles of Microsoft Silverlight Animation
- Oomph: A Microformat Toolkit
- Introducing the Microsoft Virtual Earth Silverlight Map Control CTP
- Delivering Ads to a Silverlight Media Player Application
- High-Speed RIA Development with the Microsoft Silverlight Toolkit
- Deep Zoom++ : Build Dynamic Deep Zoom Applications with Open Source
- See through the Clouds: Introduction to the Azure Services Platform
- Modeling RESTful Data Services: Present and Future
- Escaping Flatland in Application Design: Rich User Experiences
- Building High Performance Web Applications and Sites
- Sketch Flow: From Concept to Production
- Design Prototyping: Bringing Wireframes to Life
- Overview of Windows Azure
- A Website Named Desire
- Protecting Online Identities
- Web Form Design
- Simplifying Distributed Access Control with Microsoft .NET Services
- Software Entrepreneurs: Go Big with BizSpark
- How I Learned to Stop Worrying and Love the Microsoft ADO.NET Entity
Framework - Copyright Laws for Web Designers and Developers
- Effective Infographics with Interactivity
- State of the Art in Web Site Design on Microsoft SharePoint
- The Way of the Whiteboard: Persuading with Pictures
- Interaction Techniques Using the Wii Remote (and Other HCI Projects)
- Touch and Gesture Computing, What You Haven't Heard
- Enhancing Large Windows Media Platforms with Microsoft Silverlight
- The Future of Microsoft Expression Blend
- Exposing Web Content to a Global Audience Using Machine Translation
- Building Microsoft Silverlight Applications with Eclipse
(Slides) - Going Inside Microsoft Silverlight: Exploring the Core CLR
(Slides) - Web Development Using Microsoft Visual Studio: Now and in the Future
(Slides) - Building Amazing Business Centric Applications with Microsoft Silverlight 3
- Creating Media Content for Microsoft Silverlight Using Microsoft Expression
Encoder (Slides) - Design Prototyping: Bringing Wireframes to Life (Slides)
- Measuring Social Media Marketing (Slides)
- Mesh-Enabled Web Applications (Slides)
- .NET RIA Services - Building Data-Driven Applications with Microsoft
Silverlight and Microsoft ASP.NET (Slides) - Making XML Really, Really Easy with Microsoft Visual Basic 9
(Slides) - Building Accessible RIAs in Microsoft Silverlight
(Slides) - Integrating Microsoft Expression Blend with Adobe Creative Suite
(Slides) - Principles of Microsoft Silverlight Animation (Slides)
- Windows Mobile 6.5 Overview (Slides)
- How'd they do it? Real App. Real Code. Two Weeks. Nothing but .NET
- Building Web Applications with Windows Azure (Slides)
- Live Framework and Mesh Services: Live Services for Developers
(Slides) - Software Entrepreneurs: Go Big with BizSpark (Slides)
- Microsoft ASP.NET 4.0 : What's Next? (Slides)
- Building Out of Browser Experiences with Microsoft Silverlight 3
- The Microsoft Web Sandbox: An Open Source Framework for Developing
Secure Standards-Based Web Applications (Slides) - Enhancing Large Windows Media Platforms with Microsoft Silverlight
(Slides) - Microsoft Silverlight Media End-to-End (Slides)
- Improving UX through Application Lifecycle Management
(Slides) - Go Beyond Best Practices: Evolving Next Practices to Prosper in the
21st Century (Slides) - Creating Interactivity with Microsoft Expression Blend
(Slides) - See through the Clouds: Introduction to the Azure Services Platform
(Slides) - Overview of Windows Azure (Slides)
- What's New in Microsoft Silverlight 3 (Slides)
- the New Windows Live Messenger Web Toolkit for Social Websites
- Extending Your Brand to the Desktop with Windows 7 (Slides)
- RESTful
Services for the Programmable Web with Windows Communication Foundation
(Slides) - Securing Web Applications (Slides)
- How Razorfish Lights Up Brand with Microsoft SharePoint (Slides)
- Copyright Laws for Web Designers and Developers
- Improving Mobile Experiences with the Microsoft Mobile Device Browser
File - A Shot of Windows Live Messenger and a Pint of Microsoft Silverlight
(Slides) - Microsoft Silverlight Is Ready for Business (Slides)
- Exposing Web Content to a Global Audience Using Machine Translation
(Slides) - Search Engine Optimization (SEO) for Web Developers
- Five Killer Scenarios for the Windows Live Messenger Web Toolkit
(Slides) - Lighting Up Web and Client Applications with Microsoft Live Services
(Slides) - Scaling a Rich Client to Half a Billion Users (Slides)
- Windows Internet Explorer 8 in the Real World: How Is Internet Explorer
8 Used (Slides) - When Errors Happen: Debugging Microsoft Silverlight
(Slides) - Introducing the Microsoft Web Platform (Slides)
- Protecting Against Internet Service Abuse (Slides)
- Connecting Applications across Networks with Microsoft .NET Services
(Slides) - Microsoft Xbox "Lips" and "Fable II": Multi Channel Experiences
(Slides) - Windows Azure Storage (Slides)
- Sketch Flow: From Concept to Production (Slides)
- Consuming Web Services in Microsoft Silverlight 3
- A Website Named Desire
- The Microsoft Web Platform: Starring Internet Information Services
(IIS) and Your Application (Slides) - Microsoft ASP.NET: Taking AJAX to the Next Level
(Slides) - Using Microsoft ASP.NET MVC to Easily Extend a Web Site into the Mobile
Space (Slides) - Using Total Experience Design to Transform the Digital Building
(Slides) - Wireframes That Work: Designing (Rich Internet) Applications
(Slides) - Interactive Prototyping with DHTML
- Working across the Client Continuum (Slides)
- ASP.NET MVC: America's Next Top Model View Controller Framework
(Slides) - Microsoft Expression Web: No Platform Left Behind
(Slides) - Choosing between ASP.NET Web Forms and MVC (Slides)
- Customized Live Search for Web and Client Applications
(Slides) - Creating a Great Experience on Digg with Windows Internet Explorer 8
- C# for Designers (Slides)
- A Lap around Microsoft .NET Services (Slides)
- Building Scalable and Available Web Applications with Microsoft Project
Code Name "Velocity" (Slides) - Building an Optimized, Graphics-Intensive Application for Microsoft Silverlight
(Slides) - What's New in Microsoft SQL Data Services (Slides)
- What's New in Windows Presentation Foundation (WPF) 4 (Slides)
- A Lap around Windows Internet Explorer 8 (Slides)
- The Future of Microsoft Expression Blend (Slides)
- Running PHP on Microsoft Servers and Services (Slides)
- Delivering Media with Internet Information Services 7 (IIS) Media
Services and Microsoft Silverlight (Slides) - Deep Dive into Microsoft Silverlight Graphics (Slides)
- Microsoft ASP.NET Model View Controller (MVC): Ninja on Fire Black
Belt Tips (Slides) - Developing
RESTful Services and Clients with "M" (Slides) - User Experience Design Patterns for Business Applications with Microsoft
Silverlight 3 (Slides) - Using the Windows Azure Tools for Microsoft Visual Studio to Build
Cloud Services (Slides) - Standards for Aggregating Activity Feeds and Social Aggregation Services
- Offline
Network Detection in Microsoft Silverlight 3 (Slides) - File|New Company: Creating NerdDinner.com with Microsoft ASP.NET
Model View Controller (MVC)
اشتراکها
0.Visual Studio 2017 15.9 منتشر شد
Summary of Notable New Features in 15.9
- You can now import and export an installation configuration file that specifies which workloads and components should be installed with an instance of Visual Studio.
- We have improved the debugging experience for NuGet packages using the new symbol package format (.snupkg).
- Step back in debugger is now available in C++ for Enterprise customers.
- C++ IntelliSense now responds to changes in the remote environment for both CMake and MSBuild projects targeting Linux.
- We have made updates to UWP Desktop Bridge framework packages and added support for ARM64 C++ Native Desktop scenarios.
- We added support for the range-v3 library with the MSVC 15.9 compiler.
- We fixed several bugs in the F# compiler and F# tools.
- Language service support for new TypeScript features for semantic file renaming and project references.
- Improved Node.js development by updating Vue.js templates and adding support for unit testing using the Jest framework.
- We added SharePoint 2019 project templates, so you can migrate existing SharePoint 2013 and 2016 projects to SharePoint 2019.
- Visual Studio Tools for Xamarin now supports Xcode 10.
- We made improvements to the Xamarin.Android build performance.
- We have added and improved features for Universal Windows Platform developers, including ARM64 support, the latest preview SDK, better debugging of Desktop Bridge applications, and XAML Designer improvements.
- Substantial improvements were made to the experience of using authenticated package feeds.
- There is now support for lock file to enable repeatable restore for PackageReference based projects.
- We have added support for the new license format for NuGet packages.
- We have introduced NuGet client policies in Visual Studio which enables you to lock down environments such that only trusted packages can be installed.
- We made the use of .NET Core within Visual Studio more predictable.
Top Issues Fixed in 15.9
- No way to change "Find All References" background color.
- "Visual C++ Resource Editor Package" load failed.
- VS2017 v15.8 Build does not start if XAML files are not manually saved first.
- Installation failed - manifest signature verification failed.
- Update 15.8.6 breaks Installer Projects.
- Scrolling up with the arrow key causes Visual Studio to page up.
- After updating to 15.8.1, data tip does not show when debugging.
- System.InvalidProgramException: Common Language Runtime detected an invalid program..
- Solution Explorer does not remain pinned after closing Visual Studio.
- Navigation bar in editor has trouble handling long method names.
pvq.app
An OSS alternative to StackOverflow which uses the best proprietary and OSS Large Language Models to answer your technical questions. pvq.app is populated with over 1M+ answers for the highest rated StackOverflow questions - checkout pvq.app/leaderboard to find the best performing LLM models (results are surprising!)
در این بخش قصد داریم سئو را بر روی یک برنامهی نوشته شده با آنگلولار و Asp.net Mvc اعمال نماییم. انگولار جیاس، صفحات را با استفاده از جاوااسکریپت رندر میکند، ولی اکثر کرالرها نمیتوانند جاوااسکریپت را اجرا کنند و موقع اجرای صفحات سایت ما فقط یک div خالی را میبینند.
کاری که سرویس Prerender یا فیلتر سفارشی AjaxCrawlable برای ما انجام میدهد، درخواستهایی را که از طرف کرالرها آمدهاست را شناسایی میکند و مانند یک مرورگر، با استفاده از phantomjs آنرا اجرا میکند و نتیجهی کامل صفحات ما را به صورت اچ تی ام ال استاتیک برمیگرداند.
فانتوم جی اس، موتور اختصاصی برای شبیه سازی مرورگر مبتنی بر Webkit میباشد. فانتوم جی اس را میتوانید بر روی ویندوز، لینوکس و مک نصب نمایید. فانتوم جی اس یک Console در اختیار برنامه نویس قرار میدهد که میتوان توسط آن، برنامههای جاوااسکریپت را اجرا نمود. همچنین فانتوم جی اس میتواند اسکرین شاتی را نیز از محتوای وب سایت ما فراهم نماید.
برای اینکه صفحات انگولار جی اس،ایندکس شوند سه مرحله وجود دارند:
1- به کرالر اطلاع دهیم که رندر کردن سایت، توسط جاوااسکریپت انجام میگردد؛ با اضافه کردن متاتگ زیر در اچ تی ام ال سایت (البته در حالت استفاده HTML5 push state ) :
<meta name="fragment" content="!"> <base href="/">
http://www.example.com/?_escaped_fragment_=
ما در این مثال از HTML5 push state استفاده میکنیم. بنابراین لینکی مانند http://www.example.com/user/123 توسط کرالر به صورت زیر دیده میشود:
http://www.example.com/user/123?_escaped_fragment_=
3- اچ تی ام ال کاملا رندر شده توسط سایت ما به کرالر ارسال گردد.
برای رندر کردن اچ تی ام ال صفحات، چندین روش وجود دارد:
روش اول: میتوانیم از سرویسهای آمادهای همچون Prerender.io استفاده کنیم که سرویسهایی را برای زبانهای مختلف ارائه کردهاند. باتوجه به توضیحات نمونه استفاده از آن در Asp.Net Mvc کافیست در سایت Prerender.io ثبت نام کرده، Token را دریافت کنیم و در کانفیگ برنامه قرار دهیم و در کلاس PreStart قطعه کد زیر را قرار دهیم:
DynamicModuleUtility.RegisterModule(typeof(Prerender.io.PrerenderModule));
یکی از ابزارهای مناسب تست کردن اینکه صفحات توسط کرالر ایندکس میشوند یا خیر، برنامه screamingfrog میباشد.
در پنل Ajax آن، صفحات ایندکس شده ما نمایش داده میشوند. لینکی مشابه زیر را در مرورگر اجرا کرده، با ViewPage Source کردن آن میتوانید نتیجه اچ تی ام ال کاملا رندر شده را مشاهده نمایید.
http://www.example.com/user/123?_escaped_fragment_=
نسخه رایگان سرویس Prerender.io تا 250 صفحه را پوشش میدهد.
روش دوم: فیلتر سفارشی AjaxCrawlable. در اولین قدم نیاز به نصب فانتوم جی اس داریم: <package id="PhantomJS" version="1.9.2" targetFramework="net452" /> <package id="phantomjs.exe" version="1.9.2.1" targetFramework="net452" />
برای اینکه خطای معروف A potentially dangerous Request.Form value was detected from the client را دریافت نکنیم، کافیست قسمتهایی از آدرس را که شامل کاراکترهای خاصی مانند :// میباشند، از url حذف کنیم و در اکشن returnHtml قسمتهای حذف شده را به url اضافه نماییم.
کرالرها با مشاهده تگ fragment، تمام لینکها را به همراه کوئری استرینگ _escaped_fragment_ میفرستند، که ما در سرور باید آنرا با رشته خالی جایگزین نماییم.
public class AjaxCrawlableAttribute : System.Web.Mvc.ActionFilterAttribute { private const string Fragment = "_escaped_fragment_"; public override void OnActionExecuting(ActionExecutingContext filterContext) { var request = filterContext.RequestContext.HttpContext.Request; var url = request.Url.ToString(); if (request.QueryString[Fragment] != null && !url.Contains("HtmlSnapshot/returnHTML")) { url = url.Replace("?_escaped_fragment_=", string.Empty).Replace(request.Url.Scheme + "://", string.Empty); url = url.Split(':')[1]; filterContext.Result = new RedirectToRouteResult( new RouteValueDictionary { { "controller", "HtmlSnapshot" }, { "action", "returnHTML" }, { "url", url } }); } return; } }
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "HtmlSnapshot", url: "HtmlSnapshot/returnHTML/{*url}", defaults: new { controller = "HtmlSnapshot", action = "returnHTML", url = UrlParameter.Optional }); routes.MapRoute( name: "SPA", url: "{*catchall}", defaults: new { controller = "Home", action = "Index" }) }
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new AjaxCrawlableAttribute()); } }
ایجاد کنترلر HtmlSnapshot و متد returnHTML :
Url را به عنوان آرگومان به تابع page.open فایل جاوااسکریپتی فانتوم میدهیم و بعد از اجرای کامل، خروجی را درViewData قرار میدهیم
public ActionResult returnHTML(string url) { var prefix = HttpContext.Request.Url.Scheme + "://" + HttpContext.Request.Url.Host+":"; url = prefix+url; string appRoot = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); var startInfo = new ProcessStartInfo { Arguments = string.Format("{0} {1}", Path.Combine(appRoot, "Scripts\\seo.js"), url), FileName = Path.Combine(appRoot, "bin\\phantomjs.exe"), UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, RedirectStandardInput = true, StandardOutputEncoding = System.Text.Encoding.UTF8 }; var p = new Process(); p.StartInfo = startInfo; p.Start(); string output1 = p.StandardOutput.ReadToEnd(); p.WaitForExit(); ViewData["result"] = output1.Replace("<!-- ngView: -->", "").Replace("ng-view=\"\"", ""); return View(); }
در فایل renderHtml.cshtml
@{ Layout = null; } @Html.Raw(ViewBag.result)
ایجاد فایل seo.js در پوشه Scripts سایت :
در این بخش webpage را ایجاد میکنیم و آدرس صفحه را از[system.args[1 دریافت کرده و عملیات کپچر کردن را آغاز میکنیم و بعد از تکمیل اطلاعات در سرور، کد زیر اجرا میشود:
console.log(page.content)
var page = require('webpage').create(); var system = require('system'); var lastReceived = new Date().getTime(); var requestCount = 0; var responseCount = 0; var requestIds = []; var startTime = new Date().getTime();; page.onResourceReceived = function (response) { if (requestIds.indexOf(response.id) !== -1) { lastReceived = new Date().getTime(); responseCount++; requestIds[requestIds.indexOf(response.id)] = null; } }; page.onResourceRequested = function (request) { if (requestIds.indexOf(request.id) === -1) { requestIds.push(request.id); requestCount++; } }; function checkLoaded() { return page.evaluate(function () { return document.all["compositionComplete"]; }) != null; } // Open the page page.open(system.args[1], function () { }); var checkComplete = function () { // We don't allow it to take longer than 5 seconds but // don't return until all requests are finished if ((new Date().getTime() - lastReceived > 300 && requestCount === responseCount) || new Date().getTime() - startTime > 10000 || checkLoaded()) { clearInterval(checkCompleteInterval); console.log(page.content); phantom.exit(); } } // Let us check to see if the page is finished rendering var checkCompleteInterval = setInterval(checkComplete, 300);
صفحه Layout.Cshtml
<!DOCTYPE html> <html ng-app="appOne"> <head> <meta name="fragment" content="!"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta charset="utf-8" /> <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" /> <meta name="viewport" content="width=device-width" /> <base href="/"> @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") <script src="~/Scripts/angular/angular.js"></script> <script src="~/Scripts/angular/angular-route.js"></script> <script src="~/Scripts/angular/angular-animate.js"></script> <script> angular.module('appOne', ['ngRoute'], function ($routeProvider, $locationProvider) { $routeProvider.when('/one', { template: "<div>one</div>", controller: function ($scope) { } }) .when('/two', { template: "<div>two</div>", controller: function ($scope) { } }).when('/', { template: "<div>home</div>", controller: function ($scope) { } }); $locationProvider.html5Mode({ enabled: true }); }); </script> </head> <body> <div id="body"> <section ng-view></section> @RenderBody() </div> <div id="footer"> <ul class='xoxo blogroll'> <li><a href="one">one</a></li> <li><a href="two">two</a></li> </ul> </div> </body> </html>
چند نکته تکمیلی:
* فانتوم جی اس قادر به اجرای لینکهای فارسی (utf-8) نمیباشد.
* اگر خطای syntax error را دریافت کردید ممکن است پروژه شما در مسیری طولانی در روی هارد دیسک قرار داشته باشد.
یکی از حملات رایج که توسعه دهندگان با آن روبرو هستند، Brute-Force Attack میباشد. در این حملات برای کشف رمزعبور کاربران، هر ترکیب احتمالی از حروف، اعداد و نمادها را استفاده میکنند تا یک ترکیب صحیح را بدست آورند. اگر وب سایت شما نیاز به احراز هویت دارد، شما یک هدف مناسب برای حملات Brute-Force هستید! یک هکر میتواند رمزعبور را از این طریق بدست آورد؛ اما یافتن آن شاید سالها طول بکشد که بستگی به طول رمز و پیچیدگی آن، ممکن است چندین سال طول بکشد. اما یک حمله Brute-Force میتواند سریعتر انجام شود، میتواند از فرهنگ لغت استفاده کند و یا کلمات فرهنگ لغت را کمی تغییر دهد، زیرا بیشتر افراد بجای یک کلمهی عبور تصادفی و پیچیده، از آن کلمات استفاده میکنند. به این حملات dictionary attacks یا hybrid brute-force attacks نیز گفته میشود. این حملات حسابهای کاربران را در معرض خطر قرار میدهد و باعث افزایش ترافیک غیرضروری سایت شما میشود.
مسدود کردن حسابها
بدیهیترین راه برای جلوگیری از این حملات، مسدود کردن حسابها پس از تعداد مشخصی تلاش ناموفق است. مسدود شدن حسابها میتواند مدت زمان خاصی داشته باشد و یا اینکه حسابها باید توسط ادمین سایت فعال شوند. با این وجود، مسدود کردن حسابها همیشه بهترین راه برای جلوگیری از این حملات نیست؛ زیرا شخصی میتواند به راحتی از قوانین سوء استفاده کرده و تعداد زیادی از حسابها را مسدود کند. برخی از مشکلات مربوط به این راه عبارت اند از:
مسدود کردن حسابها گاهی اوقات تاثیرگذار است؛ اما در محیطهای کنترل شده. با این حال در بیشتر موارد مسدود کردن حسابها برای متوقف کردن حملات brute-force کافی نیست. به عنوان مثال یک سایت حراجی را در نظر بگیرید که چندین داوطلب برای یک کالا در حال مسابقه هستند. اگر وبسایت حراجی حسابها را مسدود کند، یک پیشنهاد دهنده میتواند در لحظات آخر نسبت به مسدود کردن حسابهای دیگران اقدام کند و مانع از ارائه هرگونه پیشنهادی شود و خود برنده شود. یک مهاجم میتواند از همین تکنیک برای مسدود کردن معاملات حساس مالی و ... اقدام کند.
همانطور که توضیخ داده شد، مسدود کردن حسابها یک راه کامل برای مقابله با این حملات نیست. اما ترفندهای دیگری نیز برای مقابله با این حملات وجود دارند. از آنجا که موفقیت این حملات به زمان بستگی دارد، یک راه حل آسان، تزریق مکثهای تصادفی هنگام چک کردن رمز عبور است. افزودن مکث حتی چند ثانیهای میتواند یک حمله را بسیار کند نماید؛ اما بیشتر کاربران را هنگام ورود به سایت، ناراحت نمیکند. توجه داشته باشید اگرچه اضافه کردن تاخیر میتواند یک حمله single-thread را کند، کند اما اگر مهاجم چندین درخواست را همزمان ارسال کند، کمتر موثر است.
راه حل دیگر مسدود کردن یک IP با چند عملیات ناموفق است. اما مشکل این راه حل آن است که شما سهوا گروههایی از کاربران را بلاک میکنید؛ مانند بلاک کردن یک پراکسی سرور که توسط یک ISP استفاده میشود. مشکل دیگر این راه حل استفادهی بسیاری از ابزارها از لیستی از پراکسیها است و با هر IP، دو یا سه درخواست را ارسال میکنند و سپس به دنبال بعدی میروند. یک مهاجم میتواند به این طریق به راحتی فرآیند مسدود کردن IP را دور بزند. بیشتر سایتها حسابها را بعد از یک عملیات ناموفق، مسدود نخواهند کرد؛ به همین دلیل یک مهاجم میتواند از هر پراکسی، دو یا سه درخواست را امتحان کند. یک هکر با داشتن لیستی از 1000 پراکسی میتواند 2000 یا 3000 کلمه عبور را امتحان کند؛ بدون مسدود شدن حسابها.
یک راه حل ساده در عین حال موثر آن است که رفتار وب سایت خود را برای پاسخ به عملیات ناموفق ورود به سایت، درست طراحی نکنید. برای مثال اکثر وب سایتها کد "HTTP 401 error" را برای کلمه عبور اشتباه میفرستند. اگرچه بعضی وب سایتها کد "HTTP 200 SUCCESS" را برمیگردانند، اما کاربر را به صفحهای هدایت میکند و توضیح میدهد که رمز عبور را اشتباه وارد کرده است، این کار برخی سیستمها را فریب میدهد اما دور زدن آن نیز راحت است. یک راه حل بهتر آن است که هر بار از پیامهای خطای مختلفی استفاده کنید و یا گاهی به کاربر اجازه رفتن به یک صفحه را بدهید و دوباره کاربر را وادار به وارد کردن رمز عبور نمایید.
بعضی از ابزارها این امکان را دارند تا مهاجم یک رشته را وارد کند تا به دنبال آن باشند که نشان میدهد عملیات ورود ناموفق بوده است. به عنوان مثال اگر صفحهای شامل عبارت "Bad username or password" باشد، به معنای آن است که عملیات ناموفق بوده است و نام کاربری و یا کلمهی عبور بعدی را امتحان میکند. یک راه ساده برای مقابله با این راه این است که از عبارتهایی استفاده کنیم که در هنگام ورود موفق استفاده میشوند.
بعد از دو یا چند تلاش ناموفق، کاربر را وادار به پاسخ دادن به یک سؤال مخفی کنید. این کار نه تنها باعث اختلال در حملات خودکار میشود بلکه از دسترسی فرد مهاجم جلوگیری میکند؛ حتی اگر نام کاربری و رمز عبور را درست وارد کرده باشد.
سایر تکنیک هایی که میتوانید در نظر بگیرید عبارتند از:
هکرها اغلب میتوانند بسیاری از تکنیکها را به تنهایی دور بزنند. اما با ترکیب چندین حالت میتوانید این حملات را به میزان قابل توجهی کاهش دهید. اگرچه جلوگیری کامل از این حملات سخت است، اما تشخیص آن آسان است؛ زیرا با هربار ورود ناموفق، یک رکورد با کد HTTP 401 در لاگهای سرور ثبت میشود. این مهم است که لاگهای سرور خود را برای این نوع حملات نظارت کنید در شرایط خاص. کد 200 به آن معناست که مهاجم یک رمز عبور معتبر را پیدا کرده است.
شرایطی که میتوانند حملات brute-force یا سوء استفاده از یک حساب را نشان دهند:
متوقف کردن این حملات دشوار است؛ اما با طراحی دقیق و اقدامات متقابل متعدد، میتوانید میزان قرار گرفتن در معرض این حملات را محدود کنید. درنهایت، بهترین راه جلوگیری این است که کاربران از قوانین اصلی رمزهای عبور قوی استفاده کنند؛ برای مثال از رمزهای عبور طولانی غیرقابل پیش بینی، اجتناب از کلمات فرهنگ لغت، جلوگیری از استفاده مجدد از گذرواژهها و تغییر گذرواژهها به طور منظم.
هکرها از ابزارهای گستردهای که لیست کلمات و قواعد هوشمندانهای را دارند استفاده میکنند تا به طور هوشمندانه و به طور خودکار، کلمههای عبور کاربران را حدس بزنند. اگرچه شناسایی چنین حملاتی آسان است، اما جلوگیری از آن کار آسانی نیست. به طور مثال برخی از ابزارهای HTTP brute-force میتوانند درخواستها را از طریق پروکسیهای باز سرور انجام دهند و از آنجا که هر درخواست از یک IP متفاوت میرسد، نمیتوانید با مسدود کردن IP، حملات را مسدود کنید. حتی برخی از ابزارها برای هر بار درخواست، یک نام کاربری و یک کلمه عبور را امتحان میکنند و شما نمیتوانید هر حساب کاربری را برای یک بار عملیات ناموفق مسدود کنید.
مسدود کردن حسابها
بدیهیترین راه برای جلوگیری از این حملات، مسدود کردن حسابها پس از تعداد مشخصی تلاش ناموفق است. مسدود شدن حسابها میتواند مدت زمان خاصی داشته باشد و یا اینکه حسابها باید توسط ادمین سایت فعال شوند. با این وجود، مسدود کردن حسابها همیشه بهترین راه برای جلوگیری از این حملات نیست؛ زیرا شخصی میتواند به راحتی از قوانین سوء استفاده کرده و تعداد زیادی از حسابها را مسدود کند. برخی از مشکلات مربوط به این راه عبارت اند از:
- یک مهاجم میتواند تعداد زیادی از حسابها را مسدود کند.
- به دلیل اینکه شما نمیتوانید حسابی را که وجود ندارد، مسدود کنید و فقط حسابهای معتبر قفل میشوند، یک مهاجم با استفاده از پاسخ خطای مربوط به مسدود شدن حساب کاربری میتواند از اینکار برای برداشتن نام کاربری استفاده کند.
- یک مهاجم میتواند با مسدود کردن تعداد زیادی از حسابها و جاری شدن تماسهای پشتیبانی برای بازیابی حساب، باعث انحراف آنها شود.
- یک مهاجم میتواند به طور مداوم یک حساب کاربری را مسدود کند، حتی چند ثانیه بعد از آنکه ادمین سایت آن حساب را فعال کند، مجددا به مسدود کردن آن اقدام کند.
- مسدود کردن حسابها برای حملاتی که کند هستند و هر ساعت فقط چند رمز عبور را امتحان میکنند، بی تاثیر است.
- مسدود کردن حسابها در برابر حملاتی که یک رمز عبور را در برابر لیستی از نام کاربری امتحان میکنند، بی تاثیر است
- مسدود کردن حسابها در برابر حملاتی که از لیستی از کلمه عبور/نام کاربری استفاده میکنند و در اولین بار به طور صحیح حدس میزنند بی تاثیر است.
مسدود کردن حسابها گاهی اوقات تاثیرگذار است؛ اما در محیطهای کنترل شده. با این حال در بیشتر موارد مسدود کردن حسابها برای متوقف کردن حملات brute-force کافی نیست. به عنوان مثال یک سایت حراجی را در نظر بگیرید که چندین داوطلب برای یک کالا در حال مسابقه هستند. اگر وبسایت حراجی حسابها را مسدود کند، یک پیشنهاد دهنده میتواند در لحظات آخر نسبت به مسدود کردن حسابهای دیگران اقدام کند و مانع از ارائه هرگونه پیشنهادی شود و خود برنده شود. یک مهاجم میتواند از همین تکنیک برای مسدود کردن معاملات حساس مالی و ... اقدام کند.
اقدامات متقابل
همانطور که توضیخ داده شد، مسدود کردن حسابها یک راه کامل برای مقابله با این حملات نیست. اما ترفندهای دیگری نیز برای مقابله با این حملات وجود دارند. از آنجا که موفقیت این حملات به زمان بستگی دارد، یک راه حل آسان، تزریق مکثهای تصادفی هنگام چک کردن رمز عبور است. افزودن مکث حتی چند ثانیهای میتواند یک حمله را بسیار کند نماید؛ اما بیشتر کاربران را هنگام ورود به سایت، ناراحت نمیکند. توجه داشته باشید اگرچه اضافه کردن تاخیر میتواند یک حمله single-thread را کند، کند اما اگر مهاجم چندین درخواست را همزمان ارسال کند، کمتر موثر است.
راه حل دیگر مسدود کردن یک IP با چند عملیات ناموفق است. اما مشکل این راه حل آن است که شما سهوا گروههایی از کاربران را بلاک میکنید؛ مانند بلاک کردن یک پراکسی سرور که توسط یک ISP استفاده میشود. مشکل دیگر این راه حل استفادهی بسیاری از ابزارها از لیستی از پراکسیها است و با هر IP، دو یا سه درخواست را ارسال میکنند و سپس به دنبال بعدی میروند. یک مهاجم میتواند به این طریق به راحتی فرآیند مسدود کردن IP را دور بزند. بیشتر سایتها حسابها را بعد از یک عملیات ناموفق، مسدود نخواهند کرد؛ به همین دلیل یک مهاجم میتواند از هر پراکسی، دو یا سه درخواست را امتحان کند. یک هکر با داشتن لیستی از 1000 پراکسی میتواند 2000 یا 3000 کلمه عبور را امتحان کند؛ بدون مسدود شدن حسابها.
یک راه حل ساده در عین حال موثر آن است که رفتار وب سایت خود را برای پاسخ به عملیات ناموفق ورود به سایت، درست طراحی نکنید. برای مثال اکثر وب سایتها کد "HTTP 401 error" را برای کلمه عبور اشتباه میفرستند. اگرچه بعضی وب سایتها کد "HTTP 200 SUCCESS" را برمیگردانند، اما کاربر را به صفحهای هدایت میکند و توضیح میدهد که رمز عبور را اشتباه وارد کرده است، این کار برخی سیستمها را فریب میدهد اما دور زدن آن نیز راحت است. یک راه حل بهتر آن است که هر بار از پیامهای خطای مختلفی استفاده کنید و یا گاهی به کاربر اجازه رفتن به یک صفحه را بدهید و دوباره کاربر را وادار به وارد کردن رمز عبور نمایید.
بعضی از ابزارها این امکان را دارند تا مهاجم یک رشته را وارد کند تا به دنبال آن باشند که نشان میدهد عملیات ورود ناموفق بوده است. به عنوان مثال اگر صفحهای شامل عبارت "Bad username or password" باشد، به معنای آن است که عملیات ناموفق بوده است و نام کاربری و یا کلمهی عبور بعدی را امتحان میکند. یک راه ساده برای مقابله با این راه این است که از عبارتهایی استفاده کنیم که در هنگام ورود موفق استفاده میشوند.
بعد از دو یا چند تلاش ناموفق، کاربر را وادار به پاسخ دادن به یک سؤال مخفی کنید. این کار نه تنها باعث اختلال در حملات خودکار میشود بلکه از دسترسی فرد مهاجم جلوگیری میکند؛ حتی اگر نام کاربری و رمز عبور را درست وارد کرده باشد.
سایر تکنیک هایی که میتوانید در نظر بگیرید عبارتند از:
- برای کاربران مهم که میخواهند از حساب خود در مقابل این نوع حملات جلوگیری کنند، به آنها این امکان را بدهید که بتوانند فقط از IP خاصی وارد سیستم شوند.
- برای جلوگیری از حملات خودکار، از captcha استفاده کنید.
- بجای قفل کردن حساب کاربری، آنرا در حالت قفل با دسترسی محدود قرار دهید.
هکرها اغلب میتوانند بسیاری از تکنیکها را به تنهایی دور بزنند. اما با ترکیب چندین حالت میتوانید این حملات را به میزان قابل توجهی کاهش دهید. اگرچه جلوگیری کامل از این حملات سخت است، اما تشخیص آن آسان است؛ زیرا با هربار ورود ناموفق، یک رکورد با کد HTTP 401 در لاگهای سرور ثبت میشود. این مهم است که لاگهای سرور خود را برای این نوع حملات نظارت کنید در شرایط خاص. کد 200 به آن معناست که مهاجم یک رمز عبور معتبر را پیدا کرده است.
شرایطی که میتوانند حملات brute-force یا سوء استفاده از یک حساب را نشان دهند:
- تعداد زیادی ورود ناموفق از یک IP
- تلاش برای ورود به سایت با چند نام کاربری از طرف یک IP
- تلاش برای ورود به یک حساب کاربری از طرف چندین IP
- تلاش برای ورود، با وارد کردن نام کاربری و کلمهی عبور، به ترتیب حروف الفبا
- تلاش برای ورود، با نام کاربری یا کلمات عبوری مانند ownsyou,washere,zealots ,hacksyou یا مشابه آنها که هکرها معمولا از آنها استفاده میکنند
متوقف کردن این حملات دشوار است؛ اما با طراحی دقیق و اقدامات متقابل متعدد، میتوانید میزان قرار گرفتن در معرض این حملات را محدود کنید. درنهایت، بهترین راه جلوگیری این است که کاربران از قوانین اصلی رمزهای عبور قوی استفاده کنند؛ برای مثال از رمزهای عبور طولانی غیرقابل پیش بینی، اجتناب از کلمات فرهنگ لغت، جلوگیری از استفاده مجدد از گذرواژهها و تغییر گذرواژهها به طور منظم.
مطالب
Vue Lifecycle hooks
هر وهله از Vue از یکسری مراحل یا (initialization steps) عبور خواهد کرد به عنوان مثال مراحلی از قبیل کامپایل شدن تمپلیت، mount شدن وهله به DOM و یا بروزرسانی DOM زمانیکه دادهها تغییر پیدا میکنند و ... در حین طی کردن این مسیر یکسری توابع ویژه با نام lifecycle hooks فراخوانی خواهند شد. بنابراین درون این توابع میتوانیم در هر مرحله کدهای موردنیازمان را قرار دهیم:
همانطور که مشاهده میکنید این چرخه حیات با وهلهسازی شیء Vue شروع میشود. در این مرحله اولین تابع یعنی beforeCreate فراخوانی میشود. در این مرحله کار مقداردهی اولیه (initialization) ایونتهای پاس داده شده به Vue instance انجام خواهد گرفت. در مرحله بعد تابع created فراخوانی و در ادامه تمپلیت (اگر تعیین شده باشد) کامپایل و بعد از آن تابع beforeMount فراخوانی خواهد شد. این تابع دقیقاً قبل از اینکه تمپلیت به DOM اضافه شود فراخوانی میشود. در اینحالت المنت تعیین شده در قسمت el با محتوای تمپلیت مقداردهی میشود. البته تا اینجا هنوز خروجی به DOM اضافه نشده است؛ تنها اعمال بایندینگ، string interpolation بر روی تمپلیت صورت خواهند گرفت تا خروجی به صورت یک HTML آماده تحویل به مرحله بعد گردد. در ادامه خروجی تهیه شده به DOM اضافه (mount) خواهد شد. در این مرحله اگر دیتایی تغییر کند، تابع beforeUpdate فراخوانی خواهد شد. بعد از اینکه تغییری توسط Vue مشاهده شد، تابع updated فراخوانی خواهد شد. در نهایت توابع beforeDestroyed و destroyed را داریم. درون این توابع فرصت آزادسازی منابع استفاده شده را خواهیم داشت.
به عنوان مثال بعد از اینکه یک وهله از Vue ایجاد شد، تابعی با نام created فراخوانی خواهد شد:
new Vue({ el: '#app', data() { return { a: 1 }; }, created: function () { // `this` points to the vm instance console.log('a is: ' + this.a) } });
در حالت کلی میتوانیم hookها را به چهار دستهبندی زیر تقسیم کنیم:
(Creation (Initialization
این نوع hook در واقع اولین توابعی هستند که درون یک کامپوننت فراخوانی خواهند شد. در اینجا میتوانیم قبل از اینکه کامپوننت به DOM اضافه شود، اکشن مورد نیازمان را قرار دهیم. باید دقت داشته باشید که درون این توابع، به target element (همان المنتی که وهلهی Vue به آن متصل خواهد شد) و همچنین DOM دسترسی ندارید و مقادیر آنها در این فاز، undefined خواهند بود:
new Vue({ el: '#app', data() { return { name: 'Sirwan' }; }, beforeCreate: function () { console.log('name is: ' + this.name); console.log(`We don't have access to target element at this point: ${this.$el}`); }, created: function () { // `this` points to the vm instance console.log('name is: ' + this.name); console.log(`We don't have access to target element at this point: ${this.$el}`); } });
(Mounting (DOM Insertion
در این مرحله میتوانیم بلافاصله قبل و بعد از اولین رندر، به کامپوننت دسترسی داشته باشیم. میتوانیم از این hookها برای تغییر کامپوننت، به محض رندر شدن استفاده کنیم. بنابراین در این فاز به target element نیز دسترسی خواهیم داشت:
beforeMount: function () { console.log(`this.$el doesn't exist yet, but it will soon!`); }, mounted: function () { console.log(this.$el); }
(Updating (Diff & Re-render
این توابع هنگامیکه دادههای تعریف شده در قسمت data تغییر پیدا کنند، فراخوانی خواهند شد. یعنی دقیقاً قبل از اینکه DOM بهروزرسانی شود، فراخوانی خواهند شد. در مثال زیر به محض ایجاد شدن وهلهی Vue، یک تایمر ایجاد شده است. این تایمر هر ثانیه یکبار مقدار دیتای counter را یک واحد افزایش خواهد داد. در حالت عادی اگر دیتایی درون DOM استفاده نشده باشد، درون کنسول خروجی را دریافت نخواهید کرد. در نتیجه این توابع تنها در صورتیکه از دیتا درون DOM استفاده شده باشد، فراخوانی خواهند شد:
new Vue({ el: '#app', data() { return { name: 'Sirwan', counter: 0 }; }, created: function () { setInterval(() => { this.counter++ }, 1000) }, beforeUpdate: function () { console.log(this.counter); // Logs the counter value every second, before the DOM updates. }, updated: function () { console.log(`new value: ${this.counter}`); } });
(Destruction (Teardown
این توابع، قبل و بعد از تخریب شدن (حذف شدن از DOM) کامپوننت فراخوانی خواهند شد:
new Vue({ el: '#app', data() { return { name: 'Sirwan', counter: 0 }; }, beforeDestroy: function () { }, destroyed() { console.log(this) // There's practically nothing here! } });
Vue.js چگونه تغییرات دادهها را متوجه خواهد شد؟
شاید تصور کنید که Vue.js به صورت مداوم تغییرات را مشاهده کرده و در صورت وجود تغییری بر روی دیتا، این تغییرات را مستقیماً به DOM اعمال خواهد کرد. هر پراپرتی دارای یک watcher است؛ یعنی وقتی یک شیء را به سازنده Vue ارسال میکنیم، یک watcher برای تمامی پراپرتیهای تعریف شده، درون پراپرتی data ایجاد خواهد کرد. این watcher در واقع مسئول کنترل تغییرات پراپرتیها میباشد و اگر تغییری صورت بگیرد، آن را به DOM اعمال خواهد کرد. اما نکته اینجاست که این تغییرات بلافاصه بر روی DOM اعمال نخواهند شد؛ زیرا از لحاظ کارآیی، اینکار بهینه نیست. فرض کنید یک پراپرتی با نام message درون قسمت data داریم که مقدار آن Hello است. اکنون همین مقدار توسط UI مجدداً به Hello ست خواهد شد. در اینحالت اعمال تغییرات نباید به DOM منعکس شود، زیرا مقدار message به همان مقدار قبلی تنظیم شده است. در عوض Vue.js از مفهومی با نام Virtual DOM استفاده میکند که در واقع یک کپی از DOM اصلی است؛ با این تفاوت که دسترسی به آن خیلی سریعتر از DOM اصلی میباشد. بنابراین Vue.js تغییرات صورت گرفته را مستقیماً به Virtual DOM اعمال میکند. در نهایت آن را با DOM اصلی مقایسه خواهد کرد و در صورت وجود تغییر، آن را با DOM اصلی ادغام خواهد کرد.
در طی چند قسمت قصد داریم مثال قسمت قبل را که کار نمایش لیستی از فیلمها را انجام میدهد، تبدیل به یک کامپوننت گرید کنیم که دارای امکانات صفحه بندی، فیلتر کردن و مرتب سازی اطلاعات است.
بررسی ساختار کامپوننت Pagination
شبیه به کامپوننت Like که در قسمت قبل ایجاد کردیم، میخواهیم کامپوننت جدید Pagination نیز به طور کامل از اشیاء movie مستقل باشد؛ تا در آینده بتوان از آن در جاهای دیگری نیز استفاده کرد. به همین جهت فایل جدید src\components\common\pagination.jsx را ایجاد کرده و سپس با استفاده از میانبرهای imrc و cc در VSCode، ساختار ابتدایی این کامپوننت را ایجاد میکنیم. هرچند میتوان این کامپوننت را به صورت «Stateless Functional Component» نیز طراحی کرد؛ چون state و متد دیگری بجز render نخواهد داشت و تمام اطلاعات خودش را از والد خود دریافت میکند.
سپس به کامپوننت movies مراجعه کرده و این کامپوننت خالی را import میکنیم:
پس از آن به متد رندر کامپوننت movies مراجعه کرده و پس از بسته شدن tag المان جدول، قصد داریم این کامپوننت صفحه بندی را نمایش دهیم. به همین جهت المان آنرا در این محل قرار میدهیم تا بتوانیم اینترفیس ابتدایی آنرا پیش از پیاده سازی آن، طراحی کنیم:
این کامپوننت نیاز به تعداد کل آیتمهای فیلمها را دارد؛ به علاوهی اندازهی صفحه، یا همان تعداد ردیفی که قرار است در هر صفحه نمایش داده شود. به این ترتیب کامپوننت صفحه بندی میتواند تعداد صفحات مورد نیاز را محاسبه کرده و سپس آنها را رندر کند.
تا اینجا اینترفیس کامپوننت صفحه بندی را پیش از پیاده سازی آن تعریف کردیم (تعیین ورودیها و خروجی آن). در مرحلهی بعد، این کامپوننت را تکمیل میکنیم.
نمایش شماره صفحات گرید، در کامپوننت صفحه بندی
برای رندر کامپوننت صفحه بندی، از کلاسهای مخصوص اینکار که در بوت استرپ تعریف شدهاند، استفاده میکنیم که ساختار کلی آن به صورت زیر است و از یک المان nav که داخل آن ul ای با کلاس pagination و liهایی با کلاس page-item هستند، تشکیل میشود. هر li، به همراه یک anchor است؛ با کلاس page-link تا لینک به صفحهای خاص را ارائه دهد که در اینجا بجای لینک، از کلیک بر روی آنها استفاده خواهیم کرد:
تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، عدد 1 را در پایین جدول فیلمها مشاهده خواهید کرد:
اکنون باید رندر این liها را بر اساس ورودیهای این کامپوننت که پیشتر معرفی کردیم، یعنی pageSize و itemsCount، پویا کنیم. به همین جهت نیاز به آرایهای داریم که بر اساس این ورودیها، شمارهی صفحات مانند [1,2,3] را ارائه دهد تا بر روی آن متد Array.map را فراخوانی کرده و liهای مورد نیاز را به صورت پویا رندر کنیم:
در اینجا بر اساس ورودیها، تعداد صفحات محاسبه شده و سپس بر اساس آنها آرایهای از این شماره صفحهها تشکیل و بازگشت داده میشود. همچنین اگر تعداد صفحات 1 بود، میخواهیم این کامپوننت چیزی را رندر نکند. به همین جهت در اینجا null بازگشت داده شدهاست. دلیل استفادهی از Math.ceil که کوچکترین عدد صحیح بزرگتر یا مساوی خروجی را بازگشت میدهد، نیز همین مورد است. توسط این متد، خروجی float دریافتی به integer تبدیل شده و سپس قابلیت مقایسهی با 1 را پیدا میکند. برای مثال اگر تعداد ردیفهای صفحه را به 10 تنظیم کنیم، خروجی این تقسیم در این مثال، 0.9 خواهد بود که شرط بررسی pagesCount === 1 را برآورده نمیکند. به همین جهت توسط متد Math.ceil، این خروجی به عدد 1 تقریب زده شده و سبب بازگشت نال از این متد خواهد شد.
سپس به کمک متد map، اعضای این آرایه را تبدیل به لیست liهای نمایش شماره صفحات میکنیم. در اینجا key هر li را نیز به شماره صفحه که منحصربفرد است، تنظیم کردهایم:
پس از ذخیرهی این کامپوننت و بارگذاری مجدد برنامه در مرورگر، شمارهی صفحات رندر شده، در پایین جدول مشخص هستند:
با داشتن 9 فیلم در آرایهی movies و نمایش 4 فیلم به ازای هر صفحه، به 3 صفحه خواهیم رسید که به درستی در اینجا رندر شدهاست. یکبار هم برای آزمایش بیشتر، مقدار pageSize را در کامپوننت movies به 10 تنظیم کنید. در این حالت کامپوننت صفحه بندی نباید رندر شود.
مدیریت انتخاب شمارههای صفحات
در این قسمت میخواهیم مدیریت onPageChange={this.handlePageChange} را که به تعریف المان صفحه بندی در کامپوننت movies اضافه کردیم، تکمیل کنیم. برای این منظور در کامپوننت صفحه بندی، قسمت anchor را به صورت زیر تغییر میدهیم تا با کلیک بر روی آن، رخداد onPageChange صادر شود:
تا اینجا اگر برنامه را آزمایش کنیم، با کلیک بر روی لینکهای شماره صفحات، شماره صفحهی انتخابی، در کنسول توسعه دهندگان مرورگر لاگ میشود.
اکنون میخواهیم اگر صفحهای انتخاب شد، شمارهی آن صفحه با رنگی دیگر نمایش داده شود. در بوت استرپ برای اینکار تنها کافی است کلاس active را به className هر li اضافه کنیم و برعکس. یعنی اگر page ای مساوی صفحهی جاری انتخاب شده بود (currentPage در اینجا)، آنگاه کلاس page-item active، به المان li اضافه شود. بنابراین در این کامپوننت نیاز است عدد currentPage را نیز دریافت کنیم. به همین جهت ویژگی currentPage را به تعریف المان Pagination در کامپوننت movies اضافه میکنیم:
این ویژگی نیز مقدار خودش را از state به روز شده دریافت میکند:
به روز رسانی state نیز در متد handlePageChange که به ویژگی onPageChange متصل است، بر اساس page دریافتی از کامپوننت صفحه بندی، رخ میدهد:
بنابراین هرگاه که بر روی یک شماره صفحه در کامپوننت صفحه بندی کلیک میشود، رخداد onPageChange متصل به تعریف المان Pagination درج شدهی در کامپوننت movies، روی داده و به همراه آن شماره صفحهای، به متد رخدادگران متصل به آن در کامپوننت movies که در اینجا handlePageChange نام دارد، ارسال میشود. در این متد state کامپوننت به روز شده و این امر سبب فراخوانی مجدد متد رندر میشود که در انتهای آن کامپوننت Pagination درج شدهاست. بنابراین به روز رسانی state، سبب رندر مجدد کامپوننت صفحه بندی با currentPage جدیدی که به آن رسیدهاست، خواهد شد.
پس از این تغییرات، به کامپوننت صفحه بندی مراجعه کرده و بر اساس currentPage دریافتی، کلاس active را به المان li اعمال میکنیم:
پس از اعمال این تغییرات، اکنون برنامه در مرورگر به صورت زیر به نظر میرسد:
در اولین بار نمایش برنامه، عدد 1 در حالت انتخاب شده قرار دارد؛ چون مقدار currentPage موجود در state، همان عدد 1 است. پس از آن با کلیک بر روی اعداد دیگر، با به روز رسانی state، مقدار currentPage تغییر کرده و کامپوننت صفحه بندی نسبت به آن واکنش نشان میدهد.
نمایش صفحه بندی شدهی اطلاعات
تا اینجا لیستی که نمایش داده میشود، حاوی تمام اطلاعات آرایهی this.state.movies است و بر اساس شمارهی صفحهی انتخابی، تغییر نمیکند. به همین جهت با استفاده از متد slice، تکهای از آرایهی movies را که بر اساس شماره صفحهی انتخابی و تعداد ردیفها در هر صفحه نیاز است نمایش داده شود، انتخاب کرده و بازگشت میدهیم:
اکنون در متد رندر کامپوننت movies، بجای کار با کل آرایهی this.state.movies، آرایهی جدید slice شده را توسط متد paginate دریافت کرده:
و سپس در همین متد رندر، فراخوانی this.state.movies.map را به movies.map تغییر میدهیم، تا تنها لیست مرتبط با هر صفحهی انتخابی نمایش داده شود.
پس از ذخیرهی تغییرات و بارگذاری مجدد برنامه، اکنون میتوان نمایش صفحه بندی شدهی اطلاعات را شاهد بود:
بررسی صحت نوع پارامترهای ارسالی به کامپوننتها
تا اینجا فرض بر این است که مصرف کنندهی کامپوننت صفحه بندی، دقیقا همان ویژگیهایی را که ما طراحی کردهایم، با همان نامها و همان نوعها را حتما به آن ارسال میکند. همچنین اگر افزونهی eslint را هم در VSCode نصب کرده باشید، به همراه نصب وابستگیهای زیر در خط فرمان:
به ازای هر خاصیت props استفاده شدهی در کامپوننت صفحه بندی، اخطاری را مانند «'currentPage' is missing in props validation eslint(react/prop-types)» مشاهده خواهید کرد:
که عنوان میکند props validation این خاصیت استفاده شده، فراموش شدهاست.
در نگارشهای قبلی React، امکانات بررسی نوعهای ارسالی به کامپوننتها، جزئی از بستهی اصلی آن بود؛ اما از نگارش 15 به بعد، به بستهی مستقلی منتقل شدهاست که باید به صورت جداگانهای در ریشهی پروژه نصب شود:
البته اگر TypeScript را بر روی سیستم خود نصب کرده باشید، دیگر نیازی به نصب بستهی npm فوق را ندارید و prop-types، جزئی از آن است که عموما در یک چنین مسیری قرار دارد و برای کار کردن با آن، تنها ذکر import مرتبط با PropType در ماژولهای برنامه کافی بوده و برنامه در این حالت بدون مشکل کامپایل میشود:
اکنون در ابتدای فایل کامپوننت صفحه بندی، تعریف زیر را اضافه میکنیم:
سپس در انتهای این فایل، اعتبارسنجی props آنرا تعریف خواهیم کرد:
همانطور که مشاهده میکنید، در اینجا خاصیت جدید propTypes (دقیقا با همین نگارش؛ در غیراینصورت بررسی نوعها کار نخواهد کرد)، به تعریف کلاس Pagination اضافه شدهاست (پس از تعریف کلاس کامپوننت به صورت مستقل اضافه میشود).
سپس مقدار این خاصیت جدید را به شیءای تنظیم میکنیم که نام خواص آن، دقیقا همان نام خواص و رویدادهای props استفاده شدهی در این کامپوننت است. در ادامه توسط PropTypes ای که در ابتدای ماژول import میشود، کار تعریف نوع این خواص و اجباری بودن آنها را میتوان مشخص کرد که برای مثال در اینجا سه خاصیت تعریف شده از نوع عددی و اجباری بوده و onPageChange، از نوع func است.
پس از اضافه کردن Pagination.propTypes و مقدار دهی آن، خطاهای eslint ای که در تصویر فوق مشاهده کردید، برطرف میشوند. همچنین اگر فراخوان کامپوننت Pagination مثلا بجای itemsCount یک رشتهی فرضی را وارد کند (برای آزمایش آن در کامپوننت movies، در تعریف المان Pagination، مقدار itemsCount را یک رشته وارد کنید)، چنین خطایی در مرورگر ظاهر خواهد شد که عنوان میکند itemsCount یک عدد را میپذیرد و نوع ورودی آن اشتباه است:
بنابراین تعریف propTypes یک best practice ایجاد کامپوننتهای React است که نه فقط بررسی نوعها را فعال میکند، بلکه میتواند به عنوان مستندات آن نیز در جهت تعیین props مورد نیاز، همچنین نوع و اجباری بودن آنها، اطلاعات کاملی را ارائه کند.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-11.zip
بررسی ساختار کامپوننت Pagination
شبیه به کامپوننت Like که در قسمت قبل ایجاد کردیم، میخواهیم کامپوننت جدید Pagination نیز به طور کامل از اشیاء movie مستقل باشد؛ تا در آینده بتوان از آن در جاهای دیگری نیز استفاده کرد. به همین جهت فایل جدید src\components\common\pagination.jsx را ایجاد کرده و سپس با استفاده از میانبرهای imrc و cc در VSCode، ساختار ابتدایی این کامپوننت را ایجاد میکنیم. هرچند میتوان این کامپوننت را به صورت «Stateless Functional Component» نیز طراحی کرد؛ چون state و متد دیگری بجز render نخواهد داشت و تمام اطلاعات خودش را از والد خود دریافت میکند.
سپس به کامپوننت movies مراجعه کرده و این کامپوننت خالی را import میکنیم:
import Pagination from "./common/pagination";
<Pagination itemsCount={this.state.movies.length} pageSize={this.state.pageSize} onPageChange={this.handlePageChange} />
state = { movies: getMovies(), pageSize: 4 };
به همین جهت دو ویژگی itemsCount و pageSize را پیش از هرکاری به تعریف المان صفحه بندی اضافه کردهایم تا دادههای ورودی آن مشخص شوند. ویژگی itemsCount از تعداد اعضای آرایهی movies حاصل میشود و pageSize را به عنوان یک خاصیت جدید شیء منتسب به state تعریف و با عدد 4 مقدار دهی کردهایم.
همچنین در لیست صفحاتی که توسط این کامپوننت رندر میشود، باید با کلیک بر روی هر کدام، اطلاعات آن صفحه رندر شود. به همین جهت در اینجا ویژگی onPageChange تعریف شده و به متد جدید handlePageChange در کامپوننت movies، متصل گردیده تا عدد page را دریافت کرده و به آن واکنش نشان دهد:handlePageChange = page => { console.log("handlePageChange", page); };
نمایش شماره صفحات گرید، در کامپوننت صفحه بندی
برای رندر کامپوننت صفحه بندی، از کلاسهای مخصوص اینکار که در بوت استرپ تعریف شدهاند، استفاده میکنیم که ساختار کلی آن به صورت زیر است و از یک المان nav که داخل آن ul ای با کلاس pagination و liهایی با کلاس page-item هستند، تشکیل میشود. هر li، به همراه یک anchor است؛ با کلاس page-link تا لینک به صفحهای خاص را ارائه دهد که در اینجا بجای لینک، از کلیک بر روی آنها استفاده خواهیم کرد:
import React, { Component } from "react"; class Pagination extends Component { render() { return ( <nav> <ul className="pagination"> <li className="page-item"> <a className="page-link">1</a> </li> </ul> </nav> ); } } export default Pagination;
تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، عدد 1 را در پایین جدول فیلمها مشاهده خواهید کرد:
اکنون باید رندر این liها را بر اساس ورودیهای این کامپوننت که پیشتر معرفی کردیم، یعنی pageSize و itemsCount، پویا کنیم. به همین جهت نیاز به آرایهای داریم که بر اساس این ورودیها، شمارهی صفحات مانند [1,2,3] را ارائه دهد تا بر روی آن متد Array.map را فراخوانی کرده و liهای مورد نیاز را به صورت پویا رندر کنیم:
class Pagination extends Component { // ... getPageNumbersArray() { const { itemsCount, pageSize } = this.props; const pagesCount = Math.ceil(itemsCount / pageSize); if (pagesCount === 1) { return null; } const pages = new Array(); for (let i = 1; i <= pagesCount; i++) { pages.push(i); } return pages; } }
سپس به کمک متد map، اعضای این آرایه را تبدیل به لیست liهای نمایش شماره صفحات میکنیم. در اینجا key هر li را نیز به شماره صفحه که منحصربفرد است، تنظیم کردهایم:
class Pagination extends Component { render() { const pages = this.getPageNumbersArray(); if (!pages) { return null; } return ( <nav> <ul className="pagination"> {pages.map(page => ( <li key={page} className="page-item"> <a className="page-link">{page}</a> </li> ))} </ul> </nav> ); }
پس از ذخیرهی این کامپوننت و بارگذاری مجدد برنامه در مرورگر، شمارهی صفحات رندر شده، در پایین جدول مشخص هستند:
با داشتن 9 فیلم در آرایهی movies و نمایش 4 فیلم به ازای هر صفحه، به 3 صفحه خواهیم رسید که به درستی در اینجا رندر شدهاست. یکبار هم برای آزمایش بیشتر، مقدار pageSize را در کامپوننت movies به 10 تنظیم کنید. در این حالت کامپوننت صفحه بندی نباید رندر شود.
مدیریت انتخاب شمارههای صفحات
در این قسمت میخواهیم مدیریت onPageChange={this.handlePageChange} را که به تعریف المان صفحه بندی در کامپوننت movies اضافه کردیم، تکمیل کنیم. برای این منظور در کامپوننت صفحه بندی، قسمت anchor را به صورت زیر تغییر میدهیم تا با کلیک بر روی آن، رخداد onPageChange صادر شود:
<a onClick={() => this.props.onPageChange(page)} className="page-link" style={{ cursor: "pointer" }} > {page} </a>
تا اینجا اگر برنامه را آزمایش کنیم، با کلیک بر روی لینکهای شماره صفحات، شماره صفحهی انتخابی، در کنسول توسعه دهندگان مرورگر لاگ میشود.
اکنون میخواهیم اگر صفحهای انتخاب شد، شمارهی آن صفحه با رنگی دیگر نمایش داده شود. در بوت استرپ برای اینکار تنها کافی است کلاس active را به className هر li اضافه کنیم و برعکس. یعنی اگر page ای مساوی صفحهی جاری انتخاب شده بود (currentPage در اینجا)، آنگاه کلاس page-item active، به المان li اضافه شود. بنابراین در این کامپوننت نیاز است عدد currentPage را نیز دریافت کنیم. به همین جهت ویژگی currentPage را به تعریف المان Pagination در کامپوننت movies اضافه میکنیم:
<Pagination itemsCount={this.state.movies.length} pageSize={this.state.pageSize} onPageChange={this.handlePageChange} currentPage={this.state.currentPage} />
class Movies extends Component { state = { movies: getMovies(), pageSize: 4, currentPage: 1 };
handlePageChange = page => { console.log("handlePageChange", page); this.setState({currentPage: page}); };
پس از این تغییرات، به کامپوننت صفحه بندی مراجعه کرده و بر اساس currentPage دریافتی، کلاس active را به المان li اعمال میکنیم:
<li key={page} className={ page === this.props.currentPage ? "page-item active" : "page-item" } >
در اولین بار نمایش برنامه، عدد 1 در حالت انتخاب شده قرار دارد؛ چون مقدار currentPage موجود در state، همان عدد 1 است. پس از آن با کلیک بر روی اعداد دیگر، با به روز رسانی state، مقدار currentPage تغییر کرده و کامپوننت صفحه بندی نسبت به آن واکنش نشان میدهد.
نمایش صفحه بندی شدهی اطلاعات
تا اینجا لیستی که نمایش داده میشود، حاوی تمام اطلاعات آرایهی this.state.movies است و بر اساس شمارهی صفحهی انتخابی، تغییر نمیکند. به همین جهت با استفاده از متد slice، تکهای از آرایهی movies را که بر اساس شماره صفحهی انتخابی و تعداد ردیفها در هر صفحه نیاز است نمایش داده شود، انتخاب کرده و بازگشت میدهیم:
paginate() { const first = (this.state.currentPage - 1) * this.state.pageSize; const last = first + this.state.pageSize; return this.state.movies.slice(first, last); }
render() { const { length: count } = this.state.movies; if (count === 0) return <p>There are no movies in the database.</p>; const movies = this.paginate();
پس از ذخیرهی تغییرات و بارگذاری مجدد برنامه، اکنون میتوان نمایش صفحه بندی شدهی اطلاعات را شاهد بود:
بررسی صحت نوع پارامترهای ارسالی به کامپوننتها
تا اینجا فرض بر این است که مصرف کنندهی کامپوننت صفحه بندی، دقیقا همان ویژگیهایی را که ما طراحی کردهایم، با همان نامها و همان نوعها را حتما به آن ارسال میکند. همچنین اگر افزونهی eslint را هم در VSCode نصب کرده باشید، به همراه نصب وابستگیهای زیر در خط فرمان:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks jshint babel-eslint eslint-plugin-react eslint-plugin-mocha
به ازای هر خاصیت props استفاده شدهی در کامپوننت صفحه بندی، اخطاری را مانند «'currentPage' is missing in props validation eslint(react/prop-types)» مشاهده خواهید کرد:
که عنوان میکند props validation این خاصیت استفاده شده، فراموش شدهاست.
در نگارشهای قبلی React، امکانات بررسی نوعهای ارسالی به کامپوننتها، جزئی از بستهی اصلی آن بود؛ اما از نگارش 15 به بعد، به بستهی مستقلی منتقل شدهاست که باید به صورت جداگانهای در ریشهی پروژه نصب شود:
> npm i prop-types --save
البته اگر TypeScript را بر روی سیستم خود نصب کرده باشید، دیگر نیازی به نصب بستهی npm فوق را ندارید و prop-types، جزئی از آن است که عموما در یک چنین مسیری قرار دارد و برای کار کردن با آن، تنها ذکر import مرتبط با PropType در ماژولهای برنامه کافی بوده و برنامه در این حالت بدون مشکل کامپایل میشود:
C:/Users/{username}/AppData/Local/Microsoft/TypeScript/3.6/node_modules/@types/prop-types/index
اکنون در ابتدای فایل کامپوننت صفحه بندی، تعریف زیر را اضافه میکنیم:
import PropTypes from "prop-types";
Pagination.propTypes = { itemsCount: PropTypes.number.isRequired, pageSize: PropTypes.number.isRequired, currentPage: PropTypes.number.isRequired, onPageChange: PropTypes.func.isRequired }; export default Pagination;
سپس مقدار این خاصیت جدید را به شیءای تنظیم میکنیم که نام خواص آن، دقیقا همان نام خواص و رویدادهای props استفاده شدهی در این کامپوننت است. در ادامه توسط PropTypes ای که در ابتدای ماژول import میشود، کار تعریف نوع این خواص و اجباری بودن آنها را میتوان مشخص کرد که برای مثال در اینجا سه خاصیت تعریف شده از نوع عددی و اجباری بوده و onPageChange، از نوع func است.
پس از اضافه کردن Pagination.propTypes و مقدار دهی آن، خطاهای eslint ای که در تصویر فوق مشاهده کردید، برطرف میشوند. همچنین اگر فراخوان کامپوننت Pagination مثلا بجای itemsCount یک رشتهی فرضی را وارد کند (برای آزمایش آن در کامپوننت movies، در تعریف المان Pagination، مقدار itemsCount را یک رشته وارد کنید)، چنین خطایی در مرورگر ظاهر خواهد شد که عنوان میکند itemsCount یک عدد را میپذیرد و نوع ورودی آن اشتباه است:
البته این خطا فقط در حالت development مشاهده میشود و در حالت توزیع برنامه، خیر.
بنابراین تعریف propTypes یک best practice ایجاد کامپوننتهای React است که نه فقط بررسی نوعها را فعال میکند، بلکه میتواند به عنوان مستندات آن نیز در جهت تعیین props مورد نیاز، همچنین نوع و اجباری بودن آنها، اطلاعات کاملی را ارائه کند.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-11.zip