مطالب
شروع کار با Apache Cordova در ویژوال استودیو #4
در قسمت قبل یک مثال ساده را کار کردیم. در این قسمت با jQuery Mobile آشنا شده و در پروژه‌ی خود استفاده خواهیم کرد.

توضیح تکمیلی در مورد ساختار فایل‌های پروژه
همان طور که در قسمتها قبل گفته شد، تگ اسکریپت زیر 
<script src="cordova.js"></script>
از استاندارد‌های Cordova است؛ وجود خارجی ندارد و بخشی از فرآیند ساخت برنامه است.
اگر توجه کنید فایلی با نام platformOverrides.js در فولدر scripts موجود در ریشه، خالی است اما در فولدر merges موجود در ریشه‌ی پروژه مربوط به هر پلتفرم و همنام آن پلتفرم قرار دارد. برای مثال برای android، یک چنین دایرکتوری merges/android/scripts وجود دارد که درون آن فایلی به‌نام  platformOverrides.js دیده می‌شود و اگر دقت کنید، همنام فایل موجود در فولدر scripts موجود در ریشه پروژه است که درون خود فایلی بنام  android2.3-jscompat.js را فراخوانی می‌کند. (برای کمک به سازگاری کتابخانه‌های ثالث)
در زمان build ، تمام فایل‌های موجود در "merges/"platformname ، در فولدر‌های هم نامی در شاخه‌ی ریشه‌ی پروژه کپی شده و جایگزین فایل‌های قبلی خواهند شد.

 مثال برای اندروید 
در زمان ساخت (build) فایل scripts/platformOverrides.js با فایل merges/windows/scripts/platformoverrides.js جایگزین خواهد شد. این امکان برای فلدر‌های css, images و بقیه‌ی آنها نیز امکان پذیر است.
توجه داشته باشید این ادغام در سطح فایل‌ها و نه در سطح محتوای فایل‌ها انجام می‌شود.

نکته 
برای محتوای موجود در فولدر res، قضیه فرق می‌کند. زیرا محتوای این resource‌ها برای اپلیکیشن پکیچ ضروریست؛ پیش از آن که کد‌های ما درون WebView یا host رندر شوند. باید توجه کرد که این فولدر به جهت اینکه منابع اصلی را (با توجه به پلتفرم باید از فایل‌های مشخص آن برای تشخیص ساختار فولدر‌های اپلیکیشن پکیچ استفاده کند) در بر دارد و این منابع باید در زمان ساخت پروژه تشخیص داده شوند.


رویداد‌های بومی
در زیر تعدادی از رخدادهایی که در Cordova گنجانده شده‌اند تا اپلیکیشن ما از رخداد‌های دستگاه با خبر شوند، نشان داده شده است. برای تست آنها به راحتی بعد از اجرای برنامه توسط شبیه ساز Ripple می‌توانید از قسمت Events، رخداد مورد نظر را شبیه سازی کنید:
(function () {
    "use strict";

    document.addEventListener( 'deviceready', onDeviceReady.bind( this ), false );

    function onDeviceReady() {
        // Handle the Cordova pause and resume events
        document.addEventListener( 'pause', onPause.bind( this ), false );
        document.addEventListener('resume', onResume.bind(this), false);
        document.addEventListener('menubutton', onMenuButton.bind(this), false);
        document.addEventListener('backbutton', onBackButton.bind(this), false);
        //document.addEventListener('searchbutton', onResume.bind(this), false);
        //document.addEventListener('endcallbutton', onResume.bind(this), false);
        //document.addEventListener('offline', onResume.bind(this), false);
        //document.addEventListener('online', onResume.bind(this), false);
        //document.addEventListener('startcallbutton', onResume.bind(this), false);
        //document.addEventListener('volumedownbutton', onResume.bind(this), false);
        //document.addEventListener('volumeupbutton', onResume.bind(this), false);
        
        // TODO: Cordova has been loaded. Perform any initialization that requires Cordova here.
    };

    function onPause() {
        // TODO: This application has been suspended. Save application state here.
        alert("paused");
    };

    function onResume() {
        alert("resume");
    };
    function onMenuButton() {
        alert("menu");
    };

    function onBackButton() {
        alert("back button");
    };
   

} )();

.در مقالات آینده از افزونه‌های موجود، برای مدیریت رخداد‌های باتری سیستم استفاده خواهیم کرد



jQuery Mobile
جی کوئری موبایل، یک فریمورک (UI Framework) جدید با قابلیت استفاده‌ی آسان برای ساخت اپلیکیشن‌های چند سکویی موبایل است. با استفاده از این فریمورک شما قادر خواهید بود اپلیکیشن‌های موبایل بهینه شده برای اجرا بر روی تمام تلفن‌ها، دسکتاپ و تبلت‌ها را بسازید. علاوه بر این، جی کوئری موبایل می‌تواند یک فریمورک ایده آل برای توسعه دهند گان و طراحان وب که قصد ساخت اپلیکیشن‌های غنی وب برای موبایل را دارند، باشد.

 Supported Devices

  Phones/Tablets 
  Android 1.6+ 
  BlackBerry 5+ 
  iOS 3+ 
  Windows Phone 7 
  WebOS 1.4+ 
  Symbian (Nokia S60) 
  Firefox Mobile Opera Mobile 11+ 
  Opera Mini 5+ 
  Desktop browsers 
  Chrome 11+ 
  Firefox 3.6+ 
  Internet Explorer 7+ 
  Safari   


برای نصب jQuery Mobile کافی است دستورات  زیر را در package manager console ویژوال استودیو استفاده کنید:

PM>install-package jquery

PM>install-package jquery.mobile.rtl

بعد از دانلود فایل‌های مورد نظر خود، فولدری بنام jquery.mobile.rtl در ریشه پروژه ایجاد خواهد شد. به ترتیب فایل های rtl.jquery.mobile-1.4.0.css و rtl.jquery.mobile-1.4.0.js موجود در زیر شاخه‌های فلدر مذکور را به head و آخر body فایل index.html اضافه کنید.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>CordovaApp01</title>

    <!-- CordovaApp01 references -->
    <link href="css/index.css" rel="stylesheet" />
    <link href="jquery.mobile.rtl/css/themes/default/rtl.jquery.mobile-1.4.0.css" rel="stylesheet" />
</head>
<body>
    <div data-role="page" id="page1">
        <div data-role="header">
            <h1>اولین برنامه</h1>
        </div>
        <div data-role="content">
            <p>سلام من محتوای اولین برنامه هستم</p>
        </div>
        <div data-role="footer">
            <h1>من فوتر هستم</h1>
        </div>
    </div>
<!-- Cordova reference, this is added to your app when it's built. -->
<script src="scripts/jquery-2.1.3.min.js"></script>
    <script src="cordova.js"></script>
    <script src="scripts/platformOverrides.js"></script>
    <script src="scripts/index.js"></script>

    <script src="jquery.mobile.rtl/js/rtl.jquery.mobile-1.4.0.js"></script>
</body>
</html>
در تکه کد بالا ما یکی از ویجت‌های jQuery Mobile را استفاده کردیم و با استفاده از ویژگی data-role که برای div اصلی با page مقدار دهی شده است، یک  کانتینر (page container) برای ویجت page جی کوئری موبایل تعریف شده‌است.

نتیجه‌ی نهایی به شکل زیر خواهد بود:

در مقاله‌ی بعد به استفاده از plugin‌ها خواهیم پرداخت.

ادامه دارد...

مطالب
بارگذاری یک یوزرکنترل با استفاده از جی‌کوئری

مزیت استفاده از یوزر کنترل‌ها، ماژولار کردن برنامه است. برای مثال اگر صفحه جاری شما قرار است از چهار قسمت اخبار، منوی پویا ، سخن روز و آمار کاربران تشکیل شود، می‌توان هر کدام را توسط یک یوزر کنترل پیاده سازی کرده و سپس صفحه اصلی را از کنار هم قرار دادن این یوزر کنترل‌ها تهیه نمود.
با این توضیحات اکنون می‌خواهیم یک یوزکنترل ASP.Net را توسط jQuery Ajax بارگذاری کرده و نمایش دهیم. حداقل دو مورد کاربرد را می‌توان برای آن متصور شد:
الف) در اولین باری که یک صفحه در حال بارگذاری است، قسمت‌های مختلف آن‌را بتوان از یوزر کنترل‌های مختلف خواند و تا زمان بارگذاری کامل هر کدام، یک عبارت لطفا منتظر بمانید را نمایش داد. نمونه‌ی آن‌را شاید در بعضی از CMS های جدید دیده باشید. صفحه به سرعت بارگذاری می‌شود. در حالیکه مشغول مرور صفحه جاری هستید، قسمت‌های مختلف صفحه پدیدار می‌شوند.
ب) بارگذاری یک قسمت دلخواه صفحه بر اساس درخواست کاربر. مثلا کلیک بر روی یک دکمه و امثال آن.

روش کلی کار:
1) تهیه یک متد وب سرویس که یوزر کنترل را بر روی سرور اجرا کرده و حاصل را تبدیل به یک رشته کند.
2) استفاده از متد Ajax جی‌کوئری برای فراخوانی این متد وب سرویس و افزودن رشته دریافت شده به صفحه.
بدیهی است زمانیکه متد Ajax فراخوانی می‌شود می‌توان عبارت یا تصویر منتظر بمانید را نمایش داد و پس از پایان کار این متد، عبارت (یا تصویر) را مخفی نمود.

پیاده سازی:
قسمت تبدیل یک یوزر کنترل به رشته را قبلا در مقاله "تهیه قالب برای ایمیل‌های ارسالی یک برنامه ASP.Net" مشاهده کرده‌اید. در این‌جا برای استفاده از این متد در یک وب سرویس نیاز به کمی تغییر وجود داشت (KeyValuePair ها درست سریالایز نمی‌شوند) که نتیجه نهایی به صورت زیر است. یک فایل Ajax.asmx را به برنامه اضافه کرده و سپس در صفحه Ajax.asmx.cs کد آن به صورت زیر می‌تواند باشد:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Script.Services;
using System.Web.Services;
using System.Web.UI;
using System.Web.UI.HtmlControls;

namespace AjaxTest
{
public class KeyVal
{
public string Key { set; get; }
public object Value { set; get; }
}

/// <summary>
/// Summary description for Ajax
/// </summary>
[ScriptService]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class Ajax : WebService
{
/// <summary>
/// Removes Form tags using Regular Expression
/// </summary>
private static string cleanHtml(string html)
{
return Regex.Replace(html, @"<[/]?(form)[^>]*?>", string.Empty, RegexOptions.IgnoreCase);
}

/// <summary>
/// تبدیل یک یوزر کنترل به معادل اچ تی ام ال آن
/// </summary>
/// <param name="path">مسیر یوزر کنترل</param>
/// <param name="properties">لیست خواص به همراه مقادیر مورد نظر</param>
/// <returns></returns>
/// <exception cref="NotImplementedException"><c>NotImplementedException</c>.</exception>
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public string RenderUserControl(string path,
List<KeyVal> properties)
{
Page pageHolder = new Page();

UserControl viewControl =
(UserControl)pageHolder.LoadControl(path);

viewControl.EnableViewState = false;

Type viewControlType = viewControl.GetType();

if (properties != null)
foreach (var pair in properties)
{
if (pair.Key != null)
{
PropertyInfo property =
viewControlType.GetProperty(pair.Key);

if (property != null)
{
if (pair.Value != null) property.SetValue(viewControl, pair.Value, null);
}
else
{
throw new NotImplementedException(string.Format(
"UserControl: {0} does not have a public {1} property.",
path, pair.Key));
}
}
}

//Form control is mandatory on page control to process User Controls
HtmlForm form = new HtmlForm();

//Add user control to the form
form.Controls.Add(viewControl);

//Add form to the page
pageHolder.Controls.Add(form);

//Write the control Html to text writer
StringWriter textWriter = new StringWriter();

//execute page on server
HttpContext.Current.Server.Execute(pageHolder, textWriter, false);

// Clean up code and return html
return cleanHtml(textWriter.ToString());
}
}
}
تا این‌جا متد وب سرویسی را داریم که می‌تواند مسیر یک یوزر کنترل را به همراه خواص عمومی آن‌را دریافت کرده و سپس یوزر کنترل را رندر نموده و حاصل را به صورت HTML به شما تحویل دهد. با استفاده از reflection خواص عمومی یوزر کنترل یافت شده و مقادیر لازم به آن‌ها پاس می‌شوند.

چند نکته:
الف) وب کانفیگ برنامه ASP.Net شما اگر با VS 2008 ایجاد شده باشد مداخل لازم را برای استفاده از این وب سرویس توسط jQuery Ajax دارد در غیر اینصورت موفق به استفاده از آن نخواهید شد.
ب) هنگام بازگرداندن این اطلاعات با فرمت json = ResponseFormat.Json جهت استفاده در jQuery Ajax ، گاهی از اوقات بسته به حجم بازگردانده شده ممکن است خطایی حاصل شده و عملیات متوقف شد. این طول پیش فرض را (maxJsonLength) در وب کانفیگ به صورت زیر تنظیم کنید تا مشکل حل شود:

<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization maxJsonLength="10000000"></jsonSerialization>
</webServices>
</scripting>
</system.web.extensions>

برای پیاده سازی قسمت Ajax آن برای اینکه کار کمی تمیزتر و با قابلیت استفاده مجدد شود یک پلاگین تهیه شده (فایلی با نام jquery.advloaduc.js) که سورس آن به صورت زیر است:

$.fn.advloaduc = function(options) {
var defaults = {
webServiceName: 'Ajax.asmx', //نام فایل وب سرویس ما
renderUCMethod: 'RenderUserControl', //متد وب سرویس
ucMethodJsonParams: '{path:\'\'}',//پارامترهایی که قرار است پاس شوند
completeHandler: null //پس از پایان کار وب سرویس این متد جاوا اسکریپتی فراخوانی می‌شود
};
var options = $.extend(defaults, options);

return this.each(function() {
var obj = $(this);
obj.prepend("<div align='center'> لطفا اندکی تامل بفرمائید... <img src=\"images/loading.gif\"/></div>");

$.ajax({
type: "POST",
url: options.webServiceName + "/" + options.renderUCMethod,
data: options.ucMethodJsonParams,
contentType: "application/json; charset=utf-8",
dataType: "json",
success:
function(msg) {
obj.html(msg.d);

// if specified make callback and pass element
if (options.completeHandler)
options.completeHandler(this);
},
error:
function(XMLHttpRequest, textStatus, errorThrown) {
obj.html("امکان اتصال به سرور در این لحظه مقدور نیست. لطفا مجددا سعی کنید.");
}
});
});
};
برای اینکه با کلیات این روش آشنا شوید می‌توان به مقاله "بررسی وجود نام کاربر با استفاده از jQuery Ajax در ASP.Net" مراجعه نمود که از ذکر مجدد آن‌ها خودداری می‌شود. همچنین در مورد نوشتن یک پلاگین جی‌کوئری در مقاله "افزونه جملات قصار jQuery" توضیحاتی داده شده است.
عمده کاری که در این پلاگین صورت می‌گیرد فراخوانی متد Ajax جی‌کوئری است. سپس به متد وب سرویس ما (که در اینجا نام آن به صورت پارامتر نیز قابل دریافت است)، پارامترهای لازم پاس شده و سپس نتیجه حاصل به یک شیء در صفحه اضافه می‌شود.
completeHandler آن اختیاری است و پس از پایان کار متد اجکس فراخوانی می‌شود. در صورتیکه به آن نیازی نداشتید یا مقدار آن را null قرار دهید یا اصلا آن‌را ذکر نکنید.

مثالی در مورد استفاده از این وب سرویس و همچنین پلاگین جی‌کوئری نوشته شده:

الف) یوزر کنترل ساده زیر را به پروژه اضافه کنید:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="part1.ascx.cs" Inherits="TestJQueryAjax.part1" %>
<asp:Label runat="server" ID="lblData" ></asp:Label>
بدیهی است یک یوزر کنترل می‌تواند به اندازه یک صفحه کامل پیچیده باشد به همراه انواع و اقسام ارتباطات با دیتابیس و غیره.

سپس کد آن‌را به صورت زیر تغییر دهید:

using System;
using System.Threading;

namespace TestJQueryAjax
{
public partial class part1 : System.Web.UI.UserControl
{
public string Text1 { set; get; }
public string Text2 { set; get; }

protected void Page_Load(object sender, EventArgs e)
{
Thread.Sleep(3000);
if (!string.IsNullOrEmpty(Text1) && !string.IsNullOrEmpty(Text2))
lblData.Text = Text1 + "<br/>" + Text2;
}
}
}
این یوزر کنترل دو خاصیت عمومی دارد که توسط وب سرویس مقدار دهی خواهد شد و نهایتا حاصل نهایی را در یک لیبل در دو سطر نمایش می‌دهد.
عمدا یک sleep سه ثانیه‌ای در اینجا در نظر گرفته شده تا اثر آن‌را بهتر بتوان مشاهده کرد.

ب) اکنون کد مربوط به صفحه‌ای که قرار است این یوزر کنترل را به صورت غیرهمزمان بارگذاری کند به صورت زیر خواهد بود (مهم‌ترین قسمت آن نحوه تشکیل پارامترها و مقدار دهی خواص یوزر کنترل است):

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TestJQueryAjax._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>

<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/jquery.advloaduc.js" type="text/javascript"></script>
<script src="js/json2.js" type="text/javascript"></script>

<script type="text/javascript">
function showAlert() {
alert('finished!');
}

//تشکیل پارامترهای متد وب سرویس جهت ارسال به آن
var fileName = 'part1.ascx';
var props = [{ 'Key': 'Text1', 'Value': 'سطر یک' }, { 'Key': 'Text2', 'Value': 'سطر 2'}];
var jsonText = JSON.stringify({ path: fileName, properties: props });

$(document).ready(function() {
$("#loadMyUc").advloaduc({
webServiceName: 'Ajax.asmx',
renderUCMethod: 'RenderUserControl',
ucMethodJsonParams: jsonText,
completeHandler: showAlert
});
});

</script>

</head>
<body>
<form id="form1" runat="server">
<div id="loadMyUc">
</div>
</form>
</body>

</html>
نکته:
برای ارسال صحیح و امن اطلاعات json به سرور، از اسکریپت استاندارد json2.js استفاده شد.

ابزار دیباگ:
بهترین ابزار برای دیباگ این نوع اسکریپت‌ها استفاده از افزونه فایرباگ فایرفاکس است. برای مثال مطابق تصویر زیر، یوزر کنترلی فراخوانی شده است که در سرور وجود ندارد:


دریافت مثال فوق


مطالب
شروع کار با Dart - قسمت 1

Dart کتابخانه ای است که توسط شرکت گوگل ارائه شده است و گفته می‌شود، قرار است جایگزین جاوا اسکریپت گردد و از آدرس https://www.dartlang.org قابل دسترسی می‌باشد. این کتابخانه، دارای انعطاف پذیری فوق العاده بالایی است و کد نویسی Java Script را راحت‌تر می‌کند. در حال حاضر هیچ مرورگری به غیر از Chromium از این تکنولوژی پشتیبانی نمی‌کند و جهت تسهیل در کدنویسی، باید از ویرایشگر Dart Editor استفاده کنید. این ویرایشگر کدهای نوشته شده را به دو صورت Native و JavaScript Compiled در اختیار مرورگر قرار می‌دهد. در ادامه با نحوه‌ی کار و راه اندازی Dart آشنا خواهید شد.

ابتدا Dart و ویرایشگر مربوط به آن را توسط لینک‌های زیر دانلود کنید:

دانلود نسخه 64 بیتی دارت + ویرایشگر

دانلود نسخه 32 بیتی دارت + ویرایشگر

بعد از اینکه فایلهای فوق را از حالت فشرده خارج کردید، پوشه ای با نام dart ایجاد می‌نماید. وارد پوشه dart شده و DartEditor را اجرا کنید.

توجه: جهت اجرای dart به JDK 6.0 یا بالاتر نیاز دارید

در مرحله بعد نمونه کدهای Dart را از لینک زیر دانلود نمایید و از حالت فشرده خارج کنید. پوشه ای با نام one-hour-codelab ایجاد می‌گردد.

دانلود نمونه کدهای دارت

از منوی File > Open Existing Folder… پوشه one-hour-codelab را باز کنید .

توضیحات

- پوشه packages و همچنین فایلهای pubspec.yaml و pubspec.lock شامل پیش نیازها و Package هایی هستند که جهت اجرای برنامه‌های تحت Dart مورد نیاز هستند. Dart Editor این نیازمندی‌ها را به صورت خودکار نصب و تنظیم می‌کند.

توجه: اگر پوشه Packages را مشاهده نکردید و یا در سمت چپ فایلها علامت X قرمز رنگ وجود داشت، بدین معنی است که package ‌ها به درستی نصب نشده اند. برای این منظور بر روی pubspec.yaml کلیک راست نموده و گزینه Get Pub را انتخاب کنید. توجه داشته باید که بدلیل تحریم ایران توسط گوگل باید از ابزارهای عبور از تحریم استفاده کنید.

- 6 پوشه را نیز در تصویر فوق مشاهده می‌کنید که نمونه کد piratebadge را بصورت مرحله به مرحله انجام داده و به پایان می‌رساند.

- Dart SDK شامل سورس کد مربوط به تمامی توابع، متغیرها و کلاس هایی است که توسط کیت توسعه نرم افزاری Dart ارائه شده است.

- Installed Packages شامل سورس کد مربوط به تمامی توابع، متغیرها و کلاس‌های کتابخانه‌های اضافه‌تری است که Application به آنها وابسته است.


گام اول: اجرای یک برنامه کوچک

در این مرحله سورس کدهای آماده را مشاهده می‌کنید و با ساختار کدهای Dart و HTML آشنا می‌شوید و برنامه کوچکی را اجرا می‌نمایید.

در Dart Editor پوشه 1-blankbadge را باز کنید و فایلهای piratebadge.html و piratebadge.dart را مشاهده نمایید.

کد موجود در فایل piratebadge.html

<html>
  <head>
    <meta charset="utf-8">
    <title>Pirate badge</title>
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="piratebadge.css">
  </head>
  <body>
    <h1>Pirate badge</h1>
    
    <div>
      TO DO: Put the UI widgets here.
    </div>
    <div>
      <div>
        Arrr! Me name is
      </div>
      <div>
        <span id="badgeName"> </span>
      </div>
    </div>

    <script type="application/dart" src="piratebadge.dart"></script>
    <script src="packages/browser/dart.js"></script>
  </body>
</html>

توضیحات

- در کد HTML ، اولین تگ <script> ، فایل piratebadge.dart را جهت پیاده سازی دستورات dart به صفحه ضمیمه می‌نماید

- Dart Virtual Machine (Dart VM) کدهای Dart را بصورت Native یا بومی ماشین اجرا می‌کند. Dart VM کدهای خود را در Dartium که یک ویرایش ویژه از مرورگر Chromium می‌باشد اجرا می‌کند که می‌تواند برنامه‌های تحت Dart را بصورت Native اجرا کند.

- فایل packages/browser/dart.js پشتیبانی مرورگر از کد Native دارت را بررسی می‌کند و در صورت پشتیبانی، Dart VM را راه اندازی می‌کند و در غیر این صورت JavaScript کامپایل شده را بارگزاری می‌نماید.


کد موجود در piratebadge.dart

void main() {
  // Your app starts here.
}

- این فایل شامل تابع main می‌باشد که تنها نقطه ورود به application است. تگ <script> موجود در piratebadge.html برنامه را با فراخوانی این تابع راه اندازی می‌کند.

- تابع main() یک تابع سطح بالا یا top-level می‌باشد.

- متغیرها و توابع top-level عناصری هستند که خارج از ساختار تعریف کلاس ایجاد می‌شوند.

جهت اجرای برنامه در Dart Editor بر روی piratebadge.html کلیک راست نمایید و گزینه Run in Dartium را اجرا کنید. این فایل توسط Dartium اجرا می‌شود و تابع main() را فراخوانی می‌کند و صفحه ای همانند شکل زیر را نمایش می‌دهد.


گام دوم: افزودن فیلد input

توجه داشته باشید که در این مرحله یا می‌توانید تغییرات مورد نظر خود را در طی آموزش بر روی پوشه‌ی 1-blankbadge اعمال کنید و یا به پوشه‌های تهیه شده در نمونه کد موجود در همین پروژه مراجعه نمایید.

در این مرحله یک تگ <input> به تگ <div class=”widgets”> اضافه کنید.

...
<div>
  <div>
    <input type="text" id="inputName" maxlength="15">
  </div>
</div>
...

سپس کتابخانه dart:html را به ابتدای فایل piratebadge.dart اضافه کنید.

import 'dart:html';

توضیحات

- دستور فوق کلاس‌ها و Resource ‌های موجود در کتابخانه dart:html را اضافه می‌کند.

- از حجیم شدن کدهای خود نگران نباشید، زیرا فرایند کامپایل کدهای اضافی را حذف خواهد کرد.

- کتابخانه dart:html شامل کلاسهایی جهت کار با عناصر DOM و توابعی جهت دسترسی به این عناصر می‌باشد.

- در مباحث بعدی یاد می‌گیرید که با استفاده از کلمه کلیدی show فقط کلاسهایی را import کنید که به آن نیاز دارید.

- اگر کتابخانه ای در هیچ بخش کد استفاده نشود، خود Dart Editor به صورت warning اخطار می‌دهد و می‌توانید آن را حذف کنید.


دستور زیر را در تابع main بنویسید تا رویداد مربوط به ورود اطلاعات  در فیلد input را مدیریت نمایید.

void main() {
  querySelector('#inputName').onInput.listen(updateBadge);
}

توضیحات

- تابع querySelector() در کتابخانه dart:html تعریف شده است و یک المنت DOM را جستجو می‌نماید. پارامتر ورودی آن یک selector می‌باشد که در اینجا فیلد input را توسط #inputName جستجو نمودیم که یک ID Selector می‌باشد.

- نوع خروجی این متد یک شی از نوع DOM می‌باشد.

- تابع onInput.Listen() رویدادی را برای پاسخگویی به ورود اطلاعات در فیلد input تعریف می‌کند. زمانی که کاربر اطلاعاتی را وارد نماید، تابع updateBadge فراخوانی می‌گردد.

- رویداد input زمانی رخ می‌دهد که کاربر کلیدی را از صفحه کلید فشار دهد.

- رشته‌ها همانند جاوا اسکریپت می‌توانند در " یا '  قرار بگیرند.


تابع زیر را به صورت top-level یعنی خارج از تابع main تعریف کنید.

...

void updateBadge(Event e) { 
  querySelector('#badgeName').text = e.target.value;
}

توضیحات

- این تابع محتوای المنت badgeName را به محتوای وارد شده در فیلد input تغییر می‌دهد.

- پارامتر ورودی این تابع شی e از نوع Event می‌باشد و به همین دلیل می‌توانیم این تابع را یک Event Handler بنامیم.

- e.target به شی ای اشاره می‌کند که موجب رخداد رویداد شده است و در اینجا همان فیلد input می‌باشد

- با نوشتن کد فوق یک warning را مشاهده می‌کنید که بیان می‌کند ممکن است خصوصیت value برای e.target وجود نداشته باشد. برای حل این مسئله کد را بصورت زیر تغییر دهید.

...

void updateBadge(Event e) { 
  querySelector('#badgeName').text = (e.target as InputElement).value;
}

توضیحات

- کلمه کلیدی as به منظور تبدیل نوع استفاده می‌شود که e.target را به یک InputElement تبدیل می‌کند.

همانند گام اول برنامه را اجرا کنید و نتیجه را مشاهده نمایید. با تایپ کردن در فیلد input به صورت همزمان در کادر قرمز رنگ نیز نتیجه تایپ را مشاهده می‌نمایید. 

مطالب
قالب‌های سفارشی برای HtmlHelperها

در ابتدای بحث، برای آشنایی بیشتر با HTML Helperها به مطالعه این مقاله بپردازین.
در این مقاله قرار است برای یک HTML Helper خاص، قالب نمایشی اختصاصی خودمان را طراحی کنیم و به نحوی HTML Helper موجود را سفارشی سازی کنیم. به عنوان مثال می‌خواهیم خروجی یک EditorFor() برای یک نوع خاص، به حالت دلخواهی باشد که ما خودمان آن را تولیدش کردیم؛ یا اصلا نه. حتی می‌شود برای خروجی یک EditorFor() که خصوصیتی از جنس string را می‌خواهیم به آن انتساب دهیم، به جای تولید input، یک مقدار متنی را برگردانیم. به این حالت:

        <div>
            @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
            <div>
                @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
            </div>
        </div>
        
        <div>
            @Html.LabelFor(model => model.Genre, htmlAttributes: new { @class = "control-label col-md-2" })
            <div>
                @Html.EditorFor(model => model.Genre, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Genre, "", new { @class = "text-danger" })
            </div>
        </div>

در ادامه یک پروژه‌ی عملی را شروع کرده و در آن کاری را که می‌خواهیم، انجام می‌دهیم. پروژه‌ی ما به این شکل می‌باشد که قرار است در آن به ثبت کتاب بپردازیم و برای هر کتاب هم یک سبک داریم و قسمت سبک کتاب‌های ما یک Enum است که از قبل می‌خواهیم مقدارهایش را تعریف کنیم. 

مدل برنامه

    public class Books
    {
        public int Id { get; set; }
        [Required]
        [StringLength(255)]
        public string Name { get; set; }
        public Genre Genre { get; set; }
    }
    public enum Genre
    {
        [Display(Name = "Non Fiction")]
        NonFiction,
        Romance,
        Action,
        [Display(Name = "Science Fiction")]
        ScienceFiction
    }

در داخل کلاس Books یک خصوصیت از جنس Genre برای سبک کتاب‌ها داریم و در داخل نوع شمارشی Genre، سبک‌های ما تعریف شده‌اند. همچنین هر کدام از سبک‌ها هم به ویژگی Display مزین شده‌اند تا بتونیم بعدا از مقدار آنها استفاده کنیم. 


کنترلر برنامه

    public class BookController : Controller
    {
        // GET: Book
        public ActionResult Index()
        {
            return View(DataAccess.DataContext.Book.ToList());
        }

        public ActionResult Create()
        {
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(Books model)
        {
            if (!ModelState.IsValid)
                return View(model);

            try
            {
                DataAccess.DataContext.Book.Add(model);
                DataAccess.DataContext.SaveChanges();
                return RedirectToAction("Index");
            }
            catch (Exception ex)
            {
                ModelState.AddModelError("", ex.Message);
                return View(model);
            }
        }

        public ActionResult Edit(int id)
        {
            try
            {
                var book = DataAccess.DataContext.Book.Find(id);
                return View(book);
            }
            catch (Exception ex)
            {
                return View("Error");
            }
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit(Books model)
        {
            if (!ModelState.IsValid)
                return View(model);

            try
            {
                DataAccess.DataContext.Book.AddOrUpdate(model);
                DataAccess.DataContext.SaveChanges();
                return RedirectToAction("Index");
            }
            catch (Exception ex)
            {
                ModelState.AddModelError("", ex.Message);
                return View(model);
            }
        }

        public ActionResult Details(int id)
        {
            try
            {
                var book = DataAccess.DataContext.Book.Find(id);
                return View(book);
            }
            catch (Exception ex)
            {
                return View("Error");
            }
        }
    }

در قسمت کنترلر هم کار خاصی جز عملیات اصلی نوشته نشده‌است. لیست کتاب‌ها را از پایگاه داده بیرون آوردیم و از طریق اکشن Index به نمایش گذاشتیم. با اکشن‌های Create، Edit و Details هم کارهای روتین مربوط به خودشان را انجام دادیم. نکته‌ی قابل تذکر، DataAccess می‌باشد که کلاسی است که با آن ارتباط برقرار شده با EF و سپس اطلاعات واکشی و تزریق می‌شوند.

View مربوط به اکشن Create برنامه

@using Book.Entities
@model Book.Entities.Books

@{
    ViewBag.Title = "Create";
}
<h2>New Book</h2>
@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
    
    <div>
        <h4>Books</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div>
            @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
            <div>
                @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
            </div>
        </div>
        
        <div>
            @Html.LabelFor(model => model.Genre, htmlAttributes: new { @class = "control-label col-md-2" })
            <div>
                @Html.EditorFor(model => model.Genre, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Genre, "", new { @class = "text-danger" })
            </div>
        </div>

        <div>
            <div>
                <input type="submit" value="Create" />
                <input type="reset" value="Reset" />
                @Html.ActionLink("Back to List", "Index", null, new {@class="btn btn-default"})
            </div>
        </div>
    </div>
}

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

View برنامه هم همان ویویی است که خود Visual Studio برای ما ساخته‌است. به جز یک سری دست‌کاری‌هایی داخل سی‌اس‌اس، هدف از گذاشتن View مربوط به Create این بود که قرار است بر روی این قسمت کار کنیم. اگر پروژه رو اجرا کنید و به قسمت Create بروید، مشاهده خواهید کرد که برای Genre یک input ساخته شده‌است که کاربر باید در آن مقدار وارد کند. ولی اگر یادتان باشد، ما سبک‌های نگارشی خودمان را در نوع شمارشی Genre ایجاد کرده بودیم. پس عملا باید یک لیست به کاربر نشان داده شود که تا از آن لیست، نوع را انتخاب کند. می‌توانیم بیایم همینجا در داخل View مربوطه، به‌جای استفاده از HTML Helper پیش‌فرض، از DropDownList یا EnumFor استفاده کنیم و به طریقی این لیست را ایجاد کنیم. ولی چون قرار است در این مثال به شرح موضوع مقاله خودمان بپردازیم، این کار را انجام نمی‌دهیم.

در حقیقیت می‌خوایم متد EditorFor را طوری سفارشی سازی کنیم که برای نوع شمارشی Genre، به صورت خودکار یک لیست ایجاد کرده و برگرداند. از نسخه‌ی سوم ASP.NET MVC به بعد این امکان برای توسعه دهنده‌ها فراهم شده‌است. شما می‌توانید در پوشه‌ی Shared داخل پوشه Views برنامه، پوشه‌ای را به اسم EditorTemplates ایجاد کنید؛ همینطور DisplayTemplates و برای نوع خاصی که می‌خواهید سفارشی‌سازی را برای آن انجام دهید، یک PartialView بسازید. 

Views/Shared/DisplayTemplates/<type>.cshtml
یک PartialView در داخل پوشه EditorTemplates به نام Genre.cshtml ایجاد کنید. برای اینکه مشاهده کنید چطور کار می‌کند، کافی‌است یک فایل متنی اینجا تهیه کرده و بعد پروژه را اجرا کرده و به قسمت Create روید تا تغییرات را مشاهده کنید. بله! به‌جای inputی که از قبل وجود داشت، فقط متن شما آنجا نوشته شده‌است. (به عکسی که در بالا قرار دارد هم می‌تونید نگاه کنید)

کاری که الان میخواهیم انجام دهیم این است که یک SelectListItem ایجاد کرده تا مقدارهای نوع Genreمان داخلش باشد و بتوانیم به راحتی برای ساختن DropDownList از آن استفاده کنیم. برای این کار Helper مخصوص خودمان را ایجاد می‌کنیم. پوشه‌ای به اسم Helpers در کنار پوشه‌های Controllers، Models ایجاد می‌کنیم و در داخل آن کلاسی به اسم EnumHelpers می‌سازیم.

    public static class EnumHelpers
    {
        public static IEnumerable<SelectListItem> GetItems(
            this Type enumType, int? selectedValue)
        {
            if (!typeof(Enum).IsAssignableFrom(enumType))
            {
                throw new ArgumentException("Type must be an enum");
            }

            var names = Enum.GetNames(enumType);
            var values = Enum.GetValues(enumType).Cast<int>();

            var items = names.Zip(values, (name, value) =>
                    new SelectListItem
                    {
                        Text = GetName(enumType, name),
                        Value = value.ToString(),
                        Selected = value == selectedValue
                    }
                );
            return items;
        }

        static string GetName(Type enumType, string name)
        {
            var result = name;

            var attribute = enumType
                .GetField(name)
                .GetCustomAttributes(inherit: false)
                .OfType<DisplayAttribute>()
                .FirstOrDefault();

            if (attribute != null)
            {
                result = attribute.GetName();
            }

            return result;
        }
    }

در توضیح کد بالا عنوان کرد که متدها به‌صورت متدهای الحاقی به نوع Type نوشته شدند. کار خاصی در بدنه‌ی متدها انجام نشده‌است. در بدنه‌ی متد اول لیست آیتم‌ها را تولید کردیم. در هنگام ساخت SelectListItem برای گرفتن Text، متد GetName را صدا زدیم. برای اینکه بتوانیم مقدار ویژگی Display که در هنگام تعریف نوع شمارشی استفاده کردیم را بدست بیاریم، باید چک کنیم ببینیم که آیا این آیتم به این ویژگی مزین شده‌است یا نه. اگر شده بود مقدار را می‌گیریم و به خصوصیت Text متد اول انتساب می‌دهیم.

@using Book.Entities
@using Book.Web.Helpers
@{
    var items = typeof(Genre).GetItems((int?)Model);
}

@Html.DropDownList("", items, new {@class="form-control"})

کدهایی که در بالا مشاهده می‌کنید کدهایی می‌باشند که قرار است داخل PartialViewی Genre قرار دهیم که در پوشه‌ی EditorTemplates ساختیم. ابتدا آمدیم آیتم‌ها را گرفتیم و بعد به DropDownList دادیم تا لیست نوع را برای ما بسازد. حالا اگه برنامه را اجرا کنید می‌بینید که EditorFor برای شما یه لیست از نوع شمارشی ساخته و حالا قابل استفاده هست.

 

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید  

مطالب
React 16x - قسمت 7 - ترکیب کامپوننت‌ها - بخش 1 - ارسال داده‌ها، مدیریت رخ‌دادها
تا اینجا، تنها با یک تک کامپوننت کار کردیم؛ اما یک برنامه‌ی واقعی ترکیبی است از چندین کامپوننت که در نهایت درخت کامپوننت‌ها را در React تشکیل می‌دهند. به همین جهت در طی چند قسمت، نکات ترکیب کامپوننت‌ها را بررسی می‌کنیم.


ترکیب کامپوننت‌ها

در ادامه، همان برنامه‌ی تا قسمت 5 را که کار نمایش یک counter را انجام می‌دهد، تکمیل می‌کنیم. در این برنامه اگر به فایل index.js دقت کنید، کار رندر تک کامپوننت Counter را انجام می‌دهیم:
ReactDOM.render(<Counter />, document.getElementById("root"));
اما یک برنامه‌ی واقعی React، متشکل از درختی از کامپوننت‌ها است. به این ترتیب با ترکیب و در کنار هم قرار دادن کامپوننت‌های مختلف، می‌توان به UI ای کارآمد و پیچیده رسید.
برای نمایش این مفهوم، کامپوننت جدید src\components\counters.jsx را ایجاد می‌کنیم. قصد داریم در این کامپوننت، لیستی از کامپوننت‌های Counter را رندر کنیم. سپس در index.js، بجای رندر کامپوننت Counter، کامپوننت جدید Counters را رندر می‌کنیم. به این ترتیب درخت کامپوننت‌های برنامه، در سطح بالایی خودش از کامپوننت Counters شروع می‌شود و سپس فرزندان آن‌را کامپوننت‌های Counter تشکیل می‌دهند. به همین جهت فایل index.js را به صورت زیر ویرایش می‌کنیم تا به کامپوننت Counters اشاره کند:
import Counters from "./components/counters";

ReactDOM.render(<Counters />, document.getElementById("root"));
سپس به فایل جدید src\components\counters.jsx مراجعه کرده و با استفاده از قطعه کدهای کمکی imrc و cc که در قسمت‌های قبل با آن‌ها آشنا شدیم، ساختار بدنه‌ی کامپوننت جدید Counters را ایجاد می‌کنیم. اکنون در متد render آن، یک div را ایجاد کرده و داخل آن، چندین کامپوننت Counter را رندر می‌کنیم:
import React, { Component } from "react";

import Counter from "./counter";

class Counters extends Component {
  state = {};

  render() {
    return (
      <div>
        <Counter />
        <Counter />
        <Counter />
        <Counter />
      </div>
    );
  }
}

export default Counters;
در این حالت اگر به مرورگر مراجعه کنیم، مشاهده خواهیم کرد که هر کامپوننت، state خاص خودش را دارد و از سایر کامپوننت‌ها ایزوله است:


در مرحله‌ی بعد، بجای رندر و درج دستی این کامپوننت‌ها، آرایه‌ای از اشیاء counter را ایجاد کرده و سپس آن‌ها را توسط متد Array.map رندر می‌کنیم:
import React, { Component } from "react";
import Counter from "./counter";

class Counters extends Component {
  state = {
    counters: [
      { id: 1, value: 0 },
      { id: 2, value: 0 },
      { id: 3, value: 0 },
      { id: 4, value: 0 }
    ]
  };

  render() {
    return (
      <div>
        {this.state.counters.map(counter => (
          <Counter key={counter.id} />
        ))}
      </div>
    );
  }
}

export default Counters;
در اینجا یک خاصیت جدید را به شیء منتسب به خاصیت state به نام counters اضافه کرده‌ایم. این خاصیت حاوی آرایه‌ای از اشیاء counter است که هر کدام دارای یک id (که در قسمت key ذکر خواهد شد) و مقداری اولیه است. سپس آرایه‌ی this.state.counters را توسط متد map، رندر کرده‌ایم. تا اینجا پس از ذخیره‌ی فایل و بارگذاری مجدد برنامه، همان خروجی قبلی را مشاهده خواهیم کرد.


ارسال داده‌ها به کامپوننت‌ها

مشکل! مقدار value هر شیء شمارشگر تعریف شده، به کامپوننت‌های مرتبط رندر شده اعمال نشده‌است. برای مثال اگر value اولین شیء را به 4 تغییر دهیم، هنوز هم این کامپوننت با همان مقدار صفر شروع به کار می‌کند. برای رفع این مشکل، به همان روشی که ویژگی key کامپوننت Counter را مقدار دهی کردیم، می‌توان ویژگی‌های سفارشی دیگری را تعریف و مقدار دهی کرد:
  render() {
    return (
      <div>
        {this.state.counters.map(counter => (
          <Counter key={counter.id} value={counter.value} selected={true} />
        ))}
      </div>
    );
پس از تعریف ویژگی‌های دلخواه value و selected که یکی از آن‌ها به مقدار value شیء counter مرتبط متصل است، به خود کامپوننت Counter مراجعه کرده و سپس در ابتدای متد render آن، خاصیت props به ارث رسیده شده‌ی از کلاس پایه‌ی Component را جهت بررسی بیشتر لاگ می‌کنیم:
class Counter extends Component {
  state = {
    count: 0
  };

  render() {
    console.log("props", this.props);
    //...
پس از ذخیره‌ی فایل counter.jsx و بارگذاری مجدد برنامه، یک چنین خروجی در کنسول توسعه دهندگان مرورگر قابل مشاهده است:


خاصیت this.props، یک شیء ساده‌ی جاوا اسکریپتی است و شامل تمام ویژگی‌هایی می‌باشد که ما در کامپوننت Counters برای هر کدام از کامپوننت‌های Counter رندر شده‌ی توسط آن، تعریف کردیم. برای نمونه دو ویژگی جدید value و selected را که به تعاریف المان‌های Counter در کامپوننت Counters اضافه کردیم، در اینجا به همراه مقادیر منتسب به آن‌ها، قابل مشاهده هستند. البته در این خروجی، key را ملاحظه نمی‌کنید؛ چون هدف اصلی آن، معرفی یکتای المان‌ها در DOM مجازی React است.
بنابراین اکنون می‌توان به value تنظیم شده‌ی در کامپوننت Counters به صورت this.props.value در کامپوننت Counter دسترسی یافت و سپس از آن جهت مقدار دهی اولیه‌ی counter استفاده کرد.
class Counter extends Component {
  state = {
    count: this.props.value
  };
اکنون اگر تغییرات کامپوننت Counter را ذخیره کرده و به مرورگر مراجعه کنیم، در اولین بار نمایش برنامه و بدون اعمال هیچگونه تغییری، یک چنین خروجی حاصل می‌شود:


یک نکته: در اینجا selected={true} را داریم. اگر مقدار آن‌را حذف کنیم، یعنی selected تنها درج شود، مقدار آن، همان true دریافت خواهد شد.


تعریف فرزند برای المان‌های کامپوننت‌ها

ویژگی‌های اضافه شده‌ی به تعاریف المان‌های کامپوننت‌ها، توسط خاصیت this.props، به هر کدام از آن کامپوننت‌ها منتقل می‌شوند. این خاصیت props، یک خاصیت ویژه را به نام children، نیز دارا است و از آن برای دسترسی به المان‌های تعریف شده‌ی بین تگ‌های یک المان اصلی استفاده می‌شود:
  render() {
    return (
      <div>
        {this.state.counters.map(counter => (
          <Counter key={counter.id} value={counter.value} selected={true}>
            <h4>‍Counter #{counter.id}</h4>
          </Counter>
        ))}
      </div>
    );
  }
در اینجا بین تگ‌های ابتدا و انتهای تعریف المان Counter، یک محتوا نیز تعریف شده‌است. اکنون اگر به خروجی کنسول توسعه دهندگان مرورگر دقت کنیم، خاصیت جدید اضافه شده‌ی children را نیز می‌توان مشاهده کرد:


یک نمونه مثال واقعی این قابلیت، امکان تعریف محتوای دیالوگ باکس‌ها، توسط استفاده کنند‌ه‌ی از آن است.


روش دیباگ برنامه‌های React

افزونه‌ی مفید React developer tools را می‌توانید برای مرورگرهای کروم و فایرفاکس، دریافت و نصب کنید. برای نمونه پس از نصب آن در مرورگر کروم، یک برگه‌ی جدید به لیست برگه‌های کنسول توسعه دهندگان آن اضافه می‌شود:


همانطور که مشاهده کنید، درخت کامپوننت‌های برنامه را در برگه‌ی جدید Components، می‌توان مشاهده کرد. در اینجا با انتخاب هر کدام از فرزندان این درخت، مشخصات آن نیز مانند props و state، در کنار صفحه ظاهر می‌شوند. همچنین در بالای همین قسمت، 4 آیکن مشاهده‌ی سورس، مشاهده‌ی DOM و یا لاگ کردن جزئیات شیء کامپوننت انتخابی در کنسول هم درج شده‌اند:


که برای نمونه چنین خروجی را لاگ می‌کند:



بررسی تفاوت‌های خواص props و state

در کامپوننت Counter، از props برای مقدار دهی اولیه‌ی state استفاده می‌کنیم:
class Counter extends Component {
  state = {
    count: this.props.value
  };
اکنون این سؤال مطرح می‌شود که چه تفاوتی بین props و state وجود دارد؟
- props حاوی اطلاعاتی است که به یک کامپوننت ارسال می‌کنیم؛ اما state حاوی اطلاعاتی است که مختص به آن کامپوننت بوده و private است. یعنی سایر کامپوننت‌ها نمی‌توانند به state کامپوننت دیگری دسترسی پیدا کنند. برای مثال در کامپوننت Counters، تمام attributes سفارشی تنظیم شده‌ی بر روی تعاریف المان‌های کامپوننت Counter، جزئی از اطلاعات props خواهند بود. در اینجا نمی‌توان به state کامپوننت مدنظری دسترسی یافت و آن‌را مقدار دهی کرد. به همین ترتیب state کامپوننت Counters نیز در سایر کامپوننت‌ها قابل دسترسی نیست.
- همچنین باید درنظر داشت که props، در مقایسه با state، فقط خواندنی است. به عبارتی مقدار ورودی به یک کامپوننت را داخل آن کامپوننت نمی‌توان تغییر داد. برای مثال سعی کنید در داخل متد رویدادگردان کلیک موجود در کامپوننت Counter، مقدار this.props.value را به صفر تنظیم کنید. در این حالت با کلیک بر روی دکمه‌ی Increment، بلافاصله خطای readonly بودن خواص شیء منتسب به props را دریافت می‌کنیم. در اینجا اگر نیاز است این مقدار را داخل کامپوننت تغییر دهیم، باید ابتدا این مقدار را دریافت کرده و سپس آن‌را داخل state قرار دهیم. پس از آن امکان ویرایش اطلاعات منتسب به state، داخل یک کامپوننت وجود خواهد داشت.


صدور و مدیریت رخ‌دادها

در ادامه می‌خواهیم در کنار هر دکمه‌ی Increment کامپوننت شمارشگر، یک دکمه‌ی Delete هم قرار دهیم:


مشکل! اگر کد مدیریتی handleDelete را در کامپوننت Counter قرار دهیم، چگونه باید به لیست آرایه‌ی اشیاء counters والد آن، یعنی کامپوننت Counters که سبب رندر شدن کامپوننت‌های شمارشگر شده (state = { counters: [ ] })، دسترسی یافت و شیء‌ای را از آن حذف کرد؟ در React، کامپوننتی که state ای را تعریف می‌کند، باید کامپوننتی باشد که قرار است آن‌را تغییر دهد و اطلاعات state هر کامپوننت، صرفا متعلق به آن کامپوننت بوده و جزو اطلاعات خصوصی آن است. بنابراین مدیریت حذف و یا افزودن کامپوننت‌ها در لیست نمایش داده شده، باید جزو وظایف کامپوننت Counters باشد و نه Counter.
برای حل این مشکل، کامپوننت Counter تعریف شده (کامپوننت فرزند) باید سبب بروز رخ‌داد onDelete شود تا کامپوننت Counters (کامپوننت والد)، آن‌را توسط متد handleDelete مدیریت کند. بنابراین ابتدا به کامپوننت Counters (کامپوننت والد) مراجعه کرده و متد رویدادگردان handleDelete را به آن اضافه می‌کنیم:
  handleDelete = () => {
    console.log("handleDelete called.");
  };
سپس ارجاعی از این متد را به صورت خاصیتی از props به کامپوننت Counter (کامپوننت فرزند) ارسال خواهیم کرد؛ برای این منظور در کامپوننت Counters (کامپوننت والد)، ویژگی onDelete را به تعریف المان Counter اضافه کرده و آن‌را با ارجاعی به متدhandleDelete  مقدار دهی می‌کنیم:
<Counter
     key={counter.id}
     value={counter.value}
     selected={true}
     onDelete={this.handleDelete}
/>
پس از آن به کامپوننت Counter مراجعه کرده و دکمه‌ی جدید Delete را به صورت زیر در کنار دکمه‌ی Increment تعریف می‌کنیم:
<button
  onClick={this.props.onDelete}
  className="btn btn-danger btn-sm m-2"
>
  Delete
</button>
در اینجا onClick، به خاصیت onDelete شیء props ارسالی به کامپوننت متصل شده‌است.
اکنون اگر برنامه را ذخیره کرده و پس از بارگذاری مجدد برنامه در مرورگر بر روی دکمه‌ی Delete کلیک کنیم، پیام «handleDelete called» در کنسول توسعه دهندگان مرورگر لاگ می‌شود. به این ترتیب کامپوننت فرزند سبب بروز رخ‌دادی شده و والد آن، این رخ‌داد را مدیریت می‌کند.


به روز رسانی state

تا اینجا دکمه‌ی Delete فرزند، به متد handleDelete والد متصل شده‌است. مرحله‌ی بعد، پیاده سازی واقعی حذف یک المان از DOM مجازی و به روز رسانی state است. برای اینکار ابتدا به رخ‌دادگردان onClick، در کامپوننت شمارشگر، مراجعه کرده و id دریافتی را به سمت والد ارسال می‌کنیم:
onClick={() => this.props.onDelete(this.props.id)}
البته در سمت والد نیز باید این id را به صورت یک خاصیت جدید به props اضافه کنیم (تا this.props.id فوق کار کند)؛ چون ویژگی key، مختص DOM مجازی بوده و به props اضافه نمی‌شود:
<Counter
  key={counter.id}
  value={counter.value}
  selected={true}
  onDelete={this.handleDelete}
  id={counter.id}
/>
اکنون این id را در کامپوننت والد دریافت و به آن واکنش نشان می‌دهیم:
  handleDelete = counterId => {
    console.log("handleDelete called.", counterId);
    const counters = this.state.counters.filter(
      counter => counter.id !== counterId
    );
    this.setState({ counters }); // = this.setState({ counters: counters });
  };
همانطور که پیشتر نیز در این سری عنوان شده، در React، مقدار state را به صورت مستقیم تغییر نمی‌دهیم و اینکار باید از طریق متد setState آن صورت گیرد. به عبارت دیگر مستقیما خاصیت counters شیء منتسب به خاصیت state را تغییر نمی‌دهیم. ابتدا یک آرایه‌ی جدید از المان‌ها را تولید کرده و به متد setState ارسال می‌کنیم. سپس React، هم خاصیت counters و هم UI را بر این اساس به روز رسانی خواهد کرد. در اینجا، لیست جدید counters، بر اساس id دریافتی از کامپوننت فرزند، تولید شده و به متد this.setState ارسال می‌شود. در این حالت اگر برنامه را ذخیره کرده و پس از بارگذاری مجدد آن در مرورگر، بر روی دکمه‌ی Delete هر ردیف کلیک کنیم، آن ردیف از UI حذف خواهد شد.

البته پیاده سازی ما تا به اینجا بدون مشکل کار می‌کند، اما به ازای هر خاصیت counter، یک ویژگی جدید را به تعریف المان مرتبط اضافه کرده‌ایم که در طول زمان بیش از اندازه طولانی خواهد شد. برای رفع این مشکل، خود شیء counter را به صورت یک ویژگی جدید به کامپوننت مرتبط با آن ارسال می‌کنیم. به این ترتیب اگر در آینده خاصیتی را به این شیء اضافه کردیم، دیگر نیازی نیست تا آن‌را به صورت دستی و مجزا تعریف کنیم. به همین جهت ابتدا تعریف المان Counter را به صورت زیر خلاصه می‌کنیم که در آن ویژگی جدید counter، حاوی کل شیء counter است:
<Counter
  key={counter.id}
  counter={counter}
  onDelete={this.handleDelete}
/>
سپس در سمت کامپوننت فرزند شمارشگر، دو تغییر this.props.counter.value و this.props.counter.id باید صورت گیرند تا مقادیر شیء counter به درستی خوانده شوند.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-07.zip
مطالب
بررسی تغییرات Blazor 8x - قسمت ششم - نکات تکمیلی ویژگی راهبری بهبود یافته‌ی صفحات SSR


در قسمت قبل، در حین بررسی رفتار جزیره‌های تعاملی Blazor Server، نکته‌ی زیر را هم درباره‌ی راهبری صفحات SSR مرور کردیم:
« اگر دقت کنید، جابجایی بین صفحات، با استفاده از fetch انجام شده؛ یعنی با اینکه این صفحات در اصل static HTML خالص هستند، اما ... کار full reload صفحه مانند ASP.NET Web forms قدیمی انجام نمی‌شود (و یا حتی برنامه‌های MVC و Razor pages) و نمایش صفحات، Ajax ای است و با fetch استاندارد آن صورت می‌گیرد تا هنوز هم حس و حال SPA بودن برنامه حفظ شود. همچنین اطلاعات DOM کل صفحه را هم به‌روز رسانی نمی‌کند؛ فقط موارد تغییر یافته در اینجا به روز رسانی خواهند شد.»
در این قسمت، نکات تکمیلی این قابلیت را که به آن enhanced navigation هم گفته می‌شود، بررسی می‌کنیم.


روش غیرفعال کردن راهبری بهبودیافته برای بعضی از لینک‌ها

ویژگی راهبری بهبودیافته فقط در حین هدایت بین صفحات مختلف یک برنامه‌ی Blazor 8x SSR، فعال است. اگر در این بین، کاربری به یک صفحه‌ی غیر بلیزری هدایت شود، راهبری بهبود یافته شکست خورده و سعی می‌کند حالت full document load را پیاده سازی و اجرا کند. مشکل اینجاست که در این حالت دو درخواست ارسال می‌شود: ابتدا حالت راهبری بهبودیافته فعال می‌شود و در ادامه پس از شکست این راهبری، هدایت مستقیم صورت می‌گیرد. برای رفع این مشکل می‌توان ویژگی جدید data-enhance-nav را با مقدار false، به لینک‌های خارجی مدنظر اضافه کرد تا برای این حالت‌ها دیگر ویژگی راهبری بهبودیافته فعال نشود:
<a href="/not-blazor" data-enhance-nav="false">A non-Blazor page</a>


فعالسازی مدیریت بهبودیافته‌ی فرم‌های SSR

در قسمت چهارم این سری با فرم‌های جدید SSR مخصوص Blazor 8x آشنا شدیم. این فرم‌ها هم می‌توانند از امکانات راهبری بهبود یافته استفاده کنند (یعنی مدیریت ارسال آن، توسط fetch API انجام شده و به روز رسانی قسمت‌های تغییریافته‌ی صفحه را Ajax ای انجام دهند)؛ برای نمونه اینبار همانند تصویر زیر، از fetch استاندارد برای ارسال اطلاعات به سمت سرور کمک گرفته می‌شود (یعنی عملیات Ajax ای شده‌؛ بجای یک post-back معمولی):


 اما ... این قابلیت به صورت پیش‌فرض در فرم‌های تعاملی SSR غیرفعال است. چون همانطور که عنوان شد، اگر مقصد این فرم، یک آدرس غیربلیزری باشد، دوبار ارسال فرم صورت خواهد گرفت؛ یکبار با استفاده از fetch API و بار دیگر پس از شکست، به صورت معمولی. اما اگر مطمئن هستید که endpoint این فرم، قطعا یک کامپوننت بلیزری است، بهتر است این قابلیت را در یک چنین فرم‌هایی نیز به صورت زیر فعال کنید:
<form method="post" @onsubmit="() => submitted = true" @formname="name" data-enhance>
    <AntiforgeryToken />
    <InputText @bind-Value="Name" />
    <button>Submit</button>
</form>

@if (submitted)
{
    <p>Hello @Name!</p>
}

@code {

    bool submitted;

    [SupplyParameterFromForm]
    public string Name { get; set; } = "";
}
البته باتوجه به اینکه در اینجا هم می‌توان از EditForm‌ها استفاده کرد، در این حالت فقط کافی است ویژگی Enhance را به آن‌ها اضافه نمائید:
<EditForm method="post" Model="NewCustomer" OnValidSubmit="() => submitted = true" FormName="customer" Enhance>
    <DataAnnotationsValidator />
    <ValidationSummary/>
    <p>
        <label>
            Name: <InputText @bind-Value="NewCustomer.Name" />
        </label>
    </p>
    <button>Submit</button>
</EditForm>

@if (submitted)
{
    <p id="pass">Hello @NewCustomer.Name!</p>
}

@code {
    bool submitted = false;

    [SupplyParameterFromForm]
    public Customer? NewCustomer { get; set; }

    protected override void OnInitialized()
    {
        NewCustomer ??= new();
    }

     public class Customer
    {
        [StringLength(3, ErrorMessage = "Name is too long")]
        public string? Name { get; set; }
    }
}

نکته‌ی مهم: در این حالت فرض بر این است که هیچگونه هدایتی به یک Non-Blazor endpoint صورت نمی‌گیرید؛ وگرنه با یک خطا مواجه خواهید شد.



غیرفعال کردن راهبری بهبودیافته برای قسمتی از صفحه

اگر با استفاده از جاواسکریپت و خارج از کدهای بیلزر، اطلاعات DOM را به‌روز رسانی می‌کنید، ویژگی راهبری بهبودیافته، از آن آگاهی نداشته و به صورت خودکار تمام تغییرات شما را بازنویسی می‌کند. به همین جهت اگر نیاز است قسمتی از صفحه را که مستقیما توسط کدهای جاواسکریپتی تغییر می‌دهید، از به‌روز رسانی‌های این قابلیت مصون نگه‌دارید، می‌توانید ویژگی جدید data-permanent را به آن قسمت اضافه کنید:
<div data-permanent>
    Leave me alone! I've been modified dynamically.
</div>


امکان آگاه شدن از بروز راهبری بهبودیافته در کدهای جاواسکریپتی

اگر به هردلیلی در کدهای جاواسکریپتی خودنیاز به آگاه شدن از وقوع یک هدایت بهبودیافته را دارید (برای مثال جهت بازنویسی تغییرات ایجاد شده‌ی توسط آن)، می‌توانید به نحو زیر، مشترک رخ‌دادهای آن شوید:
<script>
    Blazor.addEventListener('enhancedload', () => {
        console.log('enhanced load event occurred');
    });
</script>


ویژگی جدید Named Element Routing در Blazor 8x

Blazor 8x از ویژگی مسیریابی سمت کلاینت به کمک تعریف URL fragments پشتیبانی می‌کند. به این صورت رسیدن (اسکرول) به یک قسمت از صفحه‌ای طولانی، بسیار ساده می‌شود.
برای مثال المان h2 با id مساوی targetElement را درنظر بگیرید:
<div class="border border-info rounded bg-info" style="height:500px"></div>

<h2 id="targetElement">Target H2 heading</h2>
<p>Content!</p>
در Blazor 8x برای رسیدن به آن، هر سه حالت زیر میسر هستند:
<a href="/counter#targetElement">

<NavLink href="/counter#targetElement">

Navigation.NavigateTo("/counter#targetElement");


معرفی متد جدید Refresh در Blazor 8x

در Blazor 8x، امکان بارگذاری مجدد صفحه با فراخوانی متد جدید NavigationManager.Refresh(bool forceLoad = false) میسر شده‌است. این متد در حالت پیش‌فرض از قابلیت راهبری بهبودیافته برای به روز رسانی صفحه استفاده می‌کند؛ مگر اینکه اینکار میسر نباشد. اگر آن‌را با پارامتر true فراخوانی کنید، full-page reload رخ خواهد داد.
همین اتفاق در مورد متد Navigation.NavigateTo نیز رخ‌داده‌است. این متد نیز در Blazor 8x به صورت پیش‌فرض بر اساس قابلیت راهبری بهبود یافته کار می‌کند؛ مگر اینکه اینکار میسر نباشد و یا پارامتر forceLoad آن‌را به true مقدار دهی کنید.
مطالب
مسیریابی در AngularJs #بخش سوم
در بخش‌های پیشین ( بخش اول و بخش دوم) به خوبی با اصول و روش مسیریابی (Routing) در AngularJS آشناشدیم. در این بخش می‌خواهم به برخی جزئیات درباره مسیریابی بپردازم.
اولین موضوع، تغییراتی است که از نسخه 1.2 به بعد در روش استفاده از سرویس مسیریابی در AngularJS بوجود آمده است. از نسخه 1.2 سرویس مسیریابی از هسته اصلی AngularJS خارج شد و برای استفاده از امکانات این سرویس باید  فایل angular-route.js و یا angular-route.min.js را به صفحه خود بیفزاییم:  
<script src="~/Scripts/angular.min.js"></script>
<script src="~/Scripts/angular-route.min.js"></script>
سپس باید هنگام تعریف ماژول، ngRoute را به عنوان وابستگی تزریق کنیم: 
var app = angular.module("mainApp", ['ngRoute']);
روش Controller as در AngularJS که از نسخه 1.2 به بعد امکان استفاده از آن وجود دارد قبلا معرفی شده است. با پاس کردن خصوصیت controllerAs به متد when می‌توان از viewای استفاده کرد که در آن از این روش استفاده شده است.
.when('/controllerAS', {
    controller:      'testController',
    controllerAs:    'tCtrl',
    template:        '<div>{{tCtrl.Title}}</div>'
})
 باقی ماجرا مانند گذشته است. 
موضوع دیگری که پرداختن به آن می‌تواند مفید باشد، بررسی بیشتر متد when است. وقتی در متد config ماژول از routeProvider$ استفاده می‌کنیم، داریم سرویس route$ را تنظیم، مقداردهی اولیه، و نمونه گیری می‌کنیم. درواقع با استفاده از متدهای when و otherwise داریم سرویس route$ را مقداردهی اولیه می‌کنیم (برای آشنایی با تقاوت factory، service و provider کلیک کنید ). خوب! جریان این مقادیری که به عنوان پارامتر به این متدها پاس می‌کنیم چیست؟
متد when به این صورت تعریف شده است:
when(string path, object route)

 پارامتر path در بخش‌های قبل به اندازه کافی معرفی شده است. پارامتر route یک شی است شامل اطلاعاتی که با تطبیق آدرس صفحه با پارامتر path، به  route.current$  مقداردهی می‌شود (حالا باید متوجه شده باشید که روال افزودن داده‌های سفارشی به سیستم مسیریابی و دسترسی به آن‌ها که در بخش دوم مطرح شد به چه شکل کار می‌کند). این شی می‌تواند خصوصیات از قبل تعریف شده‌ای داشده باشد که در ادامه آن‌ها را مرور می‌کنیم:
controller: می‌تواند یک رشته شامل نام کنترلر از قبل تعریف شده، یا یک تابع به عنوان تابع کنترلر باشد.
controllerAs: رشته‌ای شامل نام مستعار کنترلر.
template: رشته‌ای شامل قالب html، و یا تابعی که قالب html را بازمی‌گرداند. این خصوصیت بر templateUrl اولویت دارد. اگر مقدار این خصوصیت یک تابع باشد، routeParams$ به عنوان پارامتر ورودی به آن پاس می‌شود.
templateUrl: رشته‌ای شامل مسیر فایل قالب html، و یا تابعی که این رشته را بازمی‌گرداند. اگر مقدار این خصوصیت یک تابع باشد، routeParams$ به عنوان پارامتر ورودی به آن پاس می‌شود.
redirectTo: مقداری برای به روز رسانی  location$، و فراخوانی روال مسیر یابی. این مقدار می‌تواند یک رشته، و یا تابعی که یک رشته را بازمی‌گرداند باشد. اگر مقدار این خصوصیت یک تابع باشد، این پارامترها به آن پاس می‌شود:
  •  routeParams$ برای دسترسی به پارامترهای آمده در آدرس صفحه جاری.
  • ()location.path$ جاری به صورت یک رشته.
  • ()location.search$ جاری به صورت یک شی.
caseInsensitiveMatch: یک مقدار منطقی است که مشخص می‌کند بزرگ و کوچک بودن حروف در تطبیق آدرس صفحه با پارامتر route در نظر گرفته بشود یا نه. مقدار پیشفرض این خصوصیت false است. یعنی در همه مثالهایی که تا کنون زده شده، اگر بزرگ و کوچه بودن حروف آدرس صفحه با مقدار مشخص شده برای پارامتر route متفاوت باشد، روال مسیریابی انجام نخواهد شد. برای رفع این مشکل کافی است مقدار این خصوصیت را true قرار دهیم. برای مثال، مسیر 'controllerAS/' که بالاتر تعریف کرده‌ایم را درنظر بگیرید. اگر www.mySite.com/#/ControllerAS را وارد کنیم، هیچ اتفاقی نخواهد افتاد و در واقع این آدرس با route مشخص شده تطبیق پیدا نمی‌کند. اگر بخواهیم کوچک و بزرگ بودن حروف در نظر گرفته نشود، کافیست به این ترتیب عمل کنیم:
.when('/controllerAS', {
    controller:      'testController',
    controllerAs:    'tCtrl',
    template:        '<div>{{tCtrl.Title}}</div>',
    caseInsensitiveMatch: true
})
resolve: نگاشتی از وابستگی‌هایی که می‌خواهیم به کنترلر تزریق شود. قبلا مفهوم promise توضیح داده شده است . اگر هر یک از این وابستگی‌ها یک promise باشد، مسیریاب تا resolve شدن همه آن‌ها یا reject شدن یکی از آن‌ها منتظر می‌ماند. در صورتی که همه promiseها resolve شوند، رخداد routeChangeSuccess$، و در صورتی که یکی از آن‌ها reject شود رخداد routeChangeError$ اجرا می‌شود. یکی از کاربردهای resolve زمانیست که بخواهید جلوی تغییر محتویات صفحه، پیش از بارگذاری داده‌ای که از سمت سرور درخواست کرده‌اید را بگیرید. 
$routeProvider
    .when('/resolveTest',
        {
            resolve: {
                // این وابستگی بلافاصله بازمی‌گردد
                person: function () {
                    return {
                        name: "Hamid Saberi",
                        email: "Hamid.Saberi@Gmail.com"
                    }
                },
                // بازمی‌گرداند promise این وابستگی یک
                // شدن آن به تاخیر می‌افتد resolve پس تغییر مسیر تا 
                currentDetails: function ($http) {
                    return $http({
                        method: 'Get',
                        url: '/current_details'
                    });
                },
                //می‌توانیم از یک وابستگی در وابستگی دیگر استفاده کنیم
                facebookId: function ($http, currentDetails) {
                    $http({
                        method: 'GET',
                        url: 'http://facebook.com/api/current_user',
                        params: {
                            email: currentDetails.data.emails[0]
                        }
                    })
                },
                // بارگذاری فایل‌های اسکریپت مورد نیاز
                fileDeps:function($q, $rootScope){
                    var deferred = $q.defer();
                    var dependencies =
                    [
                        'controllers/AboutViewController.js',
                        'directives/some-directive.js'
                    ];
 
                    //$Script.js بارگذاری وابستگی‌ها با استفاده از 
                    $script(dependencies, function(){
                        // همه وابستگی‌ها بارگذاری شده اند
                        $rootScope.$apply(function(){
                            deferred.resolve();
                        });
                    });
 
                    return deferred.promise;
                }
            },
            controller: function ($scope, person, currentDetails, facebookId) {
                this.Person = person;
            },
            controllerAs: 'rtCtrl',
            template: '<div>{{rtCtrl.Person.name}}</div>',
            caseInsensitiveMatch: true
        })

مطالب
React component lifecycle
فرض کنید می‌خواهیم داده‌هایی را از دیتابیس بخوانیم و در نهایت درون UI نمایش دهیم. شاید فکر کنید که متد render مکان مناسبی برای اینکار باشد:
render() {
    fetch('https://jsonplaceholder.typicode.com/users')
        .then(response => response.json())
        .then(data => this.setState({ users: data }))
        
    return (
        <ul>
            {this.state.users.map(user => <li key={user.id}>{user.name}</li>)}
        </ul>
    );
}

اما قرار دادن فراخوانی AJAX درون متد render ایده‌ی خوبی نیست؛ زیرا متد render نباید side effectی ایجاد کند. هدف متد render همانطور از نامش پیداست، رندر کردن یک کامپوننت است. در نتیجه اینچنین فراخوانی‌هایی باید درون lifecycle events تعریف شوند که در واقع یکسری رخداد هستند و این امکان را در اختیارمان قرار میدهند که بتوانیم کارهایی از این قبیل را در طول چرخه‌ی حیات یک کامپوننت انجام دهیم. در React به فرآیند ایجاد یک کامپوننت و در نهایت render شدن محتوای آن برای اولین بار، mounting گفته میشود. در این فرآیند سه متد نقش دارند:
  • constructor
  • render
  • componentDidMount
سازنده یک کامپوننت زمانی فراخوانی میشود که React نیاز به ایجاد یک وهله از کامپوننت داشته باشد. در اینجا کامپوننت فرصت این را خواهد داشت تا props مورد نیازش را از والدینش دریافت کند، state dataیش را تعریف کند و یا یکسری کارهای آماده‌سازی را انجام دهد. در مرحله‌ی بعدی، متد render فراخوانی خواهد شد. در اینجا کامپوننت، محتوایی را که قرار است به DOM اضافه شود، در اختیار React قرار میدهد. در نهایت متد componentDidMount فراخوانی خواهد شد و به این معنا است که محتوا، به DOM اضافه شده‌است. در نتیجه متد componentDidMount محل مناسبی برای قرار دادن مثال ابتدای مطلب میباشد. به عنوان مثال در کد زیر هر بار که checkbox را فعال میکنیم، یک وهله از کامپوننت ایجاد خواهد شد. یعنی در واقع هر بار از فاز mounting عبور خواهد کرد.

 

//App.js
import React, { Component } from 'react';
import { Users } from "./Users";

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      toggle: false
    };
  }

  handleChange = () => {
    this.setState({ toggle: !this.state.toggle });
  }

  render() {
    return <div className="container text-center">
      <div className="row p-2">
        <div className="form-check">
          <input type="checkbox" className="form-check-input"
            checked={this.state.toggle}
            onChange={this.handleChange} />
          <label className="form-check-label">Toggle</label>
        </div>
        <div className="row p-2">
          {this.state.toggle && <Users />}
        </div>
      </div>
    </div>
  }
}

//Users
import React, { Component } from 'react';

export class Users extends Component {
    constructor(props) {
        super(props);
        this.state = {
            users: []
        }
    }

    componentDidMount() {
        fetch('https://jsonplaceholder.typicode.com/users')
            .then(response => response.json())
            .then(data => {
                this.setState({ users: data });
                console.log(data)
            })
    }


    render() {
        return (
            <div className="bg-info text-white p-2">
                {this.state.users &&
                    <p>{JSON.stringify(this.state.users, null, 2)}</p>
                }
            </div>
        );
    }
}

Update phase
فرآیند پاسخگویی به تغییرات و طی شدن reconciliation،  به عنوان update phase شناخته میشود. در این فاز متد render جهت دریافت محتوا از کامپوننت، فراخوانی خواهد شد و سپس متد componentDidUpdate بعد از آن فراخوانی میشود:
import React, { Component } from 'react';

export class Users extends Component {
    constructor(props) {
        // as before
    }

    componentDidMount() {
        // as before
    }

    componentDidUpdate() {
        console.log("componentDidUpdate Users Component");
    }

    render() {
        // as before
    }
}
بعد از render اولیه کامپوننت، هر درخواست فراخوانی متد render بعد از آن، منجر به فراخوانی متد componentDidUpdate خواهد شد. 

Unmounting phase
در این فاز متد componentWillUnmount زمانی فراخوانی خواهد شد که کامپوننت در حال حذف از DOM است. طبیعتاً این متد میتواند محل خوبی برای آزاد کردن منابع، بستن اتصالات شبکه و یا متوقف کردن درخواست‌های asynchronous باشد؛ برای نمونه در مثال قبل در حین غیرفعال کردن checkbox فراخوانی خواهد شد:
import React, { Component } from 'react';

export class Users extends Component {
    constructor(props) {
        // as before
    }

    componentDidMount() {
        // as before
    }

    componentDidUpdate() {
        console.log("componentDidUpdate Users Component");
    }

    componentWillUnmount() {
        console.log("componentWillUnmount Users Component");
    }

    render() {
        // as before
    }
}


Effect Hook
چرخه‌ی فوق فقط مختص به کامپوننت‌هایی است که به صورت کلاس تعریف شده‌اند. کامپوننت‌هایی که به صورت تابع هستند، نمیتوانند به روش فوق باشند. با استفاده از effect hook میتوانیم مشابه متدهای componentDidMount, componentDidUpdate, componentWillUnmount را پیاده‌سازی کنیم:
import React, { useState, useEffect } from 'react';

export const Users = () => {
    const [users, setUsers] = useState([]);

    useEffect(() => {
        console.log("Same as componentDidMount")
        fetch('https://jsonplaceholder.typicode.com/users')
            .then(response => response.json())
            .then(data => {
                setUsers({ users: data });
            })
    }, []);

    useEffect(() => {
        console.log("Same as componentDidUpdate")
    });

    useEffect(() => {
        return () => {
            console.log("Same as componentWillUnmount")
        }
    }, []);

    return (
        <div className="bg-info text-white p-2">
            {users &&
                <p>{JSON.stringify(users, null, 2)}</p>
            }
        </div>
    );
}

مطالب
غیرفعال کردن کش مرورگر هنگام استفاده از jQuery Ajax

برای استفاده از قابلیت‌های Ajax کتابخانه jQuery ، شش متد زیر در اختیار برنامه نویس‌ها است:

$.ajax(), load(), $.get(), $.getJSON(), $.getScript(), and $.post()
که در حقیقت 5 مورد آخر ذکر شده صرفا بیان اولین متد ajax فوق به نحوی دیگر می‌باشند و محصور کننده‌ توانایی‌های آن هستند.

برای مثال کد زیر زمان جاری را از سرور دریافت کرده و نتیجه را در سه تکست باکس قرار داده شده در صفحه نمایش می‌دهد.
ابتدا وب سرویس ساده زیر را در نظر بگیرید که زمان شمسی جاری را بر می‌گرداند:

using System;
using System.Globalization;
using System.Web.Script.Services;
using System.Web.Services;

namespace TestJQueryAjax
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[ScriptService]
public class AjaxSrv : WebService
{
public class TimeInfo
{
public string Date { set; get; }
public string Hr { set; get; }
public string Min { set; get; }
}

[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public TimeInfo GetTime()
{
DateTime now = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
PersianCalendar pc = new PersianCalendar();
string today = string.Format("{0}/{1}/{2}",
pc.GetYear(now),
pc.GetMonth(now).ToString("00"),
pc.GetDayOfMonth(now).ToString("00"));
TimeInfo ti = new TimeInfo
{
Date = today,
Hr = DateTime.Now.Hour.ToString("00"),
Min = DateTime.Now.Minute.ToString("00")
};
return ti;
}
}
}
سپس اگر از تابع Ajax کتابخانه jQuery جهت دریافت زمان جاری از وب سرویس استفاده نمائیم، کد صفحه ما به صورت زیر خواهد بود:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestFillCtrls.aspx.cs"
Inherits="TestJQueryAjax.TestFillCtrls" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>

<script src="js/jquery.js" type="text/javascript"></script>

<script type="text/javascript">
function validate() {
$.ajax({
type: "POST",
url: 'AjaxSrv.asmx/GetTime',
data: '{}',
contentType: "application/json; charset=utf-8",
dataType: "json",
success:
function(msg) {
$("#<%=txtDate.ClientID %>").val(msg.d.Date);
$("#<%=txtHr.ClientID %>").val(msg.d.Hr);
$("#<%=txtMin.ClientID %>").val(msg.d.Min);
},
error:
function(XMLHttpRequest, textStatus, errorThrown) {
alert("خطایی رخ داده است");
}
});
//debugger;
}
</script>

</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="txtDate" runat="server" />
<br />
<asp:TextBox ID="txtHr" runat="server" />
<br />
<asp:TextBox ID="txtMin" runat="server" />
<br />
<asp:Button ID="btnGetTime" runat="server" Text="Click here!" UseSubmitBehavior="false"
OnClientClick="validate();return false;" />
</div>
</form>
</body>
</html>
تنها نکته‌ی جدید این اسکریپت، نحوه‌ی استفاده از خروجی JSON وب متد ما است که از نوع TimeInfo تعریف شده است. خروجی نمونه این وب متد به صورت زیر می‌تواند باشد:

{"d":{"__type":"TestJQueryAjax.AjaxSrv+TimeInfo","Date":"1388/07/14","Hr":"12","Min":"59"}}
که نحوه‌ی دسترسی به اجزای آن‌را در متد validate‌ ملاحظه می‌نمائید.

باید به خاطر داشت که برای هر 6 متد Ajax ایی jQuery ، عملیات کش شدن اطلاعات در مرورگر کاربر به صورت پیش فرض فعال است. اما این نکته تنها زمانیکه dataType مورد استفاده از نوع script یا jsonp باشد، صادق نبوده و کش شدن به صورت خودکار غیرفعال می‌گردد.
روش سنتی غیرفعال کردن کش در حین عملیات اجکسی، استفاده از یک کوئری استرینگ متغیر در پایان url درخواستی است. به این صورت مرورگر درخواست صادره را جدید فرض کرده و از کش خود استفاده نمی‌نماید (همین مورد در حالت کش شدن تصاویر هم صادق است).
jQuery نیز همین عملیات را در پشت صحنه انجام داده اما تنظیم آن‌را به نحوی مطلوب‌تری ارائه می‌دهد. یا پارامتر cache را در تعریف متد ajax خود اضافه نموده و مقدار آن را مساوی false قرار دهید و یا جهت تاثیر گذاری بر روی کلیه متدهای مورد استفاده، پیش از استفاده از آن‌ها این تنظیم را مشخص سازید:

$.ajaxSetup({cache: false});

مطالب
پیاده سازی یک تامین کننده MySQL برای ASP.NET Identity
در این مقاله جایگزینی پیاده سازی پیش فرض ASP.NET Identity را بررسی می‌کنیم. در ادامه خواهید خواند:

  • جزئیات نحوه پیاده سازی یک Storage Provider برای ASP.NET Identity
  • تشریح اینترفیس هایی که باید پیاده سازی شوند، و نحوه استفاده از آنها در ASP.NET Identity
  • ایجاد یک دیتابیس MySQL روی Windows Azure
  • نحوه استفاده از یک ابزار کلاینت (MySQL Workbench) برای مدیریت دیتابیس مذکور
  • نحوه جایگزینی پیاده سازی سفارشی با نسخه پیش فرض در یک اپلیکیشن ASP.NET MVC
در انتهای این مقاله یک اپلیکیشن ASP.NET MVC خواهیم داشت که از ASP.NET Identity و تامین کننده سفارشی جدید استفاده می‌کند. دیتابیس اپلیکیشن MySQL خواهد بود و روی Windows Azure میزبانی می‌شود. سورس کد کامل این مثال را هم می‌توانید از این لینک دریافت کنید.


پیاده سازی یک Storage Provider سفارشی برای ASP.NET Identity

ASP.NET Identity سیستم توسعه پذیری است که می‌توانید بخش‌های مختلف آن را جایگزین کنید.در این سیستم بناهای سطح بالایی مانند Managers و Stores وجود دارند.
Managers کلاس‌های سطح بالایی هستند که توسعه دهندگان از آنها برای اجرای عملیات مختلف روی ASP.NET Identity استفاده می‌کنند. مدیریت کننده‌های موجود عبارتند از UserManager و RoleManager. کلاس UserManager برای اجرای عملیات مختلف روی کاربران استفاده می‌شود، مثلا ایجاد کاربر جدید یا حذف آنها. کلاس RoleManager هم برای اجرای عملیات مختلف روی نقش‌ها استفاده می‌شود.

Stores کلاس‌های سطح پایین‌تری هستند که جزئیات پیاده سازی را در بر می‌گیرند، مثلا اینکه موجودیت‌های کاربران و نقش‌ها چگونه باید ذخیره و بازیابی شوند. این کلاس‌ها با مکانیزم ذخیره و بازیابی تلفیق شده اند. مثلا Microsoft.AspNet.Identity.EntityFramework کلاسی با نام UserStore دارد که برای ذخیره و بازیابی User‌ها و داده‌های مربوطه توسط EntityFramework استفاده می‌شود.

Managers از Stores تفکیک شده اند و هیچ وابستگی ای به یکدیگر ندارند. این تفکیک بدین منظور انجام شده که بتوانید مکانیزم ذخیره و بازیابی را جایگزین کنید، بدون اینکه اپلیکیشن شما از کار بیافتد یا نیاز به توسعه بیشتر داشته باشد. کلاس‌های Manager می‌توانند با هر Store ای ارتباط برقرار کنند. از آنجا که شما از API‌های سطح بالای UserManager برای انجام عملیات CRUD روی کاربران استفاده می‌کنید، اگر UserStore را با پیاده سازی دیگری جایگزین کنید، مثلا AzureTable Storage یا MySql، نیازی به بازنویسی اپلیکیشن نیست.

در مثال جاری پیاده سازی پیش فرض Entity Framework را با یک  تامین کننده MySQL جایگزین می‌کنیم.

پیاده سازی کلاس‌های Storage
برای پیاده سازی تامین کننده‌های سفارشی، باید کلاس هایی را پیاده سازی کنید که همتای آنها در Microsoft.AspNet.Identity.EntityFramework وجود دارند:
  • <UserStore<TUser
  • IdentityUser
  • <RoleStore<TRole
  • IdentityRole
پیاده سازی پیش فرض Entity Framework را در تصاویر زیر مشاهده می‌کنید.
Users

Roles

در مخزن پیش فرض ASP.NET Identity EntityFramework کلاس‌های بیشتری برای موجودیت‌ها مشاهده می‌کنید.

  • IdentityUserClaim
  • IdentityUserLogin
  • IdentityUserRole
همانطور که از نام این کلاس‌ها مشخص است، اختیارات، نقش‌ها و اطلاعات ورود کاربران توسط این کلاس‌ها معرفی می‌شوند. در مثال جاری این کلاس‌ها را پیاده سازی نخواهیم کرد، چرا که بارگذاری اینگونه رکوردها از دیتابیس به حافظه برای انجام عملیات پایه (مانند افزودن و حذف اختیارات کاربران) سنگین است. در عوض کلاس‌های backend store اینگونه عملیات را بصورت مستقیم روی دیتابیس اجرا خواهند کرد. بعنوان نمونه متد ()UserStore.GetClaimsAsync را در نظر بگیرید. این متد به نوبه خود متد (userClaimTable.FindByUserId(user.Id را فراخوانی می‌کند که یک کوئری روی جدول مربوطه اجرا می‌کند و لیستی از اختیارات کاربر را بر می‌گرداند.
public Task<IList<Claim>> GetClaimsAsync(IdentityUser user)
{
    ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id);
    return Task.FromResult<IList<Claim>>(identity.Claims.ToList());
}
برای پیاده سازی یک تامین کننده سفارشی MySQL مراحل زیر را دنبال کنید.
1. کلاس کاربر را ایجاد کنید، که اینترفیس IUser را پیاده سازی می‌کند.
public class IdentityUser : IUser
{
    public IdentityUser(){...}

    public IdentityUser(string userName) (){...}

    public string Id { get; set; }

    public string UserName { get; set; }

    public string PasswordHash { get; set; }

    public string SecurityStamp { get; set; }
}
2. کلاس User Store را ایجاد کنید، که اینترفیس‌های IUserStore, IUserClaimStore, IUserLoginStore, IUserRoleStore و IUserPasswordStore را پیاده سازی می‌کند. توجه کنید که تنها اینترفیس IUserStore را باید پیاده سازی کنید، مگر آنکه بخواهید از امکاناتی که دیگر اینترفیس‌ها ارائه می‌کنند هم استفاده کنید.
public class UserStore : IUserStore<IdentityUser>,
                         IUserClaimStore<IdentityUser>,
                         IUserLoginStore<IdentityUser>,
                         IUserRoleStore<IdentityUser>,
                         IUserPasswordStore<IdentityUser>
{
    public UserStore(){...}

    public Task CreateAsync(IdentityUser user){...}

    public Task<IdentityUser> FindByIdAsync(string userId){...}   
...
}
3. کلاس Role را ایجاد کنید که اینترفیس IRole را پیاده سازی می‌کند.
public class IdentityRole : IRole
{
    public IdentityRole(){...}

    public IdentityRole(string roleName) (){...}

    public string Id { get; set; }

    public string Name { get; set; }
}
4. کلاس Role Store را ایجاد کنید که اینترفیس IRoleStore را پیاده سازی می‌کند. توجه داشته باشید که پیاده سازی این مخزن اختیاری است و در صورتی لازم است که بخواهید از نقش‌ها در سیستم خود استفاده کنید.
public class RoleStore : IRoleStore<IdentityRole>                        
{
    public RoleStore(){...}

    public Task CreateAsync(IdentityRole role){...}

    public Task<IdentityRole> FindByIdAsync(string roleId){...}   
....
}
کلاس‌های بیشتری هم وجود دارند که مختص پیاده سازی مثال جاری هستند.
  • MySQLDatabase: این کلاس اتصال دیتابیس MySql و کوئری‌ها را کپسوله می‌کند. کلاس‌های UserStore و RoleStore توسط نمونه ای از این کلاس وهله سازی می‌شوند.
  • RoleTable: این کلاس جدول Roles و عملیات CRUD مربوط به آن را کپسوله می‌کند.
  • UserClaimsTable: این کلاس جدول UserClaims و عملیات CRUD مربوط به آن را کپسوله می‌کند.
  • UserLoginsTable: این کلاس جدول UserLogins و عملیات CRUD مربوط به آن را کپسوله می‌کند.
  • UserRolesTable: این کلاس جدول UserRoles و عملیات CRUD مربوطه به آن را کپسوله می‌کند.
  • UserTable: این کلاس جدول Users و عملیات CRUD مربوط به آن را کپسوله می‌کند.

ایجاد یک دیتابیس MySQL روی Windows Azure

1. به پورتال مدیریتی Windows Azure وارد شوید.
2. در پایین صفحه روی NEW+ کلیک کنید و گزینه STORE را انتخاب نمایید.

در ویزارد Choose Add-on به سمت پایین اسکرول کنید و گزینه ClearDB MySQL Database را انتخاب کنید. سپس به مرحله بعد بروید.

4. راهکار Free بصورت پیش فرض انتخاب شده، همین گزینه را انتخاب کنید و نام دیتابیس را به IdentityMySQLDatabase تغییر دهید. نزدیک‌ترین ناحیه (region) به خود را انتخاب کنید و به مرحله بعد بروید.

5. روی علامت checkmark کلیک کنید تا دیتابیس شما ایجاد شود. پس از آنکه دیتابیس شما ساخته شد می‌توانید از قسمت ADD-ONS آن را مدیریت کنید.

6. همانطور که در تصویر بالا می‌بینید، می‌توانید اطلاعات اتصال دیتابیس (connection info) را از پایین صفحه دریافت کنید.

7. اطلاعات اتصال را با کلیک کردن روی دکمه مجاور کپی کنید تا بعدا در اپلیکیشن MVC خود از آن استفاده کنیم.


ایجاد جداول ASP.NET Identity در یک دیتابیس MySQL

ابتدا ابزار MySQL Workbench را نصب کنید.
1. ابزار مذکور را از اینجا دانلود کنید.
2. هنگام نصب، گزینه Setup Type: Custom را انتخاب کنید.
3. در قسمت انتخاب قابلیت ها، گزینه‌های Applications و MySQLWorkbench را انتخاب کنید و مراحل نصب را به اتمام برسانید.
4. اپلیکیشن را اجرا کرده و روی MySQLConnection کلیک کنید تا رشته اتصال جدیدی تعریف کنید. رشته اتصالی که در مراحل قبل از Azure MySQL Database کپی کردید را اینجا استفاده کنید. بعنوان مثال:
 Connection Name: AzureDB; Host Name: us-cdbr-azure-west-b.cleardb.com; Username: <username>; Password: <password>; Default Schema: IdentityMySQLDatabase 
5. پس از برقراری ارتباط با دیتابیس، یک برگ Query جدید باز کنید. فرامین زیر را برای ایجاد جداول مورد نیاز کپی کنید.
CREATE TABLE `IdentityMySQLDatabase`.`users` (
  `Id` VARCHAR(45) NOT NULL,
  `UserName` VARCHAR(45) NULL,
  `PasswordHash` VARCHAR(100) NULL,
  `SecurityStamp` VARCHAR(45) NULL,
  PRIMARY KEY (`id`));

CREATE TABLE `IdentityMySQLDatabase`.`roles` (
  `Id` VARCHAR(45) NOT NULL,
  `Name` VARCHAR(45) NULL,
  PRIMARY KEY (`Id`));

CREATE TABLE `IdentityMySQLDatabase`.`userclaims` (
  `Id` INT NOT NULL AUTO_INCREMENT,
  `UserId` VARCHAR(45) NULL,
  `ClaimType` VARCHAR(100) NULL,
  `ClaimValue` VARCHAR(100) NULL,
  PRIMARY KEY (`Id`),
  FOREIGN KEY (`UserId`)
    REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade);

CREATE TABLE `IdentityMySQLDatabase`.`userlogins` (
  `UserId` VARCHAR(45) NOT NULL,
  `ProviderKey` VARCHAR(100) NULL,
  `LoginProvider` VARCHAR(100) NULL,
  FOREIGN KEY (`UserId`)
    REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) on delete cascade);

CREATE TABLE `IdentityMySQLDatabase`.`userroles` (
  `UserId` VARCHAR(45) NOT NULL,
  `RoleId` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`UserId`, `RoleId`),
  FOREIGN KEY (`UserId`)
    REFERENCES `IdentityMySQLDatabase`.`users` (`Id`) 
on delete cascade
on update cascade,
  FOREIGN KEY (`RoleId`)
    REFERENCES `IdentityMySQLDatabase`.`roles` (`Id`)
on delete cascade
on update cascade);
6. حالا تمام جداول لازم برای ASP.NET Identity را در اختیار دارید، دیتابیس ما MySQL است و روی Windows Azure میزبانی شده.


ایجاد یک اپلیکیشن ASP.NET MVC و پیکربندی آن برای استفاده از MySQL Provider

2. در گوشه سمت راست پایین صفحه روی دکمه Download Zip کلیک کنید تا کل پروژه را دریافت کنید.
3. محتوای فایل دریافتی را در یک پوشه محلی استخراج کنید.
4. پروژه AspNet.Identity.MySQL را باز کرده و آن را کامپایل (build) کنید.
5. روی نام پروژه کلیک راست کنید و گزینه Add, New Project را انتخاب نمایید. پروژه جدیدی از نوع ASP.NET Web Application بسازید و نام آن را به IdentityMySQLDemo تغییر دهید.

6. در پنجره New ASP.NET Project قالب MVC را انتخاب کنید و تنظیمات پیش فرض را بپذیرید.

7. در پنجره Solution Explorer روی پروژه IdentityMySQLDemo کلیک راست کرده و Manage NuGet Packages را انتخاب کنید. در قسمت جستجوی دیالوگ باز شده عبارت "Identity.EntityFramework" را وارد کنید. در لیست نتایج این پکیج را انتخاب کرده و آن را حذف (Uninstall) کنید. پیغامی مبنی بر حذف وابستگی‌ها باید دریافت کنید که مربوط به پکیج EntityFramework است، گزینه Yes را انتخاب کنید. از آنجا که کاری با پیاده سازی فرض نخواهیم داشت، این پکیج‌ها را حذف می‌کنیم.

8. روی پروژه IdentityMySQLDemo کلیک راست کرده و Add, Reference, Solution, Projects را انتخاب کنید. در دیالوگ باز شده پروژه AspNet.Identity.MySQL را انتخاب کرده و OK کنید.

9. در پروژه IdentityMySQLDemo پوشه Models را پیدا کرده و کلاس IdentityModels.cs را حذف کنید.

10. در پروژه IdentityMySQLDemo تمام ارجاعات ";using Microsoft.AspNet.Identity.EntityFramework" را با ";using AspNet.Identity.MySQL" جایگزین کنید.

11. در پروژه IdentityMySQLDemo تمام ارجاعات به کلاس "ApplicationUser" را با "IdentityUser" جایگزین کنید.

12. کنترلر Account را باز کنید و متد سازنده آنرا مطابق لیست زیر تغییر دهید.

public AccountController() : this(new UserManager<IdentityUser>(new UserStore(new MySQLDatabase())))
{

}

13. فایل web.config را باز کنید و رشته اتصال DefaultConnection را مطابق لیست زیر تغییر دهید.

<add name="DefaultConnection" connectionString="Database=IdentityMySQLDatabase;Data Source=<DataSource>;User Id=<UserID>;Password=<Password>" providerName="MySql.Data.MySqlClient" />

مقادیر <DataSource>, <UserId> و <Password> را با اطلاعات دیتابیس خود جایگزین کنید.


اجرای اپلیکیشن و اتصال به دیتابیس MySQL

1. روی پروژه IdentityMySQLDemo کلیک راست کرده و Set as Startup Project را انتخاب کنید.
2. اپلیکیشن را با Ctrl + F5 کامپایل و اجرا کنید.
3. در بالای صفحه روی Register کلیک کنید.
4. حساب کاربری جدیدی بسازید.

5. در این مرحله کاربر جدید باید ایجاد شده و وارد سایت شود.

6. به ابزار MySQL Workbench بروید و محتوای جداول IdentityMySQLDatabase را بررسی کنید. جدول users را باز کنید و اطلاعات کاربر جدید را بررسی نمایید.

برای ساده نگاه داشتن این مقاله از بررسی تمام کدهای لازم خودداری شده، اما اگر مراحل را دنبال کنید و سورس کد نمونه را دریافت و بررسی کنید خواهید دید که پیاده سازی تامین کنندگان سفارشی برای ASP.NET Identity کار نسبتا ساده ای است.