این مطالب را مطالعه کنید: «مسیریابی در Angular - قسمت هشتم - مسیرهای ثانویه»
و همچنین «مسیریابی در Angular - قسمت پنجم - تعریف Child Routes»
این خطا مربوط به تزریق وابستگیهای Location هست. اگر تعریف زیر را فراموش کنید:
این Location کامپایل میشود، اما از بستهی Angular تامین نخواهد شد.
import { Location } from "@angular/common";
نکته جدیدتر:
با استفاده از خط فرمان زیر میتوان یک پروژه ترکیبی از Angular و ASP.Net Core را راه اندازی کرد:
dotnet new angular
- همان «محافظت از المنتهای صفحه» در مطلب «کنترل دسترسیها در Angular با استفاده از Ng2Permission» است.
- مانند سرویس auth.service.ts در مطلب « مسیریابی در Angular - قسمت دوم - مسیریابی ماژولها » است.
- مانند سرویس auth.service.ts در مطلب « مسیریابی در Angular - قسمت دوم - مسیریابی ماژولها » است.
سری دایرکتیوهای اعتبارسنجی angular-form-for
هم میتونن ایده خوبی برای ترجمه به نگارش 2 باشند. یک سری هم در اینجا angular-validation ارائه شدن.
نحوهی پیاده سازی اعتبارسنجی با روتر جدید
Angular 2 authentication revisited
Protecting Routes using Guards in Angular 2
Angular 2 authentication revisited
Protecting Routes using Guards in Angular 2
مطالب
RequireJs
در طراحی و توسعه پروژههای تحت وب در مقیاس بزرگ برای اینکه مدیریت پروژه راحتتر شود کدهای مورد نظر را در چند ماژول قرار میدهند در نتیجه کدهای پروژه در بلاکهای کوچکتر قرار خواهند داشت. نوشتن پروژه به صورت ماژولار قابلیت استفاده مجدد از کدهای برنامه را افزایش میدهد، علاوه بر آن مدیریت پروژه در فاز نگهداری آسانتر خواهد شد؛ از طرفی دیگر وابستگی بین ماژولها و تامین آن ها، همواره مهمترین مفهوم برای توسعه دهندگان پروژههای وب است. RequireJs یکی از فریم ورکهای محبوب برای مدیریت وابستگیهای بین ماژولها است و کاربرد اصلی آن راحت سازی مفهوم modularity در اینگونه پروژه هاست.
پروژههای بزرگ عموما دارای یک یا چند فایل جاوااسکریپ هستند که برای استفاده از آنها در صفحات از تگ script استفاده میشود. اگر این فایلها دارای وابستگی به هم باشند، ترتیب فراخوانی این فایل در تگ script مهم است. برای مثال:یک پروژه دارای فایلهای زیر خواهد بود:
purchase.js
function purchaseProduct(){ console.log("Function : purchaseProduct"); var credits = getCredits(); if(credits > 0){ reserveProduct(); return true; } return false; }
function reserveProduct(){ console.log("Function : reserveProduct"); return true; }
function getCredits(){ console.log("Function : getCredits"); var credits = "100"; return credits; }
main.js
var result = purchaseProduct();
<script src="products.js"></script> <script src="purchase.js"></script> <script src="main.js"></script> <script src="credits.js"></script>
فقط کافیست تصور کنید که تعداد و حجم کدهای این فایلها در یک پروژه زیاد باشد یا حتی این فایلها توسط یک برنامه نویس دیگر تهیه و تدوین شده باشد؛ در نتیجه به خاطر سپردن این وابستگیها به طور قطع کار سخت خواهد بود و در خیلی موارد طافت فرسا.
RequireJs چگونه برای حل این مشکل به ما کمک میکند؟
با استفاده از فریم ورک RequireJs کدهای ما به ماژولهای کوچکتر شکسته میشود و وابستگی ماژولها در تنظیمات لود فایل ثبت میشود. در ضمن این فریم ورک با مرورگرها جدید و محبوب کاملا سازگار است. برای شروع فایلهای مورد نیاز را از اینجا دانلود نمایید. البته میتوانید از nuget هم استفاده کنید:
PM> Install-Package RequireJS
purchase.js
define(["credits","products"], function(credits,products) { console.log("Function : purchaseProduct"); return { purchaseProduct: function() { var credit = credits.getCredits(); if(credit > 0){ products.reserveProduct(); return true; } return false; } } });
products.js
define(function(products) { return { reserveProduct: function() { console.log("Function : reserveProduct"); return true; } } });
define(function() { console.log("Function : getCredits"); return { getCredits: function() { var credits = "100"; return credits; } } });
کافیست در فایل main.js، برای استفاده از فایل purchase.js از دستور require استفاده کنید.
require(['purchase'], function( purchase ) { var result = purchase.purchaseProduct(); });
تفاوت بین دستور require و define
دستور define صرفا برای تعیین وابستگی ماژول استفاده میشود در حالی که دستور require برای فراخوانی و لود ماژول کاربرد دارد و به نوعی به عنوان نقطه شروع اجرای برنامه خواهد بود.
در پست بعدی به پیاده سازی مثال بالا به کمک RequireJs در قالب یک پروژه Asp.Net MVC خواهم پرداخت.
در طراحی برنامههای Angular توصیه شدهاست تا هرگونه منطقی که مستقیما به View یک کامپوننت مرتبط نیست، به یک کلاس سرویس منتقل شود. در این بین ممکن است نیاز به صدور رخدادی از یک سرویس به خارج از آن باشد؛ چیزی مانند EventEmitter. اما EventEmitter برای سرویسها طراحی نشدهاست و کاربرد صحیح آن صرفا محدود به کامپوننتها است. برای حل این مساله، API سرویس ما باید یک Observable را در معرض دید استفاده کننده قرار دهد تا توسط آن بتوان رخدادهایی را به کامپوننتهای مشترک شدهی به آن، صادر کرد.
چگونه میتوان رخدادهایی از نوع Observable را ایجاد کرد؟
کلاس Subject پاسخی است به این پرسش. Subjectها Observableهایی هستند که میتوانند چندین مشترک داشته باشند و رخدادهایی را به مشترکین خود صادر کنند. برای کار با آنها باید یک private Subject را در سرویس خود ایجاد کرد و سپس جریان منتقل شدهی توسط آنرا توسط یک public Observable در اختیار مصرف کنندگان قرار داد. با فراخوانی متد next یک Subject، رخدادی به مشترکین آن منتقل میشود.
مرسوم است نام Observableهایی را که قرار است رخدادی را صادر کنند به $ ختم میکنند.
استفاده کنندگان نیز مشترک این $countdownEnd شده و هر بار که در طرف سرویس، متد next آن فراخوانی میشود، از به روز رسانی آن مطلع خواهند شد.
چرا مستقیما از مقدار countdown استفاده نکنیم؟
در قسمتی از سرویس فوق که ملاحظه میکنید، میتوان مقدار countdown را مستقیما نیز در کامپوننتها مورد استفاده قرار داد. اما این روش بهینه نیست. از این جهت که Angular باید مدام تغییرات این خاصیت را رصد کند و به آن واکنش نشان دهد. آیا بهتر نیست ما به Angular اعلام کنیم که مقدار آن تغییر کردهاست و اکنون بهتر است View را به روز رسانی کنی؟ با ارائهی مقادیر جدیدی توسط یک Observable، اکنون Angular صرفا به تغییرات آن واکنش نشان خواهد داد و دیگری نیاز به بررسی مداوم تغییرات مقدار countdown ندارد.
یک مشکل! Subject تعریف شده، مقادیر را تنها در زمان فراخوانی متد next ارائه میدهد و نه به صورت دیگری.
پیشتر با دسترسی مستقیم به خاصیت countdown، همواره به مقادیر آن هم دسترسی داشتیم. اما با استفاده از یک Subject، تنها زمانیکه متد next آن فراخوانی شود میتوان به این مقدار دسترسی یافت. برای رفع این مشکل یک Subject ویژه به نام BehaviorSubject طراحی شدهاست که به محض مشترک شدن به آن، اولین و یا آخرین مقدار آنرا میتوان دریافت کرد.
تفاوت Subject با BehaviorSubject
BehaviorSubject مانند یک Subject است؛ با این تفاوت که همواره از وضعیت خود آگاه میباشد. یک BehaviorSubject:
- همواره دارای مقداری است. حتی در زمان وهله سازی، باید مقدار اولیهای را برای آن مشخص کرد.
- در زمان اشتراک به آن، میتوان آخرین مقدار موجود در آن را که ممکن است اولین مقدار آن نیز باشد، دریافت کرد.
- همواره میتوان مقدار آنرا توسط متد getValue بدست آورد.
و مهمترین مزیت آن نسبت به Subject، همان مورد دوم است. اگر مشترک یک Subject شویم، تا متد next آن فراخوانی نشود، مقداری را دریافت نمیکنیم. اما همان لحظه که مشترک BehaviorSubject میشویم، آخرین مقدار موجود در آنرا دریافت خواهیم کرد.
برای مثال فرض کنید کامپوننتی را دارید که به خاصیت isLoggedIn از نوع Observable یک Subject گوش فرا میدهد. اما اشتراک آن پس از فراخوانی متد next در این سرویس بودهاست. از این رو این کامپوننت هیچگاه متوجه تغییر و یا مقدار نهایی isLoggedIn نخواهد شد. به همین جهت است که به BehaviorSubject نیاز داریم. در این بین مهم نیست که چه زمانی مشترک آن میشویم؛ همواره در زمان اشتراک، آخرین و یا اولین مقدار موجود در آنرا دریافت خواهیم کرد.
یک مثال: بررسی عملکرد BehaviorSubject
در ادامه یک ماژول را به همراه 4 کامپوننت و یک سرویس سفارشی ایجاد میکنیم:
هدف این است که سه کامپوننت اول، دوم و سوم را در کامپوننت final، همانند تصویر فوق نمایش دهیم.
در این بین یک سرویس انتشار اطلاعات نیز طراحی شدهاست:
کار این سرویس ارائه یک پیام از نوع BehaviorSubject از طریق خاصیت عمومی $telecast آن است که به صورت Observable در معرض دید کامپوننتهای مشترک به آن قرار خواهد گرفت. هدف این است که کامپوننتها مدام تغییرات msg را بررسی نکنند و فقط به آخرین تغییر صادر شدهی توسط کامپوننت که از طریق فراخوانی متد next در متد editMsg صورت میگیرد، واکنش نشان دهند.
در کامپوننت اول، نحوهی اشتراک به این سرویس را مشاهده میکنید:
کار اشتراک در این کامپوننت در متد ngOnInit انجام شدهاست. بسیار مهم است جهت عدم بروز نشتی حافظه، در متد ngOnDestroy کار unsubscribe بر روی این اشتراک نیز صورت گیرد.
در اینجا هر زمانیکه متد next در سرویس فراخوانی شود، this.editedMsg مقدار جدیدی را دریافت میکند.
با این قالب:
اما اگر به تصویر دقت کنید، this.editedMsg هم اکنون دارای مقدار است (در اولین بار اجرای این کامپوننت). علت آن به داشتن مقدار اولیهای در BehaviorSubject تعریف شده بر میگردد که در اولین بار اشتراک به آن، در اختیار مشترک قرار خواهد گرفت. این مورد، مهمترین تفاوت BehaviorSubject با Subject است.
در این کامپوننت اگر کاربر مقداری را در textbox وارد کند و سپس بر روی دکمهی Change کلیک نماید، این تغییر از طریق سرویس، به تمام مشترکین آن صادر خواهد شد.
کامپوننت دوم نیز مانند کامپوننت اول است، فقط یک textbox ورود اطلاعات را به همراه ندارد.
همانطور که ملاحظه میکنید، این کامپوننت نیز دارای مقدار اولیهی BehaviorSubject است.
کامپوننت سوم، اندکی متفاوت است:
در اینجا کار اشتراک در متد subscribe فراخوانی شدهی توسط قالب آن صورت میگیرد:
و چون این متد پس از ngOnInit قرار است توسط کاربر فراخوانی شود، مقدار message این کامپوننت هنوز خالی است.
اکنون اگر بر روی دکمهی Subscribe آن کلیک کنیم، بلافاصله در لحظهی اشتراک، اولین/آخرین مقدار موجود در BehaviorSubject را دریافت خواهیم کرد:
کامپوننت Final نیز تمام کامپوننتها را در صفحه نمایش میدهد:
و اگر در textbox کامپوننت اول، مقدار Test را وارد کنیم و سپس بر روی دکمهی Change آن کلیک نمائیم، این مقدار به تمام کامپوننتهای مشترک به BehaviorSubject سرویس برنامه، منتشر خواهد شد:
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
چگونه میتوان رخدادهایی از نوع Observable را ایجاد کرد؟
کلاس Subject پاسخی است به این پرسش. Subjectها Observableهایی هستند که میتوانند چندین مشترک داشته باشند و رخدادهایی را به مشترکین خود صادر کنند. برای کار با آنها باید یک private Subject را در سرویس خود ایجاد کرد و سپس جریان منتقل شدهی توسط آنرا توسط یک public Observable در اختیار مصرف کنندگان قرار داد. با فراخوانی متد next یک Subject، رخدادی به مشترکین آن منتقل میشود.
import { Subject } from “rxjs/Subject”; public countdown: number = 0; private countdownEndSource = new Subject<void>(); public countdownEnd$ = this.countdownEndSource.asObservable();
استفاده کنندگان نیز مشترک این $countdownEnd شده و هر بار که در طرف سرویس، متد next آن فراخوانی میشود، از به روز رسانی آن مطلع خواهند شد.
چرا مستقیما از مقدار countdown استفاده نکنیم؟
در قسمتی از سرویس فوق که ملاحظه میکنید، میتوان مقدار countdown را مستقیما نیز در کامپوننتها مورد استفاده قرار داد. اما این روش بهینه نیست. از این جهت که Angular باید مدام تغییرات این خاصیت را رصد کند و به آن واکنش نشان دهد. آیا بهتر نیست ما به Angular اعلام کنیم که مقدار آن تغییر کردهاست و اکنون بهتر است View را به روز رسانی کنی؟ با ارائهی مقادیر جدیدی توسط یک Observable، اکنون Angular صرفا به تغییرات آن واکنش نشان خواهد داد و دیگری نیاز به بررسی مداوم تغییرات مقدار countdown ندارد.
یک مشکل! Subject تعریف شده، مقادیر را تنها در زمان فراخوانی متد next ارائه میدهد و نه به صورت دیگری.
پیشتر با دسترسی مستقیم به خاصیت countdown، همواره به مقادیر آن هم دسترسی داشتیم. اما با استفاده از یک Subject، تنها زمانیکه متد next آن فراخوانی شود میتوان به این مقدار دسترسی یافت. برای رفع این مشکل یک Subject ویژه به نام BehaviorSubject طراحی شدهاست که به محض مشترک شدن به آن، اولین و یا آخرین مقدار آنرا میتوان دریافت کرد.
تفاوت Subject با BehaviorSubject
BehaviorSubject مانند یک Subject است؛ با این تفاوت که همواره از وضعیت خود آگاه میباشد. یک BehaviorSubject:
- همواره دارای مقداری است. حتی در زمان وهله سازی، باید مقدار اولیهای را برای آن مشخص کرد.
- در زمان اشتراک به آن، میتوان آخرین مقدار موجود در آن را که ممکن است اولین مقدار آن نیز باشد، دریافت کرد.
- همواره میتوان مقدار آنرا توسط متد getValue بدست آورد.
و مهمترین مزیت آن نسبت به Subject، همان مورد دوم است. اگر مشترک یک Subject شویم، تا متد next آن فراخوانی نشود، مقداری را دریافت نمیکنیم. اما همان لحظه که مشترک BehaviorSubject میشویم، آخرین مقدار موجود در آنرا دریافت خواهیم کرد.
برای مثال فرض کنید کامپوننتی را دارید که به خاصیت isLoggedIn از نوع Observable یک Subject گوش فرا میدهد. اما اشتراک آن پس از فراخوانی متد next در این سرویس بودهاست. از این رو این کامپوننت هیچگاه متوجه تغییر و یا مقدار نهایی isLoggedIn نخواهد شد. به همین جهت است که به BehaviorSubject نیاز داریم. در این بین مهم نیست که چه زمانی مشترک آن میشویم؛ همواره در زمان اشتراک، آخرین و یا اولین مقدار موجود در آنرا دریافت خواهیم کرد.
یک مثال: بررسی عملکرد BehaviorSubject
در ادامه یک ماژول را به همراه 4 کامپوننت و یک سرویس سفارشی ایجاد میکنیم:
ng g m ServiceComponentCommunication -m app.module --routing ng g c ServiceComponentCommunication/First ng g c ServiceComponentCommunication/Second ng g c ServiceComponentCommunication/Third ng g c ServiceComponentCommunication/Final ng g s ServiceComponentCommunication/Sample
هدف این است که سه کامپوننت اول، دوم و سوم را در کامپوننت final، همانند تصویر فوق نمایش دهیم.
در این بین یک سرویس انتشار اطلاعات نیز طراحی شدهاست:
import { Injectable } from "@angular/core"; import { BehaviorSubject } from "rxjs/BehaviorSubject"; @Injectable() export class SampleService { private msgSource = new BehaviorSubject<string>("default service value"); telecast$ = this.msgSource.asObservable(); constructor() { } editMsg(newMsg: string) { this.msgSource.next(newMsg); } }
در کامپوننت اول، نحوهی اشتراک به این سرویس را مشاهده میکنید:
import { SampleService } from "./../sample.service"; import { Component, OnInit, OnDestroy } from "@angular/core"; import { Subscription } from "rxjs/Subscription"; @Component({ selector: "app-first", templateUrl: "./first.component.html", styleUrls: ["./first.component.css"] }) export class FirstComponent implements OnInit, OnDestroy { editedMsg: string; sampleSubscription: Subscription; constructor(private sampleService: SampleService) { } ngOnInit() { this.sampleSubscription = this.sampleService.telecast$.subscribe(message => { this.editedMsg = message; }); } editMsg() { this.sampleService.editMsg(this.editedMsg); } ngOnDestroy() { this.sampleSubscription.unsubscribe(); } }
در اینجا هر زمانیکه متد next در سرویس فراخوانی شود، this.editedMsg مقدار جدیدی را دریافت میکند.
با این قالب:
<div class="panel panel-default"> <div class="panel-heading"> <h2 class="panel-title">First Component</h2> </div> <div class="panel-body"> <p> {{editedMsg}}</p> <input class="form-control" type="text" [(ngModel)]="editedMsg"> <button (click)="editMsg()" class="btn btn-primary">Change</button> </div> </div>
اما اگر به تصویر دقت کنید، this.editedMsg هم اکنون دارای مقدار است (در اولین بار اجرای این کامپوننت). علت آن به داشتن مقدار اولیهای در BehaviorSubject تعریف شده بر میگردد که در اولین بار اشتراک به آن، در اختیار مشترک قرار خواهد گرفت. این مورد، مهمترین تفاوت BehaviorSubject با Subject است.
در این کامپوننت اگر کاربر مقداری را در textbox وارد کند و سپس بر روی دکمهی Change کلیک نماید، این تغییر از طریق سرویس، به تمام مشترکین آن صادر خواهد شد.
کامپوننت دوم نیز مانند کامپوننت اول است، فقط یک textbox ورود اطلاعات را به همراه ندارد.
همانطور که ملاحظه میکنید، این کامپوننت نیز دارای مقدار اولیهی BehaviorSubject است.
کامپوننت سوم، اندکی متفاوت است:
import { SampleService } from "./../sample.service"; import { Component, OnInit, OnDestroy } from "@angular/core"; import { Subscription } from "rxjs/Subscription"; @Component({ selector: "app-third", templateUrl: "./third.component.html", styleUrls: ["./third.component.css"] }) export class ThirdComponent implements OnInit, OnDestroy { message: string; sampleSubscription: Subscription; constructor(private sampleService: SampleService) { } ngOnInit() { } subscribe() { this.sampleSubscription = this.sampleService.telecast$.subscribe(message => { this.message = message; }); } ngOnDestroy() { if (this.sampleSubscription) { this.sampleSubscription.unsubscribe(); } } }
<div class="panel panel-default"> <div class="panel-heading"> <h2 class="panel-title">Third Component</h2> </div> <div class="panel-body"> <p>{{message}}</p> <button (click)="subscribe()" class="btn btn-success">Subscribe</button> </div> </div>
و چون این متد پس از ngOnInit قرار است توسط کاربر فراخوانی شود، مقدار message این کامپوننت هنوز خالی است.
اکنون اگر بر روی دکمهی Subscribe آن کلیک کنیم، بلافاصله در لحظهی اشتراک، اولین/آخرین مقدار موجود در BehaviorSubject را دریافت خواهیم کرد:
کامپوننت Final نیز تمام کامپوننتها را در صفحه نمایش میدهد:
<div class="row"> <div class="col-md-4"> <app-first></app-first> </div> <div class="col-md-4"> <app-second></app-second> </div> <div class="col-md-4"> <app-third></app-third> </div> </div>
و اگر در textbox کامپوننت اول، مقدار Test را وارد کنیم و سپس بر روی دکمهی Change آن کلیک نمائیم، این مقدار به تمام کامپوننتهای مشترک به BehaviorSubject سرویس برنامه، منتشر خواهد شد:
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
در سری «مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger» با نحوهی تولید OpenAPI Specification، بر اساس کنترلرها و اکشن متدهای Web API خود آشنا شدیم و سپس با استفاده از ابزار Swagger-UI، یک رابط کاربری پویا را نیز برای آن تولید و سفارشی سازی کردیم. کاربرد OpenAPI Specification صرفا به مستندسازی یک Web API خلاصه نمیشود. بر اساس این استاندارد، ابزارهای متعددی جهت تولید کدهای سمت سرور و سمت کلاینت نیز طراحی شدهاند که در اینجا نمونهای از آنها را بررسی خواهیم کرد.
تولید خودکار کدها بر اساس OpenAPI Specification
فرض کنید در حال توسعهی برنامهی سمت کلاینت Angular و یا سمت سرور ASP.NET Core ای هستید که هر دوی اینها از یک Web API استفاده میکنند. همچنین فرض کنید که این Web API را نیز خودتان توسعه میدهید. بنابراین حداقل کدی که باید در اینجا به اشتراک گذاشته شود، کدهای کلاسهای DTO یا Data transfer objects هستند تا این کلاینتها بتوانند اطلاعات Web API را به نحو صحیحی Deserialize کنند و یا برعکس، بتوانند اطلاعات را با فرمت صحیحی به سمت Web API ارسال کنند.
برای مدیریت این مساله میتوان از دو روش استفاده کرد:
الف) استفاده از یک پروژهی اشتراکی
اگر کدهای مدنظر، سمت سرور باشند، میتوان یک پروژهی اشتراکی را برای این منظور ایجاد کرد و کدهای DTO را درون آن قرار داد و سپس ارجاعی به آن را در پروژههای مختلف، استفاده نمود. به این ترتیب تکرار کدها، کاهش یافته و همچنین تغییرات آن نیز به تمام پروژههای استفاده کننده به نحو یکسانی اعمال میشوند. در این حالت یک اسمبلی اشتراکی تولید شده و به صورت مستقلی توزیع میشود.
ب) استفاده از روش لینک کردن فایلها
در این روش پروژههای استفاده کننده از کلاسهای DTO، فایلهای آنرا به پروژهی خود لینک میکنند. در این حالت باز هم شاهد کاهش تکرار کدها و همچنین اعمال یک دست تغییرات خواهیم بود. اما در این روش دیگر یک اسمبلی اشتراکی وجود نداشته و کلاسهای DTO هم اکنون با اسمبلی پروژههای استفاده کننده، یکی و کامپایل شدهاند.
بدیهی است در هر دو روش، نیاز است بر روی کلاینت و API، کنترل کاملی وجود داشته باشد و بتوان به کدهای آنها دسترسی داشت. به علاوه فایلهای اشتراکی نیز باید بر اساس Target platform یکسانی تولید شده باشند. در این حالت دیگر نیازی به OpenAPI Specification برای تولید کدهای کلاینت دات نتی خود، نیست.
اما اگر کدهای API مدنظر در دسترس نباشند و یا بر اساس پلتفرم دیگری مانند node.js تولید شده باشد، کار یکپارچه سازی با آن دیگر با به اشتراک گذاری فایلهای آن میسر نیست. در این حالت اگر این API به همراه یک OpenAPI Specification باشد، میتوان از آن برای تولید خودکار کدهای کلاینتهای آن استفاده کرد.
معرفی تعدادی از ابزارهایی که قادرند بر اساس OpenAPI Specification، کد تولید کنند
برای تولید کد از روی OpenAPI Specification، گزینههای متعددی در دسترس هستند:
الف) Swagger CodeGen
این ابزار را که جزئی از مجموعه ابزارهای تولید شدهی برفراز OpenAPI است، میتوانید از آدرس swagger-codegen دریافت کنید. البته برای اجرای آن نیاز به Java Runtime است و یا نگارش آنلاین آن نیز در دسترس است: swagger.io
در ابزار آنلاین آن، در منوی generate بالای صفحه، گزینهی تولید کد برای #C نیز موجود است.
ب) AutoRest
محل دریافت: https://github.com/Azure/autorest
بر اساس node.js کار میکند و از طریق خط فرمان، قابل دسترسی است. همچنین این مورد ابزار تامین کنندهی گزینهی Add REST client در ویژوال استودیو نیز میباشد. اما در کل، امکان تنظیمات آنچنانی را به همراه ندارد.
ج) NSwagStudio
محل دریافت: https://github.com/RSuter/NSwag/wiki/NSwagStudio
همانطور که در مطلب «مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت اول - معرفی» نیز عنوان شد، NSwag یکی دیگر از تولید کنندههای OpenAPI Specification مخصوص پروژههای دات نت است. NSwagStudio نیز جزئی از این مجموعه است که به کمک آن میتوان کدهای کلاینتها و DTOها را بر اساس OpenAPI Spec تولید کرد. همچنین امکان تنظیمات قابل توجهی را در مورد نحوهی تولید کدهای نهایی به همراه دارد.
استفاده از NSwagStudio برای تولید کدهای DTOها
در اینجا از همان برنامهای که در سری «مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger» بررسی کردیم، استفاده خواهیم کرد. بنابراین این برنامه، از پیش تنظیم شدهاست و هم اکنون به همراه یک تولید کنندهی OpenAPI Specification نیز میباشد. آنرا اجرا کنید تا بتوان به OpenAPI Specification تولیدی آن در آدرس زیر دسترسی یافت:
سپس فایل msi مخصوص NSwagStudio را نیز از لینک آن در Github دریافت، نصب و اجرا کنید.
مطابق تصویر، ابتدا آدرس Swagger Specification URL یا همان آدرس فوق را وارد کنید. سپس فضای نام دلخواهی را وارد کرده و گزینهی تولید کلاسهای کلاینت را فعلا انتخاب نکنید. در لیست تنظیمات آن، گزینهی Class Style نیز مهم است. برای مثال برای پروژههای ASP.NET Core حالت POCO را انتخاب کنید (plain old clr objects) و برای پروژههای مبتنی بر XAML، گزینهی Inpc مناسبتر است چون RaisePropertyChangedها را هم تولید میکند. در آخر بر روی دکمهی Generate Outputs کلیک کنید تا خروجی ذیل حاصل شود:
یا میتوان این خروجی را copy/paste کرد و یا میتوان در برگهی Settings، در انتهای لیست آن، مقدار output file path را مشخص کرد و سپس بر روی دکمهی Generate files کلیک نمود تا فایل معادل آن تولید شود.
استفاده از NSwagStudio برای تولید کدهای کلاینت Angular استفاده کنندهی از API
NSwagStudio امکان تولید یک TypeScript Client را نیز دارد:
نکتهی جالب این خروجی، دقت داشتن به status codes درج شدهی در OpenAPI Spec است که در قسمتهای چهارم و پنجم سری «مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger» آنها را بررسی کردیم.
در اینجا نه تنها سرویسی جهت تعامل با API ما تولید شدهاست، بلکه معادل تایپاسکریپتی DTOهای برنامه را نیز تولید کردهاست:
تولید خودکار کدها بر اساس OpenAPI Specification
فرض کنید در حال توسعهی برنامهی سمت کلاینت Angular و یا سمت سرور ASP.NET Core ای هستید که هر دوی اینها از یک Web API استفاده میکنند. همچنین فرض کنید که این Web API را نیز خودتان توسعه میدهید. بنابراین حداقل کدی که باید در اینجا به اشتراک گذاشته شود، کدهای کلاسهای DTO یا Data transfer objects هستند تا این کلاینتها بتوانند اطلاعات Web API را به نحو صحیحی Deserialize کنند و یا برعکس، بتوانند اطلاعات را با فرمت صحیحی به سمت Web API ارسال کنند.
برای مدیریت این مساله میتوان از دو روش استفاده کرد:
الف) استفاده از یک پروژهی اشتراکی
اگر کدهای مدنظر، سمت سرور باشند، میتوان یک پروژهی اشتراکی را برای این منظور ایجاد کرد و کدهای DTO را درون آن قرار داد و سپس ارجاعی به آن را در پروژههای مختلف، استفاده نمود. به این ترتیب تکرار کدها، کاهش یافته و همچنین تغییرات آن نیز به تمام پروژههای استفاده کننده به نحو یکسانی اعمال میشوند. در این حالت یک اسمبلی اشتراکی تولید شده و به صورت مستقلی توزیع میشود.
ب) استفاده از روش لینک کردن فایلها
در این روش پروژههای استفاده کننده از کلاسهای DTO، فایلهای آنرا به پروژهی خود لینک میکنند. در این حالت باز هم شاهد کاهش تکرار کدها و همچنین اعمال یک دست تغییرات خواهیم بود. اما در این روش دیگر یک اسمبلی اشتراکی وجود نداشته و کلاسهای DTO هم اکنون با اسمبلی پروژههای استفاده کننده، یکی و کامپایل شدهاند.
بدیهی است در هر دو روش، نیاز است بر روی کلاینت و API، کنترل کاملی وجود داشته باشد و بتوان به کدهای آنها دسترسی داشت. به علاوه فایلهای اشتراکی نیز باید بر اساس Target platform یکسانی تولید شده باشند. در این حالت دیگر نیازی به OpenAPI Specification برای تولید کدهای کلاینت دات نتی خود، نیست.
اما اگر کدهای API مدنظر در دسترس نباشند و یا بر اساس پلتفرم دیگری مانند node.js تولید شده باشد، کار یکپارچه سازی با آن دیگر با به اشتراک گذاری فایلهای آن میسر نیست. در این حالت اگر این API به همراه یک OpenAPI Specification باشد، میتوان از آن برای تولید خودکار کدهای کلاینتهای آن استفاده کرد.
معرفی تعدادی از ابزارهایی که قادرند بر اساس OpenAPI Specification، کد تولید کنند
برای تولید کد از روی OpenAPI Specification، گزینههای متعددی در دسترس هستند:
الف) Swagger CodeGen
این ابزار را که جزئی از مجموعه ابزارهای تولید شدهی برفراز OpenAPI است، میتوانید از آدرس swagger-codegen دریافت کنید. البته برای اجرای آن نیاز به Java Runtime است و یا نگارش آنلاین آن نیز در دسترس است: swagger.io
در ابزار آنلاین آن، در منوی generate بالای صفحه، گزینهی تولید کد برای #C نیز موجود است.
ب) AutoRest
محل دریافت: https://github.com/Azure/autorest
بر اساس node.js کار میکند و از طریق خط فرمان، قابل دسترسی است. همچنین این مورد ابزار تامین کنندهی گزینهی Add REST client در ویژوال استودیو نیز میباشد. اما در کل، امکان تنظیمات آنچنانی را به همراه ندارد.
ج) NSwagStudio
محل دریافت: https://github.com/RSuter/NSwag/wiki/NSwagStudio
همانطور که در مطلب «مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت اول - معرفی» نیز عنوان شد، NSwag یکی دیگر از تولید کنندههای OpenAPI Specification مخصوص پروژههای دات نت است. NSwagStudio نیز جزئی از این مجموعه است که به کمک آن میتوان کدهای کلاینتها و DTOها را بر اساس OpenAPI Spec تولید کرد. همچنین امکان تنظیمات قابل توجهی را در مورد نحوهی تولید کدهای نهایی به همراه دارد.
استفاده از NSwagStudio برای تولید کدهای DTOها
در اینجا از همان برنامهای که در سری «مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger» بررسی کردیم، استفاده خواهیم کرد. بنابراین این برنامه، از پیش تنظیم شدهاست و هم اکنون به همراه یک تولید کنندهی OpenAPI Specification نیز میباشد. آنرا اجرا کنید تا بتوان به OpenAPI Specification تولیدی آن در آدرس زیر دسترسی یافت:
https://localhost:5001/swagger/LibraryOpenAPISpecification/swagger.json
مطابق تصویر، ابتدا آدرس Swagger Specification URL یا همان آدرس فوق را وارد کنید. سپس فضای نام دلخواهی را وارد کرده و گزینهی تولید کلاسهای کلاینت را فعلا انتخاب نکنید. در لیست تنظیمات آن، گزینهی Class Style نیز مهم است. برای مثال برای پروژههای ASP.NET Core حالت POCO را انتخاب کنید (plain old clr objects) و برای پروژههای مبتنی بر XAML، گزینهی Inpc مناسبتر است چون RaisePropertyChangedها را هم تولید میکند. در آخر بر روی دکمهی Generate Outputs کلیک کنید تا خروجی ذیل حاصل شود:
یا میتوان این خروجی را copy/paste کرد و یا میتوان در برگهی Settings، در انتهای لیست آن، مقدار output file path را مشخص کرد و سپس بر روی دکمهی Generate files کلیک نمود تا فایل معادل آن تولید شود.
استفاده از NSwagStudio برای تولید کدهای کلاینت Angular استفاده کنندهی از API
NSwagStudio امکان تولید یک TypeScript Client را نیز دارد:
در اینجا ابتدا TypeScript Client را انتخاب میکنیم و سپس در تنظیمات آن، قالب Angular را انتخاب کرده و نگارش RxJS آنرا نیز، 6 انتخاب میکنیم. در آخر بر روی Generate outputs کلیک میکنیم:
نکتهی جالب این خروجی، دقت داشتن به status codes درج شدهی در OpenAPI Spec است که در قسمتهای چهارم و پنجم سری «مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger» آنها را بررسی کردیم.
در اینجا نه تنها سرویسی جهت تعامل با API ما تولید شدهاست، بلکه معادل تایپاسکریپتی DTOهای برنامه را نیز تولید کردهاست:
با سلام؛ بنده در یک پروژه Angular 5 از Routing با استفاده از outlet name استفاده کردم. با استفاده از این امکان میتونم چندین فرم رو بصورت همزمان (در Tab) نشون بدم. برای مثال کد بنده در سمت template همچین شکلی پیدا کرده است:
این کد داخل app.component.html قرار دارد و به درستی کار میکند.
<router-outlet name="role"></router-outlet> <router-outlet name="product"></router-outlet> <router-outlet name="book"></router-outlet>
<a [routerLink]="[{ outlets: { role: ['role'] } }]">Role</a> <a [routerLink]="[{ outlets: { product: ['product'] } }]">Product</a> <a [routerLink]="[{ outlets: { book: ['book'] } }]">Book</a>
مشکلی که دارم اینه که میخوام وقتی سیستم میاد بالا بره تو فرم login به صورت تمام صفحه (که خود login یک component هست) بعدش اگه احراز هویت شد برود تو فرم اصلی که اتفاقا Tab هم آنجاست. نمیدونم خوب توضیح دادم یا نه، ولی میخوام router-outlet اصلی که بی نام هم هست یه لحظه دست login باشد بعدش تحویل بدهد به فرم اصلی سیستم که این کد <router-outlet name="book"></router-outlet> داخلش هست.
الان اتفاقی که میفته اینه که اگر من این کد را <router-outlet name=" product "></router-outlet> داخل router-outlet اصلی داشته باشم routingهای مانند زیر کار نمیکنند.
<a [routerLink]="[{ outlets: { product: ['product'] } }]">Product</a>
همچنین لازم به توضیح است که نمیتونم برای login هم outlet by name استفاده کنم. چون فکر میکنم اینکارو کنم لاگین من تمام صفحه نمیشه. با تشکر