// Named function function add(x, y) { return x + y; } // Anonymous function let myAdd = function(x, y) { return x+y; };
let z = 100; function addToZ(x, y) { return x + y + z; }
function add(x: number, y: number): number { return x + y; } let myAdd = function(x: number, y: number): number { return x+y; };
function PublicationMessage(year: number): string { return 'Date published: ' + year; }
let publishFunc: (someYear: number) => string;
publishFunc = PublicationMessage; let message: string = publishFunc(2016);
همچنین میتوان function type را به صورت inline نیز تعریف کرد:
let myAdd: (baseValue:number, increment:number) => number = function(x, y) { return x + y; };
Optional and Default Parameters
در جاوا اسکریپت تمامی پارامترهای یک تابع اختیاری هستند. اما TypeScript کمی متفاوت است. یعنی در حالت پیشفرض، ذکر تمامی پارامترها ضروری است؛ مگر اینکه پارامترهای موردنیاز را به صورت اختیاری تعیین کنید. به طور مثال در تابع زیر دو پارامتر را تعریف کردهایم:
function CreateCustomer(name: string, age?: number) {}
همانطور که مشاهده میکنید با افزودن علامت سوال بعد از نام پارامتر، توانستهایم آن را به صورت اختیاری تعریف کنیم. نکتهایی که در اینجا وجود دارد این است که تمامی پارامترهای optional، حتماً باید بعد از پارامترهای required تعریف شوند.
برای تعیین مقدار پیشفرض برای هر پارامتر نیز میتوانیم به اینصورت عمل کنیم:
function GetBookByTitle(title: string = 'C# 6.0 in a Nutshell') {}
default parameters در صورتیکه بعد از required parameters آورده شوند، به عنوان optional در نظر گرفته میشوند. یعنی در اینحالت لزومی به گذاشتن علامت سوال، بعد از نام پارامتر نیست. نکتهی قابل توجهایی که در استفاده از default parameters وجود دارد این است که علاوه بر رشتهها میتوان عبارات (expressions) را نیز به آنها اختصاص داد:
function GetBookByTitle(title: string = GetMostPopularBooks()) {}
Rest Parameters
rest parameters به شما این امکان را میدهند تا به تعداد نامحدودی پارامتر به یک تابع ارسال کنید:
function GetBooksReadForCust(name: string, ...bookIDs: number[]) {}
تابع فوق دو پارامتر را از ورودی دریافت میکند. پارامتر دوم این تابع به صورت rest تعریف شده است. یعنی برای پارامتر دوم میتوانیم هر تعداد پارامتری را به این تابع ارسال کنیم. همچنین برای نوع این پارامتر، یک آرایه از نوع number را تعیین کردهایم. یعنی پارامترهای دریافتی، درون یک آرایه از نوع number ذخیره خواهند شد. در ES 5 برای داشتن این چنین قابلیتی از شیء arguments استفاده میکردیم. یعنی تابع فوق را میبایستی اینگونه مینوشتیم:
function GetBooksReadForCust(name) { var bookIDs = []; for (var _i = 1; _i < arguments.length; _i++) { bookIDs[_i - 1] = arguments[_i]; } }
استفاده از this
درک this در جاوا اسکریپت، در ابتدا باعث مقداری سردرگمی میشود. یعنی مقدار آن در زمان فراخوانی تابع، ست خواهد شد. یعنی در هر بلاک از کد، وضعیتهای متفاوتی را ارائه میدهد. به عنوان مثال درون callback مربوط به تابع setInterval در تابع زیر میخواهیم به مقدار متغیر publishDate دسترسی داشته باشم:
function Book() { let self = this; self.publishDate = 2016; setInterval(function() { console.log(self.publishDate); }, 1000); }
همانطور که مشاهده میکنید برای دسترسی به این پراپرتی، مقدار this را درون یک متغیر با نام self، در ابتدا تعریف کردهایم. زیرا استفادهی مستقیم از this.publishDate درون callback به چیز دیگری اشاره میکند. این روش در ES 5 خیلی رایج است. اما با استفاده از Arrow Functions به راحتی میتوانیم به this در هر جایی دسترسی داشته باشیم. بنابراین کد فوق را میتوانیم به این صورت بازنویسی کنیم:
function Book() { this.publishDate = 2016; setInterval(() => { console.log(this.publishDate); }, 1000); }
در واقع Arrow Function در پشت صحنه کار capture کردن this را برایمان انجام خواهد داد.
Function overloads
قابلیت function overloading در بیشتر typed languageها در دسترس میباشد. همانطور که میدانید این قابلیت جهت تعریف امضاءهای مختلف برای یک تابع استفاده میشود. یعنی ایجاد توابعی با یک نام، اما با انواع متفاوت. از آنجائیکه TypeScript به جاوا اسکریپت کامپایل میشود، در نتیجه جاوا اسکریپت فاقد نوع (type) است. پس در زمان کامپایل نوعها برداشته خواهند شد. بنابراین داشتن توابعی همنام باعث بروز مشکلاتی خواهد شد. برای داشتن نسخههای مختلفی از یک تابع میتوانیم تعاریف موردنیازمان را ارائه داده، اما تنها یک پیادهسازی داشته باشیم. برای مثال میخواهیم یک overload دیگر برای تابع زیر داشته باشیم:
function GetTitles(author: string) : string[];
تابع فوق یک رشته را از ورودی دریافت کرده و در نهایت یک آرایه از رشتهها را بر میگرداند. برای overload دیگر این تابع میخواهیم به جای دریافت رشته، یک boolean از ورودی دریافت کنیم:
function GetTitles(available: boolean) : string[];
همانطور که مشاهده میکنید، هیچکدام از overloadهای فوق پیادهسازیایی ندارند. در واقع تا اینجا به TypeScript گفتهایم که نیاز به دو نسخه از تابع GetTitles خواهیم داشت. اکنون میتوانیم یک پیادهسازی کلی برای دو overload فوق داشته باشیم:
function GetTitles(bookProperty: any) : string[] { if(typeof bookProperty == 'string') { // some code } else if (typeof bookProperty == 'boolean') { // some code } return result; }
همانطور که عنوان شد، تنها پیادهسازی فوق را برای تمامی overloadها خواهیم داشت. در نتیجه اینبار نوع پارامتر ورودی را any تعریف کردهایم. سپس درون بدنهی تابع، نوع پراپرتی را توسط typeof تشخیص دادهایم. بنابراین برای فراخوانی هر یک از overloadها، میتوانیم کدهای خاصی را اجرا کنیم.
نصب پیشنیازهای کار با Gulp و TypeScript
فایل package.json در قسمت اول این سری معرفی شد. دراینجا قسمت devDependencies آنرا به نحو ذیل تکمیل کنید:
"devDependencies": { "typescript": "^1.8.10", "gulp": "^3.9.1", "path": "^0.12.7", "gulp-clean": "^0.3.2", "fs": "^0.0.2", "gulp-concat": "^2.6.0", "gulp-typescript": "^2.13.1", "gulp-tsc": "^1.1.5", "del": "^2.2.0", "gulp-autoprefixer": "^3.1.0", "gulp-cssnano": "^2.0.0", "gulp-html-replace": "^1.5.4", "gulp-htmlmin": "^1.0.5", "gulp-uglify": "^1.5.3", "merge-stream": "^1.0.0", "systemjs-builder": "^0.15.16", "typings": "^0.8.1" },
نکتهی مهم آن systemjs-builder است. این کتابخانه کار کامپایل systemjs.config.js را به یک تک اسکریپت انجام میدهد. به این ترتیب مشکل صدها بار رفت و برگشت به سرور، برای دریافت وابستگیهای AngularJS 2.0، به طور کامل برطرف میشود.
افزودن فایل gulpfile.js به پروژه
یا یک فایل جدید جاوا اسکریپتی را به نام gulpfile.js به ریشهی پروژه اضافه کنید و یا از منوی project -> add new item نیز میتوانید گزینهی gulp configuration file را در VS 2015 انتخاب نمائید. محتوای این فایل را به نحو ذیل تغییر دهید:
var gulp = require("gulp"), concat = require("gulp-concat"), tsc = require("gulp-typescript"), jsMinify = require("gulp-uglify"), cssPrefixer = require("gulp-autoprefixer"), cssMinify = require("gulp-cssnano"), del = require("del"), merge = require("merge-stream"), minifyHTML = require('gulp-htmlmin'), SystemBuilder = require("systemjs-builder"); var appFolder = "./app"; var outFolder = "wwwroot"; gulp.task("clean", () => { return del(outFolder); }); gulp.task("shims", () => { return gulp.src([ "node_modules/es6-shim/es6-shim.js", "node_modules/zone.js/dist/zone.js", "node_modules/reflect-metadata/Reflect.js" ]) .pipe(concat("shims.js")) .pipe(jsMinify()) .pipe(gulp.dest(outFolder + "/js/")); }); gulp.task("tsc", () => { var tsProject = tsc.createProject("./tsconfig.json"); var tsResult = gulp.src([ appFolder + "/**/*.ts" ]) .pipe(tsc(tsProject), undefined, tsc.reporter.fullReporter()); return tsResult.js.pipe(gulp.dest("build/")); }); gulp.task("system-build", ["tsc"], () => { var builder = new SystemBuilder(); return builder.loadConfig("systemjs.config.js") .then(() => builder.buildStatic(appFolder, outFolder + "/js/bundle.js")) .then(() => del("build")); }); gulp.task("buildAndMinify", ["system-build"], () => { var bundle = gulp.src(outFolder + "/js/bundle.js") .pipe(jsMinify()) .pipe(gulp.dest(outFolder + "/js/")); var css = gulp.src(outFolder + "/css/styles.css") .pipe(cssMinify()) .pipe(gulp.dest(outFolder + "/css/")); return merge(bundle, css); }); gulp.task("favicon", function () { return gulp.src("./app/favicon.ico") .pipe(gulp.dest(outFolder)); }); gulp.task("css", function () { return gulp.src(appFolder + "/**/*.css") .pipe(cssPrefixer()) .pipe(cssMinify()) .pipe(gulp.dest(outFolder)); }); gulp.task("templates", function () { return gulp.src(appFolder + "/**/*.html") .pipe(minifyHTML()) .pipe(gulp.dest(outFolder)); }); gulp.task("assets", ["templates", "css", "favicon"], function () { return gulp.src(appFolder + "/**/*.png") .pipe(gulp.dest(outFolder)); }); gulp.task("otherScriptsAndStyles", () => { gulp.src([ "jquery/dist/jquery.*js", "bootstrap/dist/js/bootstrap*.js" ], { cwd: "node_modules/**" }) .pipe(gulp.dest(outFolder + "/js/")); gulp.src([ "node_modules/bootstrap/dist/css/bootstrap.css" ]).pipe(cssMinify()).pipe(gulp.dest(outFolder + "/css/")); gulp.src([ "node_modules/bootstrap/fonts/*.*" ]).pipe(gulp.dest(outFolder + "/fonts/")); }); //gulp.task("watch.tsc", ["tsc"], function () { // return gulp.watch(appFolder + "/**/*.ts", ["tsc"]); //}); //gulp.task("watch", ["watch.tsc"]); gulp.task("default", [ "shims", "buildAndMinify", "assets", "otherScriptsAndStyles" //,"watch" ]);
در این فایل فرض شدهاست که خروجی نهایی برنامه قرار است در پوشهای به نام wwwroot کپی شود و پوشهی اصلی برنامه، همان پوشهای به نام app، در ریشهی پروژه است.
var appFolder = "./app"; var outFolder = "wwwroot";
1) وظیفهی clean، کار تمیز کردن پوشهی نهایی خروجی برنامه را انجام میدهد (حذف تمام فایلهای آن).
2) وظیفهی shims، کار بسته بندی، یکی کردن و فشرده کردن سه اسکریپت es6-shim.js، zone.js و Reflect.js را انجام میدهد. سپس تک فایل حاصل را به نام shims.js، در پوشهی wwwroot/js کپی میکند.
3) وظیفهی tsc، یکبار دیگر کامپایلر TypeScript را اجرا میکند تا مطمئن شویم با آخرین نگارش فایلهای js برنامه کار میکنیم.
4) وظیفهی system-build، کار پردازش خودکار مداخل فایل systemjs.config.js را انجام میدهد. در آخرین نگارش ارائه شدهی AngularJS 2.0، بجای ذکر مداخل مورد نیاز آن، این تک فایل systemjs.config.js را به صفحه پیوست میکنیم تا اسکریپتهای لازم را (چند صد عدد)، به صورت خودکار بارگذاری کند. برای یکی کردن این چند صد عدد اسکریپت، از کتابخانهی SystemBuilder آن کمک گرفته و کار کامپایل نهایی را انجام میدهیم. خروجی تمام این فایلها، به همراه کلیه فایلهای js حاصل از کامپایل فایلهای TypeScript برنامه، در فایلی به نام bundle.js کپی شدهی در پوشهی wwwroot/js نوشته میشود. بنابراین دیگر نیازی نیست تا فایلهای js پوشهی app و همچنین فایلهای js وابستگیهای AngularJS 2.0 را توزیع کنیم. تک فایل bundle.js، حاوی تمام اینها است.
5) وظیفهی buildAndMinify کار اجرای وظیفهی system-bulder را به همراه فشرده سازی تک فایل bundle.js، به عهده دارد. به علاوه اگر در پوشهی css آن نیز فایل styles.css موجود باشد، آن را فشرده میکند.
6) در ادامه یک سری وظیفهی کپی کردن منابع برنامه را مشاهده میکنید. مانند favicon که کار کپی کردن این آیکن را به پوشهی wwwroot انجام میدهد. وظیفهی css، فایلهای css موجود در پوشههای برنامه را به wwwroot و زیر پوشههای آن کپی میکند. وظیفهی templates، کار کپی کردن فایلهای html قالبهای کامپوننتها را بر عهده دارد. وظیفهی assets، کار کپی کردن فایلهای png را انجام میدهد.
7) وظیفهی otherScriptsAndStyles یک سری css و js ثالث را به پوشهی wwwroot کپی میکند؛ مانند فایلهای بوت استرپ و جیکوئری.
8) وظیفهی default، کار اجرای تمام این وظایف را با هم به عهده دارد.
اکنون اگر بر روی gulpfile.js کلیک راست کنید، گزینهی task runner explorer ظاهر خواهد شد. آنرا انتخاب کنید:
بر روی وظیفهی default کلیک راست کرده و آنرا اجرا کنید. پس از مدتی پوشهی جدید wwwroot ساخته شده و فایلهای نهایی برنامه به آن کپی میشوند.
اصلاح فایل index.html و یا Views\Shared\_Layout.cshtml
اکنون که تمام فایلهای مورد نیاز پروژه در پوشهی wwwroot کپی شدهاند، نیاز است فایل index.html را به نحو ذیل تغییر داد:
<!DOCTYPE html> <html> <head> <base href="/"> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@ViewBag.Title - My ASP.NET Application</title> <link href="~/wwwroot/css/bootstrap.css" rel="stylesheet" /> <link href="~/wwwroot/app.component.css" rel="stylesheet" /> <link href="~/Content/Site.css" rel="stylesheet" type="text/css" /> <script src="~/wwwroot/js/shims.js"></script> </head> <body> <div> @RenderBody() <pm-app>Loading App...</pm-app> </div> <script src="~/wwwroot/js/jquery/dist/jquery.min.js"></script> <script src="~/wwwroot/js/bootstrap/dist/js/bootstrap.min.js"></script> <script src="~/wwwroot/js/bundle.js"></script> @RenderSection("Scripts", required: false) </body> </html>
اسکریپتهای shims که برای مرورگرهای قدیمیتر درنظر گرفته شدهاند، به تک فایل wwwroot/js/shims.js منتقل شدهاند.
تمام اسکریپتهای AngularJS 2.0 و وابستگیهای آن به همراه تمام اسکریپتهای برنامهی خودمان، به تک فایل wwwroot/js/bundle.js منتقل شدهاند.
اکنون اگر برنامه را اجرا کنید، سرعت آن با قبل قابل مقایسه نیست! اینبار دیگر نه نیازی به بارگذاری تمام وابستگیهای AngularJS 2.0 به صورت مجزا توسط systemjs.config.js وجود دارد و نه به ازای مشاهدهی هر صفحهای، یکبار قرار است فایل js کامپوننت آن بارگذاری شود. تمام اینها داخل فایل wwwroot/js/bundle.js قرار گرفتهاند و تنها یکبار بارگذاری میشوند.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MVC5Angular2.part12.zip
خلاصهی بحث
با نوشتن یک Gulp Task جدید میتوان بر اساس فایل systemjs.config.js، تمام اسکریپتهای دخیل در اجرای برنامه را به صورت خودکار یافته و به صورت یک تک فایل نهایی، بسته بندی و توزیع کرد.
در ASP.NET Core 2.0، دو روش جدید برای ثبت Loggerهای مختلف ارائه شدهاست:
روش اول: معرفی Loggers در ConfigureServices فایل آغازین برنامه
public void ConfigureServices(IServiceCollection services) { services.AddLogging(builder => builder.AddConsole().AddDebug()); services.AddMvc(); }
روش دوم: معرفی Loggers در فایل Program.cs
public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); }) .Build();
{ "Logging": { "IncludeScopes": false, "Console": { "LogLevel": { "Default": "Warning" } } } }
بنابراین OpenID Connect پروتکلی است که در عمل استفاده میشود و توسعه دهنده و جایگزین کنندهی پروتکل OAuth 2.0 میباشد. هرچند ممکن است در بسیاری از منابع صرفا به OAuth 2.0 بپردازند، اما در پشت صحنه با همان OpenID Connect کار میکنند.یعنی کار Authorization رو پروتکل OpenID Connect انجام میده؟ پس OAuth 2.0 چه میشه؟
ERROR in ./node_modules/angular2-permission/index.ts Module build failed: Error: C:\Front\node_modules\angular2-permission\index.ts is missing from the TypeScript compilation. Please make sure it is in your tsconfig via the 'files' or 'include' property. The missing file seems to be part of a third party library. TS files in published libraries are often a sign of a badly packaged library. Please open an issue in the library repository to alert its author and ask them to package the library using the Angular Package Format (https://goo.gl/jB3GVv). at AngularCompilerPlugin.getCompiledFile (C:\Front\node_modules\@ngtools\webpack\src\angular_compiler_plugin.js:674:23) at plugin.done.then (C:\Front\node_modules\@ngtools\webpack\src\loader.js:467:39) at process._tickCallback (internal/process/next_tick.js:68:7)
احراز هویت و اعتبارسنجی کاربران در برنامههای Angular - قسمت چهارم - به روز رسانی خودکار توکنها
ایجاد یک تایمر برای مدیریت دریافت و به روز رسانی توکن دسترسی
در مطلب «ایجاد تایمرها در برنامههای Angular» با روش کار با تایمرهای reactive آشنا شدیم. در اینجا قصد داریم از این امکانات جهت پیاده سازی به روز کنندهی خودکار access_token استفاده کنیم. در مطلب «احراز هویت و اعتبارسنجی کاربران در برنامههای Angular - قسمت دوم - سرویس اعتبارسنجی»، زمان انقضای توکن را به کمک کتابخانهی jwt-decode، از آن استخراج کردیم:
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; }
private refreshTokenSubscription: Subscription; scheduleRefreshToken() { if (!this.isLoggedIn()) { return; } this.unscheduleRefreshToken(); const expiresAtUtc = this.getAccessTokenExpirationDateUtc().valueOf(); const nowUtc = new Date().valueOf(); const initialDelay = Math.max(1, expiresAtUtc - nowUtc); console.log("Initial scheduleRefreshToken Delay(ms)", initialDelay); const timerSource$ = Observable.timer(initialDelay); this.refreshTokenSubscription = timerSource$.subscribe(() => { this.refreshToken(); }); } unscheduleRefreshToken() { if (this.refreshTokenSubscription) { this.refreshTokenSubscription.unsubscribe(); } }
- در ابتدا بررسی میکنیم که آیا کاربر لاگین کردهاست یا خیر؟ آیا اصلا دارای توکنی هست یا خیر؟ اگر خیر، نیازی به شروع این تایمر نیست.
- سپس تایمر قبلی را در صورت وجود، خاتمه میدهیم.
- در مرحلهی بعد، کار محاسبهی میزان زمان تاخیر شروع تایمر Observable.timer را انجام میدهیم. پیشتر زمان انقضای توکن موجود را استخراج کردهایم. اکنون این زمان را از زمان جاری سیستم برحسب UTC کسر میکنیم. مقدار حاصل، initialDelay این تایمر خواهد بود. یعنی این تایمر به مدت initialDelay صبر خواهد کرد و سپس تنها یکبار اجرا میشود. پس از اجرا، ابتدا متد refreshToken ذیل را فراخوانی میکند تا توکن جدیدی را دریافت کند.
در متد unscheduleRefreshToken کار لغو تایمر جاری در صورت وجود انجام میشود.
متد درخواست یک توکن جدید بر اساس refreshToken موجود نیز به صورت ذیل است:
refreshToken() { const headers = new HttpHeaders({ "Content-Type": "application/json" }); const model = { refreshToken: this.getRawAuthToken(AuthTokenType.RefreshToken) }; return this.http .post(`${this.appConfig.apiEndpoint}/${this.appConfig.refreshTokenPath}`, model, { headers: headers }) .finally(() => { this.scheduleRefreshToken(); }) .map(response => response || {}) .catch((error: HttpErrorResponse) => Observable.throw(error)) .subscribe(result => { console.log("RefreshToken Result", result); this.setLoginSession(result); }); }
در آخر چون این تایمر، خود متوقف شوندهاست (متد Observable.timer بدون پارامتر دوم آن فراخوانی شدهاست)، یکبار دیگر کار زمانبندی دریافت توکن جدید بعدی را در متد finally انجام میدهیم (متد scheduleRefreshToken را مجددا فراخوانی میکنیم).
تغییرات مورد نیاز در سرویس Auth جهت زمانبندی دریافت توکن دسترسی جدید
تا اینجا متدهای مورد نیاز شروع زمانبندی دریافت توکن جدید، خاتمهی زمانبندی و دریافت و به روز رسانی توکن جدید را پیاده سازی کردیم. محل قرارگیری و فراخوانی این متدها در سرویس Auth، به صورت ذیل هستند:
الف) در سازندهی کلاس:
constructor( @Inject(APP_CONFIG) private appConfig: IAppConfig, private browserStorageService: BrowserStorageService, private http: HttpClient, private router: Router ) { this.updateStatusOnPageRefresh(); this.scheduleRefreshToken(); }
ب) پس از لاگین موفقیت آمیز
در متد لاگین، پس از دریافت یک response موفقیت آمیز و تنظیم و ذخیره سازی توکنهای دریافتی، کار زمانبندی دریافت توکن دسترسی بعدی بر اساس refresh_token فعلی انجام میشود:
this.setLoginSession(response); this.scheduleRefreshToken();
ج) پس از خروج از سیستم
در متد logout، پس از حذف توکنهای کاربر از کش مرورگر، کار لغو تایمر زمانبندی دریافت توکن بعدی نیز صورت خواهد گرفت:
this.deleteAuthTokens(); this.unscheduleRefreshToken();
در این حالت اگر برنامه را اجرا کنید، یک چنین خروجی را که بیانگر دریافت خودکار توکنهای جدید است، پس از مدتی در کنسول developer مرورگر مشاهده خواهید کرد:
ابتدا متد scheduleRefreshToken اجرا شده و تاخیر آغازین تایمر محاسبه شدهاست. پس از مدتی متد refreshToken توسط تایمر فراخوانی شدهاست. در آخر مجددا متد scheduleRefreshToken جهت شروع یک زمانبندی جدید، اجرا شدهاست.
اعداد initialDelay محاسبه شدهای را هم که ملاحظه میکنید، نزدیک به همان 2 دقیقهی تنظیمات سمت سرور در فایل appsettings.json هستند:
"BearerTokens": { "Key": "This is my shared key, not so secret, secret!", "Issuer": "http://localhost/", "Audience": "Any", "AccessTokenExpirationMinutes": 2, "RefreshTokenExpirationMinutes": 60 }
کدهای کامل این سری را از اینجا میتوانید دریافت کنید.
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژهی ASPNETCore2JwtAuthentication.AngularClient وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o، برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد (و یا همان اجرای فایل ng-serve.bat). همچنین باید به پوشهی ASPNETCore2JwtAuthentication.WebApp نیز مراجعه کرده و فایل dotnet_run.bat را اجرا کنید، تا توکن سرور برنامه نیز فعال شود.
پس از نصب آن، برنامهی Angular 5 آنرا در مسیر C:\Program Files\Windows Admin Center\UX.zip خواهید یافت. همچنین مایکروسافت این برنامهها را نیز بر مبنای Angular 5 تهیه کردهاست:
- Microsofts streaming website (mixer.com)
-
Office portal
- Microsoft Store for Education
- Azure functions UI