- ngIf، کل المان را از HTML نهایی رندر شده حذف میکند یا برعکس؛ اما سبب حذف آن از قالب مخصوصی که تحت کنترل و کامپایل Angular است نمیشود. در اینحالت نه کامپوننت مجددا رندر میشود و نه حلقهی for مجددا اجرا خواهد شد. فقط قسمتی که Angular آنرا مخفی و از HTML نهایی حذف کرده بوده، مجددا به HTML نهایی اضافه میشود.
تفاوت AngularJS با KnockoutJS
AngularJS #2
افزودن کامپوننت دسترسی به منابع محافظت شده، به ماژول Dashboard
در اینجا قصد داریم صفحهای را به برنامه اضافه کنیم تا در آن بتوان اطلاعات کنترلرهای محافظت شدهی سمت سرور، مانند MyProtectedAdminApiController (تنها قابل دسترسی توسط کاربرانی دارای نقش Admin) و MyProtectedApiController (قابل دسترسی برای عموم کاربران وارد شدهی به سیستم) را دریافت و نمایش دهیم. به همین جهت کامپوننت جدیدی را به ماژول Dashboard اضافه میکنیم:
>ng g c Dashboard/CallProtectedApi
import { CallProtectedApiComponent } from "./call-protected-api/call-protected-api.component"; const routes: Routes = [ { path: "callProtectedApi", component: CallProtectedApiComponent, data: { permission: { permittedRoles: ["Admin", "User"], deniedRoles: null } as AuthGuardPermission }, canActivate: [AuthGuard] } ];
لینکی را به این صفحه نیز در فایل header.component.html به صورت ذیل اضافه خواهیم کرد تا فقط توسط کاربران وارد شدهی به سیستم (isLoggedIn) قابل مشاهده باشد:
<li *ngIf="isLoggedIn" routerLinkActive="active"> <a [routerLink]="['/callProtectedApi']">Call Protected Api</a> </li>
نمایش و یا مخفی کردن قسمتهای مختلف صفحه بر اساس نقشهای کاربر وارد شدهی به سیستم
در ادامه میخواهیم دو دکمه را بر روی صفحه قرار دهیم تا اطلاعات کنترلرهای محافظت شدهی سمت سرور را بازگشت دهند. دکمهی اول قرار است تنها برای کاربر Admin قابل مشاهده باشد و دکمهی دوم توسط کاربری با نقشهای Admin و یا User.
به همین جهت call-protected-api.component.ts را به صورت ذیل تغییر میدهیم:
import { Component, OnInit } from "@angular/core"; import { AuthService } from "../../core/services/auth.service"; @Component({ selector: "app-call-protected-api", templateUrl: "./call-protected-api.component.html", styleUrls: ["./call-protected-api.component.css"] }) export class CallProtectedApiComponent implements OnInit { isAdmin = false; isUser = false; result: any; constructor(private authService: AuthService) { } ngOnInit() { this.isAdmin = this.authService.isAuthUserInRole("Admin"); this.isUser = this.authService.isAuthUserInRole("User"); } callMyProtectedAdminApiController() { } callMyProtectedApiController() { } }
<button *ngIf="isAdmin" (click)="callMyProtectedAdminApiController()"> Call Protected Admin API [Authorize(Roles = "Admin")] </button> <button *ngIf="isAdmin || isUser" (click)="callMyProtectedApiController()"> Call Protected API ([Authorize]) </button> <div *ngIf="result"> <pre>{{result | json}}</pre> </div>
دریافت اطلاعات از کنترلرهای محافظت شدهی سمت سرور
برای دریافت اطلاعات از کنترلرهای محافظت شده، باید در قسمتی که HttpClient درخواست خود را به سرور ارسال میکند، هدر مخصوص Authorization را که شامل توکن دسترسی است، به سمت سرور ارسال کرد. این هدر ویژه را به صورت ذیل میتوان در AuthService تولید نمود:
getBearerAuthHeader(): HttpHeaders { return new HttpHeaders({ "Content-Type": "application/json", "Authorization": `Bearer ${this.getRawAuthToken(AuthTokenType.AccessToken)}` }); }
روش دوم انجام اینکار که مرسومتر است، اضافه کردن خودکار این هدر به تمام درخواستهای ارسالی به سمت سرور است. برای اینکار باید یک HttpInterceptor را تهیه کرد. به همین منظور فایل جدید app\core\services\auth.interceptor.ts را به برنامه اضافه کرده و به صورت ذیل تکمیل میکنیم:
import { Injectable } from "@angular/core"; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from "@angular/common/http"; import { Observable } from "rxjs/Observable"; import { AuthService, AuthTokenType } from "./auth.service"; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private authService: AuthService) { } intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const accessToken = this.authService.getRawAuthToken(AuthTokenType.AccessToken); if (accessToken) { request = request.clone({ headers: request.headers.set("Authorization", `Bearer ${accessToken}`) }); } return next.handle(request); } }
به این ترتیب دیگری نیازی نیست تا به ازای هر درخواست و هر قسمتی از برنامه، این هدر را به صورت دستی تنظیم کرد و اضافه شدن آن پس از تنظیم ذیل، به صورت خودکار انجام میشود:
import { HTTP_INTERCEPTORS } from "@angular/common/http"; import { AuthInterceptor } from "./services/auth.interceptor"; @NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ] }) export class CoreModule {}
در این حالت اگر برنامه را اجرا کنید، خطای ذیل را در کنسول توسعهدهندههای مرورگر مشاهده خواهید کرد:
compiler.js:19514 Uncaught Error: Provider parse errors: Cannot instantiate cyclic dependency! InjectionToken_HTTP_INTERCEPTORS ("[ERROR ->]"): in NgModule AppModule in ./AppModule@-1:-1
import { Injector } from "@angular/core"; constructor(private injector: Injector) { } intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const authService = this.injector.get(AuthService);
@Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private injector: Injector) { } intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const authService = this.injector.get(AuthService); const accessToken = authService.getRawAuthToken(AuthTokenType.AccessToken); if (accessToken) { request = request.clone({ headers: request.headers.set("Authorization", `Bearer ${accessToken}`) }); } return next.handle(request); } }
تکمیل متدهای دریافت اطلاعات از کنترلرهای محافظت شدهی سمت سرور
اکنون پس از افزودن AuthInterceptor، میتوان متدهای CallProtectedApiComponent را به صورت ذیل تکمیل کرد. ابتدا سرویسهای Auth ،HttpClient و همچنین تنظیمات آغازین برنامه را به سازندهی CallProtectedApiComponent تزریق میکنیم:
constructor( private authService: AuthService, private httpClient: HttpClient, @Inject(APP_CONFIG) private appConfig: IAppConfig, ) { }
callMyProtectedAdminApiController() { this.httpClient .get(`${this.appConfig.apiEndpoint}/MyProtectedAdminApi`) .map(response => response || {}) .catch((error: HttpErrorResponse) => Observable.throw(error)) .subscribe(result => { this.result = result; }); } callMyProtectedApiController() { this.httpClient .get(`${this.appConfig.apiEndpoint}/MyProtectedApi`) .map(response => response || {}) .catch((error: HttpErrorResponse) => Observable.throw(error)) .subscribe(result => { this.result = result; }); }
در این حالت اگر برنامه را اجرا کنید، افزوده شدن خودکار هدر مخصوص Authorization:Bearer را در درخواست ارسالی به سمت سرور، مشاهده خواهید کرد:
مدیریت خودکار خطاهای عدم دسترسی ارسال شدهی از سمت سرور
ممکن است کاربری درخواستی را به منبع محافظت شدهای ارسال کند که به آن دسترسی ندارد. در AuthInterceptor تعریف شده میتوان به وضعیت این خطا، دسترسی یافت و سپس کاربر را به صفحهی accessDenied که در قسمت قبل ایجاد کردیم، به صورت خودکار هدایت کرد:
return next.handle(request) .catch((error: any, caught: Observable<HttpEvent<any>>) => { if (error.status === 401 || error.status === 403) { this.router.navigate(["/accessDenied"]); } return Observable.throw(error); });
@Injectable() export class AuthInterceptor implements HttpInterceptor { constructor( private injector: Injector, private router: Router) { } intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const authService = this.injector.get(AuthService); const accessToken = authService.getRawAuthToken(AuthTokenType.AccessToken); if (accessToken) { request = request.clone({ headers: request.headers.set("Authorization", `Bearer ${accessToken}`) }); return next.handle(request) .catch((error: any, caught: Observable<HttpEvent<any>>) => { if (error.status === 401 || error.status === 403) { this.router.navigate(["/accessDenied"]); } return Observable.throw(error); }); } else { // login page return next.handle(request); } } }
using ASPNETCore2JwtAuthentication.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; namespace ASPNETCore2JwtAuthentication.WebApp.Controllers { [Route("api/[controller]")] [EnableCors("CorsPolicy")] [Authorize(Policy = CustomRoles.Editor)] public class MyProtectedEditorsApiController : Controller { public IActionResult Get() { return Ok(new { Id = 1, Title = "Hello from My Protected Editors Controller! [Authorize(Policy = CustomRoles.Editor)]", Username = this.User.Identity.Name }); } } }
callMyProtectedEditorsApiController() { this.httpClient .get(`${this.appConfig.apiEndpoint}/MyProtectedEditorsApi`) .map(response => response || {}) .catch((error: HttpErrorResponse) => Observable.throw(error)) .subscribe(result => { this.result = result; }); }
نکتهی مهم: نیاز به دائمی کردن کلیدهای رمزنگاری سمت سرور
اگر برنامهی سمت سرور ما که توکنها را اعتبارسنجی میکند، ریاستارت شود، چون قسمتی از کلیدهای رمزگشایی اطلاعات آن با اینکار مجددا تولید خواهند شد، حتی با فرض لاگین بودن شخص در سمت کلاینت، توکنهای فعلی او برگشت خواهند خورد و از مرحلهی تعیین اعتبار رد نمیشوند. در این حالت کاربر خطای 401 را دریافت میکند. بنابراین پیاده سازی مطلب «غیرمعتبر شدن کوکیهای برنامههای ASP.NET Core هاست شدهی در IIS پس از ریاستارت آن» را فراموش نکنید.
کدهای کامل این سری را از اینجا میتوانید دریافت کنید.
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژهی ASPNETCore2JwtAuthentication.AngularClient وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o، برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد (و یا همان اجرای فایل ng-serve.bat). همچنین باید به پوشهی ASPNETCore2JwtAuthentication.WebApp نیز مراجعه کرده و فایل dotnet_run.bat را اجرا کنید، تا توکن سرور برنامه نیز فعال شود.
معماری میکرو سرویس یا یکپارچه؟
برای درک میکروسرویسها، باید بدانیم کاربرد سیستمهای یکپارچه چیست و چه چیزی باعث شد در زمانهای اخیر از برنامههای یکپارچه به میکروسرویسها حرکت کنیم.
سیستمهای یکپارچه ( Monolithic applications )
اگر تمام عملکردهای یک پروژه در یک بخش واحد وجود داشته باشند، آن برنامه به عنوان یک برنامهی یکپارچه شناخته میشود. ما برنامهی خود را در لایههای مختلفی مانند Presentation ، Service ، UI طراحی میکنیم و سپس آن بخش از کدهای نوشته شده را به عنوان یک فایل خروجی به کار میگیریم. این چیزی نیست جز یک برنامهی یکپارچه، که در آن " mono " یک پایگاه کد منفرد حاوی تمام عملکردهای مورد نیاز را نشان میدهد.
چرا اصلا به سمت میکروسرویسها برویم؟
خب برای جواب به این سوال بهتر است معایب سیستمهای یکپارچه را مرور کنیم:
- مدیریت دشوار بخاطر گسترش برنامه در گذشت زمان
- برای تغییری کوچک، کل برنامه را دوباره باید منتشر ( publish ) کنیم
- با تغییر و آپدیت برنامه، زمان انتشار افزایش مییابد.
- درک دشوار برای توسعه دهندههای جدید هر پروژه
- برای تقسیم ترافیک روی قسمتهای مختلف برنامه، باید نمونههای کل برنامه را در چندین سرور منتشر کنیم که بسیار ناکارآمد و باعث استفادهی بیهوده از منابع میشود
- اگر از فناوری یا تکنولوژیهای جدید استفاده کنیم، برای عملکردی خاص، چه از نظر هزینه و چه از نظر زمان، بر کل برنامه تاثیر گذار است
- و در نهایت وجود یک باگ در هر ماژول میتواند کل برنامه را مختل کند.
و اما مزایای سیستمهای یکپارچه:
- توسعهی آن نسبت به میکروسرویسها ساده است.
- انتشار آن آسانتر است؛ زیرا فقط یک خروجی، مستقر شدهاست.
- در مقایسه با معماری میکروسرویسها، توسعهی آن نسبتا آسانتر و سادهتر است.
- مشکلات تأخیر و امنیت شبکه در مقایسه با معماری میکروسرویسها نسبتاً کمتر است.
- توسعه دهندگان نیازی به یادگیری برنامههای مختلف ندارند؛ آنها میتوانند تمرکز خود را بر روی یک برنامه حفظ کنند.
میکروسرویس ها
این یک سبک توسعه معماری است که در آن برنامه از سرویسهای کوچکتری تشکیل شدهاست که بخش کوچکی از عملکرد و دادهها را با برقراری ارتباط مستقیم با یکدیگر، با استفاده از پروتکلی مانند HTTP مدیریت میکند. به عبارتی دیگر خدمات یا سرویسهای کوچکی هستند که با هم کار میکنند.
معماری میکروسرویس تأثیر بسزایی در رابطهی بین برنامه و پایگاه داده دارد. بجای اشتراک گذاری یک پایگاه داده با سایر میکروسرویسها، هر میکروسرویس، پایگاه داده خاص خود را دارد که اغلب منجر به تکثیر برخی از دادهها میشود، اما اگر میخواهید از این معماری بهره مند شوید، داشتن یک پایگاه داده در هر میکروسرویس، ضروری است؛ زیرا اتصال ضعیف را تضمین میکند. مزیت دیگر داشتن یک پایگاه دادهی مجزا برای هر میکروسرویس این است که هر میکروسرویس میتواند از نوع پایگاه دادهای که برای نیازهای خود مناسبتر است، استفاده کند. هر سرویس یک ماژول را ارائه میدهد، به طوری که خدمات مختلف را میتوان به زبانهای برنامه نویسی مختلف نوشت. الگوهای زیادی در معماری میکروسرویس دخیل هستند مانند discovery و registry service ، Caching ، ارتباط API ، امنیت و غیره.
اصول میکروسرویسها:
تک مسئولیتی: یکی از اصولی است که به عنوان بخشی از الگوی طراحی SOLID تعریف شده است. بیان میکند که یک Unit ، یا یک کلاس، یک متد یا یک میکروسرویس باید تنها یک مسئولیت را داشته باشد. هر میکروسرویس باید یک مسئولیت داشته باشد و یک عملکرد واحد را ارائه دهد. شما همچنین میتوانید بگویید تعداد میکروسرویسهایی که باید توسعه دهید، برابر با تعداد عملکردهای مورد نیاز شما است. پایگاه داده نیز غیرمتمرکز است و به طور کلی، هر میکروسرویس، پایگاه داده خاص خود را دارد.
بر اساس قابلیتهای تجاری ساخته شده است: در دنیای امروزی که فناوریهای زیادی وجود دارند، همیشه فناوریای وجود دارد که برای اجرای یک عملکرد خاص مناسبتر است. اما در برنامههای یکپارچه، این یک اشکال بزرگ بود؛ زیرا ما نمیتوانیم از فناوریهای مختلف برای هر عملکرد استفاده کنیم و از این رو، نیاز به مصالحه در زمینههای خاص داریم. یک میکروسرویس هرگز نباید خود را از پذیرش پشته فناوری مناسب یا ذخیرهسازی پایگاه داده پشتیبان که برای حل هدف تجاری مناسبتر است، محدود کند؛ بهعنوان مثال، هر میکروسرویس میتواند بر اساس نیازهای تجاری از فناوریهای متفاوتی استفاده کند.
طراحی برای مدیریت خطاها: میکروسرویسها باید با در نظر گرفتن مدیریت خطاها طراحی شوند. میکروسرویسها باید از مزیت این معماری استفاده کنند و پایین آمدن یک میکروسرویس نباید بر کل سیستم تأثیر بگذارد و سایر عملکردها باید در دسترس کاربر باقی بمانند. اما در برنامههای کاربردی سیستمهای یکپارچه که خطای یک ماژول منجر به سقوط کل برنامه میشود، اینگونه نبود.
مزایای میکروسرویس ها:
- مدیریت آن آسان است زیرا نسبتا کوچکتر است.
- اگر در یکی از میکروسرویسها، بروزرسانی وجود داشته باشد، باید فقط آن میکروسرویس را مجدداً منتشر کنیم.
- میکروسرویسها مستقل هستند و از این رو به طور مستقل منتشر میشوند. زمان راه اندازی و انتشار آنها نسبتاً کمتر است.
- برای یک توسعهدهنده جدید بسیار آسان است که وارد پروژه شود، زیرا او باید فقط یک میکروسرویس خاص را که عملکردی را که روی آن کار میکند، درک کند و نه کل سیستم را.
- اگر یک میکروسرویس خاص به دلیل استفاده بیش از حد کاربران از آن عملکرد، با بار زیادی مواجه است، ما باید فقط آن میکروسرویس را تنظیم کنیم. از این رو، معماری میکروسرویس از مقیاس بندی افقی پشتیبانی میکند.
- هر میکروسرویس بر اساس نیازهای تجاری میتواند از فناوریهای مختلفی استفاده کند.
- اگر یک میکروسرویس خاص به دلیل برخی باگها از کار بیفتد، بر روی سایر میکروسرویسها تأثیر نمیگذارد و کل سیستم دست نخورده باقی میماند و به ارائه سایر عملکردها به کاربران ادامه میدهد.
معایب میکروسرویس ها:
- پیچیده است و پیچیدگی آن با افزایش تعداد ریز سرویسها افزایش مییابد.
- نیاز به نیروهای متخصص
- استقرار مستقل میکروسرویسها پیچیدهاست.
- میکروسرویسها از نظر استفاده از شبکه پرهزینه هستند؛ زیرا نیاز به تعامل با یکدیگر دارند و همه این تماسهای راه دور منجر به تأخیر شبکه میشود.
- امنیت کمتر به دلیل ارتباط بین سرویسها
- اشکال زدایی دشوار است