نظرات مطالب
Blazor 5x - قسمت ششم - مبانی Blazor - بخش 3 - چرخه‌های حیات کامپوننت‌ها
یک نکته‌ی تکمیلی: تنظیم پویای عنوان صفحات در برنامه‌های Blazor

برای تنظیم پویای عنوان یک صفحه‌ی وب، نیاز است با DOM API مرورگر به صورت مستقیم کار کرد. برای مثال فایل wwwroot\main.js را که مدخل آن به کامپوننت Host_ و یا صفحه‌ی index.html اضافه می‌شود، به صورت زیر تکمیل می‌کنیم:
window.JsFunctionHelper = {
  blazorSetTitle: function (title) {
    document.title = title;
  }
};
اکنون می‌خواهیم این متد جاوااسکریپتی را که مستقیما با شیء document کار می‌کند، در کامپوننت جدید Client\Shared\PageTitle.razor استفاده کنیم:
@inject IJSRuntime JSRuntime

@code
{
   [Parameter]
   public string Title { get; set; }

   protected override async Task OnParametersSetAsync()
   {
      await JSRuntime.InvokeVoidAsync("JsFunctionHelper.blazorSetTitle", Title);
   }
}
در اینجا کامپوننت جدیدی تعریف شده‌است که به محض تنظیم مقدار پارامتر عنوان آن، سبب فراخوانی متد جاوا اسکریپتی blazorSetTitle می‌شود. برای نمونه روش استفاده‌ی از آن در کامپوننت Counter، جهت نمایش عنوانی پویا، به محض تغییر مقدار شمارشگر، به صورت زیر می‌تواند باشد:
@page "/counter"

<PageTitle Title="@GetPageTitle()" />

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }

    private string GetPageTitle() => $"Counter ({currentCount})";
}
این روش در برنامه‌های Blazor Server کار نخواهد کرد و در حین فراخوانی متد InvokeVoidAsync یک NullReferenceException مشاهده می‌شود؛ چون این نوع برنامه‌های Blazor Server به همراه یک مرحله‌ی pre-render در سمت سرور هستند که ابتدا، کار تهیه‌ی HTML ای را که باید به سمت مرورگر ارسال کنند، به پایان می‌رسانند. در این مرحله خبری از DOM نیست که بتوان به آن دسترسی یافت و تغییری را در آن ایجاد کرد.
برای رفع این مشکل همانطور که در مطلب جاری نیز عنوان شد، باید از روال رویدادگردان OnAfterRenderAsync استفاده کرد. در این حالت کدهای کامپوننت PageTitle.razor به صورت زیر تغییر می‌کنند:
@inject IJSRuntime JSRuntime

@code
{
   [Parameter]
   public string Title { get; set; }

   protected override async Task OnAfterRenderAsync(bool firstRender)
   {
      await JSRuntime.InvokeVoidAsync("JsFunctionHelper.blazorSetTitle", Title);
   }
}
روال رویدادگردان OnAfterRenderAsync پس از اینکه کار بارگذاری و تشکیل کامل DOM در مرورگر انجام شد، فراخوانی می‌شود. به همین جهت دیگر دسترسی به شیء document.title، سبب بروز یک NullReferenceException نخواهد شد.


یک نکته: قرار است در Blazor 6x، کامپوننت‌های جدید Title، Link و Meta جهت تنظیم اطلاعات تگ head صفحه، به صورت استاندارد اضافه شوند:
<Title Value="@title" />
<Meta name="description" content="Modifying the head from a Blazor component." />
<Link href="main.css" rel="stylesheet" />
مطالب دوره‌ها
ایجاد یک کلاس جدید پویا و وهله‌ای از آن در زمان اجرا توسط Reflection.Emit
توانایی‌های Reflection.Emit صرفا به ایجاد متدهایی کاملا جدید و پویا در زمان اجرا محدود نمی‌شود. برای نمونه کلاس ذیل را درنظر بگیرید:
    public class Person
    {
        private string _name;
        public string Name
        {
            get { return _name; }
        }

        public Person(string name)
        {
            _name = name;
        }
    }
در ادامه قصد داریم معادل این کلاس را به همراه وهله‌ای از آن، به صورتی کاملا پویا در زمان اجرا ایجاد کرده (تصور کنید این کلاس در برنامه وجود خارجی نداشته و تنها جهت درک بهتر کدهای IL ادامه بحث، معرفی گردیده است) و سپس مقداری را به سازنده آن ارسال کنیم.
کدهای کامل و توضیحات این typeBuilder را در ادامه ملاحظه می‌کنید:
using System;
using System.Reflection;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static void Main(string[] args)
        {
            //اسمبلی محل قرارگیری کدهای پویای نهایی در اینجا تعیین می‌شود
            //حالت دسترسی به آن اجرایی درنظر گرفته شده، امکان تعیین حالت‌های دیگری مانند ذخیره سازی نیز وجود دارد
            var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
                                      name: new AssemblyName("Demo"), access: AssemblyBuilderAccess.Run);

            // اکنون داخل این اسمبلی یک ماژول جدید را برای قرار دادن کلاس جدید خود تعریف می‌کنیم
            var moduleBuilder = assemblyBuilder.DefineDynamicModule(name: "PersonModule");

            // کار ساخت نوع و کلاس جدید شخص عمومی از اینجا شروع می‌شود
            var typeBuilder = moduleBuilder.DefineType(name: "Person", attr: TypeAttributes.Public);

            // افزودن فیلد خصوصی نام تعریف شده در سطح کلاس شخص
            var nameField = typeBuilder.DefineField(fieldName: "_name",
                                                    type: typeof(string),
                                                    attributes: FieldAttributes.Private);

            // تعریف سازنده عمومی کلاس شخص که دارای یک آرگومان رشته‌ای است
            var ctor = typeBuilder.DefineConstructor(
                                    attributes: MethodAttributes.Public,
                                    callingConvention: CallingConventions.Standard,
                                    parameterTypes: new[] { typeof(string) });
            // تعریف بدنه سازنده کلاس شخص
            // در اینجا فیلد خصوصی تعریف شده در سطح کلاس باید مقدار دهی شود
            var ctorIL = ctor.GetILGenerator();
            // نکته‌ای در مورد سازنده‌ها
            ctorIL.Emit(OpCodes.Ldarg_0); // اندیس صفر در سازنده کلاس به وهله‌ای از کلاس جاری اشاره می‌کند
            ctorIL.Emit(OpCodes.Ldarg_1); // بارگذاری آرگومان سازنده و قرار دادن آن روی پشته
            // مقدار دهی فیلد خصوصی نام که به وهله‌ای از کلاس جاری و مقدار آرگومان دریافتی نیاز دارد
            ctorIL.Emit(OpCodes.Stfld, nameField);
            ctorIL.Emit(OpCodes.Ret); // پایان کار سازنده

            // تعریف خاصیت رشته‌ای نام در کلاس شخص
            var nameProperty = typeBuilder.DefineProperty(
                                                name: "Name",
                                                attributes: PropertyAttributes.HasDefault,
                                                returnType: typeof(string),
                                                parameterTypes: null); // خاصیت پارامتر ورودی ندارد

            var namePropertyGetMethod = typeBuilder.DefineMethod(
                                                name: "get_Name",
                                                attributes: MethodAttributes.Public |
                //متد ویژه‌ای است که توسط کامپایلر پردازش و تشخیص داده می‌شود
                                                            MethodAttributes.SpecialName |
                                                            MethodAttributes.HideBySig,
                                                returnType: typeof(string),
                                                parameterTypes: Type.EmptyTypes);
            // اتصال گت متد به خاصیت رشته‌ای نام که پیشتر تعریف شد
            nameProperty.SetGetMethod(namePropertyGetMethod);

            // بدنه گت متد در اینجا تعریف خواهد شد
            var namePropertyGetMethodIL = namePropertyGetMethod.GetILGenerator();
            namePropertyGetMethodIL.Emit(OpCodes.Ldarg_0); // بارگذاری اشاره‌گری به وهله‌ای از کلاس جاری در پشته
            namePropertyGetMethodIL.Emit(OpCodes.Ldfld, nameField); // بارگذاری فیلد نام
            namePropertyGetMethodIL.Emit(OpCodes.Ret);

            var t = typeBuilder.CreateType(); // نهایی سازی کار ایجاد نوع جدید

            // ایجاد وهله‌ای از نوع جدید که پارامتری رشته‌ای به سازنده آن ارسال می‌شود
            var instance = Activator.CreateInstance(t, "Vahid");

            // دسترسی به خاصیت نام
            var nProperty = t.GetProperty("Name");
            // و دریافت مقدار آن برای نمایش
            var result = nProperty.GetValue(instance, null);

            Console.WriteLine(result);
        }
    }
}
در اینجا ایجاد یک کلاس جدید با ایجاد یک TypeBuilder واقع در فضای نام  System.Reflection.Emit آغاز می‌شود. پیش از آن نیاز است یک اسمبلی پویا و ماژولی در آن‌را برای قرار دادن کدهای پویای این TypeBuilder ایجاد کنیم. توضیحات مرتبط با دستورات مختلف را به صورت کامنت در کدهای فوق ملاحظه می‌کنید. با استفاده از TypeBuilder و متد DefineField آن می‌توان یک فیلد در سطح کلاس ایجاد کرد و یا توسط متد DefineConstructor آن، سازنده کلاس را با امضایی ویژه تعریف نمود و سپس با دسترسی به ILGenerator آن، بدنه این سازنده را همانند متدهای پویا ایجاد کرد.
اگر به کدهای فوق دقت کرده باشید، متد get_Name به خاصیت Name انتساب داده شده است. علت را در قسمت معرفی اجمالی Reflection زمانیکه لیست متدهای کلاس Person را نمایش دادیم، ملاحظه کرده‌اید. تمام خواص Auto implemented در دات نت، هر چند ظاهر ساده‌ای دارند اما در عمل به دو متد get_Name و set_Name در کدهای IL توسط کامپایلر تبدیل می‌شوند. به همین جهت در اینجا نیاز بود تا get_Name را نیز تعریف کنیم.


چند مثال تکمیلی
Populating a PropertyGrid using Reflection.Emit
Dynamically adding RaisePropertyChanged to MVVM Light ViewModels using Reflection.Emit
پاسخ به بازخورد‌های پروژه‌ها
خطای property not found
- از راه دور امکان دیباگ کدهای شما رو ندارم.
- مطمئن هستید خطا از همین قسمت است؟ چون این قسمت خواص تو در تو باید کار کنه. یک مثال
- آیا منبع داده شما lazy load است یا eager load؟ بهتره یکبار در حالت eager load که مثلا از متد Include استفاده کرده هم، مساله رو تست کنید.
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت پنجم - بررسی چرخه‌ی حیات کامپوننت‌ها
در قسمت قبل، نگاهی داشتیم به 4 نوع مختلف data binding در AngularJS 2.0. در قسمت جاری می‌خواهیم کیفیت کدهای کامپوننت لیست محصولات را با strong typing بهبود بخشیده و همچنین چرخه‌ی حیات کامپوننت‌ها را به همراه ایجاد custom pipes بررسی کنیم.


افزودن strong typing به کامپوننت نمایش لیست محصولات

یکی از مزایای کار با TypeScript امکان انتساب نوع‌های مشخص یا سفارشی، به متغیرها و اشیاء تعریف شده‌است. برای مثال تاکنون هر خاصیت تعریف شده‌ای دارای نوع است. اما هنوز نوعی را برای آرایه‌ی محصولات تعریف نکرده‌ایم و نوع آن، نوع پیش فرض any است که برخلاف رویه‌ی متداول کار با TypeScript است.
برای تعریف نوع‌های سفارشی می‌توان از اینترفیس‌های TypeScript استفاده کرد. یک اینترفیس، قراردادی است که نحوه‌ی تعریف تعدادی خاصیت و متد به هم مرتبط را مشخص می‌کند. سپس کلاس‌های مختلف می‌توانند با پیاده سازی این اینترفیس، قرارداد تعریف شده‌ی در آن را عملی کنند. همچنین از اینترفیس‌ها می‌توان به عنوان یک data type جدید نیز استفاده کرد. البته ES 5 و ES 6 از اینترفیس‌ها پشتیبانی نمی‌کنند و تعریف آن‌ها در TypeScript صرفا جهت کمک به کامپایلر، برای یافتن خطاها، پیش از اجرای برنامه است و به کدهای جاوا اسکریپتی معادلی ترجمه نمی‌شوند.

در ادامه برای تکمیل مثال این سری، فایل جدید App\products\product.ts را به پروژه اضافه کنید؛ با این محتوا:
export interface IProduct {
    productId: number;
    productName: string;
    productCode: string;
    releaseDate: string;
    price: number;
    description: string;
    starRating: number;
    imageUrl: string;
}
یک اینترفیس، با واژه‌ی کلیدی interface تعریف شده و سپس نام آن تعریف می‌شود. مرسوم است این نام با I شروع شود؛ هرچند الزامی نیست و در بسیاری از اینترفیس‌های AngularJS 2.0 از این روش نامگذاری استفاده نشده‌است.
همچنین از آنجائیکه این اینترفیس را در یک فایل ts مجزا قرار داده‌ایم، برای اینکه بتوان از آن در سایر قسمت‌های برنامه استفاده کرد، نیاز است در ابتدای آن، واژه‌ی کلیدی export را نیز ذکر کرد.

پس از تعریف این اینترفیس، برای استفاده از آن به عنوان یک data type جدید، ابتدا ماژول آن import خواهد شد و سپس از نام آن به عنوان نوع داده‌ی جدیدی، استفاده می‌شود. برای این منظور فایل product-list.component.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
import { Component } from 'angular2/core';
import { IProduct } from './product';
 
@Component({
    selector: 'pm-products',
    templateUrl: 'app/products/product-list.component.html'
})
export class ProductListComponent {
    // as before ...

    products: IProduct[] = [
        // as before ...
    ]; 

    // as before ...
}
در اینجا ابتدا IProduct را import و سپس بجای any، نوع جدید IProduct را جهت مشخص سازی نوع آرایه‌ی products تعریف کرده‌ایم.
مزیت اینکار این است که برای مثال در اینجا اگر در لیست اعضای آرایه‌ی products، نام خاصیتی اشتباه تایپ شده باشد یا حتی بجای عدد، از رشته استفاده شده باشد، بلافاصله در ادیتور مورد استفاده، خطای مرتبط گوشزد شده و همچنین این فایل دیگر کامپایل نخواهد شد. به علاوه اینبار برای تعریف خواص اعضای آرایه‌ی products، ادیتور مورد استفاده، intellisense را نیز دراختیار ما قرار می‌دهد و کاملا مشخص است که چه اعضایی مدنظر هستند و نوع آن‌ها چیست.



مدیریت cssهای هر کامپوننت به صورت مجزا

هنگام ساخت یک قالب یا template، در بسیاری از اوقات نیاز است css مرتبط با آن نیز، منحصر به همان قالب بوده و نشتی نداشته باشد. برای مثال زمانیکه یک کامپوننت را درون کامپوننتی دیگر قرار می‌دهیم، باید css آن نیز در دسترس قرار بگیرد و css فعلی کامپوننت دربرگیرنده را بازنویسی نکند. روش‌‌های مختلفی برای مدیریت این مساله وجود دارند:
الف) تعریف شیوه نامه‌ها به صورت inline داخل خود قالب‌ها. این حالت، مشکلات نگهداری و استفاده‌ی مجدد را دارد.
ب) تعریف شیوه نامه‌ها در یک فایل خارجی css و سپس لینک کردن آن به صفحه‌ای اصلی یا index.html
در این حالت به ازای هر فایل، یکبار باید این تعریف در صفحه‌ای اصلی سایت صورت گیرد. همچنین این فایل‌ها می‌توانند مقادیر یکدیگر را بازنویسی کرده و بر روی هم تاثیر بگذارند.
ج) تعریف شیوه نامه‌ها به همراه تعریف کامپوننت. این روشی است که توسط AngularJS توصیه شده‌است و نگهداری و مقیاس پذیری آن ساده‌تر است.
تزئین کننده‌ی Component به همراه دو خاصیت دیگر به نام‌های styles و stylesUrl نیز می‌باشد.
در حالت استفاده از خاصیت styles، شیوه‌نامه‌ی متناظر با کامپوننت، در همانجا به صورت inline تعریف می‌شود:
 @Component({
    //...
    styles: ['thead {color: blue;}']
})
همانطور که مشاهده می‌کنید، خاصیت styles به صورت یک آرایه تعریف شده‌است و امکان پذیرش چندین شیوه نامه‌ی جدا شده‌ی با کاما را دارد.
روش بهتر، استفاده از خاصیت styleUrls است که در آن می‌توان مسیر یک یا چند فایل css را مشخص کرد:
 @Component({
     //...
     styleUrls: ['app/products/product-list.component.css']
})
این خاصیت نیز یک آرایه را می‌پذیرد و در اینجا می‌توان مسیر چندین فایل css را در صورت نیاز ذکر کرد.

برای آزمایش آن فایل جدید product-list.component.css را به پوشه‌ی products مثال این سری اضافه کنید؛ با این محتوا:
thead {
  color: #337AB7;
}
سپس لینک این فایل را به مجموعه خواص کامپوننت لیست محصولات (product-list.component.ts) اضافه می‌کنیم:
@Component({
    selector: 'pm-products',
    templateUrl: 'app/products/product-list.component.html',
    styleUrls: ['app/products/product-list.component.css']
})
export class ProductListComponent {
   //...
در این حالت اگر برنامه را اجرا کنید، رنگ سرستون‌های جدول محصولات به نحو ذیل تغییر کرده‌اند:


یک نکته

شیوه نامه‌ای که به این صورت توسط AngularJS 2.0 اضافه می‌شود، با سایر شیوه نامه‌های موجود تداخل نخواهد کرد. علت آن‌را در تصویر ذیل که با استفاده از developer tools مرورگرها قابل بررسی است، می‌توان مشاهده کرد:


در اینجا AngularJS 2.0، با ایجاد ویژگی‌های سفارشی خودکار (attributes) میدان دید css را کنترل می‌کند. به این ترتیب شیوه نامه‌ی کامپوننت یک، که درون کامپوننت دو قرار گرفته‌است، نشتی نداشته و بر روی سایر قسمت‌های صفحه تاثیری نخواهد گذاشت؛ برخلاف شیوه نامه‌هایی که به صورت متداولی به صفحه‌ی اصلی سایت لینک شده‌‌اند.


بررسی چرخه‌ی حیات کامپوننت‌ها

هر کامپوننت دارای چرخه‌ی حیاتی است که توسط AngularJS 2.0 مدیریت می‌شود و شامل مراحلی مانند ایجاد، رندر، ایجاد و رندر فرزندان آن، پردازش تغییرات آن و در نهایت تخریب آن کامپوننت می‌شود. برای اینکه بتوان با برنامه نویسی به این مراحل چرخه‌ی حیات یک کامپوننت دسترسی یافت، تعدادی life cycle hook طراحی شده‌اند. سه مورد از مهم‌ترین life cycle hooks شامل موارد ذیل هستند:
الف) OnInit: از این hook برای انجام کارهای آغازین یک کامپوننت مانند دریافت اطلاعات از سرور، استفاده می‌شود.
ب) OnChanges: از آن جهت انجام اعمالی پس از تغییرات input properties استفاده می‌شود.
خواص ورودی و همچنین کار با سرور را در قسمت‌های بعدی بررسی خواهیم کرد.
ج) OnDestroy: از آن جهت پاکسازی منابع اختصاص داده شده استفاده می‌شود.

برای استفاده‌ی از این hookها، نیاز است اینترفیس آن‌ها را پیاده سازی کنیم. از آنجائیکه AngularJS 2.0 نیز با TypeScript نوشته شده‌است، به همراه تعدادی اینترفیس از پیش تعریف شده می‌باشد. برای مثال به ازای هر life cycle hook، یک اینترفیس تعریف شده در آن وجود دارد. برای نمونه اینترفیس hook ایی به نام OnInit، دقیقا همان OnInit، نام دارد (و با I شروع نشده‌است):
 export class ProductListComponent implements OnInit {
پس از ذکر implements OnInit در انتهای تعریف کلاس، اکنون باید ماژول مرتبط با آن نیز جهت شناسایی این اینترفیس import شود:
 import { Component, OnInit } from 'angular2/core';
و دست آخر متد ngOnInit تعریف شده‌ی در این اینترفیس باید توسط کلاس پیاده سازی کننده‌ی آن تامین شود:
ngOnInit(): void {
    console.log('In OnInit');
}
نام این متدها عموما شروع شده با ng و ختم شده به نام اینترفیس hook متناظر هستند؛ مانند ngOnInit فوق.

به عنوان تمرین، فایل product-list.component.ts را گشوده و سه مرحله‌ی implements سپس import و در آخر تعریف متد ngOnInit فوق را به آن اضافه کنید.
در ادامه برنامه را اجرا کرده و به کنسول developer tools مرورگر خود جهت مشاهده‌ی console.log فوق مراجعه کنید:



ساخت یک Pipe سفارشی جهت فعال سازی textbox فیلتر کردن محصولات

همانطور که در قسمت قبل نیز عنوان شد، کار pipes، تغییر اطلاعات حاصل از data binding، پیش از نمایش آن‌ها در رابط کاربری است و AngularJS 2.0 به همراه تعدادی pipe توکار است؛ مانند currency، percent و غیره. در ادامه قصد داریم یک pipe سفارشی را ایجاد کنیم تا بر روی حلقه‌ی ngFor* نمایش لیست محصولات تاثیرگذار شود و همچنین ورودی خود را از مقدار وارد شده‌ی توسط کاربر دریافت کند.
برای این منظور، یک فایل جدید را به نام product-filter.pipe.ts به پوشه‌ی products اضافه کنید. سپس کدهای آن‌را به نحو ذیل تغییر دهید:
import { PipeTransform, Pipe } from 'angular2/core';
import { IProduct } from './product';
 
@Pipe({
    name: 'productFilter'
})
export class ProductFilterPipe implements PipeTransform {
 
    transform(value: IProduct[], args: string[]): IProduct[] {
        let filter: string = args[0] ? args[0].toLocaleLowerCase() : null;
        return filter ? value.filter((product: IProduct) =>
            product.productName.toLocaleLowerCase().indexOf(filter) != -1) : value;
    }
}
برای تعریف یک pipe سفارشی جدید، کار با پیاده سازی اینترفیس PipeTransform شروع می‌شود. این اینترفیس دارای متدی است به نام transform که امضای آن‌را در کدهای فوق ملاحظه می‌کنید. کار آن اعمال تغییرات بر روی value دریافتی و سپس بازگشت آن است. بنابراین اولین پارامتر آن، مقادیر اصلی را که قرار است تغییر کنند، مشخص می‌کند. در اینجا نوع آن‌را از نوع اینترفیسی که در ابتدای بحث تعریف کردیم، تعیین کرده‌ایم. پارامتر دوم آن، لیست پارامترها و آرگومان‌های اختیاری این فیلتر را مشخص می‌کند.
برای مثال در اینجا می‌خواهیم شرایط فیلتر محصولات وارد شده‌ی توسط کاربر را دریافت کنیم.
خروجی این متد نیز از نوع آرایه‌ای از IProduct تعریف شده‌است؛ از این جهت که نتیجه نهایی فیلتر اطلاعات نیز آرایه‌ای از همین نوع است. کار این pipe پیاده سازی متد contains به صورت غیرحساس به کوچکی و بزرگی حروف است.
سپس بلافاصله بالای نام این کلاس، از یک decorator جدید به نام Pipe استفاده شده‌است تا به AngularJS 2.0 اعلام شود، این کلاس، صرفا یک کلاس معمولی نیست و یک Pipe است.
در ابتدای فایل هم importهای لازم جهت تعریف اینترفیس‌های مورد استفاده‌ی در این ماژول، ذکر شده‌اند.

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

پس از تعریف این pipe سفارشی، برای استفاده‌ی از آن در یک template، به فایل product-list.component.html مراجعه کرده و سپس ngFor* آن‌را به نحو ذیل تغییر می‌دهیم:
 <tr *ngFor='#product of products | productFilter:listFilter'>
همانطور که ملاحظه می‌کنید، نام این pipe جدید که در decorator مرتبط با آن، توسط خاصیت name مشخص گردیده‌است، ذکر شده‌است. پس از آن یک : قرار گرفته‌است که مشخص کننده‌ی پارامتر اول این pipe است که در اینجا خاصیت listFilter تعریف شده‌ی در قسمت قبل را به آن انتساب داده‌ایم.
اگر از قسمت قبل به خاطر داشته باشید، این خاصیت را توسط two-way binding به روز می‌کنیم (اطلاعات وارد شده‌ی در textbox، بلافاصله به این خاصیت منعکس می‌شوند و برعکس):
 <input type='text'  [(ngModel)]='listFilter' />
تا اینجا این pipe را در قالب لیست محصولات بکار بردیم؛ اما کامپوننت آن نمی‌داند که این pipe را باید از کجا تامین کند. به همین جهت فایل product-list.component.ts را گشوده و خاصیت pipes را به نحو ذیل مقدار دهی کنید:
import { Component, OnInit } from 'angular2/core';
import { IProduct } from './product';
import { ProductFilterPipe } from './product-filter.pipe';
 
@Component({
    selector: 'pm-products',
    templateUrl: 'app/products/product-list.component.html',
    styleUrls: ['app/products/product-list.component.css'],
    pipes: [ProductFilterPipe]
})
export class ProductListComponent implements OnInit {
   //...
در اینجا دو کار صورت گرفته‌است. ابتدا افزودن pipe جدید ProductFilterPipe به لیست خاصیت pipes کامپوننت و سپس import ماژول آن درابتدای فایل.

اکنون اگر برنامه را اجرا کنید، خروجی ذیل را مشاهده خواهید کرد:


در اینجا چون مقدار فیلتر وارد شده‌ی پیش فرض، cart است، فقط ردیف Garden Cart نمایش داده شده‌است. اگر این مقدار را خالی کنیم، تمام ردیف‌ها نمایش داده می‌شوند و اگر برای مثال ham را جستجو کنیم، فقط ردیف Hammer نمایش داده می‌شود.


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


خلاصه‌ی بحث

- اینترفیس‌ها یکی از روش‌های بهبود strong typing برنامه‌های AngularJS 2.0 هستند.
- جهت مدیریت بهتر شیوه‌نامه‌های هر کامپوننت بهتر است از روش styleUrls استفاده شود تا از نشتی‌های تعاریف شیوه‌نامه‌ها جلوگیری گردد.
- از life cycle hooks برای مدیریت رخدادهای مرتبط با طول عمر یک کامپوننت استفاده می‌شود؛ برای مثال دریافت اطلاعات از سرور و یا پاکسازی منابع مصرفی.
- تعریف یک pipe سفارشی با پیاده سازی اینترفیس PipeTransform انجام می‌شود. سپس نام این Pipe، به قالب مدنظر اضافه شده و در ادامه نیاز است کامپوننت استفاده کننده‌ی از این قالب را نیز از وجود این Pipe مطلع کرد.
نظرات مطالب
ایجاد پروژه‌ی «کتابخانه» توسط Angular CLI 6.0
اگر در پروژه  angular-template-driven-forms-lab که از مسیر‌های مطلق استفاده شده است و baseUrl به src  تنظیم شده است (در فایل  tsconfig.json ) کتابخانه ای اضافه کنیم
ng generate library my-lib
در زمان import کردن ماژول my-lib ناشناخته است . اگر baseUrl را به (  /.  ) یا (  . ) تنظیم کنیم باز مشکل وجود دارد.
 baseUrl باید به چه مقداری تنظیم کرد ؟
نظرات اشتراک‌ها
نگارش نهایی RTL Bootstrap 3 ویرایش Less منتشر شد!
در سایت نیوگت عضو شوید و سپس به آن لاگین کنید. به این ترتیب با مراجعه به هر کتابخانه‌ای که در آنجا آپلود شده، یک لینک download در کنار صفحه، سمت چپ ظاهر می‌شود. با کلیک بر روی آن فایل nupkg آن کتابخانه قابل دریافت خواهد بود. این فایل در حقیقت یک فایل zip است. بنابراین کار کردن با محتویات آن ساده‌است.
به صورت خلاصه:
لینک اصلی کتابخانه: https://www.nuget.org/packages/Twitter.Bootstrap.RTL.Less/3.0.0
لینک دانلود آن: https://www.nuget.org/api/v2/package/Twitter.Bootstrap.RTL.Less/3.0.0  
مطالب
ارتقاء به NH 3.2 - قسمت دوم

پیشتر مطلبی را در مورد 18 مقاله‌ای که اکثر حالت‌های Mapping موجود در NHibernate را خلاصه کرده بود، مطالعه کردید.
یک مورد هم در این مطلب به نظر در مقایسه با Fluent NHibernate درنظر گرفته نشده است و آن هم بحث AutoMapping است. Fluent NHibernate این قابلیت را دارد که بر اساس تعاریف کلاس‌های شما و روابط بین آن‌ها به صورت خودکار نگاشت‌ها را تشکیل دهید. یعنی خودش مباحث ارتباط‌های یک به چند و چند به چند و غیره را در پشت صحنه به صورت خودکار تولید کند؛ بدون حتی یک سطر کدنویسی اضافی. فقط حداکثر یک سری IAutoMappingOverride و همچنین تعدادی Convention نامگذاری را هم می‌توان جهت تنظیمات این سیستم تمام خودکار اعمال کرد. مثلا توسط IAutoMappingOverride، یکی از خاصیت‌های کلاس را به صورت Unique معرفی کرد و مابقی هم به صورت خودکار توسط قابلیت Autommaping نگاشت می‌شوند. یا توسط Convention نامگذاری سفارشی خود، به Fluent NHibernate اعلام می‌کنیم که من علاقمندم نام مثلا کلیدهای خارجی تشکیل شده بجای اعدادی منحصربفرد، از روش ویژه‌ای که تعیین می‌کنم، ساخته شوند. اینجا است که به نظر من کار با NHibernate حتی از Entity framework هم ساده‌تر است (یا ساده‌تر شده است).
قابلیت AutoMapping یاد شده، در سیستم جدید توکار Mapping by code هم وجود دارد. فقط چون جایی به صورت درست و درمان مستند نشده، همه دور خودشان می‌چرخند! به همین جهت مثالی را در این زمینه آماده کردم که موارد زیر را پوشش می‌دهد:
- نحوه اعمال تنظیمات بانک اطلاعاتی با کدنویسی در NH3,2
- نحوه یکپارچه سازی این روش جدید با کتابخانه NHibernate Validator
- استفاده از NHibernate Validator جهت تنظیم طول فیلدهای بانک اطلاعاتی تولیدی به صورت خودکار
- نحوه تعریف قراردادهای نامگذاری ویژه (مثلا نام جداول تولید شده به صورت خودکار،‌ برخلاف نام موجودیت‌ها، جمع باشد نه مفرد)
- اضافه کردن قابلیت تشخیص many-to-many به auto-mapping موجود در NH3,2 (برای تشخیص این مورد به کمک امکانات مهیای پیش فرض NH3,2، باید اندکی کدنویسی کرد)
- نحوه بازنویسی قراردادهای پیش فرض auto-mapping. مثلا اگر قرار باشد همه چیز خودکار شود اما یکی از فیلدها به صورت unique معرفی شود چکار باید کرد.
- نحوه ذخیره اطلاعات mapping کامپایل شده در فایل فایل و سپس بارگذاری خودکار آن در حین اجرای برنامه جهت بالا بردن سرعت بارگذاری اولیه برنامه.



مطالب
استفاده از EF در اپلیکیشن های N-Tier : قسمت هفتم
در قسمت قبلی مدیریت همزمانی در بروز رسانی‌ها را بررسی کردیم. در این قسمت مرتب سازی (serialization) پراکسی‌ها در سرویس‌های WCF را بررسی خواهیم کرد.


مرتب سازی پراکسی‌ها در سرویس‌های WCF

فرض کنید یک پراکسی دینامیک (dynamic proxy) از یک کوئری دریافت کرده اید. حال می‌خواهید این پراکسی را در قالب یک آبجکت CLR سریال کنید. هنگامی که آبجکت‌های موجودیت را بصورت POCO-based پیاده سازی می‌کنید، EF بصورت خودکار یک آبجکت دینامیک مشتق شده را در زمان اجرا تولید می‌کند که dynamix proxy نام دارد. این آبجکت برای هر موجودیت POCO تولید می‌شود. این آبجکت پراکسی بسیاری از خواص مجازی (virtual) را بازنویسی می‌کند تا عملیاتی مانند ردیابی تغییرات و بارگذاری تنبل را انجام دهد.

فرض کنید مدلی مانند تصویر زیر دارید.


ما از کلاس ProxyDataContractResolver برای deserialize کردن یک آبجکت پراکسی در سمت سرور و تبدیل آن به یک آبجکت POCO روی کلاینت WCF استفاده می‌کنیم. مراحل زیر را دنبال کنید.


  • در ویژوال استودیو پروژه جدیدی از نوع WCF Service Application بسازید. یک Entity Data Model به پروژه اضافه کنید و جدول Clients را مطابق مدل مذکور ایجاد کنید.
  • کلاس POCO تولید شده توسط EF را باز کنید و کلمه کلیدی virtual را به تمام خواص اضافه کنید. این کار باعث می‌شود EF کلاس‌های پراکسی دینامیک تولید کند. کد کامل این کلاس در لیست زیر قابل مشاهده است.
public class Client
{
    public virtual int ClientId { get; set; }
    public virtual string Name { get; set; }
    public virtual string Email { get; set; }
}
نکته: بیاد داشته باشید که هرگاه مدل EDMX را تغییر می‌دهید، EF بصورت خودکار کلاس‌های موجودیت‌ها را مجددا ساخته و تغییرات مرحله قبلی را بازنویسی می‌کند. بنابراین یا باید این مراحل را هر بار تکرار کنید، یا قالب T4 مربوطه را ویرایش کنید. اگر هم از مدل Code-First استفاده می‌کنید که نیازی به این کار‌ها نخواهد بود.

  • ما نیاز داریم که DataContractSerializer از یک کلاس ProxyDataContractResolver استفاده کند تا پراکسی کلاینت را به موجودیت کلاینت برای کلاینت سرویس WCF تبدیل کند. یعنی client proxy به client entity تبدیل شود، که نهایتا در اپلیکیشن کلاینت سرویس استفاده خواهد شد. بدین منظور یک ویژگی operation behavior می‌سازیم و آن را به متد ()GetClient در سرویس الحاق می‌کنیم. برای ساختن ویژگی جدید از کدی که در لیست زیر آمده استفاده کنید. بیاد داشته باشید که کلاس ProxyDataContractResolver در فضای نام Entity Framework قرار دارد.
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Data.Objects;

namespace Recipe8
{
    public class ApplyProxyDataContractResolverAttribute : 
        Attribute, IOperationBehavior
    {
        public void AddBindingParameters(OperationDescription description,
            BindingParameterCollection parameters)
        {
        }
        public void ApplyClientBehavior(OperationDescription description,
            ClientOperation proxy)
        {
            DataContractSerializerOperationBehavior
                dataContractSerializerOperationBehavior =
                    description.Behaviors
                    .Find<DataContractSerializerOperationBehavior>();
                dataContractSerializerOperationBehavior.DataContractResolver =
                    new ProxyDataContractResolver();
        }
        public void ApplyDispatchBehavior(OperationDescription description,
            DispatchOperation dispatch)
        {
            DataContractSerializerOperationBehavior
                dataContractSerializerOperationBehavior =
                    description.Behaviors
                    .Find<DataContractSerializerOperationBehavior>();
            dataContractSerializerOperationBehavior.DataContractResolver =
                new ProxyDataContractResolver();
        }
        public void Validate(OperationDescription description)
        {
        }
    }
}
  • فایل IService1.cs را باز کنید و کد آن را مطابق لیست زیر تکمیل نمایید.
[ServiceContract]
public interface IService1
{
    [OperationContract]
    void InsertTestRecord();
    [OperationContract]
    Client GetClient();
    [OperationContract]
    void Update(Client client);
}
  • فایل IService1.svc.cs را باز کنید و پیاده سازی سرویس را مطابق لیست زیر تکمیل کنید.
public class Client
{
    [ApplyProxyDataContractResolver]
    public Client GetClient()
    {
        using (var context = new EFRecipesEntities())
        {
            context.Cofiguration.LazyLoadingEnabled = false;
            return context.Clients.Single();
        }
    }
    public void Update(Client client)
    {
        using (var context = new EFRecipesEntities())
        {
            context.Entry(client).State = EntityState.Modified;
            context.SaveChanges();
        }
    }
    public void InsertTestRecord()
    {
        using (var context = new EFRecipesEntities())
        {
            // delete previous test data
            context.ExecuteSqlCommand("delete from [clients]");
            // insert new test data
            context.ExecuteStoreCommand(@"insert into
                [clients](Name, Email) values ('Armin Zia','armin.zia@gmail.com')");
        }
    }
}
  • حال پروژه جدیدی از نوع Console Application به راه حل جاری اضافه کنید. این اپلیکیشن، کلاینت تست ما خواهد بود. پروژه سرویس را ارجاع کنید و کد کلاینت را مطابق لیست زیر تکمیل نمایید.
using Recipe8Client.ServiceReference1;

namespace Recipe8Client
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var serviceClient = new Service1Client())
            {
                serviceClient.InsertTestRecord();
                
                var client = serviceClient.GetClient();
                Console.WriteLine("Client is: {0} at {1}", client.Name, client.Email);
                
                client.Name = "Armin Zia";
                client.Email = "arminzia@live.com";
                serviceClient.Update(client);
                
                client = serviceClient.GetClient();
                Console.WriteLine("Client changed to: {0} at {1}", client.Name, client.Email);
            }
        }
    }
}
اگر اپلیکیشن کلاینت را اجرا کنید با خروجی زیر مواجه خواهید شد.

Client is: Armin Zia at armin.zia@gmail.com
Client changed to: Armin Zia at arminzia@live.com



شرح مثال جاری

مایکروسافت استفاده از آبجکت‌های POCO با WCF را توصیه می‌کند چرا که مرتب سازی (serialization) آبجکت موجودیت را ساده‌تر می‌کند. اما در صورتی که از آبجکت‌های POCO ای استفاده می‌کنید که changed-based notification دارند (یعنی خواص موجودیت را با virtual علامت گذاری کرده اید و کلکسیون‌های خواص پیمایشی از نوع ICollection هستند)، آنگاه EF برای موجودیت‌های بازگشتی کوئری‌ها پراکسی‌های دینامیک تولید خواهد کرد.

استفاده از پراکسی‌های دینامیک با WCF دو مشکل دارد. مشکل اول مربوط به سریال کردن پراکسی است. کلاس DataContractSerializer تنها قادر به serialize/deserialize کردن انواع شناخته شده (known types) است. در مثال جاری این یعنی موجودیت Client. اما از آنجا که EF برای موجودیت‌ها پراکسی می‌سازد، حالا باید آبجکت پراکسی را سریال کنیم، نه خود کلاس Client را. اینجا است که از DataContractResolver استفاده می‌کنیم. این کلاس می‌تواند حین سریال کردن آبجکت ها، نوعی را به نوع دیگر تبدیل کند. برای استفاده از این کلاس ما یک ویژگی سفارشی ساختیم، که پراکسی‌ها را به کلاس‌های POCO تبدیل می‌کند. سپس این ویژگی را به متد ()GetClient اضافه کردیم. این کار باعث می‌شود که پراکسی دینامیکی که توسط متد ()GetClient برای موجودیت Client تولید می‌شود، به درستی سریال شود.

مشکل دوم استفاده از پراکسی‌ها با WCF مربوط به بارگذاری تبل یا Lazy Loading می‌شود. هنگامی که DataContractSerializer موجودیت‌ها را سریال می‌کند، تمام خواص موجودیت را دستیابی خواهد کرد که منجر به اجرای lazy-loading روی خواص پیمایشی می‌شود. مسلما این رفتار را نمی‌خواهیم. برای رفع این مشکل، مشخصا قابلیت بارگذاری تنبل را خاموش یا غیرفعال کرده ایم.

نظرات مطالب
معرفی پروژه فروشگاهی Iris Store
با سلام و تشکر بابت این پروژه سورس باز
ولی چرا لینک مربوط به مشاهده نمونه آنلاین فروشگاه آیریس در ابتدای صفحه کار نمی‌کند
مطالب
احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت ششم - کار با منابع محافظت شده‌ی سمت سرور
پس از تکمیل کنترل دسترسی‌ها به قسمت‌های مختلف برنامه بر اساس نقش‌های انتسابی به کاربر وارد شده‌ی به سیستم، اکنون نوبت به کار با سرور و دریافت اطلاعات از کنترلرهای محافظت شده‌ی آن است.



افزودن کامپوننت دسترسی به منابع محافظت شده، به ماژول Dashboard

در اینجا قصد داریم صفحه‌ای را به برنامه اضافه کنیم تا در آن بتوان اطلاعات کنترلرهای محافظت شده‌ی سمت سرور، مانند MyProtectedAdminApiController (تنها قابل دسترسی توسط کاربرانی دارای نقش Admin) و MyProtectedApiController (قابل دسترسی برای عموم کاربران وارد شده‌ی به سیستم) را دریافت و نمایش دهیم. به همین جهت کامپوننت جدیدی را به ماژول Dashboard اضافه می‌کنیم:
 >ng g c Dashboard/CallProtectedApi
سپس به فایل dashboard-routing.module.ts ایجاد شده مراجعه کرده و مسیریابی کامپوننت جدید ProtectedPage را اضافه می‌کنیم:
import { CallProtectedApiComponent } from "./call-protected-api/call-protected-api.component";

const routes: Routes = [
  {
    path: "callProtectedApi",
    component: CallProtectedApiComponent,
    data: {
      permission: {
        permittedRoles: ["Admin", "User"],
        deniedRoles: null
      } as AuthGuardPermission
    },
    canActivate: [AuthGuard]
  }
];
توضیحات AuthGuard و AuthGuardPermission را در قسمت قبل مطالعه کردید. در اینجا هدف این است که تنها کاربران دارای نقش‌های Admin و یا User قادر به دسترسی به این مسیر باشند.
لینکی را به این صفحه نیز در فایل header.component.html به صورت ذیل اضافه خواهیم کرد تا فقط توسط کاربران وارد شده‌ی به سیستم (isLoggedIn) قابل مشاهده باشد:
<li *ngIf="isLoggedIn" routerLinkActive="active">
        <a [routerLink]="['/callProtectedApi']">‍‍Call Protected Api</a>
</li>


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

در ادامه می‌خواهیم دو دکمه را بر روی صفحه قرار دهیم تا اطلاعات کنترلرهای محافظت شده‌ی سمت سرور را بازگشت دهند. دکمه‌ی اول قرار است تنها برای کاربر Admin قابل مشاهده باشد و دکمه‌ی دوم توسط کاربری با نقش‌های Admin و یا User.
به همین جهت call-protected-api.component.ts را به صورت ذیل تغییر می‌دهیم:
import { Component, OnInit } from "@angular/core";
import { AuthService } from "../../core/services/auth.service";

@Component({
  selector: "app-call-protected-api",
  templateUrl: "./call-protected-api.component.html",
  styleUrls: ["./call-protected-api.component.css"]
})
export class CallProtectedApiComponent implements OnInit {

  isAdmin = false;
  isUser = false;
  result: any;

  constructor(private authService: AuthService) { }

  ngOnInit() {
    this.isAdmin = this.authService.isAuthUserInRole("Admin");
    this.isUser = this.authService.isAuthUserInRole("User");
  }

  callMyProtectedAdminApiController() {
  }

  callMyProtectedApiController() {
  }
}
در اینجا دو خاصیت عمومی isAdmin و isUser، در اختیار قالب این کامپوننت قرار گرفته‌اند. مقدار دهی آن‌ها نیز توسط متد isAuthUserInRole که در قسمت قبل توسعه دادیم، انجام می‌شود. اکنون که این دو خاصیت مقدار دهی شده‌اند، می‌توان از آن‌ها به کمک یک ngIf، به صورت ذیل در قالب call-protected-api.component.html جهت مخفی کردن و یا نمایش قسمت‌های مختلف صفحه استفاده کرد:
<button *ngIf="isAdmin" (click)="callMyProtectedAdminApiController()">
  Call Protected Admin API [Authorize(Roles = "Admin")]
</button>

<button *ngIf="isAdmin || isUser" (click)="callMyProtectedApiController()">
  Call Protected API ([Authorize])
</button>

<div *ngIf="result">
  <pre>{{result | json}}</pre>
</div>


دریافت اطلاعات از کنترلرهای محافظت شده‌ی سمت سرور

برای دریافت اطلاعات از کنترلرهای محافظت شده، باید در قسمتی که HttpClient درخواست خود را به سرور ارسال می‌کند، هدر مخصوص Authorization را که شامل توکن دسترسی است، به سمت سرور ارسال کرد. این هدر ویژه را به صورت ذیل می‌توان در AuthService تولید نمود:
  getBearerAuthHeader(): HttpHeaders {
    return new HttpHeaders({
      "Content-Type": "application/json",
      "Authorization": `Bearer ${this.getRawAuthToken(AuthTokenType.AccessToken)}`
    });
  }

روش دوم انجام اینکار که مرسوم‌تر است، اضافه کردن خودکار این هدر به تمام درخواست‌های ارسالی به سمت سرور است. برای اینکار باید یک HttpInterceptor را تهیه کرد. به همین منظور فایل جدید app\core\services\auth.interceptor.ts را به برنامه اضافه کرده و به صورت ذیل تکمیل می‌کنیم:
import { Injectable } from "@angular/core";
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from "@angular/common/http";
import { Observable } from "rxjs/Observable";

import { AuthService, AuthTokenType } from "./auth.service";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(private authService: AuthService) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    const accessToken = this.authService.getRawAuthToken(AuthTokenType.AccessToken);
    if (accessToken) {
      request = request.clone({
        headers: request.headers.set("Authorization", `Bearer ${accessToken}`)
      });
    }

    return next.handle(request);
  }
}
در اینجا یک clone از درخواست جاری ایجاد شده و سپس به headers آن، یک هدر جدید Authorization که به همراه توکن دسترسی است، اضافه خواهد شد.
به این ترتیب دیگری نیازی نیست تا به ازای هر درخواست و هر قسمتی از برنامه، این هدر را به صورت دستی تنظیم کرد و اضافه شدن آن پس از تنظیم ذیل، به صورت خودکار انجام می‌شود:
import { HTTP_INTERCEPTORS } from "@angular/common/http";

import { AuthInterceptor } from "./services/auth.interceptor";

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ]
})
export class CoreModule {}
در اینجا نحوه‌ی معرفی این HttpInterceptor جدید را به قسمت providers مخصوص CoreModule مشاهده می‌کنید.

در این حالت اگر برنامه را اجرا کنید، خطای ذیل را در کنسول توسعه‌دهنده‌های مرورگر مشاهده خواهید کرد:
compiler.js:19514 Uncaught Error: Provider parse errors:
Cannot instantiate cyclic dependency! InjectionToken_HTTP_INTERCEPTORS ("[ERROR ->]"): in NgModule AppModule in ./AppModule@-1:-1
در سازنده‌ی کلاس سرویس AuthInterceptor، سرویس Auth تزریق شده‌است که این سرویس نیز دارای HttpClient تزریق شده‌ی در سازنده‌ی آن است. به همین جهت Angular تصور می‌کند که ممکن است در اینجا یک بازگشت بی‌نهایت بین این interceptor و سرویس Auth رخ‌دهد. اما از آنجائیکه ما هیچکدام از متدهایی را که با HttpClient کار می‌کنند، در اینجا فراخوانی نمی‌کنیم و تنها کاربرد سرویس Auth، دریافت توکن دسترسی است، این مشکل را می‌توان به صورت ذیل برطرف کرد:
import { Injector } from "@angular/core";

  constructor(private injector: Injector) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authService = this.injector.get(AuthService);
ابتدا سرویس Injector را به سازنده‌ی کلاس AuthInterceptor تزریق می‌کنیم و سپس توسط متد get آن، سرویس Auth را درخواست خواهیم کرد (بجای تزریق مستقیم آن در سازنده‌ی کلاس):
@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(private injector: Injector) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authService = this.injector.get(AuthService);
    const accessToken = authService.getRawAuthToken(AuthTokenType.AccessToken);
    if (accessToken) {
      request = request.clone({
        headers: request.headers.set("Authorization", `Bearer ${accessToken}`)
      });
    }

    return next.handle(request);
  }
}


تکمیل متدهای دریافت اطلاعات از کنترلرهای محافظت شده‌ی سمت سرور

اکنون پس از افزودن AuthInterceptor، می‌توان متدهای CallProtectedApiComponent را به صورت ذیل تکمیل کرد. ابتدا سرویس‌های Auth ،HttpClient و همچنین تنظیمات آغازین برنامه را به سازنده‌ی CallProtectedApiComponent تزریق می‌کنیم:
  constructor(
    private authService: AuthService,
    private httpClient: HttpClient,
    @Inject(APP_CONFIG) private appConfig: IAppConfig,
  ) { }
سپس متدهای httpClient.get و یا هر نوع متد مشابه دیگری را به صورت معمولی فراخوانی خواهیم کرد:
  callMyProtectedAdminApiController() {
    this.httpClient
      .get(`${this.appConfig.apiEndpoint}/MyProtectedAdminApi`)
      .map(response => response || {})
      .catch((error: HttpErrorResponse) => Observable.throw(error))
      .subscribe(result => {
        this.result = result;
      });
  }

  callMyProtectedApiController() {
    this.httpClient
      .get(`${this.appConfig.apiEndpoint}/MyProtectedApi`)
      .map(response => response || {})
      .catch((error: HttpErrorResponse) => Observable.throw(error))
      .subscribe(result => {
        this.result = result;
      });
  }

در این حالت اگر برنامه را اجرا کنید، افزوده شدن خودکار هدر مخصوص Authorization:Bearer را در درخواست ارسالی به سمت سرور، مشاهده خواهید کرد:



مدیریت خودکار خطاهای عدم دسترسی ارسال شده‌ی از سمت سرور

ممکن است کاربری درخواستی را به منبع محافظت شده‌ای ارسال کند که به آن دسترسی ندارد. در AuthInterceptor تعریف شده می‌توان به وضعیت این خطا، دسترسی یافت و سپس کاربر را به صفحه‌ی accessDenied که در قسمت قبل ایجاد کردیم، به صورت خودکار هدایت کرد:
    return next.handle(request)
      .catch((error: any, caught: Observable<HttpEvent<any>>) => {
        if (error.status === 401 || error.status === 403) {
          this.router.navigate(["/accessDenied"]);
        }
        return Observable.throw(error);
      });
در اینجا ابتدا نیاز است سرویس Router، به سازنده‌ی کلاس تزریق شود و سپس متد catch درخواست پردازش شده، به صورت فوق جهت عکس العمل نشان دادن به وضعیت‌های 401 و یا 403 و هدایت کاربر به مسیر accessDenied تغییر کند:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(
    private injector: Injector,
    private router: Router) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authService = this.injector.get(AuthService);
    const accessToken = authService.getRawAuthToken(AuthTokenType.AccessToken);
    if (accessToken) {
      request = request.clone({
        headers: request.headers.set("Authorization", `Bearer ${accessToken}`)
      });
      return next.handle(request)
        .catch((error: any, caught: Observable<HttpEvent<any>>) => {
          if (error.status === 401 || error.status === 403) {
            this.router.navigate(["/accessDenied"]);
          }
          return Observable.throw(error);
        });
    } else {
      // login page
      return next.handle(request);
    }
  }
}
برای آزمایش آن، یک کنترلر سمت سرور جدید را با نقش Editor اضافه می‌کنیم:
using ASPNETCore2JwtAuthentication.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;

namespace ASPNETCore2JwtAuthentication.WebApp.Controllers
{
    [Route("api/[controller]")]
    [EnableCors("CorsPolicy")]
    [Authorize(Policy = CustomRoles.Editor)]
    public class MyProtectedEditorsApiController : Controller
    {
        public IActionResult Get()
        {
            return Ok(new
            {
                Id = 1,
                Title = "Hello from My Protected Editors Controller! [Authorize(Policy = CustomRoles.Editor)]",
                Username = this.User.Identity.Name
            });
        }
    }
}
و برای فراخوانی سمت کلاینت آن در CallProtectedApiComponent خواهیم داشت:
  callMyProtectedEditorsApiController() {
    this.httpClient
      .get(`${this.appConfig.apiEndpoint}/MyProtectedEditorsApi`)
      .map(response => response || {})
      .catch((error: HttpErrorResponse) => Observable.throw(error))
      .subscribe(result => {
        this.result = result;
      });
  }
چون این نقش جدید به کاربر جاری انتساب داده نشده‌است (جزو اطلاعات سمت سرور او نیست)، اگر آن‌را توسط متد فوق فراخوانی کند، خطای 403 را دریافت کرده و به صورت خودکار به مسیر accessDenied هدایت می‌شود:



نکته‌ی مهم: نیاز به دائمی کردن کلیدهای رمزنگاری سمت سرور

اگر برنامه‌ی سمت سرور ما که توکن‌ها را اعتبارسنجی می‌کند، ری‌استارت شود، چون قسمتی از کلیدهای رمزگشایی اطلاعات آن با اینکار مجددا تولید خواهند شد، حتی با فرض لاگین بودن شخص در سمت کلاینت، توکن‌های فعلی او برگشت خواهند خورد و از مرحله‌ی تعیین اعتبار رد نمی‌شوند. در این حالت کاربر خطای 401 را دریافت می‌کند. بنابراین پیاده سازی مطلب «غیرمعتبر شدن کوکی‌های برنامه‌های ASP.NET Core هاست شده‌ی در IIS پس از ری‌استارت آن» را فراموش نکنید.



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