مطالب
احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت دوم - سرویس اعتبارسنجی
در قسمت قبل، ساختار ابتدایی کلاینت Angular را تدارک دیدیم. در این قسمت قصد داریم سرویسی که زیر ساخت کامپوننت لاگین و عملیات ورود به سیستم را تامین می‌کند، تکمیل کنیم.


تعریف تزریق وابستگی تنظیمات برنامه

در مطلب «تزریق وابستگی‌ها فراتر از کلاس‌ها در برنامه‌های Angular» با روش تزریق ثوابت برنامه آشنا شدیم. در این مثال، برنامه‌ی کلاینت بر روی پورت 4200 اجرا می‌شود و برنامه‌ی سمت سرور وب، بر روی پورت 5000. به همین جهت نیاز است این آدرس پایه سمت سرور را در تمام قسمت‌های برنامه که با سرور کار می‌کنند، در دسترس داشته باشیم و روش مناسب برای پیاده سازی آن همان قسمت «تزریق تنظیمات برنامه توسط تامین کننده‌ی مقادیر» مطلب یاد شده‌است. به همین جهت فایل جدید src\app\core\services\app.config.ts را در پوشه‌ی core\services برنامه ایجاد می‌کنیم:
import { InjectionToken } from "@angular/core";

export let APP_CONFIG = new InjectionToken<string>("app.config");

export interface IAppConfig {
  apiEndpoint: string;
  loginPath: string;
  logoutPath: string;
  refreshTokenPath: string;
  accessTokenObjectKey: string;
  refreshTokenObjectKey: string;
}

export const AppConfig: IAppConfig = {
  apiEndpoint: "http://localhost:5000/api",
  loginPath: "account/login",
  logoutPath: "account/logout",
  refreshTokenPath: "account/RefreshToken",
  accessTokenObjectKey: "access_token",
  refreshTokenObjectKey: "refresh_token"
};
در اینجا APP_CONFIG یک توکن منحصربفرد است که از آن جهت یافتن مقدار AppConfig که از نوع اینترفیس IAppConfig تعریف شده‌است، در سراسر برنامه استفاده خواهیم کرد.
سپس تنظیمات ابتدایی تزریق وابستگی‌های IAppConfig را در فایل src\app\core\core.module.ts به صورت ذیل انجام می‌دهیم:
import { AppConfig, APP_CONFIG } from "./app.config";

@NgModule({
  providers: [
    { provide: APP_CONFIG, useValue: AppConfig }
  ]
})
export class CoreModule {}
اکنون هر سرویس و یا کامپوننتی در سراسر برنامه که نیاز به تنظیمات AppConfig را داشته باشد، کافی است با استفاده از ویژگی Inject(APP_CONFIG)@ آن‌را درخواست کند.


طراحی سرویس Auth

پس از لاگین باید بتوان به اطلاعات اطلاعات کاربر وارد شده‌ی به سیستم، در تمام قسمت‌های برنامه دسترسی پیدا کرد. به همین جهت نیاز است این اطلاعات را در یک سرویس سراسری singleton قرار داد تا همواره یک وهله‌ی از آن در کل برنامه قابل استفاده باشد. مرسوم است این سرویس را AuthService بنامند. بنابراین محل قرارگیری این سرویس سراسری در پوشه‌ی Core\services و محل تعریف آن در قسمت providers آن خواهد بود. به همین جهت ابتدا ساختار این سرویس را با دستور ذیل ایجاد می‌کنیم:
 ng g s Core/services/Auth
با این خروجی:
   create src/app/Core/services/auth.service.ts (110 bytes)
و سپس تعریف آن‌را به مدخل providers ماژول Core اضافه می‌کنیم:
import { AuthService } from "./services/auth.service";

@NgModule({
  providers: [
    // global singleton services of the whole app will be listed here.
    BrowserStorageService,
    AuthService,
    { provide: APP_CONFIG, useValue: AppConfig }
  ]
})
export class CoreModule {}

در ادامه به تکمیل AuthService خواهیم پرداخت و قسمت‌های مختلف آن‌را مرور می‌کنیم.


اطلاع رسانی به کامپوننت Header در مورد وضعیت لاگین

در مطلب «صدور رخدادها از سرویس‌ها به کامپوننت‌ها در برنامه‌های Angular» با نحوه‌ی کار با BehaviorSubject آشنا شدیم. در اینجا می‌خواهیم توسط آن، پس از لاگین موفق، وضعیت لاگین را به کامپوننت هدر صادر کنیم، تا لینک لاگین را مخفی کرده و لینک خروج از سیستم را نمایش دهد:
import { BehaviorSubject } from "rxjs/BehaviorSubject";

@Injectable()
export class AuthService {

  private authStatusSource = new BehaviorSubject<boolean>(false);
  authStatus$ = this.authStatusSource.asObservable();

  constructor() {
    this.updateStatusOnPageRefresh();
  }

  private updateStatusOnPageRefresh(): void {
    this.authStatusSource.next(this.isLoggedIn());
  }
اکنون تمام کامپوننت‌های برنامه می‌توانند مشترک $authStatus شده و همواره آخرین وضعیت لاگین را دریافت کنند و نسبت به تغییرات آن عکس العمل نشان دهند (برای مثال قسمتی را نمایش دهند و یا قسمتی را مخفی کنند).
در اینجا در سازنده‌ی کلاس، بر اساس خروجی متد وضعیت لاگین شخص، برای اولین بار، متد next این BehaviorSubject فراخوانی می‌شود. علت قرار دادن این متد در سازنده‌ی کلاس سرویس، عکس العمل نشان دادن به refresh کامل صفحه، توسط کاربر است و یا عکس العمل نشان دادن به وضعیت به‌خاطر سپاری کلمه‌ی عبور، در اولین بار مشاهده‌ی سایت و برنامه. در این حالت متد isLoggedIn، کش مرورگر را بررسی کرده و با واکشی توکن‌ها و اعتبارسنجی آن‌ها، گزارش وضعیت لاگین را ارائه می‌دهد. پس از آن، خروجی آن (true/false) به مشترکین اطلاع رسانی می‌شود.
در ادامه، متد next این  BehaviorSubject را در متدهای login و logout نیز فراخوانی خواهیم کرد.


تدارک ذخیره سازی توکن‌ها در کش مرورگر

از طرف سرور، دو نوع توکن access_token و refresh_token را دریافت می‌کنیم. به همین جهت یک enum را جهت مشخص سازی آن‌ها تعریف خواهیم کرد:
export enum AuthTokenType {
   AccessToken,
   RefreshToken
}
سپس باید این توکن‌ها را پس از لاگین موفق در کش مرورگر ذخیره کنیم که با مقدمات آن در مطلب «ذخیره سازی اطلاعات در مرورگر توسط برنامه‌های Angular» پیشتر آشنا شدیم. از همان سرویس BrowserStorageService مطلب یاد شده، در اینجا نیز استفاده خواهیم کرد:
import { BrowserStorageService } from "./browser-storage.service";

export enum AuthTokenType {
  AccessToken,
  RefreshToken
}

@Injectable()
export class AuthService {

  private rememberMeToken = "rememberMe_token";

  constructor(private browserStorageService: BrowserStorageService) {  }

  rememberMe(): boolean {
    return this.browserStorageService.getLocal(this.rememberMeToken) === true;
  }

  getRawAuthToken(tokenType: AuthTokenType): string {
    if (this.rememberMe()) {
      return this.browserStorageService.getLocal(AuthTokenType[tokenType]);
    } else {
      return this.browserStorageService.getSession(AuthTokenType[tokenType]);
    }
  }

  deleteAuthTokens() {
    if (this.rememberMe()) {
      this.browserStorageService.removeLocal(AuthTokenType[AuthTokenType.AccessToken]);
      this.browserStorageService.removeLocal(AuthTokenType[AuthTokenType.RefreshToken]);
    } else {
      this.browserStorageService.removeSession(AuthTokenType[AuthTokenType.AccessToken]);
      this.browserStorageService.removeSession(AuthTokenType[AuthTokenType.RefreshToken]);
    }
    this.browserStorageService.removeLocal(this.rememberMeToken);
  }

  private setLoginSession(response: any): void {
    this.setToken(AuthTokenType.AccessToken, response[this.appConfig.accessTokenObjectKey]);
    this.setToken(AuthTokenType.RefreshToken, response[this.appConfig.refreshTokenObjectKey]);
  }

  private setToken(tokenType: AuthTokenType, tokenValue: string): void {
    if (this.rememberMe()) {
      this.browserStorageService.setLocal(AuthTokenType[tokenType], tokenValue);
    } else {
      this.browserStorageService.setSession(AuthTokenType[tokenType], tokenValue);
    }
  }
}
ابتدا سرویس BrowserStorageService به سازنده‌ی کلاس تزریق شده‌است و سپس نیاز است بر اساس گزینه‌ی «به‌خاطر سپاری کلمه‌ی عبور»، نسبت به انتخاب محل ذخیره سازی توکن‌ها اقدام کنیم. اگر گزینه‌ی rememberMe توسط کاربر در حین لاگین انتخاب شود، از local storage ماندگار و اگر خیر، از session storage فرار مرورگر برای ذخیره سازی توکن‌ها و سایر اطلاعات مرتبط استفاده خواهیم کرد.


- متد rememberMe مشخص می‌کند که آیا وضعیت به‌خاطر سپاری کلمه‌ی عبور توسط کاربر انتخاب شده‌است یا خیر؟ این مقدار را نیز در local storage ماندگار ذخیره می‌کنیم تا در صورت بستن مرورگر و مراجعه‌ی مجدد به آن، در دسترس باشد و به صورت خودکار پاک نشود.
- متد setToken، بر اساس وضعیت rememberMe، مقادیر توکن‌های دریافتی از سرور را در local storage و یا session storage ذخیره می‌کند.
- متد getRawAuthToken بر اساس یکی از مقادیر enum ارسالی به آن، مقدار خام access_token و یا refresh_token ذخیره شده را بازگشت می‌دهد.
- متد deleteAuthTokens جهت حذف تمام توکن‌های ذخیره شده‌ی توسط برنامه استفاده خواهد شد. نمونه‌ی کاربرد آن در متد logout است.
- متد setLoginSession پس از لاگین موفق فراخوانی می‌شود. کار آن ذخیره سازی توکن‌های دریافتی از سرور است. فرض آن نیز بر این است که خروجی json از طرف سرور، توکن‌ها را با کلیدهایی دقیقا مساوی access_token و refresh_token بازگشت می‌دهد:
 {"access_token":"...","refresh_token":"..."}
اگر این کلیدها در برنامه‌ی شما نام دیگری را دارند، محل تغییر آن‌ها در فایل app.config.ts است.


تکمیل متد ورود به سیستم

در صفحه‌ی لاگین، کاربر نام کاربری، کلمه‌ی عبور و همچنین گزینه‌ی «به‌خاطر سپاری ورود» را باید تکمیل کند. به همین جهت اینترفیسی را برای این کار به نام Credentials در محل src\app\core\models\credentials.ts ایجاد می‌کنیم:
export interface Credentials {
   username: string;
   password: string;
   rememberMe: boolean;
}
پس از آن در متد لاگین از این اطلاعات جهت دریافت توکن‌های دسترسی و به روز رسانی، استفاده خواهیم کرد:
@Injectable()
export class AuthService {
  constructor(
    @Inject(APP_CONFIG) private appConfig: IAppConfig,
    private http: HttpClient,
    private browserStorageService: BrowserStorageService   
  ) {
    this.updateStatusOnPageRefresh();
  }

  login(credentials: Credentials): Observable<boolean> {
    const headers = new HttpHeaders({ "Content-Type": "application/json" });
    return this.http
      .post(`${this.appConfig.apiEndpoint}/${this.appConfig.loginPath}`, credentials, { headers: headers })
      .map((response: any) => {
        this.browserStorageService.setLocal(this.rememberMeToken, credentials.rememberMe);
        if (!response) {
          this.authStatusSource.next(false);
          return false;
        }
        this.setLoginSession(response);
        this.authStatusSource.next(true);
        return true;
      })
      .catch((error: HttpErrorResponse) => Observable.throw(error));
  }
}
متد login یک Observable از نوع boolean را بازگشت می‌دهد. به این ترتیب می‌توان مشترک آن شد و در صورت دریافت true یا اعلام لاگین موفق، کاربر را به صفحه‌ای مشخص هدایت کرد.
در اینجا نیاز است اطلاعات شیء Credentials را به مسیر http://localhost:5000/api/account/login ارسال کنیم. به همین جهت نیاز به سرویس IAppConfig تزریق شده‌ی در سازنده‌ی کلاس وجود دارد تا با دسترسی به this.appConfig.apiEndpoint، مسیر تنظیم شده‌ی در فایل src\app\core\services\app.config.ts را دریافت کنیم.
پس از لاگین موفق:
- ابتدا وضعیت rememberMe انتخاب شده‌ی توسط کاربر را در local storage مرورگر جهت مراجعات آتی ذخیره می‌کنیم.
- سپس متد setLoginSession، توکن‌های دریافتی از شیء response را بر اساس وضعیت rememberMe در local storage ماندگار و یا session storage فرار، ذخیره می‌کند.
- در آخر با فراخوانی متد next مربوط به authStatusSource با پارامتر true، به تمام کامپوننت‌های مشترک به این سرویس اعلام می‌کنیم که وضعیت لاگین موفق بوده‌است و اکنون می‌توانید نسبت به آن عکس العمل نشان دهید.


تکمیل متد خروج از سیستم

کار خروج، با فراخوانی متد logout صورت می‌گیرد:
@Injectable()
export class AuthService {

  constructor(
    @Inject(APP_CONFIG) private appConfig: IAppConfig,
    private http: HttpClient,
    private router: Router
  ) {
    this.updateStatusOnPageRefresh();
  }

  logout(navigateToHome: boolean): void {
    this.http
      .get(`${this.appConfig.apiEndpoint}/${this.appConfig.logoutPath}`)
      .finally(() => {
        this.deleteAuthTokens();
        this.unscheduleRefreshToken();
        this.authStatusSource.next(false);
        if (navigateToHome) {
          this.router.navigate(["/"]);
        }
      })
      .map(response => response || {})
      .catch((error: HttpErrorResponse) => Observable.throw(error))
      .subscribe(result => {
        console.log("logout", result);
      });
  }
}
در اینجا در ابتدا متد logout سمت سرور که در مسیر http://localhost:5000/api/account/logout قرار دارد فراخوانی می‌شود. پس از آن در پایان کار در متد finally (چه عملیات فراخوانی logout سمت سرور موفق باشد یا خیر)، ابتدا توسط متد deleteAuthTokens تمام توکن‌ها و اطلاعات ذخیره شده‌ی در مرورگر حذف می‌شوند. در ادامه با فراخوانی متد next مربوط به authStatusSource با مقدار false، به تمام مشترکین سرویس جاری اعلام می‌کنیم که اکنون وقت عکس العمل نشان دادن به خروجی سیستم و به روز رسانی رابط کاربری است. همچنین اگر پارامتر navigateToHome نیز مقدار دهی شده بود، کاربر را به صفحه‌ی اصلی برنامه هدایت می‌کنیم.


اعتبارسنجی وضعیت لاگین و توکن‌های ذخیره شده‌ی در مرورگر

برای اعتبارسنجی access token دریافتی از طرف سرور، نیاز به بسته‌ی jwt-decode است. به همین جهت دستور ذیل را در خط فرمان صادر کنید تا بسته‌ی آن به پروژه اضافه شود:
 > npm install jwt-decode --save
در ادامه برای استفاده‌ی از آن، ابتدا بسته‌ی آن‌را import می‌کنیم:
 import * as jwt_decode from "jwt-decode";
و سپس توسط متد jwt_decode آن می‌توان به اصل اطلاعات توکن دریافتی از طرف سرور، دسترسی یافت:
  getDecodedAccessToken(): any {
    return jwt_decode(this.getRawAuthToken(AuthTokenType.AccessToken));
  }
این توکن خام، پس از decode، یک چنین فرمت نمونه‌ای را دارد که در آن، شماره‌ی کاربری (nameidentifier)، نام کاربری (name)، نام نمایشی کاربر (DisplayName)، نقش‌های او (قسمت role) و اطلاعات تاریخ انقضای توکن (خاصیت exp)، مشخص هستند:
{
  "jti": "d1272eb5-1061-45bd-9209-3ccbc6ddcf0a",
  "iss": "http://localhost/",
  "iat": 1513070340,
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "1",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Vahid",
  "DisplayName": "وحید",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber": "709b64868a1d4d108ee58369f5c3c1f3",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata": "1",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": [
    "Admin",
    "User"
  ],
  "nbf": 1513070340,
  "exp": 1513070460,
  "aud": "Any"
}
برای مثال اگر خواستیم به خاصیت DisplayName این شیء decode شده دسترسی پیدا کنیم، می‌توان به صورت ذیل عمل کرد:
  getDisplayName(): string {
    return this.getDecodedAccessToken().DisplayName;
  }
و یا خاصیت exp آن، بیانگر تاریخ انقضای توکن است. برای تبدیل آن به نوع Date، ابتدا باید به این خاصیت در توکن decode شده دسترسی یافت و سپس توسط متد setUTCSeconds آن‌را تبدیل به نوع Date کرد:
  getAccessTokenExpirationDateUtc(): Date {
    const decoded = this.getDecodedAccessToken();
    if (decoded.exp === undefined) {
      return null;
    }
    const date = new Date(0); // The 0 sets the date to the epoch
    date.setUTCSeconds(decoded.exp);
    return date;
  }
اکنون که به این تاریخ انقضای توکن دسترسی یافتیم، می‌توان از آن جهت تعیین اعتبار توکن ذخیره شده‌ی در مرورگر، استفاده کرد:
  isAccessTokenTokenExpired(): boolean {
    const expirationDateUtc = this.getAccessTokenExpirationDateUtc();
    if (!expirationDateUtc) {
      return true;
    }
    return !(expirationDateUtc.valueOf() > new Date().valueOf());
  }
و در آخر متد isLoggedIn که وضعیت لاگین بودن کاربر جاری را مشخص می‌کند، به صورت ذیل تعریف می‌شود:
  isLoggedIn(): boolean {
    const accessToken = this.getRawAuthToken(AuthTokenType.AccessToken);
    const refreshToken = this.getRawAuthToken(AuthTokenType.RefreshToken);
    const hasTokens = !this.isEmptyString(accessToken) && !this.isEmptyString(refreshToken);
    return hasTokens && !this.isAccessTokenTokenExpired();
  }

  private isEmptyString(value: string): boolean {
    return !value || 0 === value.length;
  }
ابتدا بررسی می‌کنیم که آیا توکن‌های درخواست شده‌ی از کش مرورگر، وجود خارجی دارند یا خیر؟ پس از آن تاریخ انقضای access token را نیز بررسی می‌کنیم. تا همین اندازه جهت تعیین اعتبار این توکن‌ها در سمت کاربر کفایت می‌کنند. در سمت سرور نیز این توکن‌ها به صورت خودکار توسط برنامه تعیین اعتبار شده و امضای دیجیتال آن‌ها بررسی می‌شوند.

در قسمت بعد، از این سرویس اعتبارسنجی تکمیل شده جهت ورود به سیستم و تکمیل کامپوننت header استفاده خواهیم کرد.


کدهای کامل این سری را از اینجا می‌توانید دریافت کنید.
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه‌ی ASPNETCore2JwtAuthentication.AngularClient وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o، برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد (و یا همان اجرای فایل ng-serve.bat). همچنین باید به پوشه‌ی ASPNETCore2JwtAuthentication.WebApp نیز مراجعه کرده و فایل dotnet_run.bat را اجرا کنید، تا توکن سرور برنامه نیز فعال شود.
نظرات مطالب
بررسی دقیق‌تر صفحات آبی ویندوز
برای سهولت کار در ویندوز 7 و فعال سازی ذخیره کردن فایل‌های دامپ، از این فایل reg هم می‌تونید استفاده کنید. پس از دریافت، کلیک راست و انتخاب گزینه‌ی merge جهت افزودن اطلاعات آن به registry ویندوز :
Win7-WinVista-Win2008
مطالب
معرفی فریم ورک Blueprint CSS
احتمالا با عباراتی مانند طراحی table less و مزیت‌های طراحی با CSS، همانند سرعت بالاتر بارگذاری سایت در مقایسه با نمایش یک جدول که نیازمند دریافت تمام جزئیات آن و سپس رندر نهایی اطلاعات آن توسط مرورگر است، بارها برخورد داشته‌اید. اما ... آیا یکبار سعی کرده‌اید که به صورت دستی همان کارهایی را که پیشتر با HTML table انجام می‌دادید، اینبار توسط CSS پیاده سازی کنید؟
در اکثر اوقات نتیجه کار مایوس کننده، بسیار سخت و نگهداری آن در طول زمان بسیار مشکل خواهد بود؛ به علاوه سازگاری با مرورگرهای مختلف و نکات ریز هر کدام را نیز لحاظ کنید. به همین جهت تعدادی فریم ورک CSS برای شبیه سازی گرید و جدول تهیه شده‌اند که کار طراحی table less را بسیار ساده و لذت بخش کرده‌اند. یکی از این موارد، فریم ورک Blueprint CSS نام دارد و در ادامه نحوه استفاده از آن‌را مرور خواهیم کرد. این مرور هم مستقل است از فناوری سمت سرور مورد استفاده و صرفا مباحث html و CSS آن بررسی خواهند شد.


دریافت Blueprint CSS

این فریم ورک سورس باز را از مخزن کدهای آن در GitHub می‌توانید دریافت کنید: (^)
البته نگران حجم نزدیک به 4 مگابایتی بسته دریافتی آن نباشید؛ زیرا نهایتا با سه فایل CSS از آن بیشتر کاری نداریم و مابقی مثال‌های آن هستند.
پس از دریافت آن، یک پوشه را به نام blueprint ایجاد کرده و سه فایل ie.css ،print.css و screen.css را در آن قرار دهید.
به علاوه داخل این پوشه، یک پوشه جدید دیگر را به نام src ایجاد کرده و فایل grid.png موجود در این بسته را نیز در آن کپی کنید.


ساختار ابتدایی یک صفحه مبتنی بر Blueprint CSS

پس از ایجاد پوشه blueprint و src به نحوی که توضیح داده شد، ابتدایی‌ترین ساختار یک صفحه تشکیل شده با blueprint css به نحو زیر است:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Blueprint test page</title>

    <!-- Framework CSS -->
    <link rel="stylesheet" href="blueprint/screen.css" type="text/css" media="screen, projection">
    <link rel="stylesheet" href="blueprint/print.css" type="text/css" media="print">
    <!--[if lt IE 8]><link rel="stylesheet" href="blueprint/ie.css" type="text/css" media="screen, projection"><![endif]-->
  </head>
  <body>
    <div class="container showgrid">
  test
  <hr class="space" />
  <hr class="space" />
  <hr class="space" />
  <hr class="space" />
  <hr class="space" />
  test
    </div>
  </body>
</html>
توضیحات:
پس از مشخص سازی DocType (مهم)، سه فایل CSS یاد شده به header صفحه اضافه خواهند شد. همانطور که ملاحظه می‌کنید، سازگاری با IE نیز مدنظر آن بوده است.
کار با blueprint css همواره داخل div زیر انجام می‌شود:
<div class="container">
  page
</div>
توسط کلاس container یک گرید به عرض 950px در میانه صفحه برای شما تشکیل خواهد شد.
اگر علاقمند باشید که این گرید را مشاهده نمائید و همچنین بتوانید ستون‌های آن‌را نیز شمارش کنید، تنها کافی است showgrid را به این class تعریف شده اضافه نمائید (همانند ساختار صفحه فوق). به این ترتیب شکل زیر نمایان خواهد شد:

مطابق شکل فوق، در این عرض مشخص، 24 ستون آن در اختیار ما خواهند بود.
به علاوه ذکر hr با class=space سبب خواهد شد تا مطابق تنظیمات و فاصله بندی منظم این فریم ورک، یک سطر خالی برای ما ایجاد شود.


طراحی بدون جدول با Blueprint CSS

در ادامه قصد داریم در این صفحه ابتدایی، یک جدول با دو ستون و دو ردیف را ایجاد کنیم:
  <body>
    <div class="container showgrid">
      <div class="span-12">
        row1-col1
      </div>
      <div class="span-12 last">
        row1-col2
      </div>
      <div class="span-12">
        row2-col1
      </div>
      <div class="span-12 last">
        row2-col2
      </div>
    </div>
  </body>
که شکل زیر را برای ما ایجاد خواهد کرد:

توضیحات:
ستون‌های گرید نهایی با رنگ آبی مشخص هستند (class=container showgrid). اگر نیاز به 12 ستون داریم، می‌نویسیم span-12 و ... همین! به این ترتیب یک سلول جدول، با 12 ستون در اختیار ما خواهد بود. سلول بعدی هم در اینجا 12 ستونه است. اما یک last را اضافه‌تر دارد. در span-12 last این last به معنای انتهای ردیف جاری است و ذکر آن الزامی است.
تا اینجا یک ردیف تمام شد. اکنون در ادامه ردیف دوم را نیز به همین ترتیب با دو div و class‌هایی که ملاحظه می‌کنید، مشخص خواهیم کرد.
نحوه کار کلی با Blueprint css به همین سادگی است که ملاحظه می‌کنید. تعداد ستون‌های مورد نیاز را با ذکر container showgrid به سادگی می‌توان شمارش کرد. سپس این اعداد شمارش شده و مد نظر را پس از span ذکر کنید. مثلا اگر یک طرح سه ستونه نیاز دارید به صورت زیر خواهد بود:
  <body>
    <div class="container showgrid">
      <div class="span-8">
        row1-col1
      </div>
      <div class="span-8">
        row1-col2
      </div>
      <div class="span-8 last">
        row1-col3
      </div>
    </div>
  </body>

طراحی سلول‌های تو در تو


سؤال: ما پیشتر در یک html table به سادگی می‌توانستیم داخل یک سلول آن حتی یک جدول جدید نیز قرار دهیم، اینجا چطور؟
پاسخ: در اینجا هم بجای td و tr و table، از divهای تو در تو استفاده کنید. بستن ستون آخر را با last یاد شده فراموش نکنید. مثلا:
  <body>
    <div class="container showgrid">
      <div class="span-8">
                <div class="span-4">
                    row-1, col1 : cell-1
                </div>
                <div class="span-4 last">
                    row-1, col1 : cell-2
                </div>
      </div>
      <div class="span-8">
        row1-col2
      </div>
      <div class="span-8 last">
        row1-col3
      </div>
    </div>
  </body>
در اینجا در اولین div تعریف شده دو div تو در تو اضافه شده‌اند. البته با توجه به اینکه div والد 8 ستونی است، جمع عرض divهای فرزند باید 8 باشد که در اینجا به دو div چهارستونی تقسیم شده است.


سایر امکانات Blueprint CSS

تا اینجا با کلیات نحوه طراحی یک جدول به کمک CSS و فریم ورک Blueprint CSS آشنا شدیم (به کمک container و span-n آن). در ادامه مرور سریعی خواهیم داشت بر سایر امکانات این فریم ورک CSS و منظور از این امکانات، کلمات و عبارات مجازی است که می‌توانید داخل classهای divهای تعریف شده اضافه نمائید (CSS selectors تعریف شده در آن):
prepend-n و border:
فرض کنید در divهای تو در توی قسمت قبل، قصد داریم عرض ستون اول را بجای 4 ستون به 3 ستون تبدیل کنیم، اما این div را یک ستون به سمت راست حرکت دهیم:
  <body>
    <div class="container showgrid">
      <div class="span-8">
        <div class="prepend-1 span-3 border">
               row-1, col1 : cell-1
        </div>
        <div class="span-4 last">
              row-1, col1 : cell-2
        </div>
      </div>
      <div class="span-8">
           row1-col2
      </div>
      <div class="span-8 last">
           row1-col3
      </div>
    </div>
  </body>
برای این منظور همانطور که ملاحظه می‌کنید از prepend-1 استفاده شده است. border در اینجا سبب خواهد شد تا در سمت راست div یک خط عمودی رسم شود. در مقابل آن colborder هم وجود دارد که سبب ترسیم حاشیه با فاصله بیشتری نسبت به border می‌شود.
شبیه به همین قابلیت، با append-x (افزودن تعدادی ستون به سمت راست)، prepend-top (فاصله‌ای به اندازه 1.5em را به بالای div اضافه می‌کند) و append-bottom (فاصله‌ای به اندازه 1.5em را به پایین div اضافه می‌کند) نیز وجود دارد.
در مقابل این‌ها، push-n و pull-n هم وجود دارند. کار append و prepend اضافه کردن چند ستون به بعد و قبل از یک div است. push یک div را به تعداد واحدی که مشخص می‌کنیم به سمت راست حرکت می‌دهد. pull یک div را n ستون به سمت چپ حرکت خواهد داد (بدون تغییری در تعداد ستون‌ها).


دریافت مرجع سریع Blueprint CSS
مطالب
شروع کار با webpack - قسمت دوم
در مطلب قبلی بیشتر از لحاظ تئوریک با وب‌پک آشنا شدیم و در آخر نیز یک تک اسکریپت را با استفاده از آن باندل کرده و در صفحه‌ی index.html اضافه کردیم.

توجه :
در مطلب قبلی برای استفاده و نصب وبپک دو راه پیشنهاد شد؛ یکی نصب وبپک به صورت سراسری و دیگری به صورت محلی در محیط کاری فعلی پروژه. استفاده‌ی نگارنده به صورت محلی می‌باشد و برای فراخوانی وبپک از دستور npm run webpack استفاده خواهد شد. در صورتی که از وبپک به صورت سراسری (گلوبال ) استفاده می‌کنید، به جای این دستور فقط کافی است در خط فرمان دستور webpack را نوشته و آن را اجرا کنید.

اضافه کردن فایل تنظیمات وبپک

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

ساخت فایل پیکربندی وبپک

در محیط کاری پروژه یک فایل جدید را با نام webpack.config.js ایجاد می‌کنیم، تا پیکر بندی مورد نظرمان را برای وبپک در آن مشخص کنیم (نام این فایل قراردادی است و امکان مشخص کردن فایلی با نام دیگر نیز وجود دارد که در آینده با آن برخورد خواهیم کرد).
این فایل به صورت یک ماژول در فرمت commonjs می‌باشد (در صورتی که با ماژول‌های مختلف آشنا نیستید، مطالعه‌ی این مقاله پیشنهاد می‌شود ماژول‌ها در es6).
پس از ایجاد فایل پیکربندی در محیط کاری پروژه، محتوای زیر را به آن اضافه خواهیم کرد. این حالت را می‌توان ساده‌ترین پیکربندی وبپک دانست و با دستور webpack ./main.js bundle.js که در پایان مطلب قبلی در خط فرمان اجرا کردیم، تفاوتی ندارد.
// webpack.config.js file
module.exports = {
    entry:'./main.js'
    ,output:{
        filename:'bundle.js'
    }
}
پروپرتی entry مشخص کننده‌ی فایل ورودی است که قصد پردازش آن را داریم و پروپرتی output نیز خود یک آبجکت می‌باشد که در ساده‌ترین حالت، احتیاج به تعریف یک پروپرتی با نام filename را در آن داریم که مشخص کننده‌ی نام فایل باندل شونده توسط وبپک می‌باشد.
حال با اجرای دستور npm run webpack، وبپک به صورت خودکار محتوای فایل پیکربندی را خوانده و تنظیمات تعریف شده را در فایل باندل نهایی ترتیب اثر می‌دهد.

حالت نظاره گر یا watch mode

اضافه کردن فایل پیکربندی می‌تواند مفید باشد و ما را از الزام به تکرار برای مشخص کردن پارامترهای مورد نیاز در هر بار اجرای وبپک بی‌نیاز می‌کند. ولی فرض کنید در حال توسعه‌ی پروژه‌ای هستید و مدام در حال تغییر فایل‌های پروژه می‌باشید. فایلی اضافه، حذف و یا دچار تغییر می‌شود و برای هر بار انجام شدن پروسه‌ی باندلینگ باید وبپک را فراخوانی کنیم. برای جلوگیری از این پروسه‌ی تکراری، وبپک دارای حالت نظاره‌گر یا watch mode می‌باشد. معنای این حالت این است که وبپک تغییرات محیط کاری شما را در نظر می‌گیرد و با انجام هر تغییری، دوباره باندل مربوطه را از نو می‌سازد.
برای وارد شدن به این حالت یک راه کار این می‌باشد که در هنگام فراخوانی وبپک در خط فرمان، پرچم زیر را به آن اضافه کنیم:
//for when webpack is installed globally 
webpack --watch
//for when webpack is installed locally in project 
npm run webpack -- --watch
(در فراخوانی بالا دو حالت نصب سراسری و محلی وبپک در نظر گرفته شده‌است. حالت اول نکته‌ای را ندارد. ولی در حالت دوم برای اینکه پارامترهای خط فرمان توسط npm به دست وبپک برسد، احتیاج به اضافه کردن -- می‌باشد. جهت عدم آشنایی با این مورد می‌توانید به اینجا مراجعه کنید: فرستادن پارامتر به اسکریپت‌های npm)
راه کار دوم جهت تنظیم کردن وبپک در حالت نظاره گر، اضافه کردن پروپرتی watch به فایل پیکربندی وبپک است. پس از انجام این تغییر، محتوای فایل پیکربندی به این صورت خواهد بود:
//webpack.config.js file
module.exports = {
    entry:'./main.js'
    ,output:{
        filename:'bundle.js'
    }
    ,watch :true
}
اینبار برای ورود وپ بک به حالت نظاره‌گر کافی است وبپک را یک بار از طریق خط فرمان با دستور npm run webpack، فراخوانی کنیم.
در صورتی که مشکلی وجود نداشته باشد، با اجرای این دستور، کنترل خط فرمان به شما برنخواهد گشت و وبپک در حالت اجرا باقی می‌ماند که در تصویر زیر قابل مشاهده‌است.

حال اگر در اسکریپت main.js تغییری ایجاد کنید، خواهید دید که وبپک به صورت خودکار باندل را از اول خواهد ساخت.

وب سرور وبپک

تا اینجا از وبپک به عنوان یک باندل کننده بهره برده‌ایم و جهت میزبانی فایل‌های پروژه از فایل سیستم و سیستم عامل بهره بردیم. ولی می‌دانیم که در حین توسعه دادن برنامه‌های وب، استفاده از فایل سیستم و سیستم عامل مفید نیست و دچار مشکلات عدیده‌ای هم از سمت مرورگرها و هم از سمت کتابخانه‌های معروف جاوا اسکریپتی خواهیم شد( مانند مباحث cors و ...). جهت حذف این مشکلات می‌توانیم وب سرور مورد علاقه‌ی خود را اجرا کنیم یا از وب سرور فراهم شده توسط وبپک بهره ببریم.
جهت نصب وب سرور وبپک دستور زیر را در خط فرمان  اجرا خواهیم کرد ( به صورت سراسری یا محلی به انتخاب شما خواهد بود و قبلا توضیح داده شده است).
// to install globally :
npm install -g webpack-dev-server

//to install locally in project :
npm install -D webpack-dev-server
در حالتی که وبپک به صورت سراسری نصب شده باشد، با اجرای دستور webpack-dev-server در خط فرمان، وب سرور وبپک شروع به کار خواهد کرد و تنظیمات را نیز از فایل پیکربندی اعمال می‌کند.
در صورتی که وبپک به صورت محلی نصب شده باشد، بایستی یک مدخل به قسمت اسکریپت‌های package.json برای راهنمایی npm اضافه کنیم. محتویات این فایل پس از تغییرات، از این قرار است:
//package.json file
{
  "name": "dntwebpack",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "webpack": "webpack",
    "webpackserver": "webpack-dev-server"
  },
  "author": "mehdi",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^1.13.1",
    "webpack-dev-server": "^1.14.1"
  }
}
در اینجا پروپرتی جدیدی به قسمت scripts، با نام webpackserver اضافه شده‌است. حال با فراخوانی این اسکریپت با دستور زیر، وب سرور وبپک شروع به کار خواهد کرد:
npm run webpackserver
(دقت کنید که نام‌های قرار داده شده‌ی در قسمت scripts می‌توانند به صورت دلخواه باشند و شما می‌توانید نامی را که دلخواه خودتان است، برگزینید؛ به طور مثال به جای webpackserver نام دیگری را در فایل package.json برای آن مشخص کنید و در هنگام فراخوانی از آن استفاده کنید).
در صورتی که همه چیز بدون مشکل باشد، خروجی شبیه به تصویر زیر را مشاهده خواهید کرد که آدرسی که به صورت محلی، سرور بر روی آن میزبان شده است نیز قابل مشاهده است:


باندل کردن اسکریپت‌های گوناگون توسط وبپک

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

در محیط کاری پروژه، یک فایل جدید user.js را اضافه می‌کنیم که از این فایل در فایل main.js استفاده خواهد شد.
محتوای فایل user.js یک تابع ساده‌ی جاوا اسکریپتی خواهد بود:
// user.js file 

function userLog() {
    console.log("ahooy from user module file");
}

module.exports={
    userLog:userLog
}  
حال جهت استفاده از این ماژول در فایل main.js تغییرات زیر را اعمال خواهیم کرد:
//main.js file

var user = require("./user");

user.userLog();

console.log(`i'm bundled by webpack`);
پس از ذخیره‌ی تغییرات خواهید دید که وب سرور وبپک از این تغییرات آگاه شده و باندل جدید را خواهد ساخت که در اینجا خروجی مانند تصویر زیر را خواهید دید:
در تصویر قابل مشاهده است که ماژول user.js نیز وارد باندل شده است.


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

اضافه کردن اسکریپت‌ها به باندل به صورت استاتیک توسط وبپک

قطعا در پروژه‌های خود از کتابخانه‌هایی که توسط برنامه نویسان دیگر تولید شده‌اند مانند جی کوئری و ... استفاده خواهیم کرد. استفاده از این اسکریپت‌ها به صورت داینامیک و ایمپورت کردن آنها در هر ماژول جالب نخواهد بود و یا ممکن است ماژولی که خود شما نوشته اید به صورت اشتراکی بین تمام برنامه اجرا شود. در این گونه از موارد می‌توانیم این اسکریپت‌ها را در فایل پیکربندی به وبپک معرفی کنیم تا در هنگام باندلینگ، به باندل وارد شوند.
اسکریپت جدیدی را در پروژه اضافه می‌کنیم و نامش را shared.js می‌گذاریم که دارای محتوای زیر است :
// shared.js file
console.log('log message from shared module !');
حال برای اینکه این اسکریپت را به وبپک معرفی کنیم، فایل پیکربندی وبپک را باز کرده و تغییرات زیر را در آن اعمال می‌کنیم :
//webpack.config.js file
module.exports = {
    entry:['./shared.js','./main.js']
    ,output:{
        filename:'bundle.js'
    }
    ,watch :true
}
قابل مشاهده است که قسمت entry، به جای این که یک تک فایل را معرفی کند، تبدیل به یک آرایه شده‌است که هم فایل shared.js را در بر می‌گیرد و هم فایل main.js را دارد.
در مواقعی که فایل پیکربندی دچار تغییر می‌شود، بایستی وبپک را متوقف و دوباره اجرا کنید تا تنظیمات جدید، اعمال شوند. پس از راه اندازی دوباره وبپک، در صورت موفقیت آمیز بودن تغییراتتان، خروجی را شبیه تصویر رو به رو خواهید گرفت و مشخص است که فایل shared.js نیز در باندل وارد شده است.


استفاده از Loader‌ها در وبپک

به صورت پیش فرض وبپک قابلیت باندل کردن ماژول‌های جاوا اسکریپت را دارد و همچنین می‌تواند این فایل‌ها را Minify  کند (در مطالب بعدی خواهیم دید). ولی به طور مثال استفاده از تایپ اسکریپت از توانایی‌های وبپک به صورت توکار خارج است. اینجاست که Loader‌ها وارد کار می‌شوند.
اگر بخواهیم به زبان ساده Loader‌‌ها را تعریف کنیم می‌توان  آنها را کامپوننت هایی دانست که به وبپک فوت و فن کار جدیدی را یاد می‌دهند.
در ادامه Loader تایپ اسکریپت را نصب خواهیم کرد و به کمک آن فایل‌های پروژه را تبدیل به تایپ اسکریپت کرده و در هنگام باندل کردن از وبپک می‌خواهیم که این فایل‌ها را ترنسپایل کند و سپس باندل را از روی آنها بسازد ( برای مطالعه‌ی ادامه‌ی این مطلب احتیاجی به آشنایی به تایپ اسکریپت نیست و هدف استفاده از یک loader است. ولی در صورت علاقه می‌توانید به اینجا مراجعه کنید سری آموزش تایپ اسکریپت)

نصب Loader تایپ اسکریپت 

به خط فرمان برگشته و با استفاده از npm، لودر تایپ اسکریپت مورد نیاز وبپک را نصب می‌کنیم. دستور مورد نیاز این قرار است :
npm install -D ts-loader

( توجه :
در ادامه این مطلب از پیکربندی ساده‌ی یک پروژه‌ی تایپ اسکریپتی استفاده شده است که اعم از ایجاد فایل tsconfig.json و اضافه کردن پوشه‌ی typings به پروژه می‌باشد.)
فایل main.ts را که یک فایل تایپ اسکریپتی می‌باشد، به پروژه اضافه می‌کنیم. محتوای آن به صورت زیر خواهد بود. قابل مشاهده است که از ویژگی‌های ES6 در این فایل استفاده شده و این انتظار را از لودر تایپ اسکریپت داریم که این فایل را در هنگام باندلینگ برای ما ترنسپایل کند.
// main.ts file
let user = require("./user");

user.userLog();
let mainlogger = () => {
    console.log(`i'm bundled by webpack in an arrow function`);
}

mainlogger();
برای اینکه به وبپک خبر دهیم که در پروژه در حال استفاده از تایپ اسکریپت هستیم، فایل پیکربندی وبپک را باز کرده و پروپرتی جدیدی را با نام module به آن معرفی می‌کنیم که خود یک آبجکت می‌باشد. حال در آبجکت module یک پروپرتی جدید را با نام loaders که جنس آرایه‌ای دارد، اضافه می‌کنیم. آرایه‌ی loaders شامل همه‌ی loader هایی خواهد بود که شما قصد استفاده‌ی آنها را به همراه وبپک دارید. هر عضو از این آرایه خود نیز یک آبجکت می‌باشد که دارای سه پروپرتی زیر می‌باشد:
test : یک رجکس می‌باشد که به loader می‌گوید به دنبال چه فایل‌هایی بگردد.
exclude : از جنس رجکس و مشخص کننده‌ی مسیرهایی است که از پروژه باید جدا شوند و توسط loader پردازش نشوند (مانند فایل‌های از قبل کامپایل شده‌ی کتابخانه‌ها).
loader : مشخص کننده‌ی نام loader مورد نظر .
محتوای فایل پیکربندی وبپک، پس از معرفی loader تایپ اسکریپت، به این صورت خواهد بود:
//webpack.config.js
module.exports = {
    entry:['./shared.js','./main.js']
    ,output:{
        filename:'bundle.js'
    }
    ,watch :true
    ,module:{
        loaders:[
            {
                test:/\.ts$/
                ,exclude:/node_modules/
                ,loader:'ts-loader'
            }
        ]
    }
}
حال با اجرای دوباره‌ی وبپک، loader تایپ اسکریپت ابتدا اجرا شده، سپس وبپک وارد کار می‌شود و فایل‌ها را باندل خواهد کرد. در صورتی که بدون مشکل همه چیز اجرا شود، خروجی مانند تصویر زیر را خواهید داشت:

در این مطلب تنظیمات مختلف وبپک، فایل پیکربندی، استفاده از چندین فایل به همراه وبپک، وب سرور وبپک و همچنین با loader‌های وبپک آشنا شدیم.
دریافت فایل‌ها dntwebpack-part2.zip  
مطالب
نمایش، ذخیره و چاپ فایل‌های PDF در برنامه‌های Angular
با توجه به اینکه فایل‌های PDF نیز فایل باینری هستند، کلیات نکات مطلب «دریافت و نمایش تصاویر از سرور در برنامه‌های Angular» در مورد آن‌ها هم صادق است. در اینجا به تکمیل این نکات پرداخته و مواردی را مانند ذخیره، چاپ و استفاده از اشیاء نمایشی <object>، <embed> و <iframe> نیز بررسی می‌کنیم. نمایش PDF در اینجا بر اساس امکانات توکار مرورگرها صورت می‌گیرد و نیاز به افزونه‌ی اضافه‌تری ندارد.


کدهای سمت سرور دریافت فایل PDF

در اینجا کدهای سمت سرور برنامه، نکته‌ی خاصی را به همراه نداشته و صرفا یک فایل PDF ساده (محتوای باینری) را بازگشت می‌دهد:
using Microsoft.AspNetCore.Mvc;

namespace AngularTemplateDrivenFormsLab.Controllers
{
    [Route("api/[controller]")]
    public class ReportsController : Controller
    {
        [HttpGet("[action]")]
        public IActionResult GetPdfReport()
        {
            return File(virtualPath: "~/assets/sample.pdf",
                        contentType: "application/pdf",
                        fileDownloadName: "sample.pdf");
        }
    }
}
که در نهایت با آدرس api/Reports/GetPdfReport در سمت کلاینت قابل دسترسی خواهد بود.


سرویس دریافت محتوای باینری در برنامه‌های Angular

برای اینکه HttpClient برنامه‌های Angular بتواند محتوای باینری را بجای محتوای JSON پیش‌فرض آن دریافت کند، نیاز است نوع خروجی سمت سرور آن‌را به blob تنظیم کرد:
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import { HttpClient } from "@angular/common/http";

@Injectable()
export class DownloadPdfDataService {

  constructor(private httpClient: HttpClient) { }

  public getReport(): Observable<Blob> {
    return this.httpClient.get("/api/Reports/GetPdfReport", { responseType: "blob" });
  }
}
به این ترتیب پس از اشتراک به متد getReport این سرویس، اطلاعات باینری این فایل PDF را دریافت خواهیم کرد.


اصلاح Content Security Policy سمت سرور جهت ارائه‌ی محتوای blob

پس از دریافت فایل PDF به صورت یک blob، با استفاده از متد URL.createObjectURL می‌توان آدرس موقت محلی را برای دسترسی به آن تولید کرد و یک چنین آدرس‌هایی به صورت blob:http تولید می‌شوند. در این حالت در Content Security Policy سمت سرور، نیاز است امکان دسترسی به تصاویر و همچنین اشیاء از نوع blob را نیز آزاد معرفی کنید:
img-src 'self' data: blob:
default-src 'self' blob:
object-src 'self' blob:
در غیراینصورت مرورگر نمایش یک چنین تصاویر و یا اشیایی را سد خواهد کرد.


دریافت فایل‌های PDF از سرور و نمایش آن‌ها در یک برنامه‌ی Angular

پس از این مقدمات، کامپوننتی که یک فایل PDF را از سمت سرور دریافت کرده و نمایش می‌دهد، چنین کدی را خواهد داشت:
import { DownloadPdfDataService } from "./../download-pdf-data.service";
import { WindowRefService } from "./../../core/window.service";
import { Component, OnInit } from "@angular/core";
import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser";

@Component({
  templateUrl: "./view-pdf.component.html",
  styleUrls: ["./view-pdf.component.css"]
})
export class ViewPdfComponent implements OnInit {
  private nativeWindow: Window;
  private pdfBlobUrl: string;
  sanitizedPdfBlobResourceUrl: SafeResourceUrl;

  constructor(private downloadService: DownloadPdfDataService,
    private windowRefService: WindowRefService, private sanitizer: DomSanitizer) { }

  ngOnInit() {
    this.nativeWindow = this.windowRefService.nativeWindow;
    this.downloadService.getReport().subscribe(pdfDataBlob => {
      console.log("pdfDataBlob", pdfDataBlob);
      const urlCreator = this.nativeWindow.URL;
      this.pdfBlobUrl = urlCreator.createObjectURL(pdfDataBlob);
      console.log("pdfBlobUrl", this.pdfBlobUrl);
      this.sanitizedPdfBlobResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.pdfBlobUrl);
    });
  }
}
با این قالب:
<h1>Display PDF Files</h1>

<div *ngIf="sanitizedPdfBlobResourceUrl">
  <h4>using iframe</h4>
  <iframe width="100%" height="600" [attr.src]="sanitizedPdfBlobResourceUrl" type="application/pdf"></iframe>
  <h4>using object</h4>
  <object [attr.data]="sanitizedPdfBlobResourceUrl" type="application/pdf" width="100%"
    height="100%"></object>
  <h4>usin embed</h4>
  <embed [attr.src]="sanitizedPdfBlobResourceUrl" type="application/pdf" width="100%"
    height="100%">
</div>
- در اینجا در ngOnInit، به سرویس پنجره دسترسی یافته و وهله‌ای از آن‌را جهت کار با متد createObjectURL شیء URL آن دریافت می‌کنیم.
- سپس مشترک متد getReport دریافت فایل PDF شده و اطلاعات نهایی آن‌را به صورت pdfDataBlob دریافت می‌کنیم.
- این اطلاعات باینری را به متد createObjectURL ارسال کرده و آدرس موقتی این تصویر را در مرورگر بدست می‌آوریم.
- چون در این حالت Angular این URL را امن سازی می‌کند، یک چنین خروجی unsafe:blob بجای blob تولید خواهد شد که نمایش این مورد نیز توسط مرورگر سد می‌شود. برای رفع این مشکل، می‌توان از سرویس DomSanitizer آن که به سازنده‌ی کلاس تزریق شده‌است استفاده کرد:
this.sanitizedPdfBlobResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.pdfBlobUrl);
تفاوت این مورد با حالت نمایش تصویر، استفاده از متد bypassSecurityTrustResourceUrl بجای متد bypassSecurityTrustUrl است. از این جهت که اشیاء یاد شده نیاز به SafeResourceUrl دارند و نه SafeUrl.
اینبار یک چنین انتسابی به صورت مستقیم کار می‌کند که سه نمونه‌ی این انتساب را به اشیاء iframe ،object و embed، در قالب فوق مشاهده می‌کنید.



افزودن دکمه‌ی چاپ PDF به برنامه

پس از اینکه به this.pdfBlobUrl دسترسی یافتیم، اکنون می‌توان یک iframe مخفی را ایجاد کرد، سپس src آن‌را به این آدرس ویژه تنظیم نمود و در آخر متد print آن‌را فراخوانی کرد که سبب نمایش خودکار دیالوگ چاپ مرورگر می‌شود:
  printPdf() {
    const iframe = document.createElement("iframe");
    iframe.style.display = "none";
    iframe.src = this.pdfBlobUrl;
    document.body.appendChild(iframe);
    iframe.contentWindow.print();
  }


نمایش فایل PDF در یک برگه‌ی جدید

اگر علاقمند بودید تا این فایل PDF را به صورت تمام صفحه و در برگه‌ای جدید نمایش دهید، می‌توان از متد window.open استفاده کرد:
  showPdf() {
    this.nativeWindow.open(this.pdfBlobUrl);
  }


دریافت فایل PDF

بجای نمایش فایل PDF می‌توان دکمه‌ای را بر روی صفحه قرار داد که با کلیک بر روی آن، این فایل توسط مرورگر به صورت متداولی جهت دریافت به کاربر ارائه شود:
  downloadPdf() {
    const fileName = "test.pdf";
    const anchor = document.createElement("a");
    anchor.style.display = "none";
    anchor.href = this.pdfBlobUrl;
    anchor.download = fileName;
    document.body.appendChild(anchor);
    anchor.click();
  }
در اینجا یک anchor جدید به صورت مخفی به صفحه اضافه می‌شود که href آن به this.pdfBlobUrl تنظیم شده‌است. سپس متد click آن فراخوانی خواهد شد. نام این فایل را هم توسط ویژگی download این شیء می‌توان تنظیم نمود.
این روش در مورد تدارک دکمه‌ی دریافت تمام blobهای دریافتی از سرور کاربرد دارد و منحصر به فایل‌های PDF نیست.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
ASP.NET MVC #6
شاید مطالبی که من عنوان کردم این برداشت را به وجود آورده که MVC در مورد UI/UX بحث می‌کند؟
پاسخ اینکه، خیر. بحث ما در اینجا برنامه نویسی وب است نه طراحی رابط کاربری. MVC نظری در مورد دومین شما، در مورد لایه بندی دسترسی به داده‌ها، در مورد استفاده از ORM و غیره ندارد. به همین ترتیب در مورد اینکه ظاهر برنامه رو هم به چه نحوی طراحی کنید، نظری ندارد. اینجا شما آزاد هستید که مطابق روش‌های دیگری که فکر می‌کنید مفید هستند عمل کنید. بحث ما در اینجا جدا سازی منطق برنامه از لایه نمایشی است، برای اینکه بتونیم اون رو تست کنیم. در وب فرم‌ها این منطق به لایه نمایشی گره خورده. در MVC این دو از هم جدا شدن.
بنابراین به صورت خلاصه MVC نظری در مورد نحوه طراحی رابط کاربری و همچنین نحوه دسترسی به اطلاعات ندارد. نمیاد به شما بگه باید از مثلا EF استفاده کنید یا NH . یا اینکه از فلان فریم ورک CSS باید استفاده کنید یا خیر. دسترسی پذیری سایت شما چگونه باید باشد. ترکیب رنگ‌های آن چگونه باید باشد. این‌ها خارج از بحث MVC هستند.
در مورد UI/UX یک سری سایت و وبلاگ فعال و خوب هستند که به نظرم دنبال کردن اون‌ها خیلی مفید است. مثلا:
uxbooth
uxmag
uxmovement
smashingmagazine
نظرات مطالب
کار با SignalR Core از طریق یک کلاینت Angular
دقیقا چه خطایی دریافت کردید؟ چون مشکلی نیست:

با این شرایط البته:

- سرویس مدنظر پیشتر در فایل آغازین برنامه ثبت شده باشد:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IMyService, MyService>();
}
- همچنین هاب هم ثبت شده باشد:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseSignalR(routes =>
            {
                routes.MapHub<MessageHub>(path: "/message");
            });
این ثبت هست که کلاس هاب را وارد سیستم تزریق وابستگی‌های ASP.NET Core می‌کند.
مطالب
آشنایی با CLR: قسمت ششم
در مقاله قبلی مبحث کامپایلر JIT را آغاز کردیم. در این قسمت قصد داریم مبحث کارآیی CLR و مباحث دیباگینگ را پیش بکشیم.
از آنجا که یک کد مدیریت نشده، مبحث کارهای JIT را ندارد، ولی CLR مجبور است وقتی را برای آن بگذارد، به نظر می‌رسد ما با یک نقص کوچک در کارآیی روبرو هستیم. گفتیم که جیت کدها را در حافظه‌ی پویا ذخیره می‌کند. به همین خاطر با terminate شدن یا خاتمه دادن به برنامه، این کدها از بین می‌روند یا اینکه اگر دو نمونه از برنامه را اجرا کنیم، هر کدام جداگانه کد را تولید می‌کنند و هر کدام برای خودشان حافظه‌ای بر خواهند داشت و اگر مقایسه‌ای با کدهای مدیریت نشده داشته باشید، در مورد مصرف حافظه یک مشکل ایجاد می‌کند. همچنین JIT در حین تبدیل به کدهای بومی یک بهینه سازی روی کد هم انجام میدهد که این بهینه سازی وقتی را به خود اختصاص می‌دهد ولی همین بهینه سازی کد موجب کارآیی بهتر برنامه می‌گردد.
در زبان سی شارپ دو سوئیچ وجود دارند که بر بهینه سازی کد تاثیر گذار هستند؛ سوئیچ‌های debug و optimize. در جدول زیر تاثیر هر یک از سوئیچ‌ها را بر کیفیت کد IL و JIT در تبدیل به کد بومی را نشان میدهد.

موقعیکه از دستور -optimize استفاده می‌شود، کد IL تولید شده شامل تعداد زیادی از دستورات بدون دستورالعمل No Operation یا به اختصار NOP و پرش‌های شاخه‌ای به خط کد بعدی می‌باشد. این دستور العمل‌ها ما را قادر میسازند تا ویژگی edit & Continue را برای دیباگ کردن و یک سری دستورالعمل‌ها را برای کدنویسی راحت‌تر برای دیباگ کردن و ایجاد break point‌ها داشته باشیم.

موقعی که کد IL بهینه شده تولید شود، این خصوصیات اضافه حذف خواهند شد و دنبال کردن خط به خط کد، کار سختی می‌شود. ولی در عوض فایل نهایی exe یا dll، کوچکتر خواهد شد. بهینه سازی IL توسط JIT حذف خواهد شد و برای کسانی که دوست دارند کدهای IL را تحلیل و آنالیز کنند، خواندنش ساده‌تر و آسان‌تر خواهد بود.

نکته‌ی بعدی اینکه موقعیکه شما از سوئیچ (/debug(+/full/pdbonly استفاده می‌کنید، یک فایل PDB  یا Program Database ایجاد می‌شود. این فایل به دیباگرها کمک می‌کند تا متغیرهای محلی را شناسایی و به کدهای IL متصل شوند. کلمه‌ی full بدین معنی است که JIT می‌تواند دستورات بومی را ردیابی کند تا مبداء آن کد را پیدا کند. سبب می‌شود که ویژوال استودیو به یک دیباگر متصل شده تا در حین اجرای پروسه، آن را دیباگ کند. در صورتی که این سوئیچ را استفاده نکنید، به طور پیش فرض پروسه اجرا و مصرف حافظه کمتر می‌شود. اگر شما پروسه‌ای را اجرا کنید که دیباگر به آن متصل شود، به طور اجباری JIT مجبور به انجام عملیات ردیابی خواهد شد؛ مگر اینکه گزینه‌ی suppress jit  optimization on module load را غیرفعال کرده باشید.
موقعیکه در ویژوال استودیو دو حالت دیباگ و ریلیز را انتخاب می‌کنید، در واقع تنظیمات زیر را اجرا می‌کنید:

//debug

/optimize­ 
/debug:full

//=======================

//Release

/optimize+
/debug:pdbonly
احتمالا موارد بالا به شما می‌گویند که یک سیستم مبتنی بر CLR مشکلات زیادی دارد که یکی از آن‌ها، زمان‌بر بودن انجام عملیات فرآیند پردازش است و دیگری مصرف زیاد حافظه و عدم اشترک حافظه که در مورد کامپایل جیت به آن اشاره کردیم. ولی در بند بعدی قصد داریم نظرتان را عوض کنم.

اگر خیلی شک دارید که واقعا یک برنامه‌ی CLR کارآیی یک برنامه را پایین می‌آورد، بهتر هست به بررسی کارآیی چند برنامه غیر آزمایشی noTrial که حتی خود مایکروسافت آن برنامه‌ها را ایجاد کرده است بپردازید و آن‌ها را با یک برنامه‌ی unmanaged مقایسه کنید. قطعا باعث تعجب شما خواهد شد. این نکته دلایل زیادی دارد که در زیر تعدادی از آن‌ها را بررسی می‌کنیم.
اینکه CLR در محیط اجرا قصد کمپایل دارد، باعث آشنایی کامپایلر با محیط اجرا می‌گردد. از این رو تصمیماتی را که می‌گیرد، می‌تواند به کارآیی یک برنامه کمک کند. در صورتیکه یک برنامه‌ی unmanaged که قبلا کمپایل شده و با محیط‌های متفاوتی که روی آن‌ها اجرا میشود، هیچ آشنایی ندارد و نمیتواند از آن محیط‌ها حداکثر بهره‌وری لازم را به عمل آورد.
برای آشنایی با این ویژگی‌ها توجه شما را به نکات ذیل جلب می‌کنم:

یک.  JIT می‌تواند با نوع پردازنده آشنا شود که آیا این پردازنده از نسل پنتیوم 4 است یا نسل Core i. به همین علت می‌تواند از این مزیت استفاده کرده و دستورات اختصاصی آن‌ها را به کار گیرد، تا برنامه با performance بالاتری اجرا گردد. در صورتی که unmanaged باید حتما دستورات را در پایین‌ترین سطح ممکن و عمومی اجرا کند؛ در صورتیکه شاید یک دستور اختصاصی در یک سی پی یو خاص، در یک عملیات موجب 4 برابر، اجرای سریعتر شود.

دو.  JIT میتواند بررسی هایی را که برابر false هستند، تشخیص دهد. برای فهم بهتر، کد زیر را در نظر بگیرید:
if (numberOfCPUs > 1) {
...
}

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

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

کارآیی بالاتر
اگر برنامه‌ای توسط شما بررسی شد و دیدید که نتایج مورد نیاز در مورد performance را نشان نمی‌دهد، می‌توانید از ابزار کمکی که مایکروسافت در بسته‌های فریمورک دات نت قرار داده است استفاده کنید. نام این ابزار Ngen.exe است و وظیفه‌ی آن این است که وقتی برنامه بر روی یک سیستم برای اولین مرتبه اجرا می‌گردد، کد همه‌ی اسمبلی‌ها را تبدیل کرده و آن‌ها روی دیسک ذخیره می‌کند. بدین ترتیب در دفعات بعدی اجرا، JIT بررسی می‌کند که آیا کد کامپایل شده‌ی اسمبلی از قبل موجود است یا خیر. در صورت وجود، عملیات کامپایل به کد بومی لغو شده و از کد ذخیره شده استفاده خواهد کرد.
نکته‌ای که باید در حین استفاده از این ابزار به آن دقت کنید این است که کد در محیط‌های واقعی اجرا چندان بهینه نیست. بعدا در مورد این ابزار به تفصیل صحبت می‌کنیم.

system.runtime.profileoptimization
کلاس بالا سبب می‌شود که CLR در یک فایل ثبت کند که چه متدهایی در حین اجرای برنامه کمپایل شوند تا در آینده در حین آغاز اجرای برنامه کامپایلر JIT بتواند همزمان این متدها را در ترد دیگری کامپایل کند. اگر برنامه‌ی شما روی یک پردازنده‌ی چند هسته‌ای اجرا می‌شود، در نتیجه اجرای سریعتری خواهید داشت. به این دلیل که چندین متد به طور همزمان در حال کمپایل شدن هستند و همزمان با آماده سازی برنامه برای اجرا اتفاق می‌افتد؛ به جای اینکه عمل کمپایل همزمان با تعامل کاربر با برنامه باشد.

مطالب
بیشترین کاربرد دات نت فریم ورک تابحال در کجا بوده است؟

برخلاف تصور عموم، کاربرد اصلی دات نت فریم ورک در طی این چندین و چند سالی که از ارائه آن می‌گذرد، در توسعه‌ی گسترده برنامه‌های دسکتاپ نبوده است. عمده کاربرد آن در تهیه برنامه‌های وب است. برای نمونه می‌توان به آمارگیری زیر سیستم‌های مورد استفاده دات نت در بین برنامه نویس‌ها در سال 2010 مراجعه کرد [^] و کاربردهای وب آن را حداقل باید در جمع استفاده از WebForms ، Ajax و MVC جستجو کرد (البته اگر WCF و ASMX را ندید بگیریم که آن‌ها هم عمده کاربردشان در پروژه‌های وب است). این اعداد و ارقام سال 2010 را اگر بخواهیم از بیشترین به کمترین لیست کنیم، حاصل آن به صورت زیر درخواهد آمد:

01 - WebForms
02 - Ajax
03 - WCF
04 - Linq to SQL
05 - MVC
06 - WinForms
07 - ASMX
08 - Silverlight
09 - WPF
10 - ADO DataSets
11 - Entity-Framework (EF)
12 - Workflow
13 - ADO.NET Data Services
14 - DynamicData
15 - CardSpace

مورد دیگری که شاید برای خیلی‌ها جالب توجه باشد، آمار تعداد سایت‌هایی است که از ASP.NET استفاده می‌کنند، در مقابل تعداد سایت‌هایی که بر پایه PHP تهیه شده‌اند. مطابق آمار این سایت [^] و [^] در حال حاضر در بین یک میلیون سایت برتر دنیا (سایت‌هایی که بیشترین ترافیک وب را به خود اختصاص داده‌اند) حدود 216 هزار سایت از ASP.NET و 394 هزار سایت از PHP استفاده می‌کنند. از مابقی وب سایت‌های موجود در وب، حدود 27 میلیون سایت از ASP.NET و 26 میلیون سایت از PHP استفاده می‌کنند. این اعداد و ارقام از این جهت حائز اهمیت هستند که مدت زمان ارائه ASP.NET کمتر از PHP است و همچنین بیشترین کاربرد ASP.NET در سرورهای ویندوزی است، برخلاف PHP که علاوه بر ویندوز، در بین سرورهای لینوکسی نیز گزینه‌ی بسیار محبوبی محسوب می‌شود.

مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت هشتم - دریافت اطلاعات از سرور
اغلب برنامه‌های AngularJS 2.0، اطلاعات خود را از طریق پروتکل HTTP، از سرور دریافت می‌کنند. برنامه یک درخواست Get را صادر کرده و سپس سرور پاسخ مناسبی را ارائه می‌دهد.


مقدمه‌ای بر RxJS

اگر به پیشنیازهای نصب AngularJS 2.0 در قسمت اول این سری دقت کرده باشید، یکی از موارد آن، RxJS است:
"dependencies": {
    "rxjs": "5.0.0-beta.2"
 },
یک Observable، آرایه‌ای است که اعضای آن به صورت غیر همزمان (asynchronously) در طول زمان دریافت می‌شوند. برای مثال پس از شروع یک عملیات async، ابتدا عنصر اول آرایه دریافت می‌شود، پس از مدتی عنصر دوم و در آخر عنصر سوم آن. به همین جهت از Observable‌ها برای مدیریت داده‌های async مانند دریافت اطلاعات از یک وب سرور، استفاده می‌شود.
قرار است Observableها به ES 2016 یا نگارش پس از ES 6 اضافه شوند و یکی از پیشنهادات آن هستند. اما هم اکنون AngularJS 2.0 از این امکان، توسط یک کتابخانه‌ی ثالث، به نام reactive extensions یا Rx، استفاده می‌کند. از RxJS در سرویس HTTP و همچنین مدیریت سیستم رخدادهای AngularJS 2.0 استفاده می‌شود. Observableها امکانی را فراهم می‌کنند تا به ازای دریافت هر اطلاعات async از سرور، بتوان توسط رخداد‌هایی از وقوع آن‌ها مطلع شد.

در نگارش قبلی AngularJS از Promises برای مدیریت اعمال غیرهمزمان استفاده می‌شد. Observableها تفاوت‌های قابل ملاحظه‌ای با Promises دارند:
- یک Promise تنها یک مقدار، یا خطا را بر می‌گرداند؛ اما یک Observable چندین مقدار را در طول یک بازه‌ی زمانی باز می‌گرداند.
- برخلاف Promises، می‌توان عملیات یک Observable را لغو کرد.
- Observableها از عملگرهایی مانند map، reduce، filter و غیره نیز پشتیبانی می‌کنند.

البته باید عنوان کرد که هنوز هم می‌توان از Promises در صورت تمایل در AngularJS 2.0 نیز استفاده کرد.


تنظیمات اولیه‌ی کار با RxJS در AngularJS 2.0

برای استفاده از RxJS در AngularJS 2.0، مراحلی مانند افزودن مدخل اسکریپت http.dev.js، ثبت پروایدر HTTP و importهای لازم، باید طی شوند که در ادامه آن‌ها را بررسی خواهیم کرد:
الف) سرویس HTTP جزئی از angular2/core نیست. به همین جهت مدخل اسکریپت متناظر با آن، باید به صفحه‌ی اصلی سایت اضافه شود که این مورد، در قسمت اول بررسی پیشنیازهای نصب AngularJS 2.0 صورت گرفته‌است:
 <!-- Required for http -->
<script src="~/node_modules/angular2/bundles/http.dev.js"></script>
این تعریف در فایل Views\Shared\_Layout.cshtml (و یا index.html) پروژه‌ی جاری موجود است. همچنین در این صفحه، مدخل Rx.js نیز ذکر شده‌است.

ب) اکنون فایل app.component.ts را گشوده و سرویس HTTP را به آن اضافه می‌کنیم. با نحوه‌ی ثبت سرویس‌ها در قسمت قبل آشنا شدیم:
import { Component } from 'angular2/core';
import { HTTP_PROVIDERS } from 'angular2/http';
import 'rxjs/Rx';   // Load all features
 
import { ProductListComponent } from './products/product-list.component';
import { ProductService } from './products/product.service';
 
@Component({
    selector: 'pm-app',
    template:`
    <div><h1>{{pageTitle}}</h1>
        <pm-products></pm-products>
    </div>
    `,
    directives: [ProductListComponent],
    providers: [
        ProductService,
        HTTP_PROVIDERS
    ]
})
export class AppComponent {
    pageTitle: string = "DNT AngularJS 2.0 APP";
}
از آنجائیکه می‌خواهیم سرویس HTTP، در تمام کامپوننت‌های برنامه در دسترس باشد، آن‌را در بالاترین سطح سلسه مراتب کامپوننت‌های موجود، یا همان کامپوننت ریشه‌ی سایت، ثبت و معرفی می‌کنیم. بنابراین سرویس توکار HTTP یا HTTP_PROVIDERS به لیست پروایدرها، اضافه شده‌است.

ج) پس از آن نیاز است importهای متناظر نیز به ابتدای ماژول فعلی، جهت شناسایی این سرویس و همچنین امکانات rx.js اضافه شوند.
تعریف 'import 'rxjs/Rx به این شکل، به module loader اعلام می‌کند که این کتابخانه را بارگذاری کن، اما چیزی را import نکن. هنگامیکه این کتابخانه بارگذاری می‌شود، کدهای جاوا اسکریپتی آن اجرا شده و سبب می‌شوند که عملگرهای ویژه‌ی Observable آن مانند map و filter نیز در دسترس برنامه قرار گیرند.


ساخت یک سرویس سمت سرور بازگشت لیست محصولات به صورت JSON

چون در ادامه می‌خواهیم لیست محصولات را از سرور دریافت کنیم، برنامه‌ی ASP.NET MVC فعلی را اندکی تغییر می‌دهیم تا این لیست را به صورت JSON بازگشت دهد.
بنابراین ابتدا کلاس مدل محصولات را به نحو ذیل به پوشه‌ی Models اضافه کرده:
namespace MVC5Angular2.Models
{
    public class Product
    {
        public int ProductId { set; get; }
        public string ProductName { set; get; }
        public string ProductCode { set; get; }
        public string ReleaseDate { set; get; }
        public decimal Price { set; get; }
        public string Description { set; get; }
        public double StarRating { set; get; }
        public string ImageUrl { set; get; }
    }
}
و سپس اکشن متد Products، لیست محصولات فرضی این سرویس را بازگشت می‌دهد:
using System.Collections.Generic;
using System.Text;
using System.Web.Mvc;
using MVC5Angular2.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
 
namespace MVC5Angular2.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home
        public ActionResult Index()
        {
            return View();
        }
 
        public ActionResult Products()
        {
            var products = new List<Product>
            {
               new Product
               {
                    ProductId= 2,
                    ProductName= "Garden Cart",
                    ProductCode= "GDN-0023",
                    ReleaseDate= "March 18, 2016",
                    Description= "15 gallon capacity rolling garden cart",
                    Price= (decimal) 32.99,
                    StarRating= 4.2,
                    ImageUrl= "app/assets/images/garden_cart.png"
               },
               new Product
               {
                    ProductId= 5,
                    ProductName= "Hammer",
                    ProductCode= "TBX-0048",
                    ReleaseDate= "May 21, 2016",
                    Description= "Curved claw steel hammer",
                    Price= (decimal) 8.9,
                    StarRating= 4.8,
                    ImageUrl= "app/assets/images/rejon_Hammer.png"
               }
            };
 
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(products, new JsonSerializerSettings
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                }),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
    }
}
در اینجا از JSON.NET جهت بازگشت camel case نام خواص مورد نیاز در سمت کاربر، استفاده شده‌است.
برای مطالعه‌ی بیشتر:
«استفاده از JSON.NET در ASP.NET MVC»
«تنظیمات و نکات کاربردی کتابخانه‌ی JSON.NET»

به این ترتیب، آدرس http://localhost:2222/home/products، خروجی JSON سرویس لیست محصولات را در مثال جاری، ارائه می‌دهد.


ارسال یک درخواست HTTP به سرور توسط AngularJS 2.0

اکنون پس از تنظیمات ثبت و معرفی سرویس HTTP و همچنین برپایی یک سرویس سمت سرور ارائه‌ی لیست محصولات، می‌خواهیم سرویس ProductService را که در قسمت قبل ایجاد کردیم (فایل product.service.ts)، جهت دریافت لیست محصولات از سمت سرور، تغییر دهیم:
import { Injectable } from 'angular2/core';
import { IProduct } from './product';
import { Http, Response } from 'angular2/http';
import { Observable } from 'rxjs/Observable';
 
@Injectable()
export class ProductService {
    private _productUrl = '/home/products';
 
    constructor(private _http: Http) { }
 
    getProducts(): Observable<IProduct[]> {
        return this._http.get(this._productUrl)
                         .map((response: Response) => <IProduct[]>response.json())
                         .do(data => console.log("All: " + JSON.stringify(data)))
                         .catch(this.handleError);
    }
 
    private handleError(error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }
}
از آنجائیکه این سرویس دارای یک وابستگی تزریق شده‌است، ذکر ()Injectable@، پیش از تعریف نام کلاس، ضروری است.
در سازنده‌ی کلاس ProductService، کار تزریق وابستگی سرویس Http انجام شده‌است. به این ترتیب با استفاده از متغیر خصوصی http_، می‌توان در کلاس جاری به امکانات این سرویس دسترسی یافت (همان «تزریق سرویس‌ها به کامپوننت‌ها» در قسمت قبل).
سپس متد get آن، یک درخواست HTTP از نوع GET را به آدرس مشخص شده‌ی در متغیر productUrl_ ارسال می‌کند (یا همان سرویس سمت سرور برنامه).
سرویس Http و همچنین شیء Response آن در ماژول‌های Http و Response قرار دارند که در ابتدای صفحه import شده‌اند.

متد http get یک Observable را بازگشت می‌دهد که در نهایت خروجی این متد نیز به همان <[]Observable<IProduct، تنظیم شده‌است. Observable یک شیء جنریک است و در اینجا نوع آن، آرایه‌ای از محصولات درنظر گرفته شده‌است.
اکنون که امضای این متد تغییر یافته است (پیش از این صرفا یک آرایه‌ی ساده از محصولات بود)، استفاده کننده (در کلاس ProductListComponent) باید به تغییرات آن از طریق متد subscribe گوش فرا دهد.
فعلا در کلاس جاری، پس از پایان کار دریافت اطلاعات از سرور، اطلاعات نهایی در متد map در دسترس قرار می‌گیرد (که یکی از عملگرهای RxJs است). کار متد map، اصطلاحا projection است. این متد، هر عضو دریافتی از خروجی سرور را به فرمتی جدید نگاشت می‌کند.
هر درخواست HTTP، در اصل یک عملیات async است. یعنی در اینجا توالی که در اختیار Observable ما قرار می‌گیرد، تنها یک المان دارد که همان شیء HTTP Response است.
بنابراین کار متد map فوق، تبدیل شیء خروجی از سرور، به آرایه‌ای از محصولات است.
در اینجا یک سری کدهای مدیریت استثناءها را نیز در صورت بروز مشکلی می‌توان تعریف کرد. برای مثال در اینجا متد catch، کار پردازش خطاهای رخ داده را انجام می‌دهد.
از متد do جهت لاگ کردن عملیات رخ داده و داده‌های دریافتی در کنسول developer tools مرورگرها استفاده شده‌است.

یک نکته:
اگر خروجی JSON از سرور، برای مثال داخل خاصیتی به نام data محصور شده بود، بجای ()response.json می‌بایستی از response.json().data استفاده می‌شد.


گوش فرا دادن به Observable دریافتی از سرور

تا اینجا یک درخواست HTTP GET را به سمت سرور ارسال کردیم و خروجی آن به صورت Observable در اختیار ما است. اکنون نیاز است کدهای ProductListComponent را جهت گوش فرا دادن به این Observable تغییر دهیم. برای این منظور فایل product-list.component.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
errorMessage: string;
ngOnInit(): void {
    //console.log('In OnInit');
    this._productService.getProducts()
                        .subscribe(
                              products => this.products = products,
                              error => this.errorMessage = <any>error);
}
در کلاس ProductListComponent، در متد ngOnInit که در آن کار آغاز و مقدار دهی وابستگی‌های کامپوننت انجام می‌شود، متد ()productService.getProducts_ فراخوانی شده‌است. این متد یک Observable را بر می‌گرداند. بنابراین برای پردازش نتیجه‌ی آن نیاز است متد subscribe را در ادامه‌ی آن، زنجیر وار ذکر کرد.
اولین پارامتر متد subscribe، کار دریافت نتایج حاصل را به عهده دارد. برای مثال اگر حاصل عملیات در طی سه مرحله صورت گیرد، سه بار نتیجه‌ی دریافتی را می‌توان در اینجا پردازش کرد. البته همانطور که عنوان شد، یک عملیات غیرهمزمان HTTP، تنها در طی یک مرحله، HTTP Response را دریافت می‌کند؛ بنابراین، پارامتر اول متد subscribe نیز تنها یکبار اجرا می‌شود. در اینجا فرصت خواهیم داشت تا آرایه‌ی دریافتی حاصل از متد map قسمت قبل را به خاصیت عمومی products کلاس جاری نسبت دهیم.
پارامتر دوم متد subscribe در صورت شکست عملیات فراخوانی می‌شود. در اینجا حاصل آن به خاصیت جدید errorMessage نسبت داده شده‌است.


اکنون برنامه را مجددا اجرا کنید، هنوز باید لیست محصولات، مانند قبل نمایش داده شود.


یک نکته
اگر برنامه را اجرا کردید و خروجی مشاهده نشد، به کنسول developer tools مرورگر مراجعه کنید؛ احتمالا خطای ذیل در آن درج شده‌است:
 EXCEPTION: No provider for Http!
به این معنا که پروایدر HTTP یا همان HTTP_PROVIDERS، جایی معرفی نشده‌است. البته مشکلی از این لحاظ در برنامه وجود ندارد و این پروایدر در بالاترین سطح ممکن و در فایل app.component.ts ثبت شده‌است. مشکل اینجا است که مرورگر، فایل قدیمی http://localhost:2222/app/app.component.js را کش کرده‌است (به همراه تمام اسکریپت‌های دیگر) و این فایل قدیمی، فاقد تعریف سرویس HTTP است. بنابراین با حذف کش مرورگر و دریافت فایل‌های js جدید، مشکل برطرف خواهد شد.


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


خلاصه‌ی بحث

برای کار با سرور و ارسال درخواست‌های HTTP، ابتدا نیاز است مدخل تعریف http.dev.js به index.html اضافه شود و سپس HTTP_PROVIDERS را در بالاترین سطح کامپوننت‌های تعریف شده، ثبت و معرفی کرد. پس از آن نیاز است RxJs را نیز import کرد. در ادامه، سرویس دریافت لیست محصولات، وابستگی سرویس HTTP را توسط سازنده‌ی خود دریافت کرده و از آن برای صدور یک فرمان HTTP GET استفاده می‌کند. سپس با استفاده از متد map، کار نگاشت شیء Response دریافتی، به فرمت مناسب مدنظر، صورت می‌گیرد.
در ادامه هر کلاسی که نیاز دارد با این کلاس سرویس دریافت اطلاعات کار کند، متد subscribe را فراخوانی کرده و نتیجه‌ی عملیات را پردازش می‌کند.