مطالب
بررسی تفاوت کلید اصلی و کلید یکتا
کلید اصلی ( Primary Key):
به‌منظور تشخیص هر رکورد در یک جدول بانک اطلاعاتی از کلید اصلی استفاده می‌کنیم. هر جدول بانک اطلاعاتی باید یک کلید اصلی داشته باشد. برای تعریف کلید اصلی در هر جدول از کلمه‌ی کلیدی Primary Key بعد از نام ستون استفاده می‌کنیم.

کلید یکتا ( Unique Key):
ستون با محدودیت (constraint) کلید یکتا تنها می‌تواند دربرگیرنده ارزش‌هایی یکتا باشد. برای تعریف یک ستون بصورت یکتا (unique) بعد از نام ستون از کلمه کلیدی UNIQUE استفاده می‌کنیم. 

مثالی از نحوه ایجاد کلید اصلی و کلید یکتا:
اسکریپت زیر مثالی از نحوه‌ی پیاده سازی این دو ویژگی، در بانک اطلاعاتی می‌باشد: 
CREATE DATABASE DNTSampleDB
GO
USE DNTSampleDB
GO
CREATE TABLE Cars(
ID int PRIMARY KEY,
Name VARCHAR(255) NOT NULL,
NumberPlate VARCHAR(255) UNIQUE,--شماره پلاک
Model INT);

نمایی از ساختار بانک اطلاعاتی ایجاد شده :

در این مرحله تعدادی رکورد فرضی به این جدول اضافه می‌کنیم :
INSERT INTO Cars                       
VALUES
(1,'Mazda','ABC 123',199),
(2,'Mazda','ABC 345',207),
(3,'Mazda','ABC 758',305),
(4,'Mazda','ABC 741',306),
(5,'Mazda','ABC 356',124)
نمایی از وضعیت اطلاعات ثبت شده در جدول : 


بررسی شباهت‌های بین کلید اصلی و کلید یکتا :
ستون‌هایی که بصورت کلید اصلی و یا کلید یکتا تعریف می‌شوند، نمی‌توانند ارزش‌های تکراری را درون خود ذخیره کنند.
ثبت رکورد با شماره Id تکراری 2 موجب بروز خطای زیر می‌شود:
INSERT INTO Cars                       
VALUES
(2,'Mazda','ABC 123111',199)

این خطا به وضوح محدودیت کلید اصلی را اعلام می‌کند و اجازه‌ی ثبت ارزش تکراری را در فیلد ID، نمی‌دهد و همچنین تلاش برای ثبت رکوردی با ارزش تکراری در ستونی که با محدودیت کلید یکتا است، با خطا مواجه خواهد شد:
INSERT INTO Cars                       
VALUES
(6,'Mazda','ABC 741',200)

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

تفاوت‌های کلید اصلی و کلید یکتا:
1- یک جدول تنها می‌تواند یک کلید اصلی داشته باشد؛ اما امکان تعریف چندین کلید یکتا در یک جدول وجود دارد.
2- کلید اصلی نمی‌تواند ارزش null داشته باشد و بصورت پیش فرض (Not Null) است؛ اما کلید‌های یکتا می‌توانند ارزش null داشته باشند.

3- بصورت پیش فرض شاخص خوشه‌ای (clustered index) برای کلید اصلی و شاخص غیر خوشه ای (non-clustered Index) برای کلید‌های یکتا ایجاد می‌شود .
برای مشاهده‌ی شاخص‌های برای جدول ایجاد شده‌ی فوق، پروسیجر زیر را اجرا کنید:
USE DNTSampleDB
GO
sp_help cars
خروجی دستور فوق:

 
مطالب
شروع به کار با 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] به قالب فرم متصل می‌شود و از تغییرات آن آگاه خواهد شد.
نظرات مطالب
EF Code First #3
«هر کتاب هفت تا نویسنده» یک رابطه‌ی many-to-many است؛ چون در این حالت هر نویسنده n کتاب هم می‌تواند داشته باشد.
نظرات مطالب
Implementing second level caching in EF code first
با سلام وتشکر
شما با روابط many to many مشکلی ندارید با این روش کش کردن؟