مطالب
بررسی استراتژی‌های تشخیص تغییرات در برنامه‌های Angular
وقتی تغییری را در اشیاء خود به وجود می‌آورید، Angular بلافاصله متوجه آن‌ها شده و viewها را به روز رسانی می‌کند. هدف از این مکانیزم، اطمینان حاصل کردن از همگام بودن اشیاء مدل‌ها و viewها هستند. آگاهی از نحوه‌ی انجام این عملیات، کمک شایانی را به بالابردن کارآیی یک برنامه‌ی با رابط کاربری پیچیده‌ای می‌کند. یک شیء مدل در Angular عموما به سه طریق تغییر می‌کند:
- بروز رخ‌دادهای DOM مانند کلیک
- صدور درخواست‌های Ajax ایی
- استفاده از تایمرها (setTimer, setInterval)


ردیاب‌های تغییرات در Angular

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


این پیمایش ترتیبی از بالا به پایین، از این جهت صورت می‌گیرد که اطلاعات کامپوننت‌ها از والدین آن‌ها تامین می‌شوند. تشخیص دهنده‌های تغییرات، روشی را جهت ردیابی وضعیت پیشین و فعلی یک کامپوننت ارائه می‌دهد تا Angular بتواند تغییرات رخ‌داده را منعکس کند. اگر Angular گزارش تغییری را از یک تشخیص دهنده‌ی تغییر دریافت کند، کامپوننت مرتبط را مجددا ترسیم کرده و DOM را به روز رسانی می‌کند.


استراتژی‌های تشخیص تغییرات در Angular

برای درک نحوه‌ی عملکرد سیستم تشخیص تغییرات نیاز است با مفهوم value types و reference types در JavaScript آشنا شویم. در JavaScript نوع‌های زیر value type هستند:
• Boolean
• Null
• Undefined
• Number
• String
و نوع‌های زیر Reference type محسوب می‌شوند:
• Arrays
• Objects
• Functions
مهم‌ترین تفاوت بین این دو نوع، این است که برای دریافت مقدار یک value type فقط کافی است از stack memory کوئری بگیریم. اما برای دریافت مقادیر reference types باید ابتدا در جهت یافتن شماره ارجاع آن، از stack memory کوئری گرفته و سپس بر اساس این شماره ارجاع، اصل مقدار آن‌را در heap memory پیدا کنیم.
این دو تفاوت را می‌توان در شکل زیر بهتر مشاهده کرد:



استراتژی Default یا پیش‌فرض تشخیص تغییرات در Angular

همانطور که پیشتر نیز عنوان شد، Angular تغییرات یک شیء مدل را در جهت تشخیص تغییرات و انعکاس آن‌ها به View برنامه، ردیابی می‌کند. در این حالت هر تغییری بین حالت فعلی و پیشین یک شیء مدل برای این منظور بررسی می‌گردد. در اینجا Angular این سؤال را مطرح می‌کند: آیا مقداری در این مدل تغییر یافته‌است؟
اما برای یک reference type می‌توان سؤالات بهتری را مطرح کرد که بهینه‌تر و سریعتر باشند. اینجاست که استراتژی OnPush تشخیص تغییرات مطرح می‌شود.


استراتژی OnPush تشخیص تغییرات در Angular

ایده اصلی استراتژی OnPush تشخیص تغییرات در Angular در immutable فرض کردن reference types نهفته‌است. در این حالت هر تغییری در شیء مدل، سبب ایجاد یک ارجاع جدید به آن در stack memory می‌شود. به این ترتیب می‌توان تشخیص تغییرات بسیار سریعتری را شاهد بود. چون دیگر در اینجا نیازی نیست تمام مقادیر یک شیء را مدام تحت نظر قرار داد. همینقدر که ارجاع آن در stack memory تغییر کند، یعنی مقادیر این شیء در heap memory تغییر یافته‌اند.
در این حالت Angular دو سؤال را مطرح می‌کند: آیا ارجاع به یک reference type در stack memory تغییر یافته‌است؟ اگر بله، آیا مقادیر آن در heap memory تغییر کرده‌اند؟ برای مثال جهت بررسی تغییرات یک آرایه‌ی با 30 عضو، دیگر در ابتدای کار نیازی نیست تا هر 30 عضو آن بررسی شوند (برخلاف حالت پیش‌فرض بررسی تغییرات). در حالت استراتژی OnPush، ابتدا مقدار ارجاع این آرایه در stack memory بررسی می‌شود. اگر تغییری در آن صورت گرفته بود، به معنای تغییری در اعضای آرایه‌است.
استراتژی OnPush در یک کامپوننت به نحو ذیل فعال و انتخاب می‌شود و مقدار پیش‌فرض آن ChangeDetectionStrategy.Default است:
 import {ChangeDetectionStrategy, Component} from '@angular/core';
@Component({
  // ...
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class OnPushComponent {
  // ...
}
استراتژی OnPush تغییرات یک کامپوننت را در حالت‌های ذیل نیز ردیابی می‌کند:
- اگر مقدار یک خاصیت از نوع Input@ تغییر کند.
- اگر یک event handler رخ‌دادی را صادر کند.
- اگر سیستم ردیابی به صورت دستی فراخوانی شود.
- اگر سیستم ردیاب تغییرات child component آن، اجرا شود.


نوع‌های ارجاعی Immutable

همانطور که عنوان شد، شرط کار کردن استراتژی OnPush، داشتن نوع‌های ارجاعی immutable است. اما نوع ارجاعی immutable چیست؟
Immutable بودن به زبان ساده به این معنا است که ما هیچگاه جهت تغییر مقدار خاصیتی در یک شیء، آن خاصیت را مستقیما مقدار دهی نمی‌کنیم؛ بلکه کل شیء را مجددا مقدار دهی می‌کنیم.
برای نمونه در مثال زیر، خاصیت foo شیء before مستقیما مقدار دهی شده‌است:
static mutable() {
  var before = {foo: "bar"};
  var current = before;
  current.foo = "hello";
  console.log(before === current);
  // => true
}
اما اگر بخواهیم با آن به صورت Immutable «رفتار» کنیم، کل این شیء را جهت اعمال تغییرات، مقدار دهی مجدد خواهیم کرد:
static immutable() {
  var before = {foo: "bar"};
  var current = before;
  current = {foo: "hello"};
  console.log(before === current);
  // => false
}
البته باید دقت داشت در هر دو مثال، شیء‌های ایجاد شده در اصل mutable هستند؛ اما در مثال دوم، با این شیء به صورت immutable «رفتار» شده‌است و صرفا «تظاهر» به رفتار immutable با یک شیء ارجاعی صورت گرفته‌است.


معرفی کتابخانه‌ی Immutable.js

جهت ایجاد اشیاء واقعی immutable کتابخانه‌ی Immutable.js توسط Facebook ایجاد شده‌است و برای کار با استراتژی تشخیص تغییرات OnPush در Angular بسیار مناسب است.
برای نصب آن دستور ذیل  را صادر نمائید:
npm install immutable --save
یک نمونه مثال از کاربرد ساختارهای داده‌ی List و Map آن برای کار با آرایه‌ها و اشیاء:
import {Map, List} from 'immutable';

var foobar = {foo: "bar"};
var immutableFoobar = Map(foobar);
console.log(immutableFooter.get("foo"));
// => bar

var helloWorld = ["Hello", "World!"];
var immutableHelloWorld = List(helloWorld);
console.log(immutableHelloWorld.first());
// => Hello
console.log(immutableHelloWorld.last());
// => World!
helloWorld.push("Hello Mars!");
console.log(immutableHelloWorld.last());
// => Hello Mars!


تغییر ارجاع به یک شیء با استفاده از spread properties

const user = {
  name: 'Max',
  age: 30
}
user.age = 31
در این مثال تنها خاصیت age شیء user به روز رسانی می‌شود. بنابراین ارجاع به این شیء تغییر نخواهد کرد و اگر از روش changeDetection: ChangeDetectionStrategy.OnPush استفاده کنیم، رابط کاربری برنامه به روز رسانی نخواهد شد و این تغییر صرفا با بررسی عمیق تک تک خواص این شیء با وضعیت قبلی آن قابل تشخیص است (یا همان حالت پیش فرض بررسی تغییرات در Angular و نه حالت OnPush).
اگر علاقمند به استفاده‌ی از یک کتابخانه‌ی اضافی مانند Immutable.js در کدهای خود نباشید، روش دیگری نیز برای تغییر ارجاع به یک شیء وجود دارد:
const user = {
  name: 'Max',
  age: 30
}
const modifiedUser = { ...user, age: 31 }
در اینجا با استفاده از spread properties یک شیء کاملا جدید ایجاد شده‌است و ارجاع به آن با ارجاع به شیء user یکی نیست.

نمونه‌ی دیگر آن در حین کار با متد push یک آرایه‌است:
export class AppComponent {
  foods = ['Bacon', 'Lettuce', 'Tomatoes'];
 
  addFood(food) {
    this.foods.push(food);
  }
}
متد push، بدون تغییر ارجاعی به آرایه‌ی اصلی، عضوی را به آن آرایه اضافه می‌کند. بنابراین اعضای اضافه شده‌ی به آن نیز توسط استراتژی OnPush قابل تشخیص نیستند. اما اگر بجای متد push از spread operator استفاده کنیم:
addFood(food) {
  this.foods = [...this.foods, food];
}
اینبار this.food به یک شیء کاملا جدید اشاره می‌کند که ارجاع به آن، با ارجاع به شیء this.food قبلی یکی نیست. بنابراین استراتژی OnPush قابلیت تشخیص تغییرات آن‌را دارد.


آگاه سازی دستی موتور تشخیص تغییرات Angular در حالت استفاده‌ی از استراتژی OnPush

تا اینجا دریافتیم که استراتژی OnPush تنها به تغییرات ارجاعات به اشیاء پاسخ می‌دهد و به نحوی باید این ارجاع را با هر به روز رسانی تغییر داد. اما روش دیگری نیز برای وادار کردن این سیستم به تغییر وجود دارد:
 import { Component,  Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
 
@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {
  @Input() data: string[];
 
  constructor(private cd: ChangeDetectorRef) {}
 
  refresh() {
      this.cd.detectChanges();
  }
}
در این کامپوننت از استراتژی OnPush استفاده شده‌است. در اینجا می‌توان همانند قبل با اشیاء و آرایه‌های موجود کار کرد (بدون اینکه ارجاعات به آن‌ها را تغییر دهیم و یا آن‌ها را immutable کنیم) و در پایان کار، متد detectChanges سرویس ChangeDetectorRef را به صورت دستی فراخوانی کرد تا Angular کار رندر مجدد view این کامپوننت را بر اساس تغییرات آن انجام دهد (کل کامپوننت به عنوان یک کامپوننت تغییر کرده به سیستم ردیابی تغییرات معرفی می‌شود).


کار با Observables در حالت استفاده‌ی از استراتژی OnPush

مطلب «صدور رخدادها از سرویس‌ها به کامپوننت‌ها در برنامه‌های Angular» را در نظر بگیرید. Observables نیز ما را از تغییرات رخ‌داده آگاه می‌کنند؛ اما برخلاف immutable objects با هر تغییری که رخ می‌دهد، ارجاع به آن‌ها تغییری نمی‌کند. آن‌ها تنها رخ‌دادی را به مشترکین، جهت اطلاع رسانی از تغییرات صادر می‌کنند.
بنابراین اگر از Observables و استراتژی OnPush استفاده کنیم، چون ارجاع به آن‌ها تغییری نمی‌کند، رخ‌دادهای صادر شده‌ی توسط آن‌ها ردیابی نخواهند شد. برای رفع این مشکل، امکان علامتگذاری رخ‌دادهای Observables به تغییر کرده پیش‌بینی شده‌است.
در اینجا کامپوننتی را داریم که قابلیت صدور رخ‌دادها را از طریق یک BehaviorSubject دارد:
 import { Component } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
 
@Component({ ... })
export class AppComponent {
  foods = new BehaviorSubject(['Bacon', 'Letuce', 'Tomatoes']);
 
  addFood(food) {
     this.foods.next(food);
  }
}
و کامپوننت دیگری توسط خاصیت ورودی data از نوع Observable در متد ngOnInit، مشترک آن خواهد شد:
 import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
 
@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
  @Input() data: Observable<any>;
  foods: string[] = [];
 
  constructor(private cd: ChangeDetectorRef) {}
 
  ngOnInit() {
     this.data.subscribe(food => {
        this.foods = [...this.foods, ...food];
     });
  }
در این حالت چون از ChangeDetectionStrategy.OnPush استفاده می‌شود و ارجاع به this.data این observable با هر بار صدور رخ‌دادی توسط آن، تغییر نمی‌کند، سیستم ردیابی تغییرات آن‌را به عنوان تغییر کرده درنظر نمی‌گیرد. برای رفع این مشکل تنها کافی است رخ‌دادگردان آن‌را با متد markForCheck علامتگذاری کنیم:
ngOnInit() {
  this.data.subscribe(food => {
      this.foods = [...this.foods, ...food];
      this.cd.markForCheck(); // marks path
   });
}
markForCheck به Angular اعلام می‌کند که این مسیر ویژه را در بررسی بعدی سیستم ردیابی تغییرات لحاظ کن.
مطالب
اعتبارسنجی سایتهای چند زبانه در ASP.NET MVC - قسمت اول

اگر در حال تهیه یک سایت چند زبانه هستید و همچنین سری مقالات Globalization در ASP.NET MVC رو دنبال کرده باشید میدانید که با تغییر Culture فایلهای Resource مورد نظر بارگذاری و نوشته‌های سایت تغییر میابند ولی با تغییر Culture رفتار اعتبارسنجی در سمت سرور نیز تغییر و اعتبارسنجی بر اساس Culture فعلی سایت انجام میگیرد. بررسی این موضوع را با یک مثال شروع میکنیم.

یک پروژه وب بسازید سپس به پوشه Models یک کلاس با نام ValueModel اضافه کنید. تعریف کلاس به شکل زیر هست: 

public class ValueModel
{
    [Required]
    [Display(Name = "Decimal Value")]
    public decimal DecimalValue { get; set; }

    [Required]
    [Display(Name = "Double Value")]
    public double DoubleValue { get; set; }

    [Required]
    [Display(Name = "Integer Value")]
    public int IntegerValue { get; set; }

    [Required]
    [Display(Name = "Date Value")]
    public DateTime DateValue { get; set; }
}

به سراغ کلاس HomeController بروید و کدهای زیر را اضافه کنید: 

[HttpPost]
public ActionResult Index(ValueModel valueModel)
{
    if (ModelState.IsValid)
    {
        return Redirect("Index");
    }

    return View(valueModel);
}

Culture را به fa-IR تغییر میدهیم، برای اینکار در فایل web.config در بخش system.web کد زیر اضافه نمایید: 

<globalization culture="fa-IR" uiCulture="fa-IR" />

و در نهایت به سراغ فایل Index.cshtml بروید کدهای زیر رو اضافه کنید:

@using (Html.BeginForm())
{
    <ol>
        <li>
            @Html.LabelFor(m => m.DecimalValue)
            @Html.TextBoxFor(m => m.DecimalValue)
            @Html.ValidationMessageFor(m => m.DecimalValue)
        </li>
        <li>
            @Html.LabelFor(m => m.DoubleValue)
            @Html.TextBoxFor(m => m.DoubleValue)
            @Html.ValidationMessageFor(m => m.DoubleValue)
        </li>
        <li>
            @Html.LabelFor(m => m.IntegerValue)
            @Html.TextBoxFor(m => m.IntegerValue)
            @Html.ValidationMessageFor(m => m.IntegerValue)
        </li>
        <li>
            @Html.LabelFor(m => m.DateValue)
            @Html.TextBoxFor(m => m.DateValue)
            @Html.ValidationMessageFor(m => m.DateValue)
        </li>
        <li>
            <input type="submit" value="Submit"/>
        </li>
    </ol>
}

پرژه را اجرا نمایید و در ٢ تکست باکس اول ٢ عدد اعشاری را و در ٢ تکست باکس آخر یک عدد صحیح و یک تاریخ وارد نمایید و سپس دکمه Submit را بزنید. پس از بازگشت صفحه از سمت سرور در در ٢ تکست باکس اول با این پیامها روبرو میشوید که مقادیر وارد شده نامعتبر میباشند. 

اگر پروژه رو در حالت دیباگ اجرا کنیم و نگاهی به داخل ModelState بیاندازیم، میبینیم که کاراکتر جدا کننده قسمت اعشاری برای fa-IR '/' میباشد که در اینجا برای اعداد مورد نظر کاراکتر '.' وارد شده است. 

برای فایق شدن بر این مشکل یا باید سمت سرور اقدام کرد یا در سمت کلاینت. در بخش اول راه حل سمت کلاینت را بررسی مینماییم. 

در سمت کلاینت برای اینکه کاربر را مجبور به وارد کردن کاراکترهای مربوط به Culture فعلی سایت نماییم باید مقادیر وارد شده را اعتبارسنجی و در صورت معتبر نبودن مقادیر پیام مناسب نشان داده شود. برای اینکار از کتابخانه jQuery Globalize استفاده میکنیم. برای اضافه کردن jQuery Globalize از طریق کنسول nuget فرمان زیر اجرا نمایید: 

PM> Install-Package jquery-globalize

 پس از نصب کتابخانه  اگر به پوشه Scripts نگاهی بیاندازید میبینید که پوشەای با نام jquery.globalize اضافه شده است. درداخل پوشه زیر پوشەی دیگری با نام cultures وجود دارد که در آن Cultureهای مختلف وجود دارد و بسته به نیاز میتوان از آنها استفاده کرد. دوباره به سراغ فایل Index.cshtm بروید و فایلهای جاوا اسکریپتی زیر را به صفحه اضافه کنید:

<script src="~/Scripts/jquery.validate.js"> </script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"> </script>
<script src="~/Scripts/jquery.globalize/globalize.js"> </script>
<script src="~/Scripts/jquery.globalize/cultures/globalize.culture.fa-IR.js"> </script>

در فایل globalize.culture.fa-IR.js کاراکتر جدا کننده اعشاری '.' در نظر گرفته شده است که مجبور به تغییر آن هسیتم. برای اینکار فایل را باز کرده و numberFormat را پیدا کنید و آن را به شکل زیر تغییر دهید: 

numberFormat: {
    pattern: ["n-"],
    ".": "/",
    currency: {
        pattern: ["$n-", "$ n"],
        ".": "/",
        symbol: "ریال"
    }
},

و در نهایت کدهای زیر را به فایل Index.cshtml اضافه کنید و برنامه را دوباره اجرا نمایید:

Globalize.culture('fa-IR');
$.validator.methods.number = function(value, element) {
    if (value.indexOf('.') > 0) {
        return false;
    }
    var splitedValue = value.split('/');
    if (splitedValue.length === 1) {
        return !isNaN(Globalize.parseInt(value));
    } else if (splitedValue.length === 2 && $.trim(splitedValue[1]).length === 0) {
        return false;
    }
    return !isNaN(Globalize.parseFloat(value));
};
};

در خط اول Culture را ست مینمایم و در ادامه نحوه اعتبارسنجی را در unobtrusive validation تغییر میدهیم. از آنجایی که برای اعتبارسنجی عدد وارد شده از تابع parseFloat استفاده میشود، کاراکتر جدا کننده قسمت اعشاری قابل قبول برای این تابع '.' است پس در داخل تابع دوباره '/' به '.' تبدیل میشود و سپس اعتبارسنجی انجام میشود از اینرو اگر کاربر '.' را نیز وارد نماید قابل قبول است به همین دلیل با این خط کد if (value.indexOf('.') > 0) وجود نقطه را بررسی میکنیم تا در صورت وجود '.' پیغام خطا نشان داده شود.در خط بعدی بررسی مینماییم که اگر عدد وارد شده اعشاری نباشد از تابع parseInt  استفاده نماییم. در خط بعدی این حالت را بررسی مینماییم که اگر کاربر عددی همچون /١٢ وارد کرد پیغام خطا صادر شود. 

برای اعتبارسنجی تاریخ شمسی متاسفانه توابع کمکی برای تبدیل تاریخ در فایل globalize.culture.fa-IR.js وجود ندارد ولی اگر نگاهی به فایلهای Culture عربی بیاندازید همه دارای توابع کمکی برای تبدیل تاریج هجری به میلادی هستند به همین دلیل امکان اعتبارسنجی تاریخ شمسی با استفاده از jQuery Globalize میسر نمیباشد. من خودم تعدادی توابع کمکی را به globalize.culture.fa-IR.js اضافه کردەام که از تقویم فارسی آقای علی فرهادی برداشت شده است و با آنها کار اعتبارسنجی را انجام میدهیم. لازم به ذکر است این روش ١٠٠% تست نشده است و شاید راه کاملا اصولی نباشد ولی به هر حال در اینجا توضیح میدهم. در فایل globalize.culture.fa-IR.js قسمت Gregorian_Localized را پیدا کنید و آن را با کدهای زیر جایگزین کنید: 

Gregorian_Localized: {
    firstDay: 6,
    days: {
        names: ["یکشنبه", "دوشنبه", "سه شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه"],
        namesAbbr: ["یکشنبه", "دوشنبه", "سه شنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه"],
        namesShort: ["ی", "د", "س", "چ", "پ", "ج", "ش"]
    },
    months: {
        names: ["ژانویه", "فوریه", "مارس", "آوریل", "می", "ژوئن", "ژوئیه", "اوت", "سپتامبر", "اُکتبر", "نوامبر", "دسامبر", ""],
        namesAbbr: ["ژانویه", "فوریه", "مارس", "آوریل", "می", "ژوئن", "ژوئیه", "اوت", "سپتامبر", "اُکتبر", "نوامبر", "دسامبر", ""]
    },
    AM: ["ق.ظ", "ق.ظ", "ق.ظ"],
    PM: ["ب.ظ", "ب.ظ", "ب.ظ"],
    patterns: {
        d: "yyyy/MM/dd",
        D: "yyyy/MM/dd",
        t: "hh:mm tt",
        T: "hh:mm:ss tt",
        f: "yyyy/MM/dd hh:mm tt",
        F: "yyyy/MM/dd hh:mm:ss tt",
        M: "dd MMMM"
    },
    JalaliDate: {
        g_days_in_month: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
        j_days_in_month: [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29]
    },
    gregorianToJalali: function (gY, gM, gD) {
        gY = parseInt(gY);
        gM = parseInt(gM);
        gD = parseInt(gD);
        var gy = gY - 1600;
        var gm = gM - 1;
        var gd = gD - 1;

        var gDayNo = 365 * gy + parseInt((gy + 3) / 4) - parseInt((gy + 99) / 100) + parseInt((gy + 399) / 400);

        for (var i = 0; i < gm; ++i)
            gDayNo += Globalize.culture().calendars.Gregorian_Localized.JalaliDate.g_days_in_month[i];
        if (gm > 1 && ((gy % 4 == 0 && gy % 100 != 0) || (gy % 400 == 0)))
            /* leap and after Feb */
            ++gDayNo;
        gDayNo += gd;

        var jDayNo = gDayNo - 79;

        var jNp = parseInt(jDayNo / 12053);
        jDayNo %= 12053;

        var jy = 979 + 33 * jNp + 4 * parseInt(jDayNo / 1461);

        jDayNo %= 1461;

        if (jDayNo >= 366) {
            jy += parseInt((jDayNo - 1) / 365);
            jDayNo = (jDayNo - 1) % 365;
        }

        for (var i = 0; i < 11 && jDayNo >= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[i]; ++i) {
            jDayNo -= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[i];
        }
        var jm = i + 1;
        var jd = jDayNo + 1;

        return [jy, jm, jd];
    },
    jalaliToGregorian: function (jY, jM, jD) {
        jY = parseInt(jY);
        jM = parseInt(jM);
        jD = parseInt(jD);
        var jy = jY - 979;
        var jm = jM - 1;
        var jd = jD - 1;

        var jDayNo = 365 * jy + parseInt(jy / 33) * 8 + parseInt((jy % 33 + 3) / 4);
        for (var i = 0; i < jm; ++i) jDayNo += Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[i];

        jDayNo += jd;

        var gDayNo = jDayNo + 79;

        var gy = 1600 + 400 * parseInt(gDayNo / 146097); /* 146097 = 365*400 + 400/4 - 400/100 + 400/400 */
        gDayNo = gDayNo % 146097;

        var leap = true;
        if (gDayNo >= 36525) /* 36525 = 365*100 + 100/4 */ {
            gDayNo--;
            gy += 100 * parseInt(gDayNo / 36524); /* 36524 = 365*100 + 100/4 - 100/100 */
            gDayNo = gDayNo % 36524;

            if (gDayNo >= 365)
                gDayNo++;
            else
                leap = false;
        }

        gy += 4 * parseInt(gDayNo / 1461); /* 1461 = 365*4 + 4/4 */
        gDayNo %= 1461;

        if (gDayNo >= 366) {
            leap = false;

            gDayNo--;
            gy += parseInt(gDayNo / 365);
            gDayNo = gDayNo % 365;
        }

        for (var i = 0; gDayNo >= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.g_days_in_month[i] + (i == 1 && leap) ; i++)
            gDayNo -= Globalize.culture().calendars.Gregorian_Localized.JalaliDate.g_days_in_month[i] + (i == 1 && leap);
        var gm = i + 1;
        var gd = gDayNo + 1;

        return [gy, gm, gd];
    },
    checkDate: function (jY, jM, jD) {
        return !(jY < 0 || jY > 32767 || jM < 1 || jM > 12 || jD < 1 || jD >
            (Globalize.culture().calendars.Gregorian_Localized.JalaliDate.j_days_in_month[jM - 1] + (jM == 12 && !((jY - 979) % 33 % 4))));
    },
    convert: function (value, format) {
        var day, month, year;

        var formatParts = format.split('/');
        var dateParts = value.split('/');
        if (formatParts.length !== 3 || dateParts.length !== 3) {
            return false;
        }

        for (var j = 0; j < formatParts.length; j++) {
            var currentFormat = formatParts[j];
            var currentDate = dateParts[j];
            switch (currentFormat) {
                case 'dd':
                    if (currentDate.length === 2 || currentDate.length === 1) {
                        day = currentDate;
                    } else {
                        year = currentDate;
                    }
                    break;
                case 'MM':
                    month = currentDate;
                    break;
                case 'yyyy':
                    if (currentDate.length === 4) {
                        year = currentDate;
                    } else {
                        day = currentDate;
                    }
                    break;
                default:
                    return false;
            }
        }

        year = parseInt(year);
        month = parseInt(month);
        day = parseInt(day);
        var isValidDate = Globalize.culture().calendars.Gregorian_Localized.checkDate(year, month, day);
        if (!isValidDate) {
            return false;
        }

        var grDate = Globalize.culture().calendars.Gregorian_Localized.jalaliToGregorian(year, month, day);
        var shDate = Globalize.culture().calendars.Gregorian_Localized.gregorianToJalali(grDate[0], grDate[1], grDate[2]);

        if (year === shDate[0] && month === shDate[1] && day === shDate[2]) {
            return true;
        }

        return false;
    }
},

روال کار در تابع convert به اینصورت است که ابتدا تاریخ وارد شده را بررسی مینماید تا معتبر بودن آن معلوم شود به عنوان مثال اگر تاریخی مثل 1392/12/31 وارد شده باشد و در ادامه برای بررسی بیشتر تاریخ یک بار به میلادی و تاریخ میلادی دوباره به شمسی تبدیل میشود و با تاریخ وارد شده مقایسه میشود و در صورت برابری تاریخ معتبر اعلام میشود. در فایل Index.cshtml کدهای زیر اضافی نمایید:

$.validator.methods.date = function (value, element) {
    return Globalize.culture().calendars.Gregorian_Localized.convert(value, 'yyyy/MM/dd');
};

برای اعتبارسنجی تاریخ میتوانید از ٢ فرمت استفاده کنید:

١ – yyyy/MM/dd

٢ – dd/MM/yyyy

البته از توابع اعتبارسنجی تاریخ میتوانید به صورت جدا استفاده نمایید و لزومی ندارد آنها را همراه با jQuery Globalize بکار ببرید. در آخر خروجی کار به این شکل است:

در کل استفاده از jQuery Globalize برای اعتبارسنجی در سایتهای چند زبانه به نسبت خوب میباشد و برای هر زبان میتوانید از culture مورد نظر استفاده نمایید. در قسمت دوم این مطلب به بررسی بخش سمت سرور میپردازیم.

مطالب
بررسی مفهوم Captured Variable در زبان سی شارپ
Capturing Outer Variables  
یک عبارت لامبدا می‌تواند از متغیرهای محلی و یا پارامترهای متدی که در آن تعریف شده است، استفاده نماید (Outer Variables). این متغیرها را captured variables می‌نامند. عبارت لامبدایی که از این متغیرها استفاده می‌کند، closure نامیده می‌شود. برای مثال:
static void Main()
{
 int factor = 2;
 Func<int, int> multiplier = n => n * factor;
 Console.WriteLine (multiplier (3)); // 6
}
در کد فوق multiplier یک delegate می‌باشد که ورودی صحیح n را گرفته و در مقدار factor ضرب کرده و بر می‌گرداند.

عبارت لامبدا زمانی ارزیابی می‌شود که delegate متناظر فراخوانی (Invoke) گردد؛ نه زمانیکه متغیر اصطلاحا capture می‌شود:
int factor = 2;
Func<int, int> multiplier = n => n * factor;
factor = 10;
Console.WriteLine (multiplier (3)); // 30
در کد فوق در زمانی که multiplier فراخوانی می‌شود مقدار factor برابر 10 ارزیابی شده و لذا عدد 30 چاپ خواهد شد.

عبارات لامبدا خود می‌توانند captured variable‌ها را تغییر دهند:
int seed = 0;
Func<int> natural = () => seed++;
Console.WriteLine (natural()); // 0
Console.WriteLine (natural()); // 1
Console.WriteLine (seed); // 2
در کد فوق natural یک delegate بدون ورودی و با یک خروجی integer می‌باشد. در ابتدا متغیر محلی seed تعریف شده و با مقدار اولیه 0 مقداردهی می‌شود. با هر بار اجرای natural مقدار seed به اندازه 1 واحد افزایش می‌یابد.
طول عمر(lifetime) متغیرهای captured شده در حد طول عمر delegate افزایش پیدا می‌کند. در مثال زیر متغیر محلی seed در حالت معمول، محدوده دیدی (scope) در حد تعریف این متغیر تا پایان اجرای متد دارد. اما از آنجاییکه در اینجا متغیر captured شده است، طول عمر آن در حدا طول عمر delegate افزایش می‌یابد: theNatural
static Func<int> Natural()
{
 int seed = 0;
 return () => seed++; // Returns a closure
}
static void Main()
{
 Func<int> theNatural = Natural();
 Console.WriteLine (theNatural ()); // 0
 Console.WriteLine (theNatural ()); // 1
}
اگر متغیر seed را در بدنه عبارت لامبدا تعریف نماییم، این متغیر برای هر بار اجرای delegate یکتا خواهد بود:
static Func<int> Natural()
{
 return() => { int seed = 0; return seed++; };
}
static void Main()
{
 Func<int> natural = Natural();
 Console.WriteLine (natural()); // 0
 Console.WriteLine (natural()); // 0
}

نکته: پیاده سازی پروسه Capture شدن متغیر، به این صورت است که این متغیرها به عنوان یک فیلد از یک کلاس (با سطح دسترسی private) در نظر گرفته می‌شوند. زمانیکه متد فراخوانی شد، کلاس مزبور وهله سازی شده و طول عمر آن به  طول عمر delegate گره می‌خورد.

Capturing iteration variables
در حلقه for، وقتی که متغیر حلقه توسط یک عبارت لامبدا capture می‌گردد، #C با آن متغیر طوری رفتار می‌کند که گویی در خارج از حلقه تعریف شده‌است و این بدان معناست که در هر بار تکرار حلقه، مقدار یکسانی برای متغیر در نظر گرفته می‌شود. کد زیر 333 را در خروجی چاپ می‌کند(بجای 012). 
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
actions [i] = () => Console.Write (i);
foreach (Action a in actions) a(); // 333
دلیل این موضوع این است که در هنگام اجرای delegate ها، هر delegate مقدار i را برابر مقدار آن در زمان اجرا می‌بیند و این مقدار در زمان اجرا برابر با 3 می‌باشد.
با نوشتن کد زیر می‌توان درک بهتری از موضوع پیدا کرد. 
Action[] actions = new Action[3];
int i = 0;
actions[0] = () => Console.Write (i);
i = 1;
actions[1] = () => Console.Write (i);
i = 2;
actions[2] = () => Console.Write (i);
i = 3;
foreach (Action a in actions) a(); // 333
اگر بخواهیم خروجی 012 چاپ شود راه حل به شرح زیر خواهد بود:
Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
{
 int loopScopedi = i;
 actions [i] = () => Console.Write (loopScopedi);
}
foreach (Action a in actions) a(); // 012
زیرا هر متغیر loopScopedi در هر بار تکرار حلقه مجددا تعریف می‌گردد و لذا هر بار متغیر متفاوتی capture می‌گردد.
مطالب
خواندنی‌های 21 فروردین

Velocity راه حل جامع caching مایکروسافت برای ASP.Net است که جزئی از دات نت فریم ورک 4 خواهد بود.


پروژه سورس بازی است که از بسیاری از ماژول‌های جدید دات نت فریم ورک استفاده می‌کند و نکته‌ی جالب نگارش جدید آن مهاجرت از MS Ajax به jQuery Ajax است (قسمت اجکس آن کلا از صفر بر اساس jQuery بازنویسی شده است).

  • چک لیستی که پیش از برپایی یک وب سایت باید بررسی شود.
البته این چک لیست عمومی است و صرفنظر از تکنولوژی بکار گرفته شده در مورد تمامی سایت‌ها صادق است.

ExtJs هم یک دیگر از فریم ورک‌های جاوا اسکریپتی است که با jQuery قابل قیاس است.

  • دمویی در مورد ویندوز Azure و سرویس‌های مبتنی بر آن

مطالب
احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت اول - معرفی و ایجاد ساختار برنامه
قصد داریم در طی یک سری مطلب، یک کلاینت Angular 5.x را برای مطلب «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» تهیه کنیم. البته این سری، مستقل از قسمت سمت سرور آن تهیه خواهد شد و صرفا در حد دریافت توکن از سرور و یا ارسال مشخصات کاربر جهت لاگین، نیاز بیشتری به قسمت سمت سرور آن ندارد و تاکید آن بر روی مباحث سمت کلاینت Angular است. بنابراین اینکه چگونه این توکن را تولید می‌کنید، در اینجا اهمیتی ندارد و کلیات آن با تمام روش‌های پیاده سازی سمت سرور (حتی مطلب «پیاده سازی JSON Web Token با ASP.NET Web API 2.x») سازگار است.
این سری شامل بررسی موارد ذیل خواهد بود:
  1. قسمت اول - معرفی و ایجاد ساختار برنامه
  2. قسمت دوم - سرویس اعتبارسنجی
  3. قسمت سوم - ورود به سیستم
  4. قسمت چهارم - به روز رسانی خودکار توکن‌ها
  5. قسمت پنجم - محافظت از مسیرها
  6. قسمت ششم - کار با منابع محافظت شده‌ی سمت سرور 


پیشنیازها
- آشنایی با Angular CLI
- آشنایی با مسیریابی‌ها در Angular
- آشنایی با فرم‌های مبتنی بر قالب‌ها


همچنین اگر پیشتر Angular CLI را نصب کرده‌اید، قسمت «به روز رسانی Angular CLI» ذکر شده‌ی در مطلب «Angular CLI - قسمت اول - نصب و راه اندازی» را نیز اعمال کنید. در این سری از angular/cli: 1.6.0@ استفاده شده‌است.


ایجاد ساختار اولیه و مسیریابی‌های آغازین مثال این سری

در ادامه، یک پروژه‌ی جدید مبتنی بر Angular CLI را به نام ASPNETCore2JwtAuthentication.AngularClient به همراه تنظیمات ابتدایی مسیریابی آن ایجاد می‌کنیم:
> ng new ASPNETCore2JwtAuthentication.AngularClient --routing

به علاوه، قصد استفاده‌ی از بوت استرپ را نیز داریم. به همین جهت ابتدا به ریشه‌ی پروژه وارد شده و سپس دستور ذیل را صادر کنید، تا بوت استرپ نصب شود و پرچم save آن سبب به روز رسانی فایل package.json نیز گردد:
> npm install bootstrap --save
پس از آن نیاز است به فایل angular-cli.json. مراجعه کرده و شیوه‌نامه‌ی بوت استرپ را تعریف کنیم:
  "apps": [
    {
      "styles": [
    "../node_modules/bootstrap/dist/css/bootstrap.min.css",
        "styles.css"
      ],
به این ترتیب، به صورت خودکار این شیوه نامه به همراه توزیع برنامه حضور خواهد داشت و نیازی به تعریف مستقیم آن در فایل index.html نیست.

در ادامه برای تکمیل مثال جاری، دو کامپوننت جدید خوش‌آمدگویی و همچنین یافتن نشدن مسیرها را به برنامه اضافه می‌کنیم:
>ng g c welcome
>ng g c PageNotFound
که سبب ایجاد کامپوننت‌های src\app\welcome\welcome.component.ts و src\app\page-not-found\page-not-found.component.ts خواهند شد؛ به همراه به روز رسانی خودکار فایل src\app\app.module.ts جهت تکمیل قسمت declarations آن:
@NgModule({
  declarations: [
    AppComponent,
    WelcomeComponent,
    PageNotFoundComponent
  ],
سپس فایل src\app\app-routing.module.ts را به نحو ذیل تکمیل نمائید:
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { WelcomeComponent } from './welcome/welcome.component';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
  { path: 'welcome', component: WelcomeComponent },
  { path: '', redirectTo: 'welcome', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
در اینجا زمانیکه کاربر ریشه‌ی سایت را درخواست می‌کند، به کامپوننت welcome هدایت خواهد شد.
همچنین مدیریت مسیریابی آدرس‌های ناموجود در سایت نیز با تعریف ** صورت گرفته‌است.


ایجاد ماژول Authentication و تعریف کامپوننت لاگین

کامپوننت‌های احراز هویت و اعتبارسنجی کاربران را در ماژولی به نام Authentication قرار خواهیم داد. بنابراین ماژول جدید آن‌را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد می‌کنیم:
>ng g m Authentication -m app.module --routing
با این خروجی
  create src/app/authentication/authentication-routing.module.ts (257 bytes)
  create src/app/authentication/authentication.module.ts (311 bytes)
  update src/app/app.module.ts (696 bytes)
اگر به سطر آخر آن دقت کنید، فایل app.module.ts را نیز به صورت خودکار به روز رسانی کرده‌است:
import { EmployeeRoutingModule } from './employee/employee-routing.module';
@NgModule({
  imports: [
    BrowserModule,
    AppRoutingModule,
    AuthenticationModule
  ]
در اینجا AuthenticationModule را به انتهای لیست imports افزوده‌است که نیاز به اندکی تغییر دارد و باید آن‌را پیش از AppRoutingModule تعریف کرد. علت این است که AppRoutingModule، دارای تعریف مسیریابی ** یا catch all است که آن‌را جهت مدیریت مسیرهای یافت نشده به برنامه افزوده‌ایم. بنابراین اگر ابتدا AppRoutingModule تعریف شود و سپس AuthenticationModule، هیچگاه فرصت به پردازش مسیریابی‌های ماژول اعتبارسنجی نمی‌رسد؛ چون مسیر ** پیشتر برنده شده‌است.
بنابراین فایل app.module.ts چنین تعاریفی را پیدا می‌کند:
import { EmployeeModule } from './employee/employee.module';
@NgModule({
  imports: [
    BrowserModule,
    AuthenticationModule,
    AppRoutingModule 
  ]

در ادامه کامپوننت جدید لاگین را به این ماژول اضافه می‌کنیم:
>ng g c Authentication/Login
با این خروجی
  create src/app/Authentication/login/login.component.html (24 bytes)
  create src/app/Authentication/login/login.component.ts (265 bytes)
  create src/app/Authentication/login/login.component.css (0 bytes)
  update src/app/Authentication/authentication.module.ts (383 bytes)
اگر به سطر آخر آن دقت کنید، کار به روز رسانی فایل ماژول authentication، جهت درج این کامپوننت جدید، در قسمت declarations فایل authentication.module.ts نیز به صورت خودکار انجام شده‌است:
import { LoginComponent } from "./login/login.component";

@NgModule({
  declarations: [LoginComponent]
})

در ادامه می‌خواهیم قالب این کامپوننت را در منوی اصلی سایت قابل دسترسی کنیم. به همین جهت به فایل src/app/authentication/authentication-routing.module.ts مراجعه کرده و مسیریابی این کامپوننت را تعریف می‌کنیم:
import { LoginComponent } from "./login/login.component";

const routes: Routes = [
  { path: "login", component: LoginComponent }
];
ابتدا کامپوننت لاگین import شده و سپس آرایه‌ی Routes، مسیری را به این کامپوننت تعریف کرده‌است.


ایجاد ماژول‌های Core و Shared

در مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها» در مورد اهمیت ایجاد ماژول‌های Core و Shared بحث شد. در اینجا نیز این دو ماژول را ایجاد خواهیم کرد.
فایل src\app\core\core.module.ts، جهت به اشتراک گذاری سرویس‌های singleton سراسری برنامه، یک چنین ساختاری را پیدا می‌کند:
import { NgModule, SkipSelf, Optional, } from "@angular/core";
import { CommonModule } from "@angular/common";
import { RouterModule } from "@angular/router";

// import RxJs needed operators only once
import "./services/rxjs-operators";

import { BrowserStorageService } from "./browser-storage.service";

@NgModule({
  imports: [CommonModule, RouterModule],
  exports: [
    // components that are used in app.component.ts will be listed here.
  ],
  declarations: [
    // components that are used in app.component.ts will be listed here.
  ],
  providers: [
    // global singleton services of the whole app will be listed here.
    BrowserStorageService
  ]
})
export class CoreModule {
  constructor( @Optional() @SkipSelf() core: CoreModule) {
    if (core) {
      throw new Error("CoreModule should be imported ONLY in AppModule.");
    }
  }
}
در اینجا از BrowserStorageService مطلب «ذخیره سازی اطلاعات در مرورگر توسط برنامه‌های Angular» استفاده شده‌است تا در سراسر برنامه در دسترس باشد. از آن در جهت ذخیره سازی توکن دریافتی از سرور در مرورگر کاربر، استفاده خواهیم کرد.
همچنین سطر "import "./services/rxjs-operators نیز از مطلب «روش‌هایی برای مدیریت بهتر عملگرهای RxJS در برنامه‌های Angular» کمک می‌گیرد تا مدام نیاز به import عملگرهای rxjs نباشد.

و ساختار فایل src\app\shared\shared.module.ts جهت به اشتراک گذاری کامپوننت‌های مشترک بین تمام ماژول‌ها، به صورت ذیل است:
import { NgModule, ModuleWithProviders } from "@angular/core";
import { CommonModule } from "@angular/common";

@NgModule({
  imports: [
    CommonModule
  ],
  entryComponents: [
    // All components about to be loaded "dynamically" need to be declared in the entryComponents section.
  ],
  declarations: [
    // common and shared components/directives/pipes between more than one module and components will be listed here.
  ],
  exports: [
    // common and shared components/directives/pipes between more than one module and components will be listed here.
    CommonModule
  ]
  /* No providers here! Since they’ll be already provided in AppModule. */
})
export class SharedModule {
  static forRoot(): ModuleWithProviders {
    // Forcing the whole app to use the returned providers from the AppModule only.
    return {
      ngModule: SharedModule,
      providers: [ /* All of your services here. It will hold the services needed by `itself`. */]
    };
  }
}

و در آخر تعاریف این دو ماژول جدید به فایل src\app\app.module.ts اضافه خواهند شد:
import { FormsModule } from "@angular/forms";
import { HttpClientModule } from "@angular/common/http";

import { CoreModule } from "./core/core.module";
import { SharedModule } from "./shared/shared.module";

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    CoreModule,
    SharedModule.forRoot(),
    AuthenticationModule,
    AppRoutingModule
  ]
})
export class AppModule { }
در اینجا «FormsModule» و «HttpClientModule جدید» اضافه شده از Angular 4.3 را نیز import کرده‌ایم.


افزودن کامپوننت Header

در ادامه می‌خواهیم لینکی را به این مسیریابی جدید در نوار راهبری بالای سایت اضافه کنیم. همچنین قصد نداریم فایل app.component.html را با تعاریف آن شلوغ کنیم. به همین جهت یک کامپوننت هدر جدید را برای این منظور اضافه می‌کنیم:
> ng g c Header
با این خروجی:
  create src/app/header/header.component.html (25 bytes)
  create src/app/header/header.component.ts (269 bytes)
  create src/app/header/header.component.css (0 bytes)
  update src/app/app.module.ts (1069 bytes)
سپس به فایل src\app\header\header.component.html مراجعه کرده و آن‌را به صورت ذیل تغییر می‌دهیم:
<nav>
  <div>
    <div>
      <a [routerLink]="['/']">{{title}}</a>
    </div>
    <ul>
      <li role="menuitem" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
        <a [routerLink]="['/welcome']">Home</a>
      </li>
      <li role="menuitem" routerLinkActive="active">
        <a queryParamsHandling="merge" [routerLink]="['/login']">Login</a>
      </li>
    </ul>
  </div>
</nav>
که title آن نیز به صورت ذیل تامین می‌شود:
export class HeaderComponent implements OnInit {
    title = "Angular.Jwt.Core";

در آخر به فایل app.component.html مراجعه کرده و selector این کامپوننت را در آن درج می‌کنیم:
<app-header></app-header>
<div>
  <router-outlet></router-outlet>
</div>
زمانیکه یک کامپوننت فعالسازی می‌شود، قالب آن در router-outlet نمایش داده خواهد شد. app-header نیز کار نمایش nav-bar را انجام می‌دهد.

تا اینجا اگر دستور ng serve -o را صادر کنیم (کار build درون حافظه‌ای، جهت محیط توسعه و نمایش خودکار برنامه در مرورگر)، چنین خروجی در مرورگر نمایان خواهد شد (البته می‌توان پنجره‌ی کنسول ng serve را باز نگه داشت تا کار watch را به صورت خودکار انجام دهد؛ این روش سریعتر و به همراه build تدریجی است):



انتقال کامپوننت‌هایی که در app.component.ts استفاده می‌شوند به CoreModule

 با توجه به مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها»، کامپوننت‌هایی که در app.component.ts مورد استفاده قرار می‌گیرند، باید به Core Module منتقل شوند و قسمت declarations فایل app.module.ts از آن‌ها خالی گردد. به همین جهت پوشه‌ی جدید src\app\core\component را ایجاد کرده و سپس پوشه‌ی src\app\header را به آنجا منتقل می‌کنیم (با تمام فایل‌های درون آن).
پس از آن، تعریف HeaderComponent را از قسمت declarations مربوط به AppModule حذف کرده و آن‌را به دو قسمت exports و declarations مربوط به CoreModule منتقل می‌کنیم:
import { HeaderComponent } from "./component/header/header.component";

@NgModule({
  exports: [
    // components that are used in app.component.ts will be listed here.
    HeaderComponent
  ],
  declarations: [
    // components that are used in app.component.ts will be listed here.
    HeaderComponent
  ]
})
export class CoreModule {


کدهای کامل این سری را از اینجا می‌توانید دریافت کنید.
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه‌ی ASPNETCore2JwtAuthentication.AngularClient وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد (و یا همان اجرای فایل ng-serve.bat). همچنین باید به پوشه‌ی ASPNETCore2JwtAuthentication.WebApp نیز مراجعه کرده و فایل dotnet_run.bat را اجرا کنید، تا توکن سرور برنامه نیز فعال شود.