مسیرراه‌ها
+AngularJS 2.0
شروع به کار با AngularJS 2.0 و TypeScript
فرم‌های مبتنی بر قالب‌ها در Angular
Angular Animation
آپلود فایل
Angular Material 6x
  • ابزارهای توسعه
مطالب
فراخوانی سرویس‌های OData توسط کلاینت‌های #C
فرض کنید در سرویس‌های خود، در حال استفاده از OData هستید. حال کافیست که metadata$ مربوط به سرویستان را برای استفاده‌ی کلاینت‌های دیگر، در اختیار آن‌ها قرار دهید.
وقتی از Odata استفاده میکنید، به صورت خودکار metadataی از سرویس‌ها و مدل‌های شما ساخته میشود و میتوان از آن به عنوان یک documentation کامل نام برد و حتی افرادی که استاندارد‌های Odata را نمیشناسند، به راحتی میتوانند آن را مطالعه و در صورت اجازه‌ی شما، از امکانات آن سرویس‌ها، در نرم افزار خودشان استفاده کنند.
بطور مثال میتوانید متادیتای برنامه‌ی خود را با استفاده از آدرس فرضی http://localhost:port/odata/$metadata مشاهده نمایید؛ که چیزی شبیه به محتوای زیر خواهد بود:
<edmx:Edmx xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx" Version="4.0">
<edmx:DataServices>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="OwinAspNetCore.Models">
<EntityType Name="Product">
<Key>
<PropertyRef Name="Id"/>
</Key>
<Property Name="Id" Type="Edm.Int32" Nullable="false"/>
<Property Name="Name" Type="Edm.String"/>
<Property Name="Price" Type="Edm.Decimal" Nullable="false"/>
</EntityType>
</Schema>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="Default">
<Function Name="TestFunction" IsBound="true">
<Parameter Name="bindingParameter" Type="Collection(OwinAspNetCore.Models.Product)"/>
<Parameter Name="Val" Type="Edm.Int32" Nullable="false"/>
<Parameter Name="Name" Type="Edm.String"/>
<ReturnType Type="Edm.Int32" Nullable="false"/>
</Function>
<EntityContainer Name="Container">
<EntitySet Name="Products" EntityType="OwinAspNetCore.Models.Product"/>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
در اینجا میتوان EntityTypeها ، EntitySetها و همه‌ی Action‌ها و Function‌های خود را مشاهده نمایید.
به غیر از این، وجود metadata باعث شده به راحتی کلاینت‌های #JavaScript ،Java ،Objective-C ،C و ... بتوانند به راحتی ارتباط کاملی با سرویس‌های شما برقرار نمایند.
برای مثال به صورت معمول یک کلاینت #Cی برای ارتباط برقرار کردن با یک سرویس خارجی باید اینگونه عمل کند (یک درخواست از نوع POST):
string postUrl = "http://localhost:port/....";
HttpClient client = new HttpClient();
var response = client.PostAsync(postUrl, new StringContent(JsonConvert.SerializeObject(new { Rating = 5 }), Encoding.UTF8, "application/json")).Result;
مشکلات این روش کاملا روشن و گویاست: پیچیدگی خیلی زیاد، دیباگ خیلی سخت و refactoring پیچیده و ...
اگر مطالب قبلی را دنبال کرده باشید، به پیاده سازی سرویس‌های Odata پرداختیم. در این لینک یک repository کامل برای کار با odata در asp.net core آماده شده‌است و در این مقاله از آن استفاده نموده‌ام.
بعد از clone کردن آن، پروژه را run نمایید. به چیز بیشتری از آن نیازی نداریم.
حال کافیست یک پروژه‌ی Console Application را ساخته و بعد باید از طریق منوی Tools گزینه‌ی Extensions and Updates را انتخاب و odata v4 client code generator را جستجو نماییم:

آن را نصب نموده و بعد از تکمیل شدن، visual studio را restart کنید.

پروژه‌ی console خود را باز کرده و از طریق Add -> new item، آیتم OData client را جستجو کرده و با نام ProductClient.tt آن را تولید نمایید (نام آن اختیاری است):

فایل ProductClient.tt را که یک T4 code generator میباشد، باز کرده و مقدار ثابت MetadataDocumentUri را به آدرس سرویس odata خود تغییر دهید:

public const string MetadataDocumentUri = "http://localhost:port/odata/";

روی این آیتم کلیک راست و گزینه‌ی Run Custom tool را انتخاب نمایید. این تمام کاری است که نیاز به انجام دادن دارید.

حال فایل Program.cs را باز کرده و آن‌را اینگونه تغییر دهید:

using ConsoleApplication1.OwinAspNetCore.Models;
using System;
using System.Linq;
namespace ConsoleApplication1
{
    public class Program
    {
        static void Main(string[] args)
        {
            Uri uri = new Uri("http://localhost:24977/odata");

            //var context = new Default.Container(uri);
            var context = new TestNameSpace.TestNameSpace(uri);

            //get
            var products = context.Products.Where(pr => pr.Name.Contains("a"))
                .Take(1).Select(pr => new { Firstname = pr.Name, PriceValue = pr.Price }).ToList();

            //add
            context.AddToProducts(new Product() { Name = "Name1", Price = 123 });

            //update
            Product p = context.Products.First();
            p.Name = "changed";
            context.UpdateObject(p);

            //delete
            context.DeleteObject(context.Products.Last());

            //commit
            context.SaveChanges();
        }
    }
}
اینبار همه چیز strongly typed و با همان intellisense معروف خواهد بود. فقط دقت کنید که اگر از Repository معرفی شده، برای سمت سرور خود استفاده میکنید، به دلیل اینکه از Namespace استفاده کرده‌ام، context شما، به نام namespace شما خواهد بود. در غیر اینصورت به صورت default و بدون namespace، باید از Default.Container استفاده شود.

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

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

//angularjs
$http.get("/products/get", {Name: "Test", Company: "Test"})
    .then(function(response) {
        console.log(response.data);
    });

//jquery
$.get("/products/get", {Name: "Test", Company: "Test"}, function(data, status){
        console.log("Data: " + data);
    });

با استفاده از odata و typescript و یک library مربوط به odata client در سمت کلاینت، نرم افزار شما بجای موارد، بالا چیزی شبیه به مثال زیر خواهد بود (با همراه داشتن strongly typed و intellisense کامل)

let product1 = await context.products.filter(c => c.Name.contains("Ali")).toArray();
let product2 = await context.products.getSomeFunction(1, 'Test');
context.product.add({Name: 'Test'} as Product);
await context.saveChanges()


در مقاله‌های آتی به ویژگی‌های بیشتری از Odata خواهیم پرداخت.

مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت یازدهم - کار با فرم‌ها - قسمت دوم
در قسمت قبل، فر‌مهای template driven را بررسی کردیم. همانطور که مشاهده کردید، این نوع فرم‌ها، قابلیت‌های اعتبارسنجی پیشرفته‌ای را به همراه ندارند. برای فرم‌هایی که نیاز به اعتبارسنجی‌های سفارشی دارند، فرم‌های model driven پیشنهاد می‌شوند که در این قسمت بررسی خواهند شد.


طراحی فرم ثبت نام کاربران در سایت با روش model driven

در این قسمت قصد داریم فرم ثبت نام کاربران را به همراه اعتبارسنجی‌های پیشرفته‌ای پیاده سازی کنیم. به همین منظور، ابتدا پوشه‌ی جدید App\users را به مثال سری جاری اضافه کنید و سپس سه فایل user.ts، signup-form.component.ts و signup-form.component.html را به آن اضافه نمائید.
فایل user.ts بیانگر مدل کاربران سایت است؛ با این محتوا:
export interface IUser {
    id: number;
    name: string;
    email: string;
    password: string;
}

قالب فرم یا signup-form.component.html، در حالت ابتدایی آن چنین شکل استانداردی را خواهد داشت و فاقد اعتبارسنجی خاصی است:
<form>
    <div class="form-group">
        <label form="name">Username</label>
        <input id="name" type="text" class="form-control" />
    </div>
    <div class="form-group">
        <label form="email">Email</label>
        <input id="email" type="text" class="form-control" />
    </div>
    <div class="form-group">
        <label form="password">Password</label>
        <input id="password" type="password" class="form-control" />
    </div>
    <button class="btn btn-primary" type="submit">Submit</button>
</form>
اکنون می‌خواهیم این فرم را به یک فرم AngularJS 2.0 ارتقاء دهیم. بنابراین نیاز است اشیاء Control و ControlGroup را ایجاد کنیم و اینبار نمی‌خواهیم AngularJS 2.0 مانند قسمت قبلی، به صورت خودکار (و ضمنی)، این اشیاء را برای ما ایجاد کند. می‌خواهیم آن‌ها را با کدنویسی (به صورت صریح) ایجاد کنیم تا بتوانیم بر روی آن‌ها کنترل بیشتری داشته باشیم.
بنابراین ابتدا کلاس کامپوننت این فرم را در فایل signup-form.component.ts به نحو ذیل تکمیل کنید:
import { Component } from '@angular/core';
import { Control, ControlGroup, Validators } from '@angular/common';
 
@Component({
    selector: 'signup-form',
    templateUrl: 'app/users/signup-form.component.html'
})
export class SignupFormComponent {
    form = new ControlGroup({
        name: new Control('', Validators.required),
        email: new Control('', Validators.required),
        password: new Control('', Validators.required)
    });
 
    onSubmit(): void {
        console.log(this.form.value);
    }
}
و همچنین پیام‌های اعتبارسنجی اولیه را نیز به نحو زیر به فایل signup-form.component.html اضافه می‌کنیم:
<form [ngFormModel]="form" (ngSubmit)="onSubmit()">
    <div class="form-group">
        <label form="name">Username</label>
        <input id="name" type="text" class="form-control"
               ngControl="name"/>
        <label class="text-danger" *ngIf="!form.controls['name'].valid">
            Username is required.
        </label>
    </div>
    <div class="form-group">
        <label form="email">Email</label>
        <input id="email" type="text" class="form-control"
               ngControl="email" #email="ngForm"/>
        <label class="text-danger" *ngIf="email.touched && !email.valid">
            Email is required.
        </label>
    </div>
    <div class="form-group">
        <label form="password">Password</label>
        <input id="password" type="password" class="form-control"
               ngControl="password" #password="ngForm"/>
        <label class="text-danger" *ngIf="password.touched && !password.valid">
            Password is required.
        </label>
    </div>
    <button class="btn btn-primary" type="submit">Submit</button>
</form>
توضیحات:
تفاوت مهم این فرم و اعتبارسنجی‌هایش با قسمت قبل، در ایجاد اشیاء Control و ControlGroup به صورت صریح است:
form = new ControlGroup({
    name: new Control('', Validators.required),
    email: new Control('', Validators.required),
    password: new Control('', Validators.required)
});
کلا‌س‌های Control، ControlGroup و Validators در ماژول angular/common@ تعریف شده‌اند. بنابراین import متناظری نیز به ابتدای فایل اضافه شده‌است:
 import { Control, ControlGroup, Validators } from '@angular/common';

یک نکته
اگر محل قرارگیری کلاسی را فراموش کردید، آن‌را در مستندات AngularJS 2.0 ذیل قسمت API Review جستجو کنید. نتیجه‌ی جستجو، به همراه نام ماژول کلاس‌ها نیز می‌باشد.


خاصیت عمومی form که با new ControlGroup تعریف شده‌است، حاوی تعاریف صریح کنترل‌های موجود در فرم خواهد بود. در اینجا سازنده‌ی ControlGroup، یک شیء را می‌پذیرد که کلیدهای آن، همان نام کنترل‌های تعریف شده‌ی در قالب فرم و مقدار هر کدام، یک Control جدید است که پارامتر اول آن یک مقدار پیش فرض و پارامتر دوم، اعتبارسنجی مرتبطی را تعریف می‌کند (این اعتبارسنجی معرفی شده، یک متد استاتیک در کلاس توکار Validators است).
بنابراین چون سه المان ورودی، در فرم جاری تعریف شده‌اند، سه کلید جدید هم نام نیز در پارامتر ورودی ControlGroup ذکر گردیده‌اند.

اکنون که خاصیت عمومی form، در کلاس کامپوننت فوق تعریف شد، آن‌را در قالب فرم، به ngFormModel بایند می‌کنیم:
<form [ngFormModel]="form" (ngSubmit)="onSubmit()">
به این ترتیب به AngularJS 2.0 اعلام می‌کنیم که ControlGroup و Controlهای آن‌را به صورت صریح ایجاد کرده‌ایم و بجای وهله‌‌های پیش فرض خود، از خاصیت عمومی form کلاس کامپوننت، این مقادیر را تامین کن.
مراحل بعد آن، با مراحلی که در قسمت قبل بررسی کردیم، تفاوتی ندارند:
الف) در اینجا به هر المان موجود، یک ngControl نسبت داده شده‌است تا هر المان را تبدیل به یک کنترل AngularJS2 2.0 کند.
ب) به هر المان، یک متغیر محلی شروع شده با # نسبت داده شده‌است تا با اتصال آن به ngForm بتوان به ngControl تعریف شده دسترسی پیدا کرد.
البته اکنون می‌توان از خاصیت form متصل به ngFormModel نیز بجای تعریف این متغیر محلی، به نحو ذیل استفاده کرد:
 <label class="text-danger" *ngIf="!form.controls['name'].valid">
ج) از این متغیر محلی جهت نمایش یا عدم نمایش پیام‌های خطای اعتبارسنجی، در ngIfهای تعریف شده، استفاده شده‌است.
د) و در آخر متد onSumbit موجود در کلاس کامپوننت را به رخداد ngSubmit متصل کرده‌ایم. همانطور که ملاحظه می‌کنید اینبار دیگر پارامتری را به آن ارسال نکرده‌ایم. از این جهت که خاصیت form موجود در سطح کلاس، اطلاعات کاملی را از اشیاء موجود در آن دارد و در متد onSubmit کلاس، به آن دسترسی داریم.
    onSubmit(): void {
        console.log(this.form.value);
    }
this.form.value حاوی یک شیء است که تمام مقادیر پر شده‌ی فرم را به همراه دارد.

بنابراین تا اینجا تنها تفاوت فرم جدید تعریف شده با قسمت قبل، تعریف صریح ControlGroup و کنتر‌ل‌های آن در کلاس کامپوننت و اتصال آن به ngFormModel است. به این نوع فرم‌ها، فرم‌های model driven هم می‌گویند.


نمایش فرم افزودن کاربران توسط سیستم Routing

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


معرفی کلاس FormBuilder

روش دیگری نیز برای ساخت ControlGroup و کنترل‌های آن با استفاده از کلاس و سرویس فرم ساز توکار AngularJS 2.0 وجود دارد:
import { Control, ControlGroup, Validators, FormBuilder } from '@angular/common';

form: ControlGroup;
 
constructor(formBuilder: FormBuilder) {
    this.form = formBuilder.group({
        name: ['', Validators.required],
        email: ['', Validators.required],
        password: ['', Validators.required]
    });
}
کلاس و سرویس FormBuilder نیز در ماژول angular/common@ قرار دارد. برای استفاده‌ی از آن، آن‌را در سازنده‌ی کلاس تزریق کرده و سپس از متد group آن استفاده می‌کنیم. نحوه‌ی تعریف کلی اعضای آن با اعضای ControlGroup یکی است؛ با این تفاوت که اینبار بجای ذکر new Control، یک آرایه تعریف می‌شود که دقیقا اعضای آن، همان پارامترهای شیء کنترل هستند. این روش در کل خلاصه‌تر است و در آن تعریف چندین گروه مختلف، ساده‌تر می‌باشد. همچنین با روش تزریق وابستگی‌های بکار رفته‌ی در این فریم ورک نیز سازگاری بیشتری دارد.


پیاده سازی اعتبارسنجی سفارشی

فرض کنید می‌خواهیم ورود نام کاربر‌های دارای فاصله را غیر معتبر اعلام کنیم. برای این منظور فایل جدید usernameValidators.ts را به پوشه‌ی app\users اضافه کنید؛ با این محتوا:
import { Control } from '@angular/common';
 
export class UsernameValidators {
    static cannotContainSpace(control: Control) {
        if (control.value.indexOf(' ') >= 0) {
            return { cannotContainSpace: true };
        }
        return null;
    }
}
کلاس UsernameValidators می‌تواند شامل تمام اعتبارسنجی‌های سفارشی خاصیت نام کاربری باشد. به همین جهت نام آن جمع است و به s ختم شد‌ه‌است.
هر متد پیاده سازی کننده‌ی یک اعتبار سنجی سفارشی در این کلاس، استاتیک تعریف می‌شود؛ با نام دلخواهی که مدنظر است.
پارامتر ورودی این متدهای استاتیک، یک وهله از شیء کنترل است که توسط آن می‌توان برای مثال به خاصیت value آن دسترسی یافت و بر این اساس منطق اعتبارسنجی خود را پیاده سازی نمود. به همین جهت import آن نیز به ابتدای فایل جاری اضافه شده‌است.
خروجی این متد دو حالت دارد:
الف) اگر null باشد، یعنی اعتبارسنجی موفقیت آمیز بوده‌است.
ب) اگر اعتبارسنجی با شکست مواجه شود، خروجی این متد یک شیء خواهد بود که کلید آن، نام اعتبارسنجی مدنظر است و مقدار این کلید، هر چیزی می‌تواند باشد؛ یک true و یا یک شیء دیگر که اطلاعات بیشتری را در مورد این شکست ارائه دهد.

برای مثال اگر اعتبارسنج توکار required با شکست مواجه شود، یک چنین شی‌ءایی را بازگشت می‌دهد:
 { required:true }
و یا اگر اعتبارسنج minlength باشکست مواجه شود، اطلاعات بیشتری را در قسمت مقدار این کلید بازگشتی، ارائه می‌دهد:
{
  minlength : {
     requiredLength : 3,
     actualLength : 1
  }
}
در کل اینکه چه چیزی را بازگشت دهید، بستگی به طراحی مدنظر شما دارد.

پس از پیاده سازی یک اعتبارسنجی سفارشی، برای استفاده‌ی از آن، ابتدا ماژول آن‌را به ابتدای ماژول signup-form.component.ts اضافه می‌کنیم:
 import { UsernameValidators } from './usernameValidators';
پس از آن، شبیه به افزودن متد استاتیک توکار Validators.required، این متد جدید را به لیست اعتبارسنجی‌های خاصیت name اضافه می‌کنیم. از آنجائیکه پیشتر المان دوم این آرایه مقدار دهی شده‌است، برای ترکیب چندین اعتبارسنجی با هم، از متد Validators.compose که آرایه‌ای از متدهای اعتبارسنجی را قبول می‌کند، کمک خواهیم گرفت:
 name: ['', Validators.compose([Validators.required, UsernameValidators.cannotContainSpace])],

و مرحله‌ی آخر، نمایش یک پیام اعتبارسنجی مناسب و متناظر با متد cannotContainSpace است. برای این منظور فایل signup-form.component.html را گشوده و تغییرات ذیل را اعمال کنید:
<div class="form-group">
    <label form="name">Username</label>
    <input id="name" type="text" class="form-control"
           ngControl="name"
           #name="ngForm" />
    <div *ngIf="name.touched && name.errors">
        <label class="text-danger" *ngIf="name.errors.required">
            Username is required.
        </label>
        <label class="text-danger" *ngIf="name.errors.cannotContainSpace">
            Username can't contain space.
        </label>
    </div>
</div>
همانطور که در قسمت قبل نیز عنوان شد، چون اکنون به یک المان، بیش از یک اعتبارسنجی اعمال شده‌است، استفاده از خاصیت valid، بیش از اندازه عمومی بوده و باید از خاصیت errors استفاده کرد. به همین جهت این دو اعتبارسنجی را در یک div محصور کننده قرار می‌دهیم و در صورت وجود خطایی، خاصیت name.errors، دیگر نال نبوده و دو برچسب قرار گرفته‌ی در آن بر اساس شرط‌های ngIf آن، پردازش خواهند شد.
نام خاصیت بازگشت داده شده‌ی در اعتبارسنجی سفارشی، به عنوان یک خاصیت جدید شیء errors قابل استفاده است؛ مانند name.errors.cannotContainSpace.

به عنوان تمرین ماژول جدید emailValidators.ts را افزوده و سپس اعتبارسنجی سفارشی بررسی معتبر بودن ایمیل وارد شده را تعریف کنید:
import {Control} from '@angular/common';
 
export class EmailValidators {
    static email(control: Control) {
        var regEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        var valid = regEx.test(control.value);
        return valid ? null : { email: true };
    }
}
در ادامه آن‌را به لیست formBuilder.group افزوده و همچنین پیام اعتبارسنجی ویژه‌ای را نیز به قالب فرم اضافه کنید (کدهای کامل آن، در فایل zip انتهای بحث موجود است).


یک نکته

اگر نیاز است از regular expressions مانند مثال فوق استفاده شود، می‌توان از متد توکار Validators.pattern نیز استفاده کرد و نیازی به تعریف یک متد جداگانه برای آن وجود ندارد؛ مگر اینکه نیاز به بازگشت شیء خطای کاملتری با خواص بیشتری وجود داشته باشد.


اعتبارسنجی async یا اعتبارسنجی از راه دور (remote validation)

یک سری از اعتبارسنجی‌ها را در سمت کلاینت می‌توان تکمیل کرد؛ مانند بررسی معتبر بودن فرمت ایمیل وارده. اما تعدادی دیگر، نیاز به اطلاعاتی از سمت سرور دارند. برای مثال آیا نام کاربری در حال ثبت، تکراری است یا خیر؟ این نوع اعتبارسنجی‌ها در رده‌ی async validation قرار می‌گیرند.
سازنده‌ی شیء Control در AngularJS 2.0 که در مثال‌های بالا نیز مورد استفاده قرار گرفت، پارامتر اختیاری سومی را نیز دارد که یک AsyncValidatorFn را قبول می‌کند:
 control(value: Object, validator?: ValidatorFn, asyncValidator?: AsyncValidatorFn) : Control
پیاده سازی async validators، بسیار شبیه به سایر اعتبارسنج‌ها هستند. اما از آنجائیکه نیاز به کار با سرور را دارند، استاتیک تعریف کردن آن‌ها، سبب قطع شدن دسترسی آن‌ها از context کلاس جاری شده و امکان تزریق وابستگی‌ها را از دست خواهیم داد. برای مثال دیگر نمی‌توان به سادگی، سرویس دریافت اطلاعات کاربران را در اینجا تزریق کرد. یک راه حل رفع این مشکل، تعریف همان متد اعتبارسنج در کلاس کامپوننت فرم است:
nameShouldBeUnique(control: Control) {
    let name: string = control.value;
    return new Promise((resolve) => {
        this._userService.isUserNameUnique(<IUser>{ "name": name }).subscribe(
            (result: IResult) => {
                resolve(                    
                    result.result ? null : { 'nameShouldBeUnique': true }
                );
            },
            error => {
                resolve(null);
            }
        );
    });
}
و سپس فراخوانی آن به صورت ذیل، به عنوان سومین عنصر آرایه‌ی تعریف شده:
this.form = _formBuilder.group({
    name: ['', Validators.compose([
        Validators.required,
        UsernameValidators.cannotContainSpace
    ]),
        (control: Control) => this.nameShouldBeUnique(control)],
در اینجا با استفاده از arrow functions، امکان دسترسی به این متد تعریف شده‌ی در سطح کلاس، که استاتیک هم نیست، وجود خواهد داشت. به این ترتیب دیگر context کلاس را از دست نداده‌ایم و اینبار می‌توان به this._userService، که آن‌را در ادامه تکمیل خواهیم کرد، بدون مشکلی دسترسی یافت.
امضای متد nameShouldBeUnique تفاوتی با سایر متدهای اعتبارسنج نداشته و پارامتر ورودی آن، همان کنترل است که توسط آن می‌توان به مقدار وارد شده‌ی توسط کاربر دسترسی یافت. اما تفاوت اصلی آن در اینجا است که این متد باید یک شیء Promise را بازگشت دهد. یک Promise، بیانگر نتیجه‌ی یک عملیات async است. در اینجا دو حالت resolve و error را باید پیاده سازی کرد. در حالت error، یعنی عملیات async صورت گرفته با شکست مواجه شده‌است و در حالت resolve، یعنی عملیات تکمیل شده و اکنون می‌خواهیم نتیجه‌ی نهایی را بازگشت دهیم. نتیجه نهایی بازگشت داده شده‌ی در اینجا، همانند سایر validators است و اگر نال باشد، یعنی اعتبارسنجی موفقیت آمیز بوده و اگر یک شیء را بازگشت دهیم، یعنی اعتبارسنجی با شکست مواجه شده‌است.

این Promise، از یک سرویس تعریف شده‌ی در فایل جدید user.service.ts استفاده می‌کند:
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Headers, RequestOptions } from '@angular/http';
import { IUser } from  './user';
import { IResult } from './result';
 
@Injectable()
export class UserService {
    private _checkUserUrl = '/home/checkUser';
 
    constructor(private _http: Http) { }
 
    private handleError(error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }
 
    isUserNameUnique(user: IUser): Observable<IResult> {
        let headers = new Headers({ 'Content-Type': 'application/json' }); // for ASP.NET MVC
        let options = new RequestOptions({ headers: headers });
 
        return this._http.post(this._checkUserUrl, JSON.stringify(user), options)
            .map((response: Response) => <IResult>response.json())
            .do(data => console.log("User: " + JSON.stringify(data)))
            .catch(this.handleError);
    }
}
با نحوه‌ی تعریف سرویس‌ها و همچنین کار با سرور و دریافت اطلاعات، در قسمت‌های قبلی بیشتر آشنا شدیم. در اینجا یک درخواست get، به آدرس home/checkuser سرور، ارسال می‌شود. سپس نتیجه‌ی آن در قالب اینترفیس IResult بازگشت داده خواهد شد. این اینترفیس را در فایل result.ts به صورت ذیل تعریف کرده‌ایم:
export interface IResult {
    result: boolean;
}

کدهای سمت سرور برنامه که کار بررسی یکتا بودن نام کاربری را انجام می‌دهند، به صورت ذیل در فایل Controllers\HomeController.cs تعریف شده‌اند:
namespace MVC5Angular2.Controllers
{
    public class HomeController : Controller
    {
        [HttpPost]
        public ActionResult CheckUser(User user)
        {
            var isUnique = new { result = true };
            if (user.Name?.Equals("Vahid", StringComparison.OrdinalIgnoreCase) ?? false)
            {
                isUnique = new { result = false };
            }
 
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(isUnique, new JsonSerializerSettings
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                }),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
    }
}
در اینجا اگر نام کاربری وارد شده مساوی Vahid بود، یک شیء anonymous، مطابق امضای اینترفیس IResult سمت کاربر (همان فایل result.ts عنوان شده) بازگشت داده می‌شود.

بنابراین تا اینجا مسیر سمت سرور home/checkuser تکمیل شده‌است. این مسیر توسط سرویس کاربر صدا زده شده و اگر نام کاربری وارد شده موجود باشد، نتیجه‌ای را مطابق امضای قرارداد IResult سفارشی ما بازگشت می‌دهد.
پس از آن مجددا به فایل signup-form.component.ts مراجعه کرده و سرویس جدید UserService را به سازنده‌ی آن تزریق کرده‌ایم. همچنین قسمت providers این کامپوننت را هم جهت تکمیل اطلاعات تزریق کننده‌ی توکار AngularJS 2.0 مقدار دهی کرده‌ایم. البته همانطور که در مبحث تزریق وابستگی‌ها نیز عنوان شد، اگر این سرویس قرار نیست در کلاس دیگری استفاده شود، نیازی نیست تا آن‌را در بالاترین سطح ممکن و در فایل app.component.ts ثبت و معرفی کرد:
@Component({
    selector: 'signup-form',
    templateUrl: 'app/users/signup-form.component.html',
    providers: [ UserService ]
})
export class SignupFormComponent {
 
    constructor(private _formBuilder: FormBuilder, private _userService: UserService) {
پس از ترزیق وابستگی private _userService: UserService، اکنون این سرویس به سادگی و به حالت متداولی در متد nameShouldBeUnique(control: Control) قابل دسترسی خواهد بود و از آن می‌توان جهت اعتبارسنجی‌های غیرهمزمان استفاده کرد.

اکنون که کدهای فعال سازی اعتبارسنجی از راه دور ما تکمیل شده‌است، به فایل signup-form.component.html مراجعه کرده و پیام مناسبی را نمایش خواهیم داد:
<div *ngIf="name.touched && name.errors">
    <label class="text-danger" *ngIf="name.errors.required">
        Username is required.
    </label>
    <label class="text-danger" *ngIf="name.errors.cannotContainSpace">
        Username can't contain space.
    </label>
    <label class="text-danger" *ngIf="name.errors.nameShouldBeUnique">
        This username is already taken.
    </label>
</div>
در ادامه اگر برنامه را اجرا کنید، با ورود نام کاربری Vahid، یک چنین پیام خطایی، مشاهده خواهد شد:



نمایش پیام loading در حین انجام اعتبارسنجی از راه دور

شاید بد نباشد که در حین انجام عملیات اعتبارسنجی از راه دور و ارسال درخواستی به سرور و بازگشت نتیجه‌ی آن، یک پیام loading را نیز نمایش داد. برای انجام این‌کار نیاز است تغییرات ذیل را به فایل signup-form.component.html اضافه کنیم:
<input id="name" type="text" class="form-control"
       ngControl="name"
       #name="ngForm" />
<div *ngIf="name.control.pending">
    Checking server, Please wait ...
</div>
در اینجا یک div جدید را ذیل المان ورود نام کاربری اضافه کرده‌ایم. همچنین نحوه‌ی نمایش آن‌را با دسترسی به متغیر name# و کنترل منتسب، به آن مدیریت می‌کنیم. اگر عملیات async ایی بر روی این کنترل در حال اجرا باشد، Promise تعریف شده، وضعیت pending را بازگشت می‌دهد. به همین جهت می‌توان از این خاصیت، جهت نمایش دادن یا مخفی کردن عبارت و یا تصویری استفاده کرد.

 
اعتبارسنجی ترکیبی در حین submit یک فرم

فرض کنید می‌خواهید منطقی را که حاصل اعتبارسنجی تمام فیلدهای فرم است (و نه هر کدام به تنهایی)، در حین submit آن اعمال کنید. برای مثال آیا ترکیب نام کاربری و کلمه‌ی عبور شخصی در حین login معتبر است یا خیر؟ در این حالت پس از بررسی‌های لازم در متد onSubmit، می‌توان با استفاده از متد find شیء form، به یکی از کنترل‌های فرم دسترسی یافت و سپس با استفاده از متد setErrors، خطای اعتبارسنجی سفارشی را به آن اضافه کرد:
onSubmit(): void {
    console.log(this.form.value);
 
    this.form.find('name').setErrors({
        invalidData : true
    }); 
}
سپس در سمت قالب این کامپوننت، نحوه‌ی نمایش این اعتبارسنجی سفارشی، همانند قبل است:
<div *ngIf="name.touched && name.errors">
    <label class="text-danger" *ngIf="name.errors.invalidData">
        Check the inputs....
    </label>
</div>


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

اکنون که دسترسی به خاصیت this.form را داریم و این خاصیت توسط [ngFormModel] به تمام اشیاء تعریف شده‌ی در فرم و تغییرات آن‌ها دسترسی دارد، می‌توان از آن برای دسترسی به شیء‌ایی که حاوی مدل فرم است، استفاده کرد. برای نمونه در مثال فوق، خاصیت value آن، چنین خروجی را دارد:
  { name="VahidN", email="email@site.com", password="123"}
بنابراین برای ارسال اطلاعات این فرم به سرور، تنها کافی است این شیء را ارسال کنیم. به همین جهت در فایل user.service.ts، به کلاس سرویس کاربران، متد addUser را اضافه می‌کنیم:
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Headers, RequestOptions } from '@angular/http';
import { IUser } from  './user';
import { IResult } from './result';
 
@Injectable()
export class UserService {
    private _addUserUrl = '/home/addUser';
 
    constructor(private _http: Http) { }
 
    private handleError(error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }
 
    addUser(user: IUser): Observable<IUser> {
        let headers = new Headers({ 'Content-Type': 'application/json' }); // for ASP.NET MVC
        let options = new RequestOptions({ headers: headers });
 
        return this._http.post(this._addUserUrl, JSON.stringify(user), options)
            .map((response: Response) => <IUser>response.json())
            .do(data => console.log("User: " + JSON.stringify(data)))
            .catch(this.handleError);
    }
}
کدهای سمت سرور آن در فایل Controllers\HomeController.cs نیز چنین شکلی را می‌توانند داشته باشند:
[HttpPost]
public ActionResult AddUser(User user)
{
    user.Id = 1; //todo: save user and get id from db
 
    return new ContentResult
    {
        Content = JsonConvert.SerializeObject(user, new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        }),
        ContentType = "application/json",
        ContentEncoding = Encoding.UTF8
    };
}
و پس از آن کدهای متد onSubmit فایل signup-form.component.ts برای ارسال این شیء به صورت ذیل خواهند بود:
onSubmit(): void {
    console.log(this.form.value);
 
    /*this.form.find('name').setErrors({
            invalidData : true
        });*/
 
    this._userService.addUser(<IUser>this.form.value)
        .subscribe((user: IUser) => {
            console.log(`ID: ${user.id}`);
        });
}


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: (این کدها مطابق نگارش RC 1 هستند)
MVC5Angular2.part11.zip


خلاصه‌ی بحث

برای اینکه بتوان کنترل بیشتری را بر روی المان‌های فرم داشت، ابتدا سرویس FormBuilder را در سازنده‌ی کلاس کامپوننت فرم تزریق می‌کنیم. سپس با استفاده از متد group آن، المان‌های فرم را به صورت کلیدهای شیء پارامتر آن تعریف می‌کنیم. در اینجا می‌توان اعتبارسنجی‌های توکار AngularJS 2.0 را که در کلاس پایه‌ی Validators مانند Validators.required وجود دارند، تعریف کرد. با استفاده از متد compose آن‌ها را ترکیب نمود و یا پارامتر سومی را جهت اعتبارسنجی‌های async اضافه نمود. در این حالت شیء form تعریف شده به صورت [ngFormModel] به قالب فرم متصل می‌شود و از تغییرات آن آگاه خواهد شد.
مطالب
پیاده سازی یک سیستم دسترسی Role Based در Web API و AngularJs - بخش سوم (پایانی)
در بخش پیشین  به بررسی جزئی‌تر ایجاد پایگاه داده و همچنین توسعه Custom Filter Attribute پرداختیم که وظیفه تایید صلاحیت کاربر جاری و بررسی دسترسی وی به API Method مورد نظر را بررسی می‌کرد. در این مقاله به این بحث می‌پردازیم که در Filter Attribute توسعه داده شده، قصد داریم یک سرویس Access Control ایجاد نماییم.
این سرویس وظیفه تمامی اعمال مربوط به نقش‌ها و دسترسی‌های کاربر را بر عهده خواهد داشت. این سرویس به صورت زیر تعریف می‌گردد:
public class AccessControlService
{
        private DbContext db;

        public AccessControlService()
        {
            db = new DbContext();
        }

        public IEnumerable<Permission> GetUserPermissions(string userId)
        {
            var userRoles = this.GetUserRoles(userId);
            var userPermissions = new List<Permission>();
            foreach (var userRole in userRoles)
            {
                foreach (var permission in userRole.Permissions)
                {
                    // prevent duplicates
                    if (!userPermissions.Contains(permission))
                        userPermissions.Add(permission);
                }
            }
            return userPermissions;
        }
        public IEnumerable<Role> GetUserRoles(string userId)
        {
            return db.Users.FirstOrDefault(x => x.UserId == userId).Roles.ToList();
        }

        public bool HasPermission(string userId, string area, string control)
        {
            var found = false;
            var userPermissions = this.GetUserPermissions(userId);
            var permission = userPermissions.FirstOrDefault(x => x.Area == area && x.Control == control);
            if (permission != null)
                found = true;
            return found;
        }
{
همانطور که ملاحظه می‌کنید، ما سه متد GetUserPermissions، GetUserRoles و HasPermission را توسعه داده‌ایم. حال اینکه بر حسب نیاز، می‌توانید متدهای بیشتری را نیز به این سرویس اضافه نمایید. متد اول، وظیفه‌ی واکشی تمامی permissionهای کاربر را عهده دار می‌باشد. متد GetUserRoles نیز تمامی نقش‌های کاربر را در سیستم، بازمی‌گرداند و در نهایت متد سوم، همان متدی است که ما در Filterattribute از آن استفاده کرد‌ه‌ایم. این متد با دریافت پارامترها و بازگردانی یک مقدار درست یا نادرست، تعیین می‌کند که کاربر جاری به آن محدوده دسترسی دارد یا خیر.
تمامی حداقل‌هایی که برای نگارش سمت سرور نیاز بود، به پایان رسید. حال به بررسی این موضوع خواهیم پرداخت که چگونه این سطوح دسترسی را با سمت کاربر همگام نماییم. به طوری که به عنوان مثال اگر کاربری حق دسترسی به ویرایش مطالب یک سایت را ندارد، دکمه مورد نظر که او را به آن صفحه هدایت می‌کند نیز نباید به وی نشان داده شود. سناریویی که ما برای این کار در نظر گرفته‌ایم، به این گونه می‌باشد که در هنگام ورود کاربر، لیستی از تمامی دسترسی‌های او به صورت JSON به سمت کلاینت ارسال می‌گردد. حال وظیفه مدیریت نمایش یا عدم نمایش المان‌های صفحه، بر عهده زبان سمت کلاینت، یعنی AngularJs خواهد بود. بنابراین در ابتدایی‌ترین حالت، ما نیاز به یک کنترلر و متد Web API داریم تا لیست دسترسی‌های کاربر را بازگرداند. این کنترلر در حال حاضر شامل یک متد است. اما بر حسب نیاز، می‌توانید متدهای بیشتری را به کنترلر اضافه نمایید.
    [RoutePrefix("َAuth/permissions")]
    public class PermissionsController : ApiController
    {
        private AccessControlService _AccessControlService = null;
        public PermissionsController()
        {
            _AccessControlService = new AccessControlService();
        }

        [Route("GetUserPermissions")]
        public async Task<IHttpActionResult> GetUserPermissions()
        {
            if (!User.Identity.IsAuthenticated)
            {
                return Unauthorized();
            }
            return Ok(_AccessControlService.GetPermissions(User.Identity.GetCurrentUserId()));
        }        
    }
در متد فوق ما از متد سرویس Access Control که لیست تمامی permissionهای کاربر را باز می‌گرداند، کمک گرفتیم. متد GetUserPermissions پس از ورود کاربر توسط کلاینت فراخوانی می‌گردد و لیست تمامی دسترسی‌ها در سمت کلاینت، در rootScope انگیولار ذخیره سازی می‌گردد. حال نوبت به آن رسیده که به بررسی عملیات سمت کلاینت بپردازیم.

توسعه سرویس‌ها و فرآیندهای سمت وب کلاینت AngularJS

در ابتدا در سمت کلاینت نیاز به سرویسی داریم که دسترسی‌های سمت سرور را دریافت نماید. از این رو ما نام این سرویس را permissionService می‌نامیم.
'use strict';
angular.module('App').factory('permissionService', ['$http', '$q', function ($http, $q) {

    var _getUserPermissions = function () {
        return $http.get(serviceBaseUrl + '/api/permissions/GetUserPermissions/');
    }

    var _isAuthorize = function (area, control) {
        return _.some($scope.permissions, { 'area': area, 'control': control });
    }

    return {
        getUserPermissions: _getUserPermissions,
        isAuthorize: _isAuthorize
    };
}]);
اگر بخواهیم مختصری درباره‌ی این سرویس صحبت کنیم، متد اول که یک دستور GET ساده است و لیست دسترسی‌ها را از PermissionController دریافت می‌کند. متد بعدی که در آینده بیشتر با آن آشنا می‌شویم، عملیات تایید صلاحیت کاربر را به ناحیه مورد نظر، انجام می‌دهد (همان مثال دسترسی به دکمه ویرایش مطلب در یک صفحه). در این متد برای جستجوی لیست permissions از کتابخانه‌ای با نام Lodash کمک گرفته‌ایم. این کتابخانه کاری شبیه به دستورات Linq را در کالکشن‌ها و آرایه‌های جاوااسکریپتی، انجام می‌دهد. متد some یک مقدار درست یا نادرست را بازمی‌گرداند. بازگردانی مقدار درست، به این معنی است که کاربر به ناحیه‌ی مورد نظر اجازه‌ی دسترسی را خواهد داشت.
حال باید متد‌های این سرویس را در کنترلر لاگین فراخوانی نماییم. در این مرحله ما از rootScope dependency استفاده می‌کنیم. برای نحوه‌ی عملکرد rootScope میتوانید به مقاله‌ای در این زمینه در وب سایت toddomotto مراجعه کنید. در این مقاله ویژگی‌ها و اختلاف‌های scope و rootScope به تفصیل بیان شده است. مقاله‌ای دیگر در همین زمینه نوشته شده است که در انتهای مقاله به بررسی چند نکته در مورد کدهای مشترک پرداخته شده‌است. تکه کد زیر، متد login را نمایش می‌دهد که در loginController قرار گرفته است و ما در آن از نوشتن کل بلاک loginController چشم پوشی کرده‌ایم. متد savePermissions تنها یک کار را انجام می‌دهد و آن هم این است که در ابتدا، به سرویس permissionService متصل شده و تمامی دسترسی‌ها را واکشی می‌نماید و پس از آن، آنها را درون rootScope قرار می‌دهد تا در تمامی کنترلرها قابل دسترسی باشد.
    $scope.login = function () {
        authService.login($scope.loginData).then(function (response) {
            savePermissions();
            $location.path('/userPanel');
        },
         function (err) {
             $scope.message = err.error_description;
         });
    };

    var savePermissions = function () {
        permissionService.getUserPermissions().then(function (response) {
            $rootScope.permissions = response.data;
        },
        function (err) {
        });
    }
}

   حال تمامی اطلاعات دسترسی، در سمت کلاینت نیز قابل دسترسی می‌باشد. تنها کاری که نیاز است، broadCast کردن متد isAuthorize است که آن هم باید در rootScope قرار بگیرد. ما برای این انتساب یک راهکار را ارائه کرده‌ایم. معماری سیستم کلاینت به این صورت است که تمامی کنترلرها درون یک parentController قرار گرفته‌اند. از این رو می‌توان در parentController این انتساب (ایجاد دسترسی عمومی برایisAuthorize) صورت گیرد. برای این کار در parentController تغییرات زیر صورت می‌گیرد:
App.controller('parentController', ['$rootScope', '$scope', 'authService', 'permissionService', function ($rootScope, $scope, authService, permissionService) {
        $scope.authentication = authService.authentication;

        // isAuthorize Method
        $scope.isAuthorize = permissionService.isAuthorize();

        // rest of codes
}]);
در کد فوق ما isAuthorize را درون scope قرار داده‌ایم. دلیل آن هم این است که هر چه که در scope قرار بگیرد، تمامی کنترلر‌های child نیز به آن دسترسی خواهند داشت. البته ممکن است که این بهترین نوع پیاده سازی برای به اشتراک گذاری یک منبع نباشد.
 در گام بعدی کافیست المان‌های صفحه را بر اساس همین دسترسی‌ها فعال یا غیر فعال کنیم. برای این کار از دستور ng-if میتوان استفاده کرد. برای این کار به مثال زیر توجه کنید:
<div ng-controller="childController">
    <div ng-if="isAuthorize('articles', 'edit')" >
    <!-- the block that we want to not see unauthorize person -->
    </div>
</div>
همانطور که مشاهده می‌کنید، تمامی المان‌ها را می‌توان با دستور ساده ng-if، از دید کاربران بدون صلاحیت، پنهان نمود. البته توجه داشته باشید که شما نمی‌توانید تنها به پنهان کردن این اطلاعات اکتفا کنید. بلکه باید تمامی متدهای کنترلرهای سمت سرور را هم با همین روش (فیلتر کردن با Filter Attribute) بررسی نمایید. به ازای هر درخواست کاربر باید بررسی شود که او به منبع مورد نظر دسترسی دارد یا خیر.
تنها نکته‌ای که باقی می‌ماند این است که طول عمر scope و rootScope چقدر است؟! برای پاسخ به این سوال باید بگوییم هر بار که صفحه refresh می‌شود، تمامی مقادیر scope و rootScope خالی می‌شوند. برای این کار هم یک راهکار خیلی ساده (و شاید کمی ناشیانه) در نظر گرفته شده‌است. میتوان بلاک مربوط به پر کردن rootScope.permissions را که در loginController نوشته شده بود، به درون parentController انتقال داد و آن را با استفاده از emit اجرا کرد و در حالت عادی، در هنگام refresh شدن صفحه نیز چون parentController در اولین لحظه اجرا می‌شود، میتوان تمامی مقادیر rootScope.permissions را دوباره از سمت سرور دریافت کرد.
مطالب
پیاده سازی یک سیستم دسترسی Role Based در Web API و AngularJs - بخش دوم
در بخش پیشین مروری اجمالی را بر روی یک سیستم مبتنی بر نقش کاربر داشتیم. در این بخش تصمیم داریم تا به جزئیات بیشتری در مورد سیستم دسترسی ارائه شده بپردازیم.
همانطور که گفتیم ما به دو صورت قادر هستیم تا دسترسی‌های (Permissions) یک سیستم را تعریف کنیم. روش اول این بود که هر متد از یک کنترلر، دقیقا به عنوان یک آیتم در جدول Permissions قرار گیرد و در نهایت برای تعیین نقش جدید، مدیر باید جزء به جزء برای هر نقش، دسترسی به هر متد را مشخص کند. در روش دوم مجموعه‌ای از API Methodها به یک دسترسی تبدیل شده است.
مراحل توسعه این روش به صورت زیر خواهند بود:
  1. توسعه پایگاه داده سیستم دسترسی مبتنی بر نقش
  2. توسعه یک Customized Filter Attribute بر پایه Authorize Attribute
  3. توسعه سرویس‌های مورد استفاده در Authorize Attribute
  4. توسعه کنترلر Permissions: تمامی APIهایی که در جهت همگام سازی دسترسی‌ها بین کلاینت و سرور را بر عهده دارند در این کنترلر توسعه داده میشود.
  5. توسعه سرویس مدیریت دسترسی در کلاینت توسط AngularJS

توسعه پایگاه داده

در این مرحله پایگاه داده را به صورت Code First پیاده سازی مینماییم. مدل Permissions به صورت زیر میباشد:
    public class Permission
    {
        [Key]
        public string Id { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public string Area { get; set; }
        public string Control { get; set; }
        public virtual ICollection<Role> Roles { get; set; }
    }
در مدل فوق همانطور که مشاهده میکنید یک ارتباط چند به چند، به Roles وجود دارد که در EF به صورت توکار یک جدول اضافی Junction اضافه خواهد شد با نام RolesPermissions. Area و Control نیز طبق تعریف شامل محدوده مورد نظر و کنترل‌های روی ناحیه در نظر گرفته می‌شوند. به عنوان مثال برای یک سایت فروشگاهی، برای بخش محصولات می‌توان حوزه‌ها و کنترل‌ها را به صورت زیر تعریف نمود:
 Control Area 
 view  products
 add  products
 edit  products
 delete  products

با توجه به جدول فوق همانطور که مشاهده می‌کنید تمامی آنچه که برای دسترسی Products مورد نیاز است در یک حوزه و 4 کنترل گنجانده میشود. البته توجه داشته باشید سناریویی که مطرح کردیم برای روشن سازی مفهوم ناحیه یا حوزه و کنترل بود. همانطور که میدانیم در AngularJS تمامی اطلاعات توسط APIها فراخوانی می‌گردند. از این رو یک موهبت دیگر این روش، خوانایی مفهوم حوزه و کنترل نسبت به نام کنترلر و متد است.

مدل Roles را ما به صورت زیر توسعه داده‌ایم:

    public class Role
    {
        [Key]
        public string Id { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public virtual ICollection<Permission> Permissions { get; set; }
        public virtual ICollection<User> Users { get; set; }
    }

در مدل فوق می‌بینید که دو رابطه چند به چند وجود دارد. رابطه اول که همان Permissions است و در مدل پیشین تشریح شد. رابطه‌ی دوم رابطه چند به چند بین کاربر و نقش است. چند کاربر قادرند یک نقش در سیستم داشته باشند و همینطور چندین نقش میتواند به یک کاربر انتساب داده شود.

ما در این سیستم از ASP.NET Identity 2.1 استفاده و کلاس IdentityUser را override کرده‌ایم. در مدل override شده، برخی اطلاعات جدید کاربر، به جدول کاربر اضافه شده‌اند. این اطلاعات شامل نام، نام خانوادگی، شماره تماس و ... می‌باشد.

public class ApplicationUser : IdentityUser
    {
        [MaxLength(100)]
        public string FirstName { get; set; }
        [MaxLength(100)]
        public string LastName { get; set; }
        public bool IsSysAdmin { get; set; }
        public DateTime JoinDate { get; set; }

        public virtual ICollection<Role> Roles { get; set; }
    }

در نهایت تمامی این مدل‌ها به وسیله EF Code First پایگاه داده سیستم ما را تشکیل خواهند داد.

توسعه یک Customized Filter Attribute بر پایه Authorize Attribute 

اگر در مورد Custom Filter Attributeها اطلاعات ندارید نگران نباشید! مقاله «فیلترها در MVC» تمامی آنچه را که باید در اینباره بدانید، به شما خواهد گفت. همچنین در  مقاله وب سایت  مایکروسافت به صورت عملی (ایجاد یک سیستم Logger) همه چیز را برای شما روشن خواهد کرد. حال بپردازیم به Filter Attribute نوشته شده که قرار است وظیفه پیش پردازش تمامی درخواست‌های کاربر را انجام دهد. در ابتدا کمی در اینباره بگوییم که این فیلتر قرار است چه کاری را دقیقا انجام دهد!
این فیلتر قرار است پیش از پردازش هر API Method، درخواست کاربر را با استفاده از نقشی که او در سیستم دارد، بررسی نماید که آیا کاربر به API اجازه دسترسی دارد یا خیر. برای این کار باید ما در ابتدا نقش‌های کاربر را بررسی نماییم. پس از اینکه نقش‌های کاربر واکشی شدند، باید بررسی کنیم آیا نقشی که کاربر دارد، شامل این حوزه و کنترل بوده است یا خیر؟ Area و Control دو پارامتری هستند که در سیستم پیش از هر متد، Hard Code شده‌اند و در ادامه نمونه‌ای از آن را نمایش خواهیم داد.
    public class RBACAttribute : AuthorizeAttribute
    {
        public string Area { get; set; }
        public string Control { get; set; }
        AccessControlService _AccessControl = new AccessControlService();
        public override void OnAuthorization(HttpActionContext actionContext)
        {
            var userId = HttpContext.Current.User.Identity.GetCurrentUserId();
            // If User Ticket is Not Expired
            if (userId == null || !_AccessControl.HasPermission(userId, this.Area, this.Control))
            {
                actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
            }
        }
    }
در خط پنجم، سرویس AccessControl را فراخوانی کرده‌ایم که در ادامه به پیاده سازی آن نیز خواهیم پرداخت. متد HasPermission از این سرویس دو پارامتر id کاربر و Area و Control را دریافت میکند و با استفاده از این سه پارامتر بررسی میکند که آیا کاربر جاری به این بخش دسترسی دارد یا خیر؟ در صورت منقضی شدن ticket کاربر و یا عدم دسترسی، سرور Unauthorized status code را به کاربر باز می‌گرداند.
بلوک زیر استفاده از این فیلتر را نمایش می‌دهد:
[HttpPost]
[Route("ChangeProductStatus")]
[RBAC(Area = "products", Control = "edit")]
public async Task<HttpResponseMessage> ChangeProductStatus(StatusCodeBindingModel model)
{
// Method Body
}
همانطور که مشاهده می‌کنید کافیست RBAC را با دو پارامتر، پیش از متد نوشت. به صورت خودکار پیش از فراخوانی این متد که وظیفه تغییر وضعیت کالا را بر عهده دارد، فیلتر نوشته شده فراخوانی خواهد شد.
در بخش بعدی به بیان ادامه جریان و توسعه سرویس Access Control خواهیم پرداخت.
مطالب
واکشی اطلاعات سرویس Web Api با استفاده از TypeScript و AngularJs
در پست‌های قبلی با TypeScript، AngularJs و Web Api آشنا شدید. در این پست قصد دارم از ترکیب این موارد برای پیاده سازی عملیات واکشی اطلاعات سرویس Web Api در قالب یک پروژه استفاده نمایم. برای شروع ابتدا یک پروژه Asp.Net MVC ایجاد کنید.
در قسمت مدل ابتدا یک کلاس پایه برای مدل ایجاد خواهیم کرد:
public abstract class Entity
    {
        public Guid Id { get; set; }
    }
حال کلاسی به نام Book ایجاد می‌کنیم:
public class Book : EntityBase
    {
        public string Name { get; set; }
        public decimal Author { get; set; }
    }
در پوشه مدل یک کلاسی به نام BookRepository ایجاد کنید و کد‌های زیر را در آن کپی نمایید(به جای پیاده سازی بر روی بانک اطلاعاتی، عملیات بر روی لیست درون حافظه انجام می‌گیرد):
 public class BookRepository
    {
        private readonly ConcurrentDictionary<Guid, Book> result = new ConcurrentDictionary<Guid, Book>();

        public IQueryable<Book> GetAll()
        {
            return result.Values.AsQueryable();
        }        

        public Book Add(Book entity)
        {
            if (entity.Id == Guid.Empty) entity.Id = Guid.NewGuid();

            if (result.ContainsKey(entity.Id)) return null;

            if (!result.TryAdd(entity.Id, entity)) return null;

            return entity;
        }     
    }

نوبت به کلاس کنترلر می‌رسد. یک کنترلر Api به نام BooksController ایجاد کنید و سپس کد‌های زیر را در آن کپی نمایید:
 public class BooksController : ApiController
    {
        public static BookRepository repository = new BookRepository();       

public BooksController()
        {
            repository.Add(new Book 
            {
                Id=Guid.NewGuid(),
                Name="C#",
                Author="Masoud Pakdel"
            });

            repository.Add(new Book
            {
                Id = Guid.NewGuid(),
                Name = "F#",
                Author = "Masoud Pakdel"
            });

            repository.Add(new Book
            {
                Id = Guid.NewGuid(),
                Name = "TypeScript",
                Author = "Masoud Pakdel"
            });
        }

        public IEnumerable<Book> Get()
        {
            return repository.GetAll().ToArray();
        }          
    }

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

حال نویت به عملیات سمت کلاینت میرسد. برای استفاده از قابلیت‌های TypeScript و AngularJs در Vs.Net از این مقاله کمک بگیرید. بعد از آماده سازی در فولدر script، پوشه ای به نام app می‌سازیم و یک فایل TypeScript به نام  BookModel  در آن ایجاد می‌کنیم:
module Model {
    export class Book{
        Id: string;
        Name: string;
        Author: string;
    }
}
واضح است که ماژولی به نام Model داریم که در آن کلاسی به نام Book ایجاد شده است. برای انتقال اطلاعات از طریق سرویس http$ در Angular نیاز به سریالایز کردن این کلاس به فرمت Json خواهیم داشت. قصد داریم View مورد نظر را به صورت زیر ایجاد نماییم:
 <div ng-controller="Books.Controller">       
        <table class="table table-striped table-hover" style="width: 500px;">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Author</th>              
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="book in books">
                    <td>{{book.Name}}</td>
                    <td>{{book.Author}}</td>                                     
                </tr>
            </tbody>
        </table>
    </div>

توضیح کد‌های بالا:
ابتدا یک کنترلری که به نام Controller که در ماژولی به نام Book تعریف شده است باید ایجاد شود. اطلاعات تمام کتب ثبت شده باید از سرویس مورد نظر دریافت و با یک ng-repeat در جدول نمایش داده خواهند شود.
در پوشه app یک فایل TypeScript دیگر برای تعریف برخی نیازمندی‌ها به نام  AngularModule ایجاد می‌کنیم که کد آن به صورت زیر خواهد بود:
declare module AngularModule {
    export interface HttpPromise {
        success(callback: Function) : HttpPromise;       
    }
    export interface Http {
        get(url: string): HttpPromise;   
    }
}
در این ماژول دو اینترفیس تعریف شده است. اولی به نام HttpPromise است که تابعی به نام success  دارد. این تابع باید بعد از موفقیت آمیز بودن  عملیات فراخوانی شود. ورودی آن از نوع Function است. بعنی اجازه تعریف یک تابع را به عنوان ورودی برای این توابع دارید.
در اینترفیس Http نیز تابعی به نام get تعریف شده  است که  برای دریافت اطلاعات از سرویس api، مورد استفاده قرار خواهد گرفت. از آن جا که تعریف توابع در اینترفیس فاقد بدنه است در نتیجه این جا فقط امضای توابع مشخص خواهد شد. پیاده سازی توابع به عهده کنترلر‌ها خواهد بود:
مرحله بعد مربوط است به تعریف کنترلری  به نام BookController تا اینترفیس بالا را پیاده سازی نماید. کد‌های آن به صورت زیر خواهد بود:
/// <reference path='AngularModule.ts' />
/// <reference path='BookModel.ts' />

module Books {
    export interface Scope {        
        books: Model.Book[];
    }

    export class Controller {
        private httpService: any;

        constructor($scope: Scope, $http: any) {
            this.httpService = $http;

            this.getAllBooks(function (data) {
                $scope.books = data;
            });
            var controller = this;
    }

        getAllBooks(successCallback: Function): void {
            this.httpService.get('/api/books').success(function (data, status) {
                successCallback(data);
            });
        }
    }
}


توضیح کد‌های بالا:
برای دسترسی به تعاریف انجام شده در سایر ماژول‌ها باید ارجاعی به فایل تعاریف ماژول‌های مورد نظر داشته باشیم. در غیر این صورت هنگام استفاده از این ماژول‌ها با خطای کامپایلری روبرو خواهیم شد. عملیات ارجاع به صورت زیر است:
/// <reference path='AngularModule.ts' />
/// <reference path='BookModel.ts' />
در پست قبلی توضیح داده شد که برای مقید سازی عناصر بهتر است یک اینترفیس به نام Scope تعریف کنیم تا بتوانیم متغیر‌های مورد نظر برای مقید سازی را در آن تعریف نماییم در این جا تعریف آن به صورت زیر است:
export interface Scope {  
        books: Model.Book[];      
    }
در این جا فقط نیاز به لیستی از کتاب‌ها داریم تا بتوان در جدول مورد نظر در View آنرا پیمایش کرد. تابعی به نام getAllBooks در کنترلر مورد نظر نوشته شده است که ورودی آن یک تابع خواهد بود که باید بعد از واکشی اطلاعات از سرویس، فراخوانی شود. اگر به کد‌های بالا دقت کنید می‌بینید که در ابتدا سازنده کنترلر،سرویس http$ موجود در Angular به متغیری به نام httpService نسبت داده می‌شود. با فراخوانی تابع get و ارسال آدرس سرویس که با توجه به مقدار مسیر یابی پیش فرض کلاس WebApiConfig باید با api شروع شود به راحتی اطلاعات مورد نظر به دست خواهد آمد. بعد از واکشی در صورت موفقیت آمیز بودن عملیات تابع success اجرا می‌شود که نتیجه آن انتساب مقدار به دست آمده به متغیر books تعریف شده در scope$ می‌باشد.

در نهایت خروجی به صورت زیر خواهد بود:


سورس پیاده سازی مثال بالا در Visual Studio 2013
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت چهارم - data binding
در قسمت قبل، نگاهی مقدماتی داشتیم به مبحث data binding. در ادامه، این مبحث را به همراه pipes، جهت اعمال تغییرات بر روی اطلاعات، پیگیری خواهیم کرد.


انقیاد به خواص یا property binding

قابلیت property binding این امکان را فراهم می‌کند که یکی از خواص المان‌های HTML را به مقادیر دریافتی از کلاس کامپوننت، متصل کنیم:
 <img [src]='producr.imageUrl'>
در این مثال، خاصیت src المان تصویر، به آدرس تصویر یک محصول متصل شده‌است.
در حین تعریف property binding، مقصد اتصال، داخل براکت‌ها قرار می‌گیرد و خاصیت مدنظر المان را مشخص می‌کند. منبع اتصال همیشه داخل "" در سمت راست علامت مساوی قرار می‌گیرد.
اگر اینکار را بخواهیم با interpolation معرفی شده‌ی در قسمت قبل انجام دهیم، به کد ذیل خواهیم رسید:
 <img src={{producr.imageUrl}}>
در اینجا نه از []، برای معرفی مقصد اتصال استفاده شده‌است و نه از "" برای مشخص سازی منبع اتصال. این نوع اتصال یک طرفه است (از منبع به مقصد).

خوب، در یک چنین مواردی property binding بهتر است یا interpolation؟
توصیه‌ی کلی ترجیح property binding به interpolation است. اما اگر در اینجا نیاز به انجام محاسباتی بر روی عبارت منبع وجود داشت، باید از interpolation استفاده کرد؛ مانند:
 <img src='http://www.mysite.com/images/{{producr.imageUrl}}'>


تکمیل قالب کامپوننت لیست محصولات

اگر از قسمت قبل به خاطر داشته باشید، در فایل product-list.component.html، لیست پردازش شده‌ی توسط ngFor*، فاقد ستون نمایش تصاویر محصولات است. به همین جهت فایل یاد شده را گشوده و سپس با استفاده از property binding، دو خاصیت src و title تصویر را به منبع داده‌ی آن متصل می‌کنیم:
<tbody>
    <tr *ngFor='#product of products'>
        <td>
            <img [src]='product.imageUrl'
                 [title]='product.productName'>
        </td>
        <td>{{ product.productName }}</td>
        <td>{{ product.productCode }}</td>
        <td>{{ product.releaseDate }}</td>
        <td>{{ product.price }}</td>
        <td>{{ product.starRating }}</td>
    </tr>
</tbody>
در این حالت اگر برنامه را اجرا کنیم به خروجی ذیل خواهیم رسید:


هرچند اینبار تصاویر محصولات نمایش داده شده‌اند، اما اندکی بزرگ هستند. بنابراین در ادامه با استفاده از property binding، خواص style آن‌را تنظیم خواهیم کرد. برای این منظور فایل product-list.component.ts را گشوده و به کلاس ProductListComponent، دو خاصیت imageWidth و imageMargin را اضافه می‌کنیم:
export class ProductListComponent {
    pageTitle: string = 'Product List';
    imageWidth: number = 50;
    imageMargin: number = 2;
    products: any[] = [
        // as before...
    ];
}
البته همانطور که پیشتر نیز ذکر شد، چون مقادیر پیش فرض این خواص عددی هستند، نیازی به ذکر صریح نوع number در اینجا وجود ندارد (type inference).
پس از تعریف این خواص، امکان دسترسی به آن‌ها در قالب کامپوننت وجود خواهد داشت:
<tbody>
    <tr *ngFor='#product of products'>
        <td>
            <img [src]='product.imageUrl'
                 [title]='product.productName'
                 [style.width.px]='imageWidth'
                 [style.margin.px]='imageMargin'>
        </td>
همانطور که مشاهده می‌کنید، چون خاصیت‌های جدید تعریف شده، جزئی از خواص اصلی کلاس هستند و نه خواص اشیاء لیست محصولات، دیگر به همراه .product ذکر نشده‌اند.
همچنین در اینجا نحوه‌ی style binding را نیز مشاهده می‌کنید. مقصد اتصال همیشه با [] مشخص می‌شود و سپس کار با ذکر .style شروع شده و پس از آن نام خاصیت مدنظر عنوان خواهد شد. اگر نیاز به ذکر واحدی وجود داشت، پس از درج نام خاصیت، قید خواهد شد. برای مثال [style.fontSize.em] و یا [%.style.fontSize]


یک نکته:
اگر مثال را قدم به قدم دنبال کرده باشید، با افزودن style binding و بارگذاری مجدد صفحه، احتمالا تغییراتی را مشاهده نخواهید کرد. این مورد به علت کش شدن قالب قبلی و یا فایل جاوا اسکریپتی متناظر با آن است (فایلی که خواص عرض و حاشیه‌ی تصویر به آن اضافه شده‌اند).
یک روش ساده‌ی حذف کش آن، بازکردن آدرس http://localhost:2222/app/products/product-list.component.js در مرورگر به صورت مجزا و سپس فشردن دکمه‌های ctrl+f5 بر روی آن است.


پاسخ دادن به رخدادها و یا event binding

تا اینجا تمام data binding‌های تعریف شده‌ی ما یک طرفه بودند؛ از خواص کلاس کامپوننت به اجزای قالب متناظر با آن. اما گاهی از اوقات نیاز است تا با کلیک کاربر بر روی دکمه‌ای، عملی خاص صورت گیرد و در این حالت، جهت ارسال اطلاعات، از قالب کامپوننت، به متدها و خواص کلاس متناظر با آن خواهند بود. کامپوننت به اعمال کاربر از طریق event binding گوش فرا می‌دهد:
 <button (click)='toggleImage()'>
syntax آن بسیار شبیه است به حالت property binding و در اینجا بجای [] از () جهت مشخص سازی رخدادی خاص از المان مدنظر استفاده می‌شود. سمت راست این انتساب، متدی است که داخل "" قرار می‌گیرد و این متد متناظر است با متدی مشخص در کلاس متناظر با کامپوننت جاری.
در این حالت اگر کاربر روی دکمه‌ی تعریف شده کلیک کند، متد toggleImage موجود در کلاس متناظر، فراخوانی خواهد شد.
چه رخدادهایی را در اینجا می‌توان ذکر کرد؟ پاسخ آن‌را در آدرس ذیل می‌توانید مشاهده کنید:
https://developer.mozilla.org/en-US/docs/Web/Events

این syntax جدید AngularJS 2.0 سطح API آن‌را کاهش داده است. دیگر در اینجا نیازی نیست تا به ازای هر رخداد ویژه‌ای، یک دایرکتیو و یا syntax خاص آن‌را در مستندات آن
جستجو کرد. فقط کافی است syntax جدید (نام رخداد) را مدنظر داشته باشید.


تکمیل مثال نمایش لیست محصولات با فعال سازی دکمه‌ی Show Image آن

در اینجا قصد داریم با کلیک بر روی دکمه‌ی Show image، تصاویر موجود در ستون تصاویر، مخفی و یا نمایان شوند. برای این منظور خاصیت جدیدی را به نام showImage به کلاس ProductListComponent اضافه می‌کنیم. بنابراین فایل product-list.component.ts را گشوده و تغییر ذیل را به آن اعمال کنید:
export class ProductListComponent {
    pageTitle: string = 'Product List';
    imageWidth: number = 50;
    imageMargin: number = 2;
    showImage: boolean = false;
در اینجا خاصیت Boolean جدیدی به نام showImage با مقدار اولیه‌ی false تعریف شده‌است. به این ترتیب تصاویر، در زمان اولین بارگذاری صفحه نمایش داده نخواهند شد.
سپس به انتهای کلاس، پس از تعاریف خواص، متد جدید toggleImage را اضافه می‌کنیم:
export class ProductListComponent {
    // as before ...
 
    toggleImage(): void {
        this.showImage = !this.showImage;
    }
}
در کلاس‌های TypeScript نیازی به ذکر صریح واژه‌ی کلیدی function برای تعریف متدی وجود ندارد. این متد، خروجی هم ندارد، بنابراین نوع خروجی آن void مشخص شده‌است. در بدنه‌ی این متد، وضعیت خاصیت نمایش تصویر معکوس می‌شود.
پس از این تغییرات، اکنون می‌توان به قالب این کامپوننت یا فایل product-list.component.html مراجعه و event binding را تنظیم کرد:
<button class='btn btn-primary'
        (click)='toggleImage()'>
    Show Image
</button>
در اینجا click به عنوان رخداد مقصد، مشخص شده‌است. سپس آن‌را به متد toggleImage کلاس ProductListComponent متصل می‌کنیم.
خوب، تا اینجا اگر کاربر بر روی دکمه‌ی show image کلیک کند، مقدار خاصیت showImage کلاس ProductListComponent با توجه به کدهای متد toggleImage، معکوس خواهد شد.
مرحله‌ی بعد، استفاده از مقدار این خاصیت، جهت مخفی و یا نمایان ساختن المان تصویر جدول نمایش داده شده‌است. اگر از قسمت قبل به خاطر داشته باشید، کار ngIf*، حذف و یا افزودن المان‌های DOM است. بنابراین ngIf* را به المان تصویر جدول اضافه می‌کنیم:
<tr *ngFor='#product of products'>
    <td>
        <img *ngIf='showImage'
             [src]='product.imageUrl'
             [title]='product.productName'
             [style.width.px]='imageWidth'
             [style.margin.px]='imageMargin'>
    </td>
با توجه به ngIf* تعریف شده، المان تصویر تنها زمانی به DOM اضافه خواهد شد که مقدار خاصیت showImage مساوی true باشد.

اکنون برنامه را اجرا کنید. در اولین بار اجرای صفحه، تصاویر ستون اول جدول، نمایش داده نمی‌شود. پس از کلیک بر روی دکمه‌ی Show image، این تصاویر نمایان شده و اگر بار دیگر بر روی این دکمه کلیک شود، این تصاویر مخفی خواهند شد.

یک مشکل! در هر دو حالت نمایش و مخفی سازی تصاویر، برچسب این دکمه Show image است. بهتر است زمانیکه قرار است تصاویر مخفی شوند، برچسب hide image نمایش داده شود و برعکس. برای حل این مساله از interpolation به نحو ذیل استفاده خواهیم کرد:
<button class='btn btn-primary'
        (click)='toggleImage()'>
    {{showImage ? 'Hide' : 'Show'}} Image
</button>
در اینجا اگر مقدار خاصیت showImage مساوی true باشد، مقدار رشته‌ای Hide و اگر false باشد، مقدار رشته‌ای show بجای {{}} درج خواهد شد.



بررسی انقیاد دو طرفه یا two-way binding

تا اینجا، اتصال مقدار یک خاصیت عمومی کلاس متناظر با قالبی، به اجزای مختلف آن، یک طرفه بودند. اما در ادامه نیاز است تا بتوان برای مثال در textbox قسمت filter by مثال جاری بتوان اطلاعاتی را وارد کرد و سپس بر اساس آن ردیف‌های جدول نمایش داده شده را فیلتر نمود. این عملیات نیاز به انقیاد دو طرفه یا two-way data binding دارد.
برای تعریف انقیاد دو طرفه در AngularJS 2.0 از دایرکتیو توکاری به نام ngModel استفاده می‌شود:
 <input [(ngModel)]='listFilter' >
ابتدا [] ذکر می‌شود تا مشخص شود که این عملیات در اصل یک property binding است؛ از خاصیت عمومی به نام listFilter به المان textbox تعریف شده.
سپس () تعریف شده‌است تا event binding را نیز گوشزد کند. کار آن انتقال تعاملات کاربر، با المان رابط کاربری جاری، به خاصیت عمومی کلاس یا همان listFilter است.

در اینجا ممکن است که فراموش کنید [()] صحیح است یا ([]) . به همین جهت به این syntax، نام banana in the box را داده‌اند یا «موز درون جعبه»! موز همان event binding است که داخل جعبه‌ی property binding قرار می‌گیرد!

خوب، برای اعمال انقیاد دو طرفه، به مثال جاری، فایل product-list.component.ts را گشوده و خاصیت رشته‌ای listFilter را به آن اضافه می‌کنیم:
export class ProductListComponent {
    pageTitle: string = 'Product List';
    imageWidth: number = 50;
    imageMargin: number = 2;
    showImage: boolean = false;
    listFilter: string = 'cart';
سپس فایل قالب product-list.component.html را گشوده و انقیاد دو طرفه را به آن اعمال می‌کنیم:
<div class='panel-body'>
    <div class='row'>
        <div class='col-md-2'>Filter by:</div>
        <div class='col-md-4'>
            <input type='text'
                   [(ngModel)]='listFilter' />
        </div>
    </div>
    <div class='row'>
        <div class='col-md-6'>
            <h3>Filtered by: {{listFilter}}</h3>
        </div>
    </div>
در اینجا انقیاد دو طرفه، توسط ngModel، به خاصیت listFilter کلاس، در المان input تعریف شده، صورت گرفته است‌. سپس توسط interpolation مقدار این تغییر را در قسمت Filtered by به صورت یک برچسب نمایش می‌دهیم.


پس از اجرای برنامه، تکست باکس تعریف شده، مقدار اولیه‌ی cart را خواهد داشت و اگر آن‌را تغییر دهیم، بلافاصله این مقدار تغییر یافته را در برچسب Filtered by می‌توان مشاهده کرد. به این رخداد two-way binding می‌گویند.
البته هنوز کار فیلتر لیست محصولات در اینجا انجام نمی‌شود که آن‌را در قسمت بعد تکمیل خواهیم کرد.


فرمت کردن اطلاعات نمایش داده شده‌ی در جدول با استفاده از Pipes

تا اینجا لیست محصولات نمایش داده شد، اما نیاز است برای مثال فرمت ستون نمایش قیمت آن بهبود یابد. برای این منظور، از ویژگی دیگری به نام pipes استفاده می‌شود و کار آن‌ها تغییر داده‌ها، پیش از نمایش آن‌ها است. AngularJS 2.0 به همراه تعدادی pipe توکار برای فرمت مقادیر است؛ مانند date، number، decimal، percent و غیره. همچنین امکان ساخت custom pipes نیز پیش بینی شده‌است.
در اینجا یک مثال ساده‌ی pipes را مشاهده می‌کنید:
 {{ product.productCode | lowercase }}
پس از قید نام خاصیتی که قرار است نمایش داده شود، حرف pipe یا | قرار گرفته و سپس نوع pipe ذکر می‌شود. به این ترتیب کد محصول، پیش از نمایش، ابتدا به حروف کوچک تبدیل شده و سپس نمایش داده می‌شود.

از pipes در property binding هم می‌توان استفاده کرد:
 [title]='product.productName | uppercase'
در اینجا برای مثال عنوان تصویر با حروف بزرگ نمایش داده خواهد شد.

و یا می‌توان pipes را به صورت زنجیره‌ای نیز تعریف کرد:
 {{ product.price | currency | lowercase }}
در اینجا pipe توکار currency سبب نمایش سه حرف اول واحد پولی، با حروف بزرگ می‌شود. اگر علاقمند بودیم که آن‌را با حروف کوچک نمایش دهیم می‌توان یک pipe دیگر را در انتهای این زنجیره قید کرد.

بعضی از pipes، پارامتر هم قبول می‌کنند:
 {{ product.price | currency:'USD':true:'1.2-2' }}
در اینجا هر پارامتر با یک : مشخص می‌شود. برای مثال pipe واحد پولی، سه پارامتر را دریافت می‌کند: یک کد دلخواه، نمایش یا عدم نمایش علامت پولی، بجای کد دلخواه و مشخصات ارقام نمایش داده شده. برای مثال 2-1.2، یعنی حداقل یک عدد پیش از اعشار، حداقل دو عدد پس از اعشار و حداکثر دو عدد پس از اعشار باید ذکر شوند (یعنی در نهایت دو رقم اعشار مجاز است).
مبحث ایجاد custom pipes را در قسمت بعدی دنبال خواهیم کرد.

در ادامه برای ویرایش مثال جاری، فایل قالب product-list.component.html را گشوده و سطرهای جدول را به نحو ذیل تغییر دهید:
<td>{{ product.productName }}</td>
<td>{{ product.productCode | lowercase }}</td>
<td>{{ product.releaseDate }}</td>
<td>{{ product.price | currency:'USD':true:'1.2-2'}}</td>
<td>{{ product.starRating }}</td>
در اینجا با استفاده از pipes، شماره محصول با حروف کوچک و قیمت آن تا حداکثر دو رقم اعشار، فرمت خواهند شد.
اینبار اگر برنامه را اجرا کنید، یک چنین خروجی را مشاهده خواهید کرد:


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


خلاصه‌ی بحث

data binding سبب سهولت نمایش مقادیر خواص کلاس یک کامپوننت، در قالب آن می‌شود. در AngularJS 2.0، چهار نوع binding وجود دارند:


interpolation، عبارت رشته‌ای محاسبه شده را در بین المان‌های DOM درج می‌کند و یا می‌تواند خاصیت المانی را مقدار دهی نماید.
property binding سبب اتصال مقدار خاصیتی، به یکی از خواص المانی مشخص در DOM می‌شود.
event binding به رخ‌دادها گوش فرا داده و سبب اجرای متدی در کلاس کامپوننت، در صورت بروز رخداد متناظری می‌شود.
حالت two-way binding، کار دریافت اطلاعات از کلاس و همچنین بازگشت مقادیر تغییر یافته‌ی توسط کاربر را به کلاس انجام می‌دهد.
اطلاعات نمایش داده شده‌ی توسط binding عموما فرمت مناسبی را ندارد. برای رفع این مشکل از pipes استفاده می‌شود.
مطالب
معرفی ES 6
جاوا اسکریپت، پیاده سازی ECMA Script است (با تلفظ اکما اسکریپت) که به صورت مخفف به آن ES هم گفته می‌شود و ES 6، در حقیقت نگارش 6 این استاندارد است که به آن ECMA Script 2015 نیز می‌گویند (زمان نهایی شدن استاندارد آن سال 2015 است) و از زمان ارائه‌ی جاوا اسکریپت، بزرگترین تغییر و تحول آن نیز محسوب می‌گردد. با ES 6 مواردی مانند کلاس‌ها، برنامه نویسی تابعی (functional programming)، ماژول‌ها و غیره اضافه شده‌اند.



چرا باید ES 6 را آموخت؟

در طی 2 سال آینده، تمام فریم ورک‌های جدید جاوا اسکریپتی، از بوت استرپ 4 تا AngularJS 2 تا Aurelia و غیره، همگی به ES 6 کوچ خواهند کرد (و این اتفاق هم اکنون در حال رخ دادن است). بنابراین به زودی بدون فراگیری و تسلط بر ES 6، در حوزه‌ی وب، «بی‌سواد» محسوب خواهید شد و فراگیری آن یک «باید» است.


وضعیت پشتیبانی از ES 6 در مرورگرهای مختلف

برای مشاهده‌ی پیشرفت‌های مرورگرهای کنونی در زمینه‌ی پشتیبانی از ES 6‌، می‌توان به صفحه‌ی ES 6 compatibility table مراجعه کرد.


برای نمونه در حال حاضر، فایرفاکس بهترین پشتیبانی از ES 6 را ارائه می‌دهد (با پیاده سازی 85 درصد از قابلیت‌ها) و بعد از آن مرورگر جدید مایکروسافت قرار دارد.
وضعیت IE 10,11 در این بین تغییری نخواهند کرد؛ زیرا پشتیبانی رسمی از تمام آن‌ها به زودی خاتمه می‌یابد (در سه شنبه، ۲۲ دی ۱۳۹۴).
در همین صفحه، در ابتدای چارت، ستون current browser نیز قرار دارد. این ستون، وضعیت مرورگر جاری شما را از لحاظ درصد پیاده سازی قابلیت‌های ES 6 نمایش می‌دهد.


اهمیت دریافت آخرین نگارش‌های مرورگرها

با توجه به ES 6 compatibility table، اکثر مرورگرها در نسخه‌های شبانه و همچنین آزمایشی آن‌ها، به مرور در حال افزودن قابلیت‌های باقیمانده‌ی ES 6 هستند. بنابراین اگر با فایرفاکس کار می‌کنید، نیاز است Firefox nightly builds را نصب کنید. اگر از مرورگرهای مایکروسافت استفاده می‌کنید، آخرین نگارش MS Edge بهترین پشتیبانی از ES 6 را ارائه می‌دهد و اگر از کروم استفاده می‌کنید، نگارش‌‌های بتا و Dev آن را می‌توانید دریافت کنید.

علاوه بر این‌ها، نگارش‌های فعلی این مرورگرها نیز دارای امکانات آزمایشی هستند که می‌توان آن‌ها را به صورت دستی فعال کرد. برای مثال در مرورگر کروم، به آدرس chrome://flags مراجعه کنید و در صفحه‌ی باز شده، کلمه‌ی JavaScript را جستجو کنید. در اینجا نیاز است گزینه‌ی «Enable Experimental JavaScript» را فعال کنید (بر روی لینک enable ذیل آن کلیک نمائید).


به این ترتیب قادر خواهید بود آخرین افزونه‌های ES 6 را در developer tools console آن اجرا کنید.

چنین تنظیمی به MS Edge نیز اضافه شده‌است. پس از اجرای آن، به آدرس about:flags مراجعه کنید:


در اینجا نیز می‌توانید گزینه‌ی «Enable experimental JavaScript features» را انتخاب کنید.


معرفی traceur-compiler

هرچند قابلیت‌های فعلی آخرین نگارش‌های مرورگرها برای اجرای بسیاری از امکانات ES 6 کفایت می‌کنند، اما اگر علاقمند به اجرای تمامی آن‌ها هستید، می‌توان از traceur-compiler گوگل نیز کمک گرفت (با تلفظ تریسر). این کامپایلر، قابلیت‌های جدید ES 6 را تبدیل به نگارش‌های فعلی قابل درک برای مرورگرهای قدیمی‌تر می‌کند. به این ترتیب امکان اجرای آزمایشات مرتبط با ES 6 را خواهید یافت.
روش استفاده‌ی از آن هم به صورت ذیل است:
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<script type="module">
// ES 6
</script>
ابتدا نیاز است دو اسکریپت اصلی تریسر را به صفحه الحاق کنید. پس از آن نوع تگ اسکریپت را به module تنظیم کنید. در این بین قادر خواهید بود به تمامی امکانات ES 6 در مرورگر فعلی خود دسترسی داشته باشید.
مطالب
به اشتراک گذاری داده ها بین کنترلرها در AngularJs
در پست قبلی با مفاهیم کنترلر و مدل در AngularJs آشنا شدید. قصد دارم روشی را بررسی کنم که یک منبع داده را بین کنترل‌های تعریف شده در یک ماژول را به اشتراک بگذاریم.
ابتدا یک فایل جاوااسکریپ به نام module1 ایجاد می‌کنیم . در این فایل ابتدا ماژول خود را به Angular معرفی کرده و سپس با استفاده از دستور factory سرویس مورد نظر برای به اشتراک گذاری داده را می‌سازیم:
var app = angular.module('myApp', []);

app.factory('BookData', function () {
    var books = [
        { code: 1, name: 'book1', },
        { code: 2, name: 'book2', },
        { code: 3, name: 'book3', },
        { code: 4, name: 'book4', },
        { code: 5, name: 'book5', }
    ];
    return books;    
});
همان طور که در پست قبلی شرح داده شده برای تعریف ماژول از دستور angular.module استفاده می‌کنیم. در خط بعدی یک سرویس به نام BookData را با استفاده از دستور factory در ماژول مربوطه ساخته می‌شود. تابع مورد نظر بک آرایه از کتاب‌ها را که هر کدام از آن‌ها شامل کد و نام است برگشت می‌دهد. قصد داریم کنترل‌های تعریف شده در ماژول myApp بتوانند به این لیست این کتاب‌ها دسترسی داشته باشند. در این مرحله ابتدا یک کنترلر به نام  به controller1 به صورت زیر می‌سازیم:
app.controller('controller1', function ($scope, BookData) {
    $scope.books = BookData;
});
تنها نکته قابل ذکر، تزریق مقادیر scope$ و BookData به تابع سازنده کنترلر مربوطه است. از scope$ برای مقید سازی مقادیر مدل به عناصر dom در view استفاده می‌شود و BookData در این جا دقیقا به مقدار برگشت داده شده از سرویس BookData اشاره می‌کند(نام سرویس مورد نظر دقیقا باید با مقداری که به عنوان آرگومان اول در تابع factory پاس می‌دهید یکی باشد). در نتیجه این مقدار را به متغیر books در scope$ نسبت می‌دهیم. برای کنترلر دوم نیز همین مراحل را تکرار می‌کنیم:
app.controller('controller2', function ($scope, BookData) {
    $scope.books = BookData;
});
در View مورد نظر نیز یک ارجاع به فایل ساخته شده بالا خواهیم داشت و سپس کدهای مربوط به نمایش را به صورت زیر می‌نویسیم(البته ارجاع به فایل اصلی angular.js فراموش نشود):

<script type="text/javascript" src="~/scripts/app/controller1.js"></script>  
<div ng-app="myApp"> <div ng-controller="controller1"> <p>Data from controller1</p> <table> <tr ng-repeat="book in books"> <td> {{book.code}} </td> <td> {{book.name}} </td> </tr> </table> </div> <div ng-controller="controller2"> <p>Data from controller2</p> <table> <tr ng-repeat="book in books"> <td> {{book.code}} </td> <td> {{book.name}} </td> </tr> </table> </div> </div>
ابتدا در تگ div اول با استفاده از ng-app محدوده ماژول مورد نظر در صفحه را تعیین کرده سپس با استفاده از تگ‌های div جداگانه  هر کدام از نواحی تحت کنترل مربوط به کنترلر‌های تعریف شده را مشخص می‌کنیم.
با استفاده از ng-repeat به راحتی در بین آرایه کتاب‌ها پیمایش کرده و لیست مورد نظر در صفحه نمایش داده می‌شود. (توضیحات مربوط به ng-repeat و {{}} در پست قبلی شرح داده شده است). خروجی به صورت زیر خواهد بود. واضح است که اطلاعات نمایش داده شده توسط هر دو کنترلر به دلیل استفاده از منبع داده ای یکسان، به یک شکل خواهد بود.

نظرات نظرسنجی‌ها
آیا با وجود سی‌ام‌اس فروشگاهی قدرتمندی مثل nopCommerce یا SmartStore آیا منطقی است که ما دوباره خودمان از صفر کد بزنیم؟
چند تا از نمونه هایی که عملا به صورت عمیق (درگیر شدن در کد‌های هسته و ماژول‌های آن و توسعه سیستم) با آنها کار کرده ایم:
برای PHP
  1. woocommerce (فروشگاه‌های تا متوسط و عملیات تجاری سبک) - سفارشی سازی در حد وردپرس - راحته ولی رو اعصابه
  2. prestashop (فروشگاه‌های تا متوسط و عملیات تجاری متوسط) - سفارشی سازی متوسط - زیرساخت‌های خیلی پیشرفته درش وجود ندارد
  3. magento (فروشگاه‌های تا سایز بزرگ و عملیات تجاری بزرگ) - سفارشی سازی پیشرفته 
برای .NET
  1. nopCommerce(فروشگاه‌های تا متوسط و عملیات تجاری متوسط) -سفارشی سازی متوسط
  2. VirtoCommerce(فروشگاه‌های تا سایز بزرگ و عملیات تجاری بزرگ) -سفارشی سازی پیشرفته، زمان بر است ولی برای کارهای بزرگ لازم است.
موارد سفارشی 
  1. با ASP.NET چند مورد توسعه داشتیم.
هر کدام از اینها سطح مختلفی از پیچیدگی دارند و بر اساس این پیچیدگی سطوح مختلفی از ماژول‌های تجاری پیشرفته درآنها وجود دارد.
بری مثال در nopCommerce شما C# و ASP.NET و jQuery بلد باشید عمده کار انجام می‌شود. و پروژه لایه بندی پیچیده ای ندارد. شما با یک اپلیکیشن طرف هستید
ولی در VirtoCommerce باید تسلط کافی به مفاهیم برنامه نویسی شی گرا، لایه بندی، معماری سرویس گرا، C# و AngularJs و liquid و چندین مورد دیگر داشته باشید تا پروژه جلو برود. اینجا با چند اپلیکیشن طرف هستید و این پروژه برای اجرا شدن روی shared hosting ساخته نشده است.
آن چیزی که مشتری از یک سیستم ecommerce می‌بیند با چیزی که مدیران و پرسنل مجموعه فروشگاه می‌بینند متفاوت است، در اغلب سیستم‌ها چیزی که مستری می‌بیند تقریبا مشابه است ولی پشت صحنه زمین تا آسمان تفاوت دارد.
پشت صحنه یک فروشگاه اتفاقات زیادی می‌تواند در جریان باشد، چیزی که در بخش مدیریت سفارش nopcommerce وجود دارد ساده است، مدیریت امور مالی، مدیریت تحویل کالا، مدیریت اسناد، مدیریت موجودی و ... می‌تواند بسیار پیچیده‌تر باشد و حتی به کمک نرم افزارهای دیگر یا سرویس‌های آنلاین دیگر مدیریت شود.
Delivery, Payment, Tax, Inventory, Warehouse, Localization, Globalization و موارد متعدد دیگری هر کدام در این فروشگاه‌های آماده در حد نیاز پیاده سازی شده اند، و این نیازی که توسط تیم توسعه آن تعریف شده مشتریان هدف آن را مشخص می‌کند. اگر کمی در marketplace هر کدام از این‌ها چرخی بزنید و ماژول‌های مشابه را بررسی کنید صرفا از تفاوت قیمت و سطح پشتیبانی کیفیت هر کدام مشخص می‌شود.
مثلا ما در یکی از پروژه‌ها VirtoCommerce را به عنوان پایه پروژه انتخاب کردیم ولی بر اساس منطق تجاری تعریف شده ماژول‌های زیادی برای آن توسعه داده شد، حتی تغییراتی در هسته آن ایجاد شد. این پروژه 6 ماه با یک تیم 5 (مدیر پروژه+2 نفر دات نت+ گرافیست+1 نفر آندروید+ 1 نفر IOS) نفره طول کشید. در صورتی که مشابه همین کار را با Prestashop برای یک پروژه 15 درصد کوچکتر با یک تیم 3 نفره در مدت 4 ماه انجام دادیم. تازه در virtocommerce هیچ گزارشی وجود ندارد، همه چیز با PowerBi باید انجام شود.
قیمت اولی تقریبا 4 برابر دومی بود. هر دو پروژه وب سایت و نسخه native موبایل داشته اند.
ما درگیر تکنولوژی وابزار نبودیم چون تیم مسلط به هر کدام را داریم، ولی اگر مثلا فقط به .net مسلط هستید در همان حوزه ادامه دهید. البته PHP هزینه‌های کمتری دارد. 
حرف آخر: حتی در مایکروسافت هم چند وقت یک بار میندازن دور از اول می‌نویسند، رسیدن به ebay کار زیادی می‌برد شاید در طول مسیر حتی چند بار تغییر جهت بدهید. دیجی کالا اصلا اندازه ebay نیست. با هم مقایسه نکنید. در حال حاظر متد‌های تجارت الکترونیک در حال پیشرفت و تحول هستند. دیجیکالا فقط چند متد را در خود دارد فروشگا هایی هستند که چندین متد فروش را همزمان پیاده سازی کرده اند و مدیریت آنها به صورت یکپارچه انجام می‌شود.
یک فروشگاه آنلاین فقط نمای کار است کار فیزیکی پشت صحنه بسیار پیچیده‌تر است.