در ادامه قصد داریم نحوه پیاده سازی آنرا در ASP.NET MVC به کمک امکانات jQuery بررسی کنیم.
مدل برنامه
namespace jQueryMvcSample02.Models { public class BlogPost { public int Id { set; get; } public string Title { set; get; } public string Body { set; get; } } }
منبع داده فرضی برنامه
using System.Collections.Generic; using System.Linq; using jQueryMvcSample02.Models; namespace jQueryMvcSample02.DataSource { public static class BlogPostDataSource { private static IList<BlogPost> _cachedItems; /// <summary> /// با توجه به استاتیک بودن سازنده کلاس، تهیه کش، پیش از سایر فراخوانیها صورت خواهد گرفت /// باید دقت داشت که این فقط یک مثال است و چنین کشی به معنای /// تهیه یک لیست برای تمام کاربران سایت است /// </summary> static BlogPostDataSource() { _cachedItems = createBlogPostsInMemoryDataSource(); } /// <summary> /// هدف صرفا تهیه یک منبع داده آزمایشی ساده تشکیل شده در حافظه است /// </summary> private static IList<BlogPost> createBlogPostsInMemoryDataSource() { var results = new List<BlogPost>(); for (int i = 1; i < 30; i++) { results.Add(new BlogPost { Id = i, Title = "عنوان " + i, Body = "متن ... متن ... متن " + i }); } return results; } /// <summary> /// پارامترهای شماره صفحه و تعداد رکورد به ازای یک صفحه برای صفحه بندی نیاز هستند /// شماره صفحه از یک شروع میشود /// </summary> public static IList<BlogPost> GetLatestBlogPosts(int pageNumber, int recordsPerPage = 4) { var skipRecords = pageNumber * recordsPerPage; return _cachedItems .OrderByDescending(x => x.Id) .Skip(skipRecords) .Take(recordsPerPage) .ToList(); } } }
تنها نکته مهم آن، نحوه تعریف متد GetLatestBlogPosts میباشد که برای صفحه بندی اطلاعات بهینه سازی شده است. در اینجا توسط متدهای Skip و Take، تنها بازهای از اطلاعات که قرار است نمایش داده شوند، دریافت میگردد. خوشبختانه این متدها معادلهای مناسبی را در اکثر بانکهای اطلاعاتی داشته و استفاده از آنها بر روی یک بانک اطلاعاتی واقعی نیز بدون مشکل کار میکند و تنها بازه محدودی از اطلاعات را واکشی خواهد کرد که از لحاظ مصرف حافظه و سرعت کار بسیار مقرون به صرفه و سریع است.
کنترلر برنامه
using System.Linq; using System.Web.Mvc; using System.Web.UI; using jQueryMvcSample02.DataSource; using jQueryMvcSample02.Security; namespace jQueryMvcSample02.Controllers { public class HomeController : Controller { [HttpGet] public ActionResult Index() { //آغاز کار با صفحه صفر است var list = BlogPostDataSource.GetLatestBlogPosts(pageNumber: 0); return View(list); //نمایش ابتدایی صفحه } [HttpPost] [AjaxOnly] [OutputCache(Location = OutputCacheLocation.None, NoStore = true)] public virtual ActionResult PagedIndex(int? page) { var pageNumber = page ?? 0; var list = BlogPostDataSource.GetLatestBlogPosts(pageNumber); if (list == null || !list.Any()) return Content("no-more-info"); //این شرط ما است برای نمایش عدم یافتن رکوردها return PartialView("_ItemsList", list); } [HttpGet] public ActionResult Post(int? id) { if (id == null) return Redirect("/"); //todo: show the content here return Content("Post " + id.Value); } } }
الف) یک متد که بر اساس HttpGet کار میکند. این متد در اولین بار نمایش صفحه فراخوانی میگردد و اطلاعات صفحه آغازین را نمایش میدهد.
ب) متد دومی که بر اساس HttpPost کار کرده و محدود است به درخواستیهای AjaxOnly همانند متد PagedIndex.
از این متد دوم برای پردازش کلیکهای کاربر بر روی دکمه «بیشتر» استفاده میگردد. بنابراین تنها کاری که افزونه جیکوئری تدارک دیده شده ما باید انجام دهد، ارسال شماره صفحه است. سپس با استفاده از این شماره، بازه مشخصی از اطلاعات دریافت و نهایتا یک PartialView رندر شده برای افزوده شدن به صفحه بازگشت داده میشود.
دو View برنامه
همانطور که برای بازگشت اطلاعات نیاز به دو اکشن متد است، برای رندر اطلاعات نیز به دو View نیاز داریم:
الف) یک PartialView که صرفا لیستی از اطلاعات را مطابق سلیقه ما رندر میکند. از این PartialView در متد PagedIndex استفاده خواهد شد:
@model IList<jQueryMvcSample02.Models.BlogPost> <ul> @foreach (var item in Model) { <li> <h5> @Html.ActionLink(linkText: item.Title, actionName: "Post", controllerName: "Home", routeValues: new { id = item.Id }, htmlAttributes: null) </h5> @item.Body </li> } </ul>
@model IList<jQueryMvcSample02.Models.BlogPost> @{ ViewBag.Title = "Index"; var loadInfoUrl = Url.Action(actionName: "PagedIndex", controllerName: "Home"); } <h2> اسکرول نامحدود</h2> @{ Html.RenderPartial("_ItemsList", Model); } <div id="MoreInfoDiv"> </div> <div align="center" style="margin-bottom: 9px;"> <span id="moreInfoButton" style="width: 90%;" class="btn btn-info">بیشتر</span> </div> <div id="ProgressDiv" align="center" style="display: none"> <br /> <img src="@Url.Content("~/Content/images/loadingAnimation.gif")" alt="loading..." /> </div> @section JavaScript { <script type="text/javascript"> $(document).ready(function () { $("#moreInfoButton").InfiniteScroll({ moreInfoDiv: '#MoreInfoDiv', progressDiv: '#ProgressDiv', loadInfoUrl: '@loadInfoUrl', loginUrl: '/login', errorHandler: function () { alert('خطایی رخ داده است'); }, completeHandler: function () { // اگر قرار است روی اطلاعات نمایش داده شده پردازش ثانوی صورت گیرد }, noMoreInfoHandler: function () { alert('اطلاعات بیشتری یافت نشد'); } }); }); </script> }
1) مسیر دقیق اکشن متد PagedIndex توسط متد Url.Action تهیه شده است.
2) در ابتدای نمایش صفحه، متد Html.RenderPartial کار نمایش اولیه اطلاعات را انجام خواهد داد.
3) از div خالی MoreInfoDiv، به عنوان محل افزوده شدن اطلاعات Ajax ایی دریافتی استفاده میکنیم.
4) دکمه بیشتر در اینجا تنها یک span ساده است که توسط css به شکل یک دکمه نمایش داده خواهد شد (فایلهای آن در پروژه پیوست موجود است).
5) ProgressDiv در ابتدای نمایش صفحه مخفی است. زمانیکه کاربر بر روی دکمه بیشتر کلیک میکند، توسط افزونه جیکوئری ما نمایان شده و در پایان کار مجددا مخفی میگردد.
6) section JavaScript کار استفاده از افزونه InfiniteScroll را انجام میدهد.
و کدهای افزونه اسکرول نامحدود
// <![CDATA[ (function ($) { $.fn.InfiniteScroll = function (options) { var defaults = { moreInfoDiv: '#MoreInfoDiv', progressDiv: '#Progress', loadInfoUrl: '/', loginUrl: '/login', errorHandler: null, completeHandler: null, noMoreInfoHandler: null }; var options = $.extend(defaults, options); var showProgress = function () { $(options.progressDiv).css("display", "block"); } var hideProgress = function () { $(options.progressDiv).css("display", "none"); } return this.each(function () { var moreInfoButton = $(this); var page = 1; $(moreInfoButton).click(function (event) { showProgress(); $.ajax({ type: "POST", url: options.loadInfoUrl, data: JSON.stringify({ page: page }), contentType: "application/json; charset=utf-8", dataType: "json", complete: function (xhr, status) { var data = xhr.responseText; if (xhr.status == 403) { window.location = options.loginUrl; } else if (status === 'error' || !data) { if (options.errorHandler) options.errorHandler(this); } else { if (data == "no-more-info") { if (options.noMoreInfoHandler) options.noMoreInfoHandler(this); } else { var $boxes = $(data); $(options.moreInfoDiv).append($boxes); } page++; } hideProgress(); if (options.completeHandler) options.completeHandler(this); } }); }); }); }; })(jQuery); // ]]>
هربار که کاربر بر روی دکمه بیشتر کلیک میکند، progress div ظاهر میگردد. سپس توسط امکانات jQuery Ajax، شماره صفحه (بازه انتخابی) به اکشن متد صفحه بندی اطلاعات ارسال میگردد. در نهایت اطلاعات را از کنترلر دریافت و به moreInfoDiv اضافه میکند. در آخر هم شماره صفحه را یکی افزایش داده و سپس progress div را مخفی میکند.
دریافت مثال و پروژه کامل این قسمت
jQueryMvcSample02.zip
- Custom Elements
- Shadow DOM
- HTML Templates
Custom Elements
//app.component.js class AppComponent extends HTMLElement { static is = 'app-root' connectedCallback(){ this.innerHTML=`<h1>Hello World!</h1>` } } customElements.define(AppComponent.is, AppComponent)
//index.html <app-root></app-root>
//confirm-link.component.js class ConfirmLinkComponent extends HTMLAnchorElement { static is = "confim-link"; connectedCallback() { this.addEventListener("click", e => { if (!confirm("Are you sure?")) { e.preventDefault(); } }); } } customElements.define(ConfirmLinkComponent.is, ConfirmLinkComponent, { extends: "a" });
<a href="http://google.com" is='confirm-link'> google.com </a>
Shadow DOM
var div = document.createElement('div'); var shadowRoot = div.attachShadow({mode: 'open'}); //or mode: 'closed' shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>';
مفهوم دیگری وجود دارد تحت عنوان Shadow Boundary که تعیین کنندهی مرز بین Light DOM و Shadow DOM و همان لایهی مهیا کنندهی کپسولهسازی Styling و Markup میباشد. در مطالب آتی خواهیم دید که به چه شکلی رخدادهای سفارشی ما قابلیت عبور از این لایه را خواهند داشت.
HTML Templates
- استفاده از DOM و آگاه بودن مرورگر از وجود آن، عملیات clone را ساده خواهد کرد.
- محتوای داخل آن رندر نخواهد شد.
- اگرچه محتوای آن مخفی میباشد، با این حال درخواستهای مرتبط با تصاویر یا اسکریپتها انجام خواهد شد.
<div id="template" hidden> <img src="logo.png"> <div class="comment"></div> </div>
<script id="template" type="text/x-template"> <img src="logo.png"> <div class="comment"></div> </script> <script id="template" type="text/x-kendo-template"> <ul> # for (var i = 0; i < data.length; i++) { # <li>#= data[i] #</li> # } # </ul> </script>
- از آنجا که تگ script به صورت پیشفرض دارای استایل display:none میباشد، محتوای داخل آن رندر نخواهد شد.
- به دلیل عدم مقداردهی ویژگی type آن با "text/javascript"، مرورگر محتوای آن را به عنوان کد جاوااسکریپت parse نخواهد کرد.
- استفاده از خصوصیت innerHTML مشکل امنیتی XSS را بدنبال خواهد داشت .
<template id="template"> <img src="logo.png"> <div class="comment"></div> </template>
var template = document.querySelector("template"); var clonedNode = template.content.cloneNode(true); //deep:true document.body.appendChild(clonedNode);
reCAPTCHA چیست؟
استفاده آسان و امنیت بالا، جملهای میباشد که گوگل در سرتیتر تعریف آن جای داده که البته عنوان «من روبات نیستم» در سرویس استفاده شدهاست. reCAPTCHA یک سرویس رایگان برای وب سایتهای شما در جهت حفظ آن در برابر روباتهای مخرب است و از موتور تجزیه و تحلیل پیشرفتهی تشخیص انسان در برابر روباتها استفاده مینماید. reCAPTCHA را میتوان به صورت ماژول در بلاگ و یا فرمهای ثبت نام و ... جای داد که فقط با یک کلیک هویت سنجی انجام خواهد شد. گاها ممکن است بجای کلیک از شما سوالی پرسیده شود که در این صورت میبایستی تصاویر مرتبط با آن سوال را تیک زده باشید. دلیل استفاده از reCAPTCHA:
- گزارش روزانه از وضعیت موفقیت آمیز بودن هویت سنجی
- سهولت استفاده برای کاربران
- سهولت استفاده جهت برنامه نویسان
- دسترسی پذیری مناسب بدلیل وجود سؤالات تصویری و تلفظ و پخش عبارت بصورت صوتی
- امنیت بالا
آیا میتوان قالب reCAPTCHA را تغییر داد؟
جواب این سوال بله میباشد. این سرویس در دو قالب سفید و مشکی ارائه شدهاست که به صورت پیش فرض قالب سفید آن انتخاب میشود. در تصویر زیر قالبهای این سرویس را مشاهده خواهید کرد.
زبانهای پشتیبانی شده در این سرویس:
اضافه نمودن reCAPTCHA به سایت:
اگر قبلا در گوگل ثبت نام نمودهاید کافیست وارد این سایت شوید و بر روی Get reCAPTCHA کلیک نمائید؛ در غیر اینصورت میبایستی یک حساب کاربری ایجاد نماید. بعد از ورود، به کنترل پنل هدایت خواهید شد. در نمای اول به تصویر زیر برخورد خواهید کرد که از شما ثبت سایت جدید را خواستار است:
نام، دامنه سایت و مالک را وارد و ثبت نام نماید.
پس از آنکه بر روی دکمهی ثبت نام کلیک نمودید، برای شما دو کلید جدید را ثبت مینماید که منحصر به سایت شماست. Site Key رشته ای را داراست که در کدهای HTML قرار خواهد گرفت و کلید بعدی Secret Key میباشد. ارتباط سایت شما با گوگل میبایستی به صورت محرمانه محفوظ بماند.
گامهای لازم جهت نمایش سرویس در سایت:
- دستورات سمت کاربر
- دستورات سمت سرور
دستورات سمت کاربر:
کد زیر را در قبل از بسته شدن تک <head/> قرار دهید:
<script src='https://www.google.com/recaptcha/api.js'></script>
<div data-sitekey="6LdHGgwTAAAAAClKFhGthRrjBXh5AUGd4eWNCQq7"></div>
نکته: مقدار data-sitekey برابر است با رشته Site Key که گوگل برای شما ثبت نمود.
دستورات سمت سرور:
وقتی کاربر فرم حاوی کپچا را که به صورت صحیح هویت سنجی آن انجام شده باشد به سمت سرور ارسال کند، به عنوان بخشی از دادهی ارسال شده، یک رشته با نام g-recaptcha-response با دستور Request دریافت خواهید کرد که به منظور بررسی اینکه آیا گوگل تایید کرده است که کاربر، یک درخواست POST ارسال نموداست. با این پارامترها یک مقدار json برگشت داده خواهد شد که میبایستی کلاسی متناظر با آن جهت خواندن ساخته شود.
با استفاده از کد زیر مقدار برگشتی Json را در کلاس مپ مینمائیم:using System.Collections.Generic; using Newtonsoft.Json; namespace BaseConfig.Security.Captcha { public class RepaptchaResponse { [JsonProperty("success")] public bool Success { get; set; } [JsonProperty("error-codes")] public List<string> ErrorCodes { get; set; } } }
با استفاده از کلاس زیر درخواستی به گوگل ارسال شده و در صورتیکه با خطا مواجه شود با استفاده از دستور switch به آن دسترسی خواهیم یافت.
using System.Configuration; using System.Net; using Newtonsoft.Json; namespace BaseConfig.Security.Captcha { public class ReCaptcha { public static string _secret; static ReCaptcha() { _secret = ConfigurationManager.AppSettings["ReCaptchaGoogleSecretKey"]; } public static bool IsValid(string response) { //secret that was generated in key value pair var client = new WebClient(); var reply = client.DownloadString($"https://www.google.com/recaptcha/api/siteverify?secret={_secret}&response={response}"); var captchaResponse = JsonConvert.DeserializeObject<RepaptchaResponse>(reply); // when response is false check for the error message if (!captchaResponse.Success) { //if (captchaResponse.ErrorCodes.Count <= 0) return View(); //var error = captchaResponse.ErrorCodes[0].ToLower(); //switch (error) //{ // case ("missing-input-secret"): // ViewBag.Message = "The secret parameter is missing."; // break; // case ("invalid-input-secret"): // ViewBag.Message = "The secret parameter is invalid or malformed."; // break; // case ("missing-input-response"): // ViewBag.Message = "The response parameter is missing."; // break; // case ("invalid-input-response"): // ViewBag.Message = "The response parameter is invalid or malformed."; // break; // default: // ViewBag.Message = "Error occured. Please try again"; // break; //} return false; } // Captcha is valid return true; } } }
تابع IsValid از نوع برگشتی Boolean بوده و خطایی برگشت داده نخواهد شد و از این جهت به صورت کامنت برای شما گذاشته شده که میتوان متناظر با کد نویسی آن را تغییر دهید.
در اکشن زیر مقدار response برسی میشود تا خالی نباشد و همچنین مقدار آن را میتوان با استفاده از تابع IsValid در کلاس ReCaptcha به سمت گوگل فرستاد.
// // POST: /Account/Login [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public virtual async Task<ActionResult> Login(LoginPageModel model, string returnUrl) { var response = Request["g-recaptcha-response"]; if (response != null && ReCaptcha.IsValid(response)) { // } }
گاها اتفاق میافتد که از دستورات Ajax برای ارسال اطلاعات به سمت سرور استفاده میشود که در این صورت لازم است بعد از پایان عملیات، کپچا ریفرش گردد. برای این کار میتوان از دستور جاوا اسکریپتی زیر استفاده نمود. در صورتیکه صفحه Postback شود، کپچا مجددا ریفرش خواهد شد.
/** * * @param {} data * @returns {} */ function Success(data) { grecaptcha.reset(); }
تا اینجا موفق شدیم تا فرم ارسالی همراه کپچا را به سمت سرور ارسال کنیم. اما ممکن است در یک صفحه از چند کپچا استفاده شود که در این صورت میبایستی دستورات سمت کاربر تغییر نمایند.
برای این کار دستور
<div data-sitekey="6LdHGgwTAAAAAClKFhGthRrjBXh5AUGd4eWNCQq7"></div>
<script> var recaptcha1; var recaptcha2; var myCallBack = function () { //Render the recaptcha1 on the element with ID "recaptcha1" recaptcha1 = grecaptcha.render('recaptcha1', { 'sitekey': '6Lf9FQwTAAAAAE6XlDqrey24K4xJOPM5nNVBmNO9', 'theme': 'light' }); //Render the recaptcha2 on the element with ID "recaptcha2" recaptcha2 = grecaptcha.render('recaptcha2', { 'sitekey': '6Lf9FQwTAAAAAE6XlDqrey24K4xJOPM5nNVBmNO9', 'theme': 'light' }); //Render the recaptcha3 on the element with ID "recaptcha3" recaptcha2 = grecaptcha.render('recaptcha3', { 'sitekey': '6Lf9FQwTAAAAAE6XlDqrey24K4xJOPM5nNVBmNO9', 'theme': 'light' }); }; </script>
برای نمایش کپچا، تگهای div با id متناظر با recaptcha1, recaptcha2, recaptcha3 ( در این مثال از سه کپچا در صفحه استفاده شده است ) در صفحه قرار خواهند گرفت.
<div id="recaptcha1"></div> <div id="recaptcha2"></div> <div id="recaptcha3"></div>
کار ما تمام شد. حال اگر پروژه را اجرا نمائید، در صفحه سه کپچا مشاهده خواهید کرد.
چند زبانه کردن کپچا:
برای چند زبانه کردن کافیست با مراجعه به این لینک و یا استفاده از تصویر بالا ( زبانهای پشتیبانی ) مقدار آن زبان را برابر با پراپرتی hl که به صورت کوئری استرینگ برای گوگل ارسال میگردد، استفاده نمود. کد زیر نمونهی استفاده شده برای زبانهای انگلیسی و فارسی میباشد که با ریسورس مقدار دهی میشود.<script src='https://www.google.com/recaptcha/api.js?hl=@(App_GlobalResources.CP.CurrentAbbrivation)'></script>
در صورتی که از فایل ریسوس استفاده نمیکنید میتوان به صورت مستقیم مقدار دهی نمائید:
<script src='https://www.google.com/recaptcha/api.js?hl=fa'></script>
برای دوستانی که با تکنولوژی ASP.Net کار میکنند، این روال هم برای آنها هم صادق میباشد.
برای دریافت پروژه اینجا کلیک نمائید.
{"jti":"26bdfd20-104f-45d4-a4e1-111044808041", "iss":"http://localhost:5000/", "iat":1531729854, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier":"1", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"Vahid", "DisplayName":"وحید", "http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber":"046fb152a7474043952475cfa952cdc9", "http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata":"1", "DynamicPermission":[":MyProtectedApi2:Get", ":MyProtectedEditorsApi:Get", ":MyProtectedApi3:Get", ":MyProtectedApi4:Get"], "http://schemas.microsoft.com/ws/2008/06/identity/claims/role":["Admin", "Editor", "User"], "nbf":1531729855, "exp":1531729975, "aud":"Any"}
public bool CanUserAccess(ClaimsPrincipal user, string area, string controller, string action) { var currentClaimValue = $"{area}:{controller}:{action}"; var securedControllerActions = _mvcActionsDiscoveryService.GetAllSecuredControllerActionsWithPolicy(ConstantPolicies.DynamicPermission); if (!securedControllerActions.SelectMany(x => x.MvcActions).Any(x => x.ActionId == currentClaimValue)) { throw new KeyNotFoundException($@"The `secured` area={area}/controller={controller}/action={action} with `ConstantPolicies.DynamicPermission` policy not found. Please check you have entered the area/controller/action names correctly and also it's decorated with the correct security policy."); } if (!user.Identity.IsAuthenticated) { return false; } if (user.IsInRole("Admin")) { // Admin users have access to all of the pages. return true; } // Check for dynamic permissions // A user gets its permissions claims from the `ApplicationClaimsPrincipalFactory` class automatically and it includes the role claims too. //for check user has claim for access to action return user.HasClaim(claim => claim.Type == ConstantPolicies.DynamicPermissionClaimType && claim.Value == currentClaimValue); }
دریافت وابستگیهای سمت کاربر مباحث اعتبارسنجی
زمانیکه گزینهی ایجاد یک پروژهی جدید 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>
<link href="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Content/css")"rel="stylesheet" type="text/css" /> <link href="@System.Web.Optimization.BundleTable. Bundles.ResolveBundleUrl("~/Content/themes/base/css")" rel="stylesheet" type="text/css" /> <script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/js")"></script>
<link href="/Content/css?v=ji3nO1pdg6VLv3CVUWntxgZNf1z" rel="stylesheet" type="text/css" /> <link href="/Content/themes/base/css?v=UM624qf1uFt8dYti" rel="stylesheet" type="text/css" /> <script src="/Scripts/js?v=4h5lPNUsLiFoa0vqrItjS2Jp"></script>
BundleTable.Bundles.RegisterTemplateBundles();
public static void EnableBootstrapBundle(this BundleCollection bundles) { var bootstrapCss = new Bundle("~/bootstrap/css", new CssMinify()); bootstrapCss.AddFile("~/css/bootstrap.css"); bootstrapCss.AddFile("~/css/bootstrap-responsive.css"); bootstrapCss.AddFile("~/css/application.css"); bootstrapCss.AddFile("~/css/prettify.css"); bundles.Add(bootstrapCss); var bootstrapJs = new Bundle("~/bootstrap/js", new JsMinify()); bootstrapJs.AddFile("~/js/jquery-1.7.1.js"); bootstrapJs.AddFile("~/js/bootstrap.js"); bootstrapJs.AddFile("~/js/prettify.js"); bundles.Add(bootstrapJs); }
BundleTable.Bundles.EnableBootstrapBundle();
<link href="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/bootstrap/css")" rel="stylesheet"> <script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/bootstrap/js")"></script>
<link href="/bootstrap/css?v=uBlJsuIAGAF93nUTTez8" rel="stylesheet"> <script src="/bootstrap/js?v=O6HaHC8QqtB"></script>
//قرار دهید containerBuilder.RegisterRequiredServices(); این دو را بعد از containerBuilder.RegisterHttpClient(); containerBuilder.RegisterIdentityClient();
توضیحات این کد در ادامه آمده است.
public virtual HttpClient HttpClient { get; set; } async Task CallUsersListApiUsingHttpClient() { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://reqres.in/api/users"); // Use request.Headers to set jwt token, ... // Use request.Content to send body. You can use StringContent, StreamContent, etc. HttpResponseMessage response = await HttpClient.SendAsync(request); response.EnsureSuccessStatusCode(); using (StreamReader streamReader = new StreamReader(await response.Content.ReadAsStreamAsync())) using (JsonReader jsonReader = new JsonTextReader(streamReader)) { List<UserDto> users = (await JToken.LoadAsync(jsonReader))["data"].ToObject<List<UserDto>>(); } }
{ "page": 2, "per_page": 3, "total": 12, "total_pages": 4, "data": [ { "id": 4, "first_name": "Eve", "last_name": "Holt", "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg" }, { "id": 5, "first_name": "Charles", "last_name": "Morris", "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/stephenmoon/128.jpg" } ] }
public class UserDto { [JsonProperty("id")] public int Id { get; set; } [JsonProperty("first_name")] public string FirstName { get; set; } [JsonProperty("last_name")] public string LastName { get; set; } [JsonProperty("avatar")] public string Avatar { get; set; } }
معرفی ES 6
چرا باید ES 6 را آموخت؟
در طی 2 سال آینده، تمام فریم ورکهای جدید جاوا اسکریپتی، از بوت استرپ 4 تا AngularJS 2 تا Aurelia و غیره، همگی به ES 6 کوچ خواهند کرد (و این اتفاق هم اکنون در حال رخ دادن است). بنابراین به زودی بدون فراگیری و تسلط بر ES 6، در حوزهی وب، «بیسواد» محسوب خواهید شد و فراگیری آن یک «باید» است.
وضعیت پشتیبانی از ES 6 در مرورگرهای مختلف
برای مشاهدهی پیشرفتهای مرورگرهای کنونی در زمینهی پشتیبانی از ES 6، میتوان به صفحهی ES 6 compatibility table مراجعه کرد.
برای نمونه در حال حاضر، فایرفاکس بهترین پشتیبانی از ES 6 را ارائه میدهد (با پیاده سازی 85 درصد از قابلیتها) و بعد از آن مرورگر جدید مایکروسافت قرار دارد.
وضعیت IE 10,11 در این بین تغییری نخواهند کرد؛ زیرا پشتیبانی رسمی از تمام آنها به زودی خاتمه مییابد (در سه شنبه، ۲۲ دی ۱۳۹۴).
در همین صفحه، در ابتدای چارت، ستون current browser نیز قرار دارد. این ستون، وضعیت مرورگر جاری شما را از لحاظ درصد پیاده سازی قابلیتهای ES 6 نمایش میدهد.
اهمیت دریافت آخرین نگارشهای مرورگرها
با توجه به ES 6 compatibility table، اکثر مرورگرها در نسخههای شبانه و همچنین آزمایشی آنها، به مرور در حال افزودن قابلیتهای باقیماندهی ES 6 هستند. بنابراین اگر با فایرفاکس کار میکنید، نیاز است Firefox nightly builds را نصب کنید. اگر از مرورگرهای مایکروسافت استفاده میکنید، آخرین نگارش MS Edge بهترین پشتیبانی از ES 6 را ارائه میدهد و اگر از کروم استفاده میکنید، نگارشهای بتا و Dev آن را میتوانید دریافت کنید.
علاوه بر اینها، نگارشهای فعلی این مرورگرها نیز دارای امکانات آزمایشی هستند که میتوان آنها را به صورت دستی فعال کرد. برای مثال در مرورگر کروم، به آدرس chrome://flags مراجعه کنید و در صفحهی باز شده، کلمهی JavaScript را جستجو کنید. در اینجا نیاز است گزینهی «Enable Experimental JavaScript» را فعال کنید (بر روی لینک enable ذیل آن کلیک نمائید).
به این ترتیب قادر خواهید بود آخرین افزونههای ES 6 را در developer tools console آن اجرا کنید.
چنین تنظیمی به MS Edge نیز اضافه شدهاست. پس از اجرای آن، به آدرس about:flags مراجعه کنید:
در اینجا نیز میتوانید گزینهی «Enable experimental JavaScript features» را انتخاب کنید.
معرفی traceur-compiler
هرچند قابلیتهای فعلی آخرین نگارشهای مرورگرها برای اجرای بسیاری از امکانات ES 6 کفایت میکنند، اما اگر علاقمند به اجرای تمامی آنها هستید، میتوان از traceur-compiler گوگل نیز کمک گرفت (با تلفظ تریسر). این کامپایلر، قابلیتهای جدید ES 6 را تبدیل به نگارشهای فعلی قابل درک برای مرورگرهای قدیمیتر میکند. به این ترتیب امکان اجرای آزمایشات مرتبط با ES 6 را خواهید یافت.
روش استفادهی از آن هم به صورت ذیل است:
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script> <script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script> <script type="module"> // ES 6 </script>