مطالب
برنامه نویسی پیشرفته JavaScript - قسمت 1 - توابع

در این مجموعه مقالات، به بررسی و آموزش برنامه نویسی شیء گرا در جاوا اسکریپت می‌پردازیم. در طول آموزش، فرض را بر این قرار دادیم که شما به عنوان خواننده‌ی این مقاله، با مبانی جاوا اسکریپت آشنا می‌باشید و حداقل چند قطعه کد مفید را با جاوا اسکریپت نوشته‌اید. همچنین کمی هم با مباحث شیء گرایی آشنا می‌باشید.

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

با چند مثال ساده و بصورت تقریبا مبتدی مسائل پایه را تشریح نموده و آرام آرام مباحث حرفه ای را آغاز خواهیم کرد. قرار است در پایان این آموزش‌ها به بررسی الگوهای موجود در جاوا اسکریپت نیز بپردازم که مجموعه مقالات مربوط به الگوها را در قالب مجموعه مقالاتی مجزا ارائه می‌نمایم.

در تهیه‌ی این مجموعه مقالات از منابع زیر استفاده شده است:

1.  Pro JavaScript Techniques  (John Resig ، خالق JQuery  – فصول 2 و 3)

2.  Professional JavaScript for Web Developers (Third Edition)  (Nicholas C. Zakas  – فصول 3، 4، 5، 6 و 7)

3.  Object-Oriented JavaScript  (Stoyan Stefanov  – فصول 3، 4، 5، 6 و 8)

4.  و تجربه‌ی ناچیز اینجانب


توابع  (Functions)

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

function <functionName> ([arg0, arg1, …, argN]) {
<statements>
}

به عنوان مثال:

function sayHello(name, message) {
   alert("Hello " + name + ", " + message);
}

این تابع شامل دو آرگومان ورودی است که می‌تواند به صورت زیر فراخوانی شود:

sayHello("Meysam", "welcome to site");

خروجی این تابع  “Hello Meysam, welcome to site”  می باشد که به صورت یک پنجره‌ی پیغام، نمایش می‌یابد. تابع فوق هیچ مقداری را به عنوان خروجی به برنامه‌ی اصلی یا محل فراخوانی خود بر نمی‌گرداند. اگر بخواهیم توسط تابع مقداری را برگردانیم می‌توانیم از دستور return  برای این منظور استفاده نماییم. به مثال زیر توجه کنید:

function sum(num1, num2) {
   return num1 + num2;
}

تابع  sum  دو عدد را به عنوان آرگومان ورودی دریافت نموده و حاصل جمع آن‌ها را توسط دستور  return  به عنوان خروجی بر می‌گرداند. تابع فوق را می‌توان به صورت زیر فراخوانی نمود:

alert(sum(10, 20));

خروجی :

30

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

function sum(num1, num2) {
   return num1 + num2;
   alert("Hello");
}

در مثال فوق دستور  alert به هیچ عنوان اجرا نخواهد شد؛ زیرا تابع با رسیدن به دستور return خاتمه می‌یابد. یک تابع می‌تواند شامل بیش از یک دستور return باشد.

function diff(num1,num2) {
   if (num1 > num2)
     return num1 - num2;
   else
     return num2 - num1;
}

تابع فوق اختلاف دو عدد را بدست می‌آورد و اگر عدد اول بزرگتر باشد، عدد دوم را از عدد اول تفریق می‌کند؛ در غیر اینصورت عدد اول را از عدد دوم تفریق می‌کند و به عنوان خروجی بر می‌گرداند.  نکته‌ی دیگری که لازم است بدانید این است که دستور  return  می تواند هیچ مقداری را بر نگرداند و به تنهایی بکار گرفته شود. در این صورت دقیقا بعد از دستور  return  سمی کالن  (;)  قرار می‌دهیم.

function checkNumber(num) {
   if (isNaN(num)) {
     alert("Not a number");
     return;
   }
   alert(num+" is a number");
}

تابع فوق یک ورودی را دریافت می‌نماید و در صورتی که آرگومان ورودی عدد نباشد پیغام  “Not a number”  را نمایش می‌دهد و از تابع خارج می‌شود. در صورتی که آرگومان ورودی یک عدد باشد، پیغام دوم را نمایش می‌دهد.

توجه:

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

function sum(num1, num2) {
   if (isNaN(num1) || isNaN(num2))
     return; // بهتر است حداقل مقدار 0 برگردانده شود
   return num1 + num2;
}


کار با آرگومان ها

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

با توجه به قابلیتی که در مورد آرگومانها ذکر شد و به دلیل عدم مدیریت نوع و تعداد آرگومان‌های ارسالی، مطمئنا جهت جلوگیری از بروز خطا در توابع، باید تعداد و نوع آرگومان‌های ارسالی بررسی و مدیریت شوند. همچنین در هر تابع، آرایه‌ای به نام arguments  به صورت توکار تعبیه شده است که مدیریت آرگومان‌ها را تسهیل می‌بخشد. به مثال زیر توجه کنید:

function sayHello() {
   alert("Hello " + arguments[0] + ", " + arguments[1]);
}

sayHello("Meysam", "welcome to site");

خروجی :

"Hello Meysam, welcome to site"

تابع فوق هیچ آرگومان ورودی ندارد ولی با دو آرگومان ورودی فراخوانی شده است. در داخل تابع توسط آرایه arguments  به آرگومانهای ارسالی دسترسی پیدا کردیم. حال به مثال زیر توجه کنید:

function sum() {
   var s = 0;
   for (var i = 0; i < arguments.length; i++)
     s += arguments[i];
   return s;
}

alert(sum());
alert(sum(10, 20, 30, 40, 50));
alert(sum(10));

خروجی :

0

150

10

در تابع فوق هیچ آرگومان ورودی وجود ندارد ولی این تابع را با 0، 5 و 1 آرگومان ورودی فراخوانی نمودیم. این تابع مجموع چند عدد را محاسبه و بر می‌گرداند و می‌تواند به تعداد نامحدودی عدد دریافت نماید. البته بهتر است نوع آرگومانهای ارسالی نیز بررسی شوند تا خطایی در محاسبات رخ ندهد. همچنین بجای حلقه for  از حلقه for…in  استفاده خواهم کرد.

function sum() {
   var s = 0;
   for (var i in arguments) {
     if (isNaN(arguments[i]))
       continue;
    s += arguments[i];
   }
   return s;
}

alert(sum());
alert(sum(10, 20, "a", 40, 50));
alert(sum(10));

خروجی :

0

120

10

اگر دقت کرده باشید در فراخوانی دوم، رشته  “a”  به تابع ارسال شده است و چون آرگومانهای نامعتبر را مدیریت نموده‌ایم، خطایی در خروجی رخ نمی‌دهد. به مثال زیر نیز توجه نمایید:

function sum(a,b,c) {
   return a + b + c;
}

alert(sum(10, 20, 30));
alert(sum(10, 20));
alert(sum());

خروجی :

60

NaN

NaN

تابع فوق دارای 3 آرگومان ورودی می‌باشد؛ ولی ما این تابع را با 2 و 0 آرگومان ورودی فراخوانی نمودیم که خروجی نامناسبی را تولید نموده است. برای رفع این مشکل و معتبر سازی آرگومان‌های ارسالی می‌توانیم به صورت زیر عمل نماییم:

function sum(a, b, c) {
   if (isNaN(a)) a = 0;
   if (isNaN(b)) b = 0;
   if (isNaN(c)) c = 0;
   return a + b + c;
}

alert(sum(10, 20, 30));
alert(sum(10, 20));
alert(sum());

خروجی :

60

30

0

در تابع، قبل از انجام عمل محاسبه، بررسی کردیم که آرگومانهای ارسالی مقدار دهی شده باشند. در صورت عدم مقداردهی و یا مقداردهی نامناسب، آرگومان ارسالی را با صفر مقداردهی می‌نماید. اگر آرگومان مورد نظر به تابع ارسال نشود، مقدار پیش فرض آن undefined می‌باشد.

با توجه به مسائل مطرح شده در مورد توابع، این روش استفاده و کاربرد توابع، جزء معایب توابع در جاوا اسکریپت محسوب نمی‌شود. این تکنیک استفاده از توابع، موجب افزایش انعطاف پذیری توابع و آزادی عمل برنامه نویس می‌گردد که لذت بیشتری را به برنامه نویسی می‌دهد.

توجه:

به آرگومانهایی که در تابع دارای نام می‌باشند و یا به عبارتی، آرگومانهایی که نام آنها در تابع ذکر می‌شود، Named Arguments  یا آرگومانهای نامی (اسمی و یا نامدار) می‌گویند. مثل آرگومان های a ، b  وc  در تابع sum


عدم پشتیبانی از سربارگذاری یا  Overloading

در زبان‌های برنامه نویسی شیء گرا، امکان تعریف توابع هم نام وجود دارد؛ به شرطی که امضای این توابع با هم متفاوت باشند. منظور از امضاء، تعداد و نوع آرگومان‌های ورودی می‌باشد. از آنجاییکه در سیستم داخلی جاوا اسکریپت، آرگومانها بصورت یک آرایه ارسال می‌شوند، بنابراین امضاء برای توابع مفهومی ندارد؛ پس نمی‌توانیم توابع هم نام یا overloading داشته باشیم.

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

function calc(num1,num2) {
   return num1 + num2;
}

function calc(num1,num2) {
   return num1 - num2;
}

alert(calc(200,100));

خروجی :

100

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

اشتراک‌ها
وبینار تولید واسط کاربری Future-proof با استفاده از Web Components

دوستان، همکاران سیستم یک وبینار رایگان در مورد Web Components برگزار می‌کند که من ارائه می‌کنم 🙂. اگر علاقه‌مند بودید ثبت نام کنید.


Web Components مجموعه‌ای از تکنولوژی‌هایی هستند که امکان ساخت المان‌های سفارشی استاندارد با قابلیت استفاده‌ی مجدد و به همراه کپسوله‌سازی ساختار، استایل و عملکرد را در اختیار ما قرار می‌دهند. این المان‌های استاندارد، وابستگی به فریم‌ورک خاصی ندارند.
در این وبینار به بررسی مسائلی خواهیم پرداخت که با استفاده از Web Components قابل حل هستند و در ادامه روش تولید آن‌ها با استفاده از JavaScript/HTML/CSS ارائه خواهد شد. در نهایت ابزارهایی مانند StencilJS و Polymer برای توسعه ساده‌تر و بهینه‌تر این کامپوننت‌ها معرفی می‌شوند.


محورهای اصلی این وبینار: 
  • چرا Web Components؟
  • وضعیت پشتیبانی و به کار گیری Web Components
  • توسعه چند کامپوننت نمونه با استفاده از Web Components
  • بررسی ابزارهای جانبی
  • استفاده به شکل Framework agnostic
 📌تاریخ برگزاری: شنبه 20 دی، ساعت 18:30 تا 20:00 
وبینار تولید واسط کاربری Future-proof با استفاده از Web Components
مطالب
نگاهی به هویت سنجی کاربران در ASP.NET MVC 5
در مقاله پیش رو، سعی شده‌است به شکلی تقریبا عملی، کلیاتی در مورد Authentication در MVC5 توضیح داده شود. هدف روشن شدن ابهامات اولیه در هویت سنجی MVC5 و حل شدن مشکلات اولیه برای ایجاد یک پروژه است.
در MVC 4 برای دسترسی به جداول مرتبط با اعتبار سنجی (مثلا لیست کاربران) مجبور به استفاده از متدهای از پیش تعریف شده‌ی رفرنس‌هایی که برای آن نوع اعتبار سنجی وجود داشت، بودیم. راه حلی نیز برای دسترسی بهتر وجود داشت و آن هم ساختن مدل‌های مشابه آن جدول‌ها و اضافه کردن چند خط کد به برنامه بود. با اینکار دسترسی ساده به Roles و Users برای تغییر و اضافه کردن محتوای آنها ممکن می‌شد. در لینک زیر توضیحاتی در مورد روش اینکار وجود دارد.
 در MVC5 داستان کمی فرق کرده است. برای درک موضوع پروژه ای بسازید و حالت پیش فرض آن را تغییر ندهید و آن را اجرا کنید و ثبت نام را انجام دهید، بلافاصله تصویر زیر در دیتابیس نمایان خواهد شد.

دقت کنید بعد از ایجاد پروژه در MVC5 دو پکیج بصورت اتوماتیک از طریق Nuget به پروژه شما اضافه میشود:
 Microsoft.AspNet.Identity.Core
Microsoft.AspNet.Identity.EntityFrameWork
عامل اصلی تغییرات جدید، همین دو پکیج فوق است.
 اولین پکیج شامل اینترفیس‌های IUser و IRole است که شامل فیلدهای مرتبط با این دو می‌باشد. همچنین اینترفیسی به نام IUserStore وجود دارد که چندین متد داشته و وظیفه اصلی هر نوع اضافه و حذف کردن یا تغییر در کاربران، بر دوش آن است.
 دومین پکیج هم وظیفه پیاده سازی آن‌چیزی را دارد که در پکیج اول معرفی شده است. کلاس‌های موجود در این پکیج ابزارهایی برای ارتباط EntityFramework با دیتابیس هستند.
اما از مقدمات فوق که بگذریم برای درک بهتر رفتار با دیتابیس یک مثال را پیاده سازی خواهیم کرد.

 فرض کنید میخواهیم چنین ارتباطی را بین سه جدول در دیتابیس برقرار کنیم، فقط به منظور یادآوری، توجه کنید که جدول ASPNetUsers جدولی است که به شکل اتوماتیک پیش از این تولید شد و ما قرار است به کمک یک جدول واسط (AuthorProduct) آن را به جدول Product مرتبط سازیم تا مشخص شود هر کتاب (به عنوان محصول) به کدام کاربر (به عنوان نویسنده) مرتبط است.
 بعد از اینکه مدل‌های مربوط به برنامه خود را ساختیم، اولا نیاز به ساخت کلاس کانتکست نداریم چون خود MVC5 کلاس کانتکست را دارد؛ ثانیا نیاز به ایجاد مدل برای جداول اعتبارسنجی نیست، چون کلاسی برای فیلدهای اضافی ما که علاقمندیم به جدول Users اضافه شود، از پیش تعیین گردیده است.

دو کلاسی که با فلش علامت گذاری شده اند، تنها فایل‌های موجود در پوشه مدل، بعد از ایجاد یک پروژه هستند. فایل IdentityModel را به عنوان فایل کانتکست خواهیم شناخت (چون یکی از کلاسهایش Context است). همانطور که پیش از این گفتیم با وجود این فایل نیازی به ایجاد یک کلاس مشتق شده از DbContext نیست. همانطور که در کد زیر میبینید این فایل دارای دو کلاس است:
namespace MyShop.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
    }

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection")
        {
        }
      
}
کلاس اول همان کلاسی است که اگر به آن پراپرتی اضافه کنیم، بطور اتوماتیک آن پراپرتی به جدول ASPNetUsers در دیتابیس اضافه می‌شود و دیگر نگران فیلدهای نداشته‌ی جدول کاربران ASP.NET نخواهیم بود. مثلا در کد زیر چند عنوان به این جدول اضافه کرده ایم.
namespace MyShop.Models
{
    // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
    public class ApplicationUser : IdentityUser
    {
        [Display(Name = "نام انگلیسی")]
        public string EnglishName { get; set; }

        [Display(Name = "نام سیستمی")]
        public string NameInSystem { get; set; }

        [Display(Name = "نام فارسی")]
        public string PersianName { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "آدرس ایمیل")]
        public string Email { get; set; }
     }
}
کلاس دوم نیز محل معرفی مدلها به منظور ایجاد در دیتابیس است. به ازای هر مدل یک جدول در دیتابیس خواهیم داشت. مثلا در شکل فوق سه پراپرتی به جدول کاربران اضافه میشود. دقت داشته باشید با اینکه هیچ مدلی برای جدول کاربران نساخته ایم اما کلاس ApplicatioUsers کلاسی است که به ما امکان دسترسی به مقادیر این جدول را می‌دهد(دسترسی به معنای اضافه و حذف وتغییر مقادیر این جدول است) (در MVC4 به کمک کلاس membership کارهای مشابهی انجام میدادیم)
 در ساختن مدل هایمان نیز اگر نیاز به ارتباط با جدول کاربران باشد، از همین کلاس فوق استفاده میکنیم. کلاس واسط(مدل واسط) بین AspNetUsers و Product در کد زیر زیر نشان داده شده است :
namespace MyShop.Models
{
    public class AuthorProduct
    {
        [Key]
        public int AuthorProductId { get; set; }
       /* public int UserId { get; set; }*/

        [Display(Name = "User")]
        public string ApplicationUserId { get; set; }

        public int ProductID { get; set; }

        public virtual Product Product { get; set; }
    
        public virtual ApplicationUser ApplicationUser { get; set; }
    }
}
همانطور که مشاهده میکنید، به راحتی ارتباط را برقرار کردیم و برای برقراری این ارتباط از کلاس ApplicationUser استفاده کردیم. پراپرتی ApplicationUserId نیز فیلد ارتباطی ما با جدول کاربران است. جدول product هم نکته خاصی ندارد و به شکل زیر مدل خواهد شد.
namespace MyShop.Models
{
    [DisplayName("محصول")]
    [DisplayPluralName("محصولات")]
    public class Product
    {
        [Key]
        public int ProductID { get; set; }

        [Display(Name = "گروه محصول")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public int ProductGroupID { get; set; }

        [Display(Name = "مدت زمان")]
        public string Duration { get; set; }

   
        [Display(Name = "نام تهیه کننده")]
        public string Producer { get; set; }

        [Display(Name = "عنوان محصول")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public string ProductTitle { get; set; }

        [StringLength(200)]
        [Display(Name = "کلید واژه")]
        public string MetaKeyword { get; set; }

        [StringLength(200)]
        [Display(Name = "توضیح")]
        public string MetaDescription { get; set; }

        [Display(Name = "شرح محصول")]
        [UIHint("RichText")]
        [AllowHtml]
        public string ProductDescription { get; set; }

        [Display(Name = "قیمت محصول")]
        [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:#,0 ریال}")]
        [UIHint("Integer")]
        [Required(ErrorMessage = "لطفا {0} را وارد کنید")]
        public int ProductPrice { get; set; }
        [Display(Name = "تاریخ ثبت محصول")]

        public DateTime? RegisterDate { get; set; }

    }
}
به این ترتیب هم ارتباطات را برقرار کرده‌ایم و هم از ساختن یک UserProfile اضافی خلاص شدیم.
برای پر کردن مقادیر اولیه نیز به راحتی از seed موجود در Configuration.cs مربوط به migration استفاده میکنیم. نمونه‌ی اینکار در کد زیر موجود است:
protected override void Seed(MyShop.Models.ApplicationDbContext context)
        {
            context.Users.AddOrUpdate(u => u.Id,
                      new ApplicationUser() {  Id = "1",EnglishName = "MortezaDalil", PersianName = "مرتضی دلیل", UserDescription = "توضیح در مورد مرتضی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "2", EnglishName = "MarhamatZeinali", PersianName = "محسن احمدی", UserDescription = "توضیح در مورد محسن", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "3", EnglishName = "MahdiMilani", PersianName = "مهدی محمدی", UserDescription = "توضیح در مورد مهدی", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" },
                            new ApplicationUser() { Id = "4", EnglishName = "Babak", PersianName = "بابک", UserDescription = "کاربر معمولی بدون توضیح", Email = "mm@mm.com", Phone = "2323", Address = "test", NationalCode = "2222222222", ZipCode = "2222222222" }
                     
                        );


            context.AuthorProducts.AddOrUpdate(u => u.AuthorProductId,
              new AuthorProduct() { AuthorProductId = 1, ProductID = 1, ApplicationUserId = "2" },
              new AuthorProduct() { AuthorProductId = 2, ProductID = 2, ApplicationUserId = "1" },
              new AuthorProduct() { AuthorProductId = 3, ProductID = 3, ApplicationUserId = "3" }

          );
 می‌توانیم از کلاس‌های خود Identity برای انجام روش فوق استفاده کنیم؛ فرض کنید بخواهیم یک کاربر به نام admin و با نقش admin به سیستم اضافه کنیم.
            if (!context.Users.Where(u => u.UserName == "Admin").Any())
            {
                var roleStore = new RoleStore<IdentityRole>(context);
                var rolemanager = new RoleManager<IdentityRole>(roleStore);

                var userstore = new UserStore<ApplicationUser>(context);
                var usermanager = new UserManager<ApplicationUser>(userstore);
                
                var user = new ApplicationUser {UserName = "Admin"};
                
                usermanager.Create(user, "121212");
                rolemanager.Create(new IdentityRole {Name = "admin"});
                
                usermanager.AddToRole(user.Id, "admin");
            }
   در عبارت شرطی موجود کد فوق، ابتدا چک کردیم که چنین یوزری در دیتابیس نباشد، سپس از کلاس RoleStore که پیاده سازی شده‌ی اینترفیس IRoleStore است استفاده کردیم. سازنده این کلاس به کانتکست نیاز دارد؛ پس به آن context را به عنوان ورودی می‌دهیم. در خط بعد، کلاس rolemanager را داریم که بخشی از پکیج Core است و پیش از این درباره اش توضیح دادیم ( یکی از دو رفرنسی که خوبخود به پروژه اضافه میشوند) و از ویژگی‌های Identity است. به آن آبجکتی که از RoleStore ساختیم را پاس میدهیم و خود کلاس میداند چه چیز را کجا ذخیره کند.
برای ایجاد کاربر نیز همین روند را انجام می‌دهیم. سپس یک آبجکت به نام user را از روی کلاس ApplicationUser میسازیم. برای آن پسورد 121212 سِت میکنیم و نقش ادمین را به آن نسبت میدهیم. این روش قابل تسری به تمامی بخش‌های برنامه شماست. میتوانید عملیات کنترل و مدیریت اکانت را نیز به همین شکل انجام دهید. ساخت کاربر و لاگین کردن یا مدیریت پسورد نیز به همین شکل قابل انجام است.
 بعد از آپدیت دیتابیس تغییرات را مشاهده خواهیم کرد. 
مطالب
فراخوانی سرویس های WCF به صورت Async
هنگام تولید و توسعه سیستم‌های مبتنی بر WCF حتما نیاز به سرویس هایی داریم که متد‌ها را به صورت Async اجرا کنند. در دات نت 4.5 از Async&Await استفاده می‌کنیم(^). ولی در پروژه هایی که تحت دات نت 4 هستند این امکان وجود ندارد(البته می‌تونید Async&Await CTP رو برای دات نت 4 هم نصب کنید(^ )). فرض کنید پروژه ای داریم تحت دات نت 3.5 یا 4 و قصد داریم یکی از متد‌های سرویس WCF آن را به صورت Async پیاده سازی کنیم. ساده‌ترین روش این است که هنگام Add Service Reference از پنجره Advanced  به صورت زیر عمل کنیم:


مهم‌ترین عیب این روش این است که در این حالت تمام متد‌های این سرویس رو هم به صورت Sync و هم به صورت Async تولید می‌کنه در حالی که ما فقط نیاز به یک متد Async داریم.

 در این پست قصد دارم پیاده سازی این متد رو بدون استفاده از Async&Await و Code Generation توکار دات نت شرح بدم که با دات نت 3.5 هم سازگار است.

ابتدا یک پروژه از نوع WCF Service Application ایجاد کنید.
یک ClassLibrary جدید به نام Model بسازید و کلاس زیر را به عنوان مدل در آن قرار دهید.(این اسمبلی باید هم به پروژه‌های کلاینت و هم به پروژه‌های سرور رفرنس داده شود)
    [DataContract]
    public class Book
    {
        [DataMember]
        public int Code { get; set; }

        [DataMember]
        public string Title { get; set; }

        [DataMember]
        public string Author { get; set; }
    }
حال  پیاده سازی سرویس و Contract مربوطه را شروع می‌کنیم.
#Class Library به نام Contract بسازید. قصد داریم از این لایه به عنوان قرارداد‌های سمت کلاینت و سرور استفاده کنیم. اینترفیس زیر را به عنوان BookContract در آن بسازید.
   [ServiceContract]
    public interface IBookService
    {
        [OperationContract( AsyncPattern = true )]
        IAsyncResult BeginGetAllBook( AsyncCallback callback, object state );

        IEnumerable<Book> EndGetAllBook( IAsyncResult asyncResult ); 
    }
برای پیاده سازی متد‌های Async به این روش باید دو متد داشته باشیم. یکی به عنوان شروع عملیات و دیگری اتمام. دقت کنید نام گذاری به صورت Begin و End کاملا اختیاری است و برای خوانایی بهتر از این روش نام گذاری استفاده می‌کنم. متدی که به عنوان شروع عملیات استفاده می‌شود باید حتما OperationContractAttribute رو داشته باشد و مقدار خاصیت AsyncPattern اون هم true باشد. همان طور که می‌بیند این متد دارای 2 آرگومان وروردی است. یکی از نوع AsyncCallback و دیگری از نوع object. تمام متد‌های Async به این روش باید این دو آرگومان ورودی را حتما داشته باشند. خروجی این متد حتما باید از نوع IAsyncResult باشد. متد دوم که به عنوان اتمام عملیات استفاده می‌شود نباید OperationContractAttribute را داشته باشد. ورودی اون هم فقط یک آرگومان از نوع IAsyncResult است. خروجی اون هم هر نوعی که سمت کلاینت احتیاج دارید می‌تونه باشه . در صورت عدم رعایت نکات فوق، هنگام ساخت  ChannelFactory یا خطا روبرو خواهید شد. اگر نیاز به پارامتر دیگری هم داشتید باید آن‌ها را قبل از این دو پارامتر قرار دهید. برای مثال:
[OperationContract]
IEnumerable<Book> GetAllBook(int code , AsyncCallback callback, object state );
قبل از پیاده سازی سرویس باید ابتدا یک AsyncResult سفارشی بسازیم. ساخت AsyncResult سفارشی بسیار ساده است. کافی است کلاسی بسازیم که اینترفیس IAsyncResult را به ارث ببرد.
 public class CompletedAsyncResult<TEntity> : IAsyncResult where TEntity : class , new()
    {
        public IList<TEntity> Result
        {
            get
            {
                return _result;
            }
            set
            {
                _result = value;
            }
        }
        private IList<TEntity> _result;

        public CompletedAsyncResult( IList<TEntity> data )
        {
            this.Result = data;
        }

        public object AsyncState
        {
            get
            {
                return ( IList<TEntity> )Result;
            }
        }

        public WaitHandle AsyncWaitHandle
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public bool CompletedSynchronously
        {
            get
            {
                return true;
            }
        }

        public bool IsCompleted
        {
            get
            {
                return true;
            }
        }
    }
در کلاس بالا یک خاصیت به نام Result درنظر گرفتم که لیستی از نوع TEntity است.(TEntityبه صورت generic تعریف شده و نوع ورودی آن هر نوع کلاس غیر abstract می‌تواند باشد). این کلاس برای تمام سرویس‌های Async یک پروژه مورد استفاده قرار خواهد گرفت برای همین ورودی آن به صورت generic در نظر گرفته شده است.
#پیاده سازی سرویس
 public class BookService : IBookService
    {
        public BookService()
        {
            ListOfBook = new List<Book>();
        }

        public List<Book> ListOfBook
        {
            get;
            private set;
        }

        private List<Book> CreateListOfBook()
        {
            Parallel.For( 0, 10000, ( int counter ) =>
            {
                ListOfBook.Add( new Book()
                {
                    Code = counter,
                    Title = String.Format( "Book {0}", counter ),
                    Author = "Masoud Pakdel"
                } );
            } );

            return ListOfBook;
        }

        public IAsyncResult BeginGetAllBook( AsyncCallback callback, object state )
        {
            var result = CreateListOfBook();
            return new CompletedAsyncResult<Book>( result );
        }

        public IEnumerable<Book> EndGetAllBook( IAsyncResult asyncResult )
        {
            return ( ( CompletedAsyncResult<Book> )asyncResult ).Result;
        }
    }
*در متد BeginGetAllBook ابتدا به تعداد 10,000 کتاب در یک لیست ساخته می‌شوند و بعد این لیست در کلاس CompletedAsyncResult که ساختیم به عنوان ورودی سازنده پاس داده می‌شوند. چون CompletedAsyncResult از IAsyncResult ارث برده است پس return آن به عنوان خروجی مانعی ندارد. با فراخوانی متد EndGetAllBook سمت کلاینت  مقدار asyncResult به عنوان خروجی برگشت داده می‌شود. به عملیات casting برای دستیابی به مقدار Result در CompletedAsyncResult دقت کنید.
#کد‌های سمت کلاینت:
اکثر برنامه نویسان با استفاده از روش AddServiceReference یک سرویس کلاینت در اختیار خواهند داشت که با وهله سازی از این کلاس یک ChannelFactory ایجاد می‌شود. در این پست به جای استفاده از Code Generation توکار دات نت برای ساخت ChannelFactory از روش دیگری استفاده خواهیم کرد. به عنوان برنامه نویس باید بدانیم که در پشت پرده عملیات ساخت ChannelFactory چگونه است.
 روش AddServiceReference
بعد از اضافه شدن سرویس سمت کلاینت کد‌های زیر برای سرویس Book به صورت زیر تولید می‌شود.
[System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    public partial class BookServiceClient : System.ServiceModel.ClientBase<UI.BookService.IBookService>, UI.BookService.IBookService {
        
        public BookServiceClient() {
        }
        
        public BookServiceClient(string endpointConfigurationName) : 
                base(endpointConfigurationName) {
        }
        
        public BookServiceClient(string endpointConfigurationName, string remoteAddress) : 
                base(endpointConfigurationName, remoteAddress) {
        }
        
        public BookServiceClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : 
                base(endpointConfigurationName, remoteAddress) {
        }
        
        public BookServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 
                base(binding, remoteAddress) {
        }
        
        public UI.BookService.Book[] BeginGetAllBook() {
            return base.Channel.BeginGetAllBook();
        }
    }
همانطور که می‌بینید سرویس بالا از کلاس ClientBase ارث برده است. ClientBase دارای خاصیتی به نام ChannelFactory است که فقط خواندنی می‌باشد. با استفاده از مقادیر EndPointConfiguration یک وهله از کلاس ChannelFactory با توجه به مقدار generic کلاس clientBase ایجاد خواهد شد. در کد زیر مقدار TChannel برابر IBookService است:
System.ServiceModel.ClientBase<UI.BookService.IBookService>
وهله سازی از ChannelFactory به صورت دستی
یک پروژه ConsoleApplication سمت کلاینت ایجاد کنید. برای فراخوانی متد‌های سرویس سمت سرور باید ابتدا تنظیمات EndPoint رو به درستی انجام دهید. سپس با استفاده از EndPoint به راحتی می‌توانیم Channel مربوطه را بسازیم.
کلاسی به نام ServiceMapper ایجاد می‌کنیم که وظیفه آن ساخت ChannelFactory به ازای درخواست‌ها است.
public class ServiceMapper<TChannel>
    {
        public static TChannel CreateChannel()
        {
            TChannel proxy;

            var endPointAddress = new EndpointAddress( "http://localhost:7000/" + typeof( TChannel ).Name.Remove( 0, 1 ) + ".svc" );

            var httpBinding = new BasicHttpBinding();
            
            ChannelFactory<TChannel> factory = new ChannelFactory<TChannel>( httpBinding, endPointAddress );

            proxy = factory.CreateChannel();

            return proxy;
        }
    }
در متد CreateChannel یک وهله از کلاس EndPointAddress ایجاد شده است. پارامتر ورودی آن آدرس سرویس هاست شده می‌باشد برای مثال:

"http://localhost:7000/" +  "BookService.svc"
دستور Remove برای حذف I از ابتدای نام سرویس است. پارامتر‌های ورودی برای سازنده کلاس ChannelFactory ابتدا یک نمونه از کلاس BasicHttpBinding می‌باشد. می‌توان از WSHttpBinding یا NetTCPBinding یا WSDLHttpBinding هم استفاده کرد. البته هر کدام از انواع Binding‌ها تنظیمات خاص خود را می‌طلبد که در مقاله ای جداگانه بررسی خواهم کرد. پارامتر دوم هم EndPoint ساخته شده می‌باشد. در نهایت با دستور CreateChannel عملیات ساخت Channel به پایان می‌رسد.

بعد از اعمال تغییرات زیر در فایل Program پروژه Console و اجرای آن، خروجی به صورت زیر می‌باشد.
  var channel = ServiceMapper<Contract.IBookService>.CreateChannel();
            channel.BeginGetAllBook( new AsyncCallback( ( asyncResult ) => 
            {
                channel.EndGetAllBook( asyncResult ).ToList().ForEach( _record => 
                {
                    Console.WriteLine( _record.Title );
                } );
            } ) , null );
            Console.WriteLine( "Loading..." );
            Console.ReadLine();
همان طور که می‌بینید ورودی متد BeginGtAllBook یک AsyncCallback است که در داخل آن متد EndGetAllBook صدا زده شده است. مقدار برگشتی متد EndGetAllBook خروجی مورد نظر ماست.
خروجی : 


نکته: برای اینکه مطمئن شوید که سرویس مورد نظر در آدرس "http"//localhost:7000/" هاست شده است(یعنی همان آدرسی که در EndPointAddress از آن استفاه کردیم) کافیست از پنجره Project Properties  برای پروژه سرویس وارد برگه Web شده و از بخش Servers گزینه Use Visual Studio Development Server و Specific Port 7000 رو انتخاب کنید.

 
مطالب
React 16x - قسمت 1 - معرفی و شروع به کار
React یک کتابخانه‌ی جاوا اسکریپتی، برای ساخت رابط‌های کاربری سریع و تعاملی است. توسعه‌ی آن از سال 2011 در فیسبوک شروع شد و در حال حاضر محبوب‌ترین کتابخانه‌ی جاوا اسکریپتی در این رده‌است:


به همین جهت اگر می‌خواهید رزومه‌ی غنی‌تری را ارائه دهید، فراگیری React می‌تواند موقعیت‌های شغلی بیشتری را نصیب شما کند.


ساختار کلی یک برنامه‌ی React

کامپوننت‌ها (جزئی از یک رابط کاربری) قلب هر برنامه‌ی React ای را تشکیل می‌دهند. برای ساخت یک برنامه‌ی React، تعدادی کامپوننت مستقل را تهیه و با هم ترکیب می‌کنیم تا به رابط کاربری نهایی برسیم.
هر برنامه‌ی React، حداقل از یک کامپوننت تشکیل می‌شود که به آن Root component هم می‌گویند. این کامپوننت بیانگر کل برنامه‌است و دربرگیرنده‌ی مابقی Child components برنامه است. بنابراین ساختار هر برنامه‌ی React، شبیه به درختی از کامپوننت‌ها است. اگر با Angular 2 به بعد کار کرده باشید، این مفهوم برای شما آشنا است.
یک مثال: فرض کنید می‌خواهیم UI برنامه‌ای را به مانند رابط کاربری Twitter، ایجاد کنیم. هر قسمت یک صفحه‌ی توئیتر، به کامپوننت‌هایی شکسته می‌شود؛ مانند منوی راهبری، نمایش پروفایل شخص، نمایش لیست آخرین اخبار مورد علاقه‌ی شخص و نمایش فید. اگر بخواهیم این ساختار را توسط یک برنامه‌ی React شبیه سازی کنیم، در بالاترین سطح، کامپوننت root را خواهیم داشت که کار ترکیب و نمایش سایر کامپوننت‌های برنامه مانند nav bar ، trends ، profile و feed را انجام می‌دهد. اکنون در این ساختار ایجاد شده، برای مثال کامپوننت feed نیز می‌تواند از چندین کامپوننت مجزا تشکیل شود؛ مانند کامپوننت‌های tweet و like.
بنابراین هر کامپوننت، قسمتی از UI را تشکیل می‌دهد. هر کدام از آن‌ها به صورت مجزای از دیگری ساخته شده و سپس در کنار هم قرار می‌گیرند تا UI نهایی را شکل دهند:



هر کامپوننت در React به صورت یک کلاس ES6، با ساختاری که دارای یک شیء state و متد render است، تشکیل می‌شود:
class Tweet {
 state = {};
 
 render() {
 } 
}
state در اینجا همان اطلاعاتی است که قرار است در زمان نمایش این کامپوننت، رندر شود. کار متد render نیز همانطور که از نام آن نیز مشخص است، بیان نحوه‌ی تشکیل و رندر UI است. خروجی این متد، یک React Element است که در حقیقت یک شیء جاوا اسکریپتی خالص است و در نهایت به المان‌های DOM، نگاشت می‌شود. یک React Element، یک DOM Element واقعی نیست؛ بلکه تنها یک شیء جاوا اسکریپتی بیانگر DOM Element، در حافظه‌است. بنابراین یک برنامه‌ی React تشکیل شده‌است از لیستی از React Elementها در حافظه که به آن Virtual DOM هم گفته می‌شود.
مزیت کارکردن با Virtual DOM، سادگی ایجاد، تغییر و به روز رسانی آن در مقایسه با DOM واقعی است که در نهایت کار رندر عناصر UI را در مرورگر انجام می‌دهد. زمانیکه در state کامپوننتی تغییری رخ می‌دهد، یک React Element جدید تولید می‌شود. سپس React این شیء جدید را با نمونه‌ی قبلی آن مقایسه کرده و تغییرات رخ‌داده را محاسبه می‌کند. در آخر این تغییرات را به DOM واقعی اعمال می‌کند تا با Virtual DOM موجود هماهنگ شود.
بنابراین در حین کار با React، دیگر همانند کار با جاوا اسکریپت خالص و یا jQuery، مستقیما عناصر UI و DOM واقعی را تغییر نمی‌دهیم. در اینجا فقط state یک کامپوننت را تغییر می‌دهیم و سپس React، کار ایجاد شیء UI درون حافظه‌ای متناظر با آن و سپس اعمال آن‌را به UI نهایی قابل مشاهده‌ی در مرورگر، انجام می‌دهد. به همین جهت به این کتابخانه React می‌گویند! چون به تغییرات state کامپوننت‌ها واکنش نشان می‌دهد و سپس DOM واقعی را به روز می‌کند.


Angular یا React؟!

هر دوی React و Angular از لحاظ طراحی کامپوننت‌ها بسیار شبیه به هم هستند؛ اما Angular یک فریم‌ورک است و React تنها یک کتابخانه. تنها کاری را که React انجام می‌دهد، رندر View است و هماهنگ نگه داشتن آن با state کامپوننت‌ها. این تمام کاری است که React انجام می‌دهد؛ نه بیشتر و نه کمتر! بنابراین یادگیری React، بسیار سریع‌تر و ساده‌تر از Angular است. بدیهی است یک برنامه‌ی تک صفحه‌ای وب، از اجزای دیگری مانند مسیریابی و یا کار با سرویس‌های HTTP نیز تشکیل می‌شود. در React شما مختار هستید که کتابخانه‌های جانبی فراهم شده‌ی برای آن‌را خودتان انتخاب کرده و استفاده کنید؛ برخلاف روشی که در Angular مرسوم است و به صورت مشخص و ثابتی به همراه این فریم‌ورک ارائه می‌شوند.


برپایی محیط توسعه‌ی React

اولین برنامه‌ای را که برای کار با React باید نصب کنید، node.js است. البته ما در این سری قرار نیست با node.js کار کنیم؛ اما از یکی از اجزای آن به نام node package manager یا npm، برای نصب کتابخانه‌ی جاوا اسکریپتی ثالث، زیاد استفاده خواهیم کرد. پس از نصب آن، به خط فرمان مراجعه کرد و دستور زیر را صادر کنید:
> npm install -g npm@latest
این دستور npm قدیمی موجود بر روی سیستم را به روز رسانی می‌کند (اگر پیشتر یک node.js قدیمی را نصب و اکنون آن‌را به روز رسانی کرده‌اید).

اگر هم خیلی پیشترها node.js را نصب کرده‌اید (برای مثال چند سال قبل!)، نصب نگارش جدید آن احتمالا کار نخواهد کرد. حتی عزل و نصب مجدد آن نیز کارساز نیست. در این حالت باید پس از عزل آن، پوشه‌های قدیمی آن‌را یکی یکی یافته و دستی حذف کنید . سپس مجددا آن‌را نصب کنید.

در ادامه در خط فرمان و توسط npm، قالب create-react-app را نصب خواهیم کرد:
> npm i -g create-react-app
در اینجا سوئیچ i به معنای install است و g یعنی نصب global و سراسری بسته‌ی create-react-app. نصب سراسری یک بسته یعنی در هر پوشه‌ای می‌توان به امکانات آن دسترسی یافت و از آن استفاده کرد. اگر از سوئیچ g استفاده نمی‌شد، این بسته تنها در پوشه‌ی جاری و با سطح دید مختص به آن، نصب و قابل استفاده می‌شد.

ابزار دیگری که در این سری از آن استفاده خواهیم کرد، ادیتور بسیار معروف و محبوب VSCode است. پس از دریافت و نصب آن، چند افزونه‌ی زیر را نیز به آن اضافه خواهیم کرد:
برای نصب آن‌ها، پنل extensions را در VSCode، از نوار ابزار کنار صفحه‌ی آن، انتخاب کرده و نام‌های فوق را در آن جستجو و سپس نصب کنید.

و یا می‌توانید این فایل را اجرا کرده و تعدادی از افزونه‌های مفید VSCode را یکجا نصب کنید: install-addons.zip

همچنین قابلیت فرمت‌کردن پس از Save را نیز در VSCode فعال کنید تا پس از هربار Save، اعمال این افزونه‌ها به صورت خودکار صورت گیرد. برای این منظور گزینه‌ی file->preferences->settings را در VSCode انتخاب کرده و سپس save را جستجو کرده و Format On Save را انتخاب کنید:


علاوه بر این‌ها، جهت کار بهتر با VSCode، بهتر است بررسی کننده‌های کدهای جاوا اسکریپتی (static code analyzers) را نیز با اجرای دستور زیر نصب کنید:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks

پس از این تغییرات، نیاز است یکبار VSCode را بسته و مجددا باز کنید. سپس مجددا گزینه‌ی file->preferences->settings را در VSCode انتخاب کرده و ابتدا eslint را در اینجا جستجو کنید. در صفحه‌ی نمایش تنظیمات آن، گزینه‌ی Auto fix on save آن‌را انتخاب نمائید. در آخر در همین قسمت settings، عبارت prettier را انتخاب کنید. در اینجا اگر گزینه‌ی قدیمی یکپارچگی با eslint آن هنوز وجود دارد، آن‌را از حالت انتخاب شده خارج کنید (به صورت قرمز و deprecated نمایش داده می‌شود) تا افزونه‌ی prettier بدون مشکل و خطا کار کند (disable Prettier ESLint integration).


ایجاد قالب اولین برنامه‌ی React

در ادامه برای ایجاد اولین برنامه‌ی React، از بسته‌ی create-react-app که پیشتر آن‌را نصب کردیم، استفاده می‌کنیم. برای این منظور در خط فرمان دستور زیر را صادر کنید:
> create-react-app sample-01
در اینجا sample-01 یک نام دلخواه است و در حین اجرای این دستور باید به اینترنت متصل باشید تا وابستگی‌های مرتبط با پروژه را نیز دریافت کند. برای بار اول، اجرای آن ممکن است کمی طول بکشد. اما از دفعات آتی، چون بسته‌های مرتبط را در npm-cache سیستم نیز ذخیره می‌کند، اجرای آن بسیار سریع خواهد بود.
این قالب نه تنها React را نصب می‌کند، بلکه یک development server را برای اجرا و مشاهده‌ی سریع برنامه، webpack را برای یکی کردن فایل‌ها (bundling & minification)، Babel را برای کامپایل کدهای فایل‌های JSX و ... نیز نصب می‌کند. بنابراین به این ترتیب، یک پروژه‌ی تنظیم شده و آماده‌ی استفاده و توسعه را شاهد خواهیم بود که نیازی به تنظیمات اولیه‌ی آن نیست.
پس ایجاد برنامه، وارد پوشه‌ی sample-01 شده و دستور npm start را صادر کنید:
> cd sample-01
> npm start
به این ترتیب برنامه بر روی پورت 3000، قابل دسترسی و مشاهده می‌شود:


development server آن، تغییرات فایل‌های برنامه را تحت نظر قرار می‌دهد و با هر تغییری، به صورت خودکار برنامه را در مرورگر بارگذاری مجدد خواهد کرد.


بررسی ساختار اولین پروژه‌ی React ایجاد شده

ساختار پوشه‌ها و فایل‌های مثال اولیه‌ی ایجاد شده توسط قالب create-react-app به صورت زیر است:


البته شما در این تصویر پوشه‌ی node_modules را که در کنار این پوشه‌ها قرار دارد، مشاهده نمی‌کنید. وجود یک چنین پوشه‌ی سنگینی با هزاران فایل داخل آن، کار نمایشی IDEها را با مشکل مواجه می‌کند (مصرف حافظه‌ی بالا، به همراه کند شدن شدید آن). اگر نمی‌خواهید این پوشه نمایش داده شود، در مسیر file->preferences->settings، عبارت npm را جستجو کرده و سپس در قسمت npm: exclude آن، بر روی لینک edit in settings.json کلیک کنید:


 و سپس در فایل باز شده، یک چنین تنظیمی را می‌توانید اضافه و یا ویرایش و تکمیل کنید:
  "files.exclude": {
    "**/.git": true,
    "**/.svn": true,
    "**/.hg": true,
    "**/CVS": true,
    "**/.DS_Store": true,
    "**/node_modules": true,
    "**/wwwroot": true,
    "**/bower_components": true,
    "**/**/bin": true,
    "**/**/obj": true,
    "**/packages": true
  },

در ادامه پوشه‌ی public این پروژه را مشاهده می‌کنید. تمام فایل‌هایی که قرار است به صورت عمومی توسط برنامه ارائه شوند، مانند favicon.ico و غیره، در این پوشه قرار می‌گیرند.
در این پوشه بر روی فایل index.html آن کلیک کنید تا بتوان محتوای آن‌را بهتر بررسی کرد. برای مثال در ابتدای آن، درج تعدادی متادیتا را که یکی از آن‌ها ذکر manifest.json است، مشاهده می‌کنید. کار فایل manifest.json، ارائه‌ی یک سری متادیتای خاص مخصوص دستگاه‌های موبایل است که در آن‌ها بجای favicon.ico، می‌توان از تصاویر و یا آیکن‌های بزرگتری مانند فایل‌های png موجود در پوشه‌ی public، استفاده کرد. در ادامه‌ی این فایل، به تنظیم زیر می‌رسیم:
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
div با id مساوی root، محل ارائه‌ی کل برنامه‌ی React ما است.

در پوشه‌ی src و فایل App.js آن، شاهد یک کامپوننت ابتدایی هستید که کار رندر صفحه‌ی مشکی پیش‌فرض این قالب را انجام می‌دهد. در این فایل، شاهد بازگشت یک چنین تگ‌هایی هستیم:
  return (
    <div className="App">
      <header className="App-header">
       ... 
      </header>
    </div>
  );
احتمالا تابحال چنین return ای را در برنامه‌های جاوا اسکریپتی مشاهده نکرده‌اید؛ چون درج آن‌ها در فایل‌های js به این نحو، غیرمجاز است. این تگ‌ها نه رشته‌ای هستند و نه HTML خالص. به آن jsx گفته می‌شود که مخفف JavaScript XML می‌باشد. کار آن ارائه‌ی ساختار UI ای است که قرار است رندر شود. یک چنین کدی برای اینکه قابل تفسیر و اجرا باشد، از درون کامپایلر ویژه‌ای به نام Babel عبور می‌کند و تبدیل به کدهای جاوا اسکریپتی خالصی می‌شود که برای مرورگرها قابل درک و اجرا است.
برای درک بهتر آن به آدرس https://babeljs.io/repl مراجعه کنید. سپس در سمت چپ صفحه، یک قطعه کد jsx را به یک ثابت انتساب دهید:
const element = <h1>Hello World!</h1>;


همانطور که مشاهده می‌کنید، این قطعه کد jsx (که یک رشته‌ی معمولی نیست)، توسط Babel به یک قطعه کد کاملا جاوا اسکریپتی قابل درک برای مرورگر تبدیل شده‌است:
"use strict";

var element = React.createElement("h1", null, "Hello World!");

بدیهی است نوشتن کدهای jsx، ساده‌تر از نوشتن قطعه کد فوق است و درک آن نیز به علت شباهت آن به HTML، آسان‌تر است. به همین جهت در کدهای React، ما از jsx استفاده می‌کنیم و تفسیر آن‌را به Babel واگذار خواهیم کرد.

در پوشه‌ی src، فایل مهم دیگری که وجود دارد، index.js است. این فایل نقطه‌ی آغازین برنامه را مشخص می‌کند. در قسمت‌های بعدی، محتویات این فایل را بیشتر بررسی خواهیم کرد.
در اینجا فایل serviceWorker.js را نیز مشاهده می‌کنید. این فایل به صورت خودکار توسط قالب create-react-app ایجاد شده‌است و کار آن کمک به ارائه‌ی محلی برنامه، توسط development server آن است. بنابراین ما کاری با این فایل نخواهیم داشت.


نوشتن اولین برنامه‌ی React

به پوشه‌ی src ایجاد شده مراجعه کرده و تمام فایل‌های موجود و پیش‌فرض آن‌را حذف کنید. در ادامه خودمان آن‌ها را از صفر ایجاد خواهیم کرد. برای این منظور فایل جدید و خالی src\index.js را ایجاد می‌کنیم. در ابتدای کار نیاز است تعدادی ماژول React را import کنیم.
import React from "react";

const element = <h1>Hello World!</h1>;
console.log(element);
در اینجا شیء React از ماژول react دریافت شده و سپس یک ثابت را با یک عبارت jsx مقدار دهی کرده‌ایم. چون از jsx استفاده می‌کنیم، ذکر import ابتدای فایل الزامی است؛ از این جهت که Babel به کمک آن است که می‌تواند معادل React.createElement را تولید کند.
اگر هنوز برنامه توسط دستور npm start در حال اجرا است، هر بار که فایل index.js را ذخیره می‌کنیم، خروجی نهایی را در مرورگر نمایش می‌دهد (اگر هم آن‌را بسته‌اید، یکبار از طریق خط فرمان، دستور npm start را در ریشه‌ی پروژه، صادر کنید). به این قابلیت hot module reloading هم گفته می‌شود.
در این حالت اگر به مرورگر مراجعه کنید، یک صفحه‌ی سفید را مشاهده خواهید کرد. اکنون دکمه‌ی F12 را فشرده (و یا ctrl+shift+i) و developer console مرورگر را باز کنید.


شیءای را که در اینجا مشاهده می‌کنید، همان حاصل console.log کدهای فوق است؛ به عبارتی Babel، عبارت jsx ما را تبدیل به یک شیء جاوا اسکریپتی قابل فهم برای مرورگر کرده‌است که از دیدگاه React، جزئی از همان Virtual DOM ای است که پیشتر معرفی شد (نمایش درون حافظه‌ای DOM مختص React، جهت محاسبه‌ی تغییرات، با تغییر state هر کامپوننت و سپس اعمال آن‌ها به DOM اصلی در مرورگر).
اکنون می‌خواهیم این المان را در DOM اصلی، رندر کرده و نمایش دهیم:
import React from "react";
import ReactDOM from "react-dom";

const element = <h1>Hello World!</h1>;
console.log(element);

ReactDOM.render(element, document.getElementById("root"));
برای این منظور نیاز است از متد ReactDOM.render استفاده کرد. این شیء در ماژول react-dom قرار دارد؛ به همین جهت در ابتدای فایل import شده‌است. سپس در متد render آن، ابتدا المانی که قرار است رندر شود ذکر خواهد شد و سپس محل درج آن‌را مشخص می‌کنیم که دقیقا به همان div با id مساوی root در فایل public\index.html اشاره می‌کند.
اکنون پس از ذخیره سازی فایل index.js، اگر به مرورگر مراجعه کنید، عبارت Hello World! را مشاهده خواهید کرد:


همانطور که در این تصویر نیز مشخص است، المان h1 ما را داخل div ای با id مساوی root، درج کرده‌است.

هدف از این مثال ساده، نمایش نحوه‌ی کارکرد React، در پشت صحنه بود. در یک برنامه‌ی واقعی، بجای رندر یک المان ساده در DOM، کار رندر App component را انجام خواهیم داد. کامپوننت App، کامپوننت ریشه‌ای برنامه بوده و می‌تواند شامل درختی از کامپوننت‌ها که UI نهایی را تشکیل می‌دهند، شود.


نگاهی به تنظیمات پروژه‌ی ایجاد شده

اگر فایل package.json پروژه را باز کنید، یک چنین بسته‌هایی در آن درج شده‌است:
{
  "name": "sample-01",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.11.0",
    "react-dom": "^16.11.0",
    "react-scripts": "3.2.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
در اینجا صرفا سه بسته‌ی react، react-dom و react-scripts را در قسمت dependencies مشاهده می‌کنید که کل Importهای ما را تشکیل می‌دهند.
بسته‌ی react-scripts است که کار مدیریت چهار جزء قسمت scripts این فایل را انجام می‌دهد. برای نمونه دستور npm start ای که در اینجا تعریف شده، سبب اجرای react-scripts start می‌شود. در ادامه اگر دستور npm run build را اجرا کنیم، یک بسته‌ی نهایی بهینه سازی شده را تولید می‌کند.
آخرین دستور آن eject است. اگر دستور npm run eject را اجرا کنید، امکان سفارشی سازی پشت صحنه‌ی create-react-app را خواهید داشت؛ اما در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر می‌شوند). همچنین این عملیات نیز یک طرفه‌است. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانی‌های بعدی create-react-app را با مشکل مواجه می‌کند. این گزینه صرفا مختص توسعه دهندگان پیشرفته‌ی React است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-01.zip

در قسمت بعد، پیشنیازهای جاوا اسکریپتی شروع به کار با React را بررسی می‌کنیم.
اشتراک‌ها
JSON Web Token چیست؟

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with HMAC algorithm) or a public/private key pair using RSA. 

JSON Web Token چیست؟
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت دهم - کار با فرم‌ها - قسمت اول
هر برنامه‌ی وبی، نیاز به کار با فرم‌های وب را دارد و به همین جهت، AngularJS 2.0 به همراه دو نوع از فرم‌ها است: فرم‌های مبتنی بر قالب‌ها و فرم‌های مبتنی بر مدل‌ها.
کار با فرم‌های مبتنی بر قالب‌ها ساده‌تر است؛ اما کنترل کمتری را بر روی مباحث اعتبارسنجی داده‌های ورودی توسط کاربر، در اختیار ما قرار می‌دهند. اما فرم‌های مبتنی بر مدل‌ها هر چند به همراه اندکی کدنویسی بیشتر هستند، اما کنترل کاملی را جهت اعتبارسنجی ورودی‌های کاربران، ارائه می‌دهند. در این قسمت فرم‌های مبتنی بر قالب‌ها (Template-driven forms) را بررسی می‌کنیم.


ساخت فرم مبتنی بر قالب‌های ثبت یک محصول جدید

در ادامه‌ی مثال این سری، می‌خواهیم به کاربران، امکان ثبت اطلاعات یک محصول جدید را نیز بدهیم. به همین جهت فایل‌های جدید product-form.component.ts و product-form.component.html را به پوشه‌ی App\products برنامه اضافه می‌کنیم (جهت تعریف کامپوننت فرم جدید به همراه قالب HTML آن).
الف) محتوای کامل product-form.component.html
<form #f="ngForm" (ngSubmit)="onSubmit(f.form)">
    <div class="panel panel-default">
        <div class="panel-heading">
            <h3 class="panel-title">
                Add Product
            </h3>
        </div>
        <div class="panel-body form-horizontal">
            <div class="form-group">
                <label for="productName" class="col col-md-2 control-label">Name</label>
                <div class="controls col col-md-10">
                    <input ngControl="productName" id="productName" required
                           #productName="ngForm"
                           (change)="log(productName)"
                           minlength="3"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.productName"/>
                    <div *ngIf="productName.touched && productName.errors">
                        <label class="text-danger" *ngIf="productName.errors.required">
                            Name is required.
                        </label>
                        <label class="text-danger" *ngIf="productName.errors.minlength">
                            Name should be minimum {{ productName.errors.minlength.requiredLength }} characters.
                        </label>
                    </div>
                </div>
            </div>
            <div class="form-group">
                <label for="productCode" class="col col-md-2 control-label">Code</label>
                <div class="controls col col-md-10">
                    <input ngControl="productCode" id="productCode" required
                           #productCode="ngForm"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.productCode"/>
                    <label class="text-danger" *ngIf="productCode.touched && !productCode.valid">
                        Code is required.
                    </label>
                </div>
            </div>
            <div class="form-group">
                <label for="releaseDate" class="col col-md-2 control-label">Release Date</label>
                <div class="controls col col-md-10">
                    <input ngControl="releaseDate" id="releaseDate" required
                           #releaseDate="ngForm"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.releaseDate"/>
                    <label class="text-danger" *ngIf="releaseDate.touched && !releaseDate.valid">
                        Release Date is required.
                    </label>
                </div>
            </div>
            <div class="form-group">
                <label for="price" class="col col-md-2 control-label">Price</label>
                <div class="controls col col-md-10">
                    <input ngControl="price" id="price" required
                           #price="ngForm"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.price"/>
                    <label class="text-danger" *ngIf="price.touched && !price.valid">
                        Price is required.
                    </label>
                </div>
            </div>
            <div class="form-group">
                <label for="description" class="col col-md-2 control-label">Description</label>
                <div class="controls col col-md-10">
                    <textarea ngControl="description" id="description" required
                              #description="ngForm"
                              rows="10" type="text" class="form-control"
                              [(ngModel)]="productModel.description"></textarea>
                    <label class="text-danger" *ngIf="description.touched && !description.valid">
                        Description is required.
                    </label>
                </div>
            </div>
            <div class="form-group">
                <label for="imageUrl" class="col col-md-2 control-label">Image</label>
                <div class="controls col col-md-10">
                    <input ngControl="imageUrl" id="imageUrl" required
                           #imageUrl="ngForm"
                           type="text" class="form-control"
                           [(ngModel)]="productModel.imageUrl"/>
                    <label class="text-danger" *ngIf="imageUrl.touched && !imageUrl.valid">
                        Image is required.
                    </label>
                </div>
            </div>
        </div>
        <footer class="panel-footer">
            <button [disabled]="!f.valid"
                    type="submit" class="btn btn-primary">
                Submit
            </button>
        </footer>
    </div>
</form>

ب) محتوای کامل product-form.component.ts
import { Component } from 'angular2/core';
import { Router } from 'angular2/router';
import { IProduct } from './product';
import { ProductService } from './product.service';
 
@Component({
    //selector: 'product-form',
    templateUrl: 'app/products/product-form.component.html'
})
export class ProductFormComponent {
 
    productModel = <IProduct>{}; // creates an empty object of an interface
 
    constructor(private _productService: ProductService, private _router: Router) { }
 
    log(productName): void {
        console.log(productName);
    }
 
    onSubmit(form): void {
        console.log(form);
        console.log(this.productModel);
 
        this._productService.addProduct(this.productModel)
            .subscribe((product: IProduct) => {
                console.log(`ID: ${product.productId}`);
                this._router.navigate(['Products']);
            });
    }
}

اکنون ریز جزئیات و تغییرات این دو فایل را قدم به قدم بررسی خواهیم کرد.

تا اینجا در فایل product-form.component.html یک فرم ساده‌ی HTML ایی مبتنی بر بوت استرپ 3 را تهیه کرده‌ایم. نکات ابتدایی آن، دقیقا مطابق است با مستندات بوت استرپ 3؛ از لحاظ تعریف form-horizontal و سپس ایجاد یک div با کلاس form-group و قرار دادن المان‌هایی با کلاس‌های form-control در آن. همچنین برچسب‌های تعریف شده‌ی با ویژگی for، در این المان‌ها، جهت بالارفتن دسترسی پذیری به عناصر فرم، اضافه شده‌اند. این مراحل در مورد تمام فرم‌های استاندارد وب صادق هستند و نکته‌ی جدیدی ندارند.

در ادامه تعاریف AngularJS 2.0 را به این فرم اضافه کرد‌ه‌ایم. در اینجا هر کدام از المان‌های ورودی، تبدیل به Controlهای AngularJS 2.0 شده‌اند. کلاس Control، خواص ویژه‌ای را در اختیار ما قرار می‌دهد. برای مثال value یا مقدار این المان چیست؟ وضعیت touched و untouched آن چیست؟ (آیا کاربر فوکوس را به آن منتقل کرده‌است یا خیر؟) آیا dirty است؟ (مقدار آن تغییر کرده‌است؟) و یا شاید هم pristine است؟ (مقدار آن تغییری نکرده‌است). علاوه بر این‌ها دارای خاصیت valid نیز می‌باشد (آیا اعتبارسنجی آن موفقیت آمیز است؟)؛ به همراه خاصیت errors که مشکلات اعتبارسنجی موجود را باز می‌گرداند.
<div class="form-group">
    <label for="description" class="col col-md-2 control-label">Description</label>
    <div class="controls col col-md-10">
        <textarea ngControl="description" id="description" required
                  #description="ngForm"
                  rows="10" type="text" class="form-control"
                  [(ngModel)]="productModel.description"></textarea>
        <label class="text-danger" *ngIf="description.touched && !description.valid">
            Description is required.
        </label>
    </div>
</div>
در اینجا کلاس مفید دیگری به نام ControlGroup نیز درنظر گرفته شده‌است. برای مثال هر فرم، یک ControlGroup است (گروهی متشکل از کنترل‌ها، در صفحه). البته می‌توان یک فرم بزرگ را به چندین ControlGroup نیز تقسیم کرد. تمام خواصی که برای کلاس Control ذکر شدند، در مورد کلاس ControlGroup نیز صادق هستند. با این تفاوت که این‌بار اگر به خاصیت valid آن مراجعه کردیم، یعنی تمام کنترل‌های قرار گرفته‌ی در آن گروه معتبر هستند و نه صرفا یک تک کنترل خاص. به همین ترتیب خاصیت errors نیز تمام خطاهای اعتبارسنجی یک گروه را باز می‌گرداند.
هر دو کلاس Control و ControlGroup از کلاس پایه‌ای به نام AbstractControl مشتق شده‌اند و این کلاس پایه است که خواص مشترک یاد شده را به همراه دارد.

بنابراین برای کار ساده‌تر با یک فرم AngularJS 2.0، کل فرم را تبدیل به یک ControlGroup کرده و سپس هر کدام از المان‌های ورودی را تبدیل به یک Control مجزا می‌کنیم. کار برقراری این ارتباط، با استفاده از دایرکتیو ویژه‌ای به نام ngControl انجام می‌شود. بنابراین دایرکتیو ngControl، با نامی دلخواه و معین، به تمام المان‌های ورودی، انتساب داده شده‌است.
هرچند در این مثال نام ngControl‌ها با مقدار id هر کنترل یکسان درنظر گرفته شده‌است، اما ارتباطی بین این دو نیست. مقدار id جهت استفاده‌ی در DOM کاربرد دارد و مقدار ngControl توسط AngularJS 2.0 استفاده می‌شود. جهت رسیدن به کدهایی یکدست، بهتر است این نام‌ها را یکسان درنظر گرفت؛ اما هیچ الزامی هم ندارد.

برای بررسی جزئیات این اشیاء کنترل، در المان productName، یک متغیر محلی را به نام productName# تعریف کرده‌ایم و آن‌را به دایرکتیو ngControl انتساب داده‌ایم. این انتساب توسط ngForm انجام شده‌است. زمانیکه AngularJS 2.0 یک متغیر محلی تنظیم شده‌ی به ngForm را مشاهده می‌کند، آن‌را به صورت خودکار به ngControl همان المان ورودی متصل می‌کند. سپس این متغیر محلی را به متد log ارسال کرده‌ایم. این متد در کلاس کامپوننت جاری تعریف شده‌است و کار آن نمایش شیء Control جاری در کنسول developer tools مرورگر است.
<input ngControl="productName" id="productName" required
       #productName="ngForm"
       (change)="log(productName)"
       minlength="3"
       type="text" class="form-control"
       [(ngModel)]="productModel.productName"/>


همانطور که در تصویر مشاهده می‌کنید، عناصر یک شیء Control، در کنسول نمایش داده شده‌اند و در اینجا بهتر می‌توان خواصی مانند valid و امثال آن‌را که به همراه این کنترل وجود دارند، مشاهده کرد. برای مثال خاصیت dirty آن true است چون مقدار آن المان ورودی، تغییر کرده‌است.

بنابراین تا اینجا با استفاده از دایرکتیو ngControl، یک المان ورودی را به یک شیء Control متصل کردیم. همچنین نحوه‌ی تعریف یک متغیر محلی را در المانی و سپس ارسال آن را به کلاس متناظر با کامپوننت فرم، نیز بررسی کردیم.


افزودن اعتبارسنجی به فرم ثبت محصولات

به کنترل‌هایی که به صورت فوق توسط ngControl ایجاد می‌شوند، اصطلاحا implicitly created controls می‌گویند؛ یا به عبارتی ایجاد آن‌ها به صورت «ضمنی» توسط AngularJS 2.0 انجام می‌شود که نمونه‌ای از آن‌را در تصویر فوق نیز مشاهده کردید. این نوع کنترل‌های ضمنی، امکانات اعتبارسنجی محدودی را در اختیار دارند؛ که تنها سه مورد هستند:
الف) required
ب) minlength
ج) maxlength

این‌ها ویژگی‌های استاندارد اعتبارسنجی HTML 5 نیز هستند. نمونه‌ای از اعمال این موارد را با افزودن ویژگی required، به المان‌های فرم ثبت محصولات فوق، مشاهده می‌کنید.
سپس نیاز داریم تا خطاهای اعتبارسنجی را در مقابل هر المان ورودی نمایش دهیم.
<textarea ngControl="description" id="description" required
          #description="ngForm"
          rows="10" type="text" class="form-control"></textarea>
<div class="alert alert-danger" *ngIf="description.touched && !description.valid">
    Description is required.
</div>
پس از افزودن ویژگی required به یک المان، افزودن و نمایش خطاهای اعتبارسنجی، شامل سه مرحله‌ی زیر است:
الف) ایجاد یک div ساده جهت نمایش پیام خطای اعتبار سنجی
ب) افزودن یک متغیر محلی با # و تنظیم شده‌ی به ngForm، جهت دسترسی به شیء کنترل ایجاد شده
ج) استفاده از این متغیر محلی در دایرکتیو ساختاری ngIf* جهت دسترسی به خاصیت valid آن کنترل. بر مبنای مقدار این خاصیت است که تصمیم گرفته می‌شود، پیام اجباری بودن پر کردن فیلد نمایش داده شود یا خیر.
در اینجا یک سری کلاس بوت استرپ 3 هم جهت نمایش بهتر این پیام خطای اعتبارسنجی، اضافه شده‌اند.

علت استفاده از خاصیت touched این است که اگر آن‌را حذف کنیم، در اولین بار نمایش فرم، ذیل تمام المان‌های ورودی، پیام اجباری بودن تکمیل آن‌ها نمایش داده می‌شود. با استفاده از خاصیت touched، اگر کاربر به المانی مراجعه کرد و سپس آن‌را تکمیل نکرد، آنگاه پیام خطای اعتبارسنجی را دریافت می‌کند.



بهبود شیوه نامه‌ی پیش فرض المان‌های ورودی اطلاعات در AngularJS 2.0

می‌خواهیم اگر اعتبارسنجی یک المان ورودی با شکست مواجه شد، یک حاشیه‌ی قرمز، در اطراف آن نمایش داده شود. این مورد را با توجه به اینکه AngularJS 2.0، شیوه نامه‌های ویژه‌ای را به صورت خودکار به المان‌ها اضافه می‌کند، می‌توان به صورت سراسری به تمام فرم‌ها اضافه کرد. برای این منظور فایل app.component.css واقع در ریشه‌ی پوشه‌ی app را گشوده و تنظیمات ذیل را به آن اضافه کنید:
.ng-touched.ng-invalid{
    border: 1px solid red;
}

ویژگی‌های اضافه شده‌ی در حالت شکست اعتبارسنجی؛ مانند ng-invalid


ویژگی‌های اضافه شده‌ی در حالت موفقیت اعتبارسنجی؛ مانند ng-valid



مدیریت چندین ویژگی اعتبارسنجی یک المان با هم

گاهی از اوقات نیاز است برای یک المان ورودی، چندین نوع اعتبارسنجی مختلف را تعریف کرد. برای مثال فرض کنید که ویژگی‌های required و همچنین minlength، برای نام محصول تنظیم شده‌اند. در این حالت ذکر productName.valid خیلی عمومی است و هر دو حالت اجباری بودن فیلد و حداقل طول آن‌را با هم به همراه دارد:
<div class="alert alert-danger" *ngIf="productName.touched && !productName.valid">
   Name is required.
</div>
بنابراین در این حالت از روش ذیل استفاده می‌شود:
<div *ngIf="productName.touched && productName.errors">
    <div class="alert alert-danger" *ngIf="productName.errors.required">
        Name is required.
    </div>
    <div class="alert alert-danger" *ngIf="productName.errors.minlength">
        Name should be minimum 3 characters.
    </div>
</div>
خاصیت errors نیز یکی دیگر از خواص شیء کنترل است. اگر نال بود، یعنی خطایی وجود ندارد و در غیراینصورت، به ازای هر نوع اعتبارسنجی تعریف شده، خواصی به آن اضافه می‌شوند. بنابراین ذکر productName.errors.required به این معنا است که آیا خاصیت errors، دارای کلیدی به نام required است؟ اگر بله، یعنی این فیلد هنوز پر نشده‌است.
همچنین چون در این حالت productName.touched نیاز است چندین بار تکرار شود، می‌توان آن‌را در یک div محصور کننده‌ی دو div مورد نیاز جهت نمایش خطاهای اعتبارسنجی قرار داد. به علاوه بررسی نال نبودن productName.errors نیز در div محصور کننده صورت گرفته‌است و دیگر نیازی نیست این بررسی را به ngIfهای داخلی اضافه کرد.

نکته 1
اگر علاقمند بودید تا جزئیات خاصیت errors را مشاهده کنید، آن‌را می‌توان توسط pipe توکاری به نام json به صورت موقت نمایش داد و بعد آن‌را حذف کرد:
 <div *ngIf="productName.touched && productName.errors">
  {{ productName.errors | json }}

نکته 2
بجای ذکر مستقیم عدد سه در «minimum 3 characters»، می‌توان این عدد را مستقیما از تعریف ویژگی minlength نیز استخراج کرد:
 Name should be minimum {{ productName.errors.minlength.requiredLength }} characters.


بررسی ngForm

شبیه به ngControl که یک المان ورودی را به یک کنترل AngularJS 2.0 متصل می‌کند، دایرکتیو دیگری نیز به نام ngForm وجود دارد که کل فرم را به شیء ControlGroup بایند می‌کند و برخلاف ngControl، نیازی به ذکر صریح آن وجود ندارد. هر زمانیکه AngularJS 2.0، المان استاندارد فرمی را در صفحه مشاهده می‌کند، این اتصالات را به صورت خودکار برقرار خواهد کرد.
ngForm دارای خاصیتی است به نام ngSumbit که از نوع EventEmitter است (نمونه‌ای از آن را در مبحث کامپوننت‌های تو در تو پیشتر ملاحظه کرده‌اید). بنابراین از آن می‌توان جهت اتصال رخداد submit فرم، به متدی در کلاس کامپوننت خود، استفاده کرد. متد متصل به این رخداد، زمانی فراخوانی می‌شود که کاربر بر روی دکمه‌ی submit کلیک کند:
 <form #f="ngForm" (ngSubmit)="onSubmit(f.form)">
همچنین در اینجا متغیر محلی f جهت دسترسی به شیء ControlGroup و ارسال آن به متد onSubmit تعریف شده‌است (شبیه به متغیرهای محلی دسترسی به ngControl که پیشتر جهت نمایش خطاهای اعتبارسنجی، اضافه کردیم).

پس از تعریف این رخداد و اتصال آن در قالب کامپوننت، اکنون می‌توان متد onSubmit را در کلاس آن نیز اضافه کرد.
onSubmit(form): void {
   console.log(form);
}
فعلا هدف از این متد، نمایش جزئیات شیء form دریافتی، در کنسول developer tools است.



غیرفعال کردن دکمه‌ی submit در صورت وجود خطاهای اعتبارسنجی

در قسمت بررسی ngForm، یک متغیر محلی را به نام f ایجاد کردیم که به شیء ControlGroup فرم جاری اشاره می‌کند. از این متغیر و خاصیت valid آن می‌توان با کمک property binding به خاصیت disabled یک دکمه، آن‌را به صورت خودکار فعال یا غیرفعال کرد:
<button [disabled]="!f.valid"
        type="submit" class="btn btn-primary">
    Submit
</button>
هر زمانیکه کل فرم از لحاظ اعتبارسنجی مشکلی نداشته باشد، دکمه‌ی submit فعال می‌شود و برعکس.



نمایش فرم افزودن محصولات توسط سیستم Routing

با نحوه‌ی تعریف مسیریابی‌ها در قسمت قبل آشنا شدیم. برای نمایش فرم افزودن محصولات، می‌توان تغییرات ذیل را به فایل app.component.ts اعمال کرد:
//same as before...
import { ProductFormComponent }  from './products/product-form.component';
 
@Component({
    //same as before…
    template: `
                //same as before…
                    <li><a [routerLink]="['AddProduct']">Add Product</a></li>
               //same as before…
    `,
    //same as before…
})
@RouteConfig([
    //same as before…
    { path: '/addproduct', name: 'AddProduct', component: ProductFormComponent }
])
//same as before...
ابتدا به RouteConfig، مسیریابی کامپوننت فرم افزودن محصولات اضافه شده‌است. سپس ماژول این کلاس در ابتدای فایل import شده و در آخر routerLink آن به قالب سایت و منوی بالای سایت اضافه شده‌است.



اتصال المان‌های فرم به مدلی جهت ارسال به سرور

برای اتصال المان‌های فرم به یک مدل، این مدل را به صورت یک خاصیت عمومی، در سطح کلاس کامپوننت فرم، تعریف می‌کنیم:
 productModel = <IProduct>{}; // creates an empty object of an interface
اگر از اینترفیسی مانند IProduct که در قسمت‌های قبل این سری تعریف شد، نیاز است شیء جدیدی ساخته شود، الزاما نیازی نیست تا یک کلاس جدید را از آن مشتق کرد و بعد متغیر new ClassName را تهیه کرد. در TypeScript می‌توان به صورت خلاصه از syntax فوق نیز استفاده کرد.
پس از تعریف خاصیت productModel، اکنون کافی است با استفاده از two-way data binding، آن‌را به المان‌های فرم نسبت دهیم. برای مثال:
<textarea ngControl="description" id="description" required
          #description="ngForm"
          rows="10" type="text" class="form-control"
          [(ngModel)]="productModel.description"></textarea>
در اینجا با استفاده از ngModel و انقیاد دو طرفه، کار اتصال به خاصیت توضیحات شیء محصول انجام شده‌است. اکنون بلافاصله تغییرات اعمالی به فرم، به مدل متناظر منعکس می‌شود و برعکس. این ngModel را به تمام المان‌های ورودی فرم متصل خواهیم کرد.
پس از تعریف یک چنین اتصالی، دیگر نیازی به مقدار دهی پارامتر onSubmit(f.form) نیست. زیرا شیء productModel، در متد onSumbit در دسترس است و این شیء همواره حاوی آخرین تغییرات اعمالی به المان‌های فرم است.

پس از اینکه فرم را به مدل آن متصل کردیم، فایل product.service.ts را گشوده و متد جدید addProduct را به آن اضافه کنید:
addProduct(product: IProduct): Observable<IProduct> {
    let headers = new Headers({ 'Content-Type': 'application/json' }); // for ASP.NET MVC
        let options = new RequestOptions({ headers: headers });
 
    return this._http.post(this._addProductUrl, JSON.stringify(product), options)
        .map((response: Response) => <IProduct>response.json())
        .do(data => console.log("Product: " + JSON.stringify(data)))
        .catch(this.handleError);
}
کار این متد، ارسال شیء محصول به یک اکشن متد برنامه‌ی ASP.NET MVC جاری است. با جزئیات کار با obsevables درمطلب «دریافت اطلاعات از سرور» پیشتر آشنا شده‌ایم.
نکته‌ی مهم اینجا است که content type پیش فرض ارسالی متد post آن، plain text است و در این حالت ASP.NET MVC شیء JSON دریافتی از کلاینت را پردازش نخواهد کرد. بنابراین نیاز است تا هدر content type را به صورت صریحی در اینجا ذکر نمود؛ در غیراینصورت در سمت سرور، شاهد نال بودن مقادیر دریافتی از کاربران خواهیم بود.
امضای سمت سرور متد دریافت اطلاعات از کاربر، چنین شکلی را دارد (تعریف شده در فایل Controllers\HomeController.cs):
 [HttpPost]
public ActionResult AddProduct(Product product)
{

اشیاء هدرها و تنظیمات درخواست، در متد addProduct سرویس ProductService، در ماژول‌های ذیل تعریف شده‌اند که باید به ابتدای فایل product.service.ts اضافه شوند:
 import { Headers, RequestOptions } from 'angular2/http';

پس از تعریف متد addProduct در سرویس ProductService، اکنون با استفاده از ترزیق این سرویس به سازنده‌ی کلاس فرم ثبت یک محصول جدید، می‌توان متد this._productService.addProduct را جهت ارسال productModel به سمت سرور، در متد onSubmit فراخوانی کرد:
//Same as before…
import { IProduct } from './product';
import { ProductService } from './product.service';
 
@Component({
//Same as before…
})
export class ProductFormComponent {
 
    productModel = <IProduct>{}; // creates an empty object of an interface
 
    constructor(private _productService: ProductService, private _router: Router) { }
 
    //Same as before… 

    onSubmit(form): void {
        console.log(form);
        console.log(this.productModel);
 
        this._productService.addProduct(this.productModel)
            .subscribe((product: IProduct) => {
                console.log(`ID: ${product.productId}`);
                this._router.navigate(['Products']);
            });
    }
}
همانطور که ذکر شد، از آنجائیکه شیء productModel حاوی آخرین تغییرات اعمالی توسط کاربر است، اکنون می‌توان پارامتر form متد onSubmit را حذف کرد.
در اینجا پس از فراخوانی متد addProduct، متد subscribe، در انتهای زنجیره، فراخوانی شده‌است. کار آن هدایت کاربر به صفحه‌ی نمایش لیست محصولات است. در اینجا this._router از طریق تزریق وابستگی‌های سرویس مسیریاب به سازنده‌ی کلاس، تامین شده‌است. نمونه‌ی آن‌را در قسمت «افزودن دکمه‌ی back با کدنویسی» مربوطه به مطلب آشنایی با مسیریابی، پیشتر مطالعه کرده‌اید.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MVC5Angular2.part10.zip


خلاصه‌ی بحث

فرم‌های template driven در AngularJS 2.0 به این نحو طراحی می‌شوند:
 1) ابتدا فرم HTML را به حالت معمولی آن طراحی می‌کنیم؛ با تمام المان‌های آن.
 2) به تمام المان‌های فرم، دیراکتیو ngControl را متصل می‌کنیم، تا AngularJS 2.0 آن‌را تبدیل به یک کنترل خاص خودش کند. کنترلی که دارای خواصی مانند valid و touched است.
 3) سپس برای دسترسی به این کنترل ایجاد شده‌ی به صورت ضمنی، یک متغیر محلی آغاز شده‌ی با # را به تمام المان‌ها اضافه می‌کنیم.
 4) اعتبارسنجی‌هایی را مانند required  به المان‌های فرم اضافه می‌کنیم.
 5) از متغیر محلی تعریف شده و ngIf* برای بررسی خواصی مانند valid و touched برای نمایش خطاهای اعتبارسنجی کمک گرفته می‌شود.
 6) پس از تعریف فرم، تعریف ngControlها، تعریف متغیر محلی شروع شده‌ی با # و افزودن خطاهای اعتبارسنجی، اکنون نوبت به ارسال این اطلاعات به سرور است. بنابراین رخداد ngSubmit را باید به متدی در کلاس کامپوننت جاری متصل کرد.
 7) اکنون که با کلیک بر روی دکمه‌ی submit فرم، متد onSubmit متصل به ngSubmit فراخوانی می‌شود، نیاز است بین المان‌های فرم HTML و کلاس کامپوننت، ارتباط برقرار کرد. این‌کار را توسط two-way data binding و تعریف ngModel بر روی تمام المان‌های فرم، انجام می‌دهیم. این ngModelها، به یک خاصیت عمومی که متناظر است با وهله‌ای از شیء مدل فرم، متصل هستند. بنابراین این مدل، در هر لحظه، بیانگر آخرین تغییرات کاربر است و از آن می‌توان برای ارسال اطلاعات به سرور استفاده کرد.
 8) پس از اتصال فرم به کلاس متناظر با آن، اکنون سرویس محصولات را تکمیل کرده و به آن متد HTTP Post را جهت ارسال اطلاعات سمت کاربر، به سرور، اضافه می‌کنیم. در اینجا نکته‌ی مهم، تنظیم content type ارسالی به سمت سرور است. در غیراینصورت فریم ورک سمت سرور قادر به تشخیص JSON بودن این اطلاعات نخواهد شد.
نظرات مطالب
ASP.NET MVC #5
سلام
واقعا از آموزش هایی که میگزارید ممنونم.
من از این سری مقالات علاوه بر MVC خیلی نکته های جالب دیگه رو هم دارم یاد میگیرم.
فقط لطفا سطح آموزش هاتون رو همینطور آروم آروم بالا ببرید تا ما هم بتونیم پا به پای آموزشها یاد بگیریم.
نظرات مطالب
تاریخ شمسی با Extension Method برای DateTime
سلام . من وقتی تاریخ رو به فرمت خورشیدی با همین روش ودر global اعمال میکنم ، در kendoui در grid وقتی حالت ajax() رو فعال میکنم این Error رو میگیرم .
Specified time is not supported in this calendar. It should be between 03/21/0622 00:00:00 (Gregorian date) and 12/31/9999 23:59:59 (Gregorian date), inclusive.
Parameter name: time
اما  در حالت server()  مشکلی نداره . اشکال از کجاست و چه جوری میشه حل کرد ؟
نظرات مطالب
استفاده از LINQ جهت تهیه کدهایی کوتاه‌تر و خواناتر
سلام
تلاش‌هایی به صورت مستقل برای جاوا هم شده (یکپارچه با زبان نیست)
http://xircles.codehaus.org/projects/quaere
اما باز هم پیاده سازی آن در بسیاری از موارد type safety دات نت را ندارد و از رشته‌ها کمک گرفته.
در کل جاوا به دلیل نداشتن معادلی برای lambda expressions که پایه و اساس LINQ را تشکیل می‌دهد، هنوز در این زمینه کار پایه‌ای را انجام نداده است و در کل قسمت LI مربوط به LINQ را ندارد (language integrated)