- استفاده از Postman برای آزمایش یک برنامهی Web API
- استفاده از strest برای آزمایش یک برنامهی Web API
روش سومی هم برای انجام اینکار وجود دارد که به صورت توکار از زمان ارائهی ASP.NET Core 2.1 به همراه TestServer آزمایشی آن میسر شد. این روش در نگارش 3.1، با تغییر روش تعریف فایل program.cs، جهت سازگاری آن با آزمونهای یکپارچگی/آزمایش کل سیستم، بهبود یافتهاست که خلاصهای از آن را در این مطلب بررسی میکنیم.
آزمونهای یکپارچگی در ASP.NET Core
آزمونهای یکپارچگی، برخلاف آزمونهای واحد که عموما از اشیاء تقلیدی استفاده میکنند، دقیقا بر روی همان سیستمی که قرار است به کاربر نهایی ارائه شود، اجرا میشوند. به همین جهت تنظیمات اولیهی آنها کمی بیشتر است و همچنین زمان اجرای آنها نیز به علت وابستگی به بانک اطلاعاتی واقعی، فایل سیستم، شبکه و غیره، نسبت به آزمونهای واحد بیشتر است.
برای ایجاد آزمونهای یکپارچگی در برنامههای ASP.NET Core، حداقل سه مرحله باید طی شوند:
الف) ایجاد یک class library که ارجاعی را به پروژهی اصلی دارد. این پروژه حاوی آزمایشهای ما خواهد بود.
ب) راه اندازی یک هاست وب آزمایشی برای ارسال درخواستها به آن و دریافت پاسخهای نهایی.
ج) استفاده از یک test runner (انواع و اقسام فریم ورکهای unit testing) برای اجرای آزمایشها
ایجاد یک پروژهی کتابخانه برای هاست و اجرای آزمایشهای یکپارچگی
فرض کنید میخواهیم برای همان پروژهی ایجاد JWTها، آزمایش یکپارچگی بنویسیم. پس از ایجاد یک پروژهی کتابخانهی جدید که قرار است هاست آزمایشهای ما شود، نیاز است محتوای فایل csproj آنرا به صورت زیر تغییر داد:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> <NoWarn>RCS1090</NoWarn> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\ASPNETCore2JwtAuthentication.WebApp\ASPNETCore2JwtAuthentication.WebApp.csproj" /> </ItemGroup> <ItemGroup> <None Include="..\ASPNETCore2JwtAuthentication.WebApp\appsettings.json" CopyToOutputDirectory="PreserveNewest" /> </ItemGroup> <ItemGroup> <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c329}" /> </ItemGroup> <ItemGroup> <PackageReference Include="fluentassertions" Version="5.10.3" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.8" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" /> <PackageReference Include="MSTest.TestAdapter" Version="2.1.2" /> <PackageReference Include="MSTest.TestFramework" Version="2.1.2" /> </ItemGroup> </Project>
1) TargetFramework آن باید به netcoreapp تنظیم شود.
2) باید ارجاع مستقیمی به کل پروژهی نهایی WebApp در آن وجود داشته باشد. چون در ادامه میخواهیم فایل Program.cs آنرا برای راه اندازی یک هاست وب آزمایشی، فراخوانی کنیم.
3) بستهی نیوگتی که کار راه اندازی هاست وب آزمایشی را انجام میدهد، Microsoft.AspNetCore.Mvc.Testing نام دارد. این بسته، کار کپی فایلهای پروژهی اصلی و همچنین تنظیم مسیر پروژه را به این مسیر جدید نیز انجام میدهد.
4) روش افزودن بستههای MSTest را مشاهده میکنید.
5) همچنین جهت سادهتر شدن بررسی نتایج آزمونهای انجام شده میتوان از fluentassertions نیز استفاده کرد.
راه اندازی هاست وب آزمایشی جهت انجام آزمونهای واحد
پس از انجام تنظیمات ابتدایی پروژهی آزمون یکپارچگی، نیاز است یک WebApplicationFactory سفارشی را ایجاد کرد:
using ASPNETCore2JwtAuthentication.WebApp; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; namespace ASPNETCore2JwtAuthentication.IntegrationTests { public class CustomWebApplicationFactory : WebApplicationFactory<Program> { protected override IWebHostBuilder CreateWebHostBuilder() { var builder = base.CreateWebHostBuilder(); builder.ConfigureLogging(logging => { //TODO: ... }); return builder; } protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureTestServices(services => { // Don't run `IHostedService`s when running as a test services.RemoveAll(typeof(IHostedService)); }); } } }
در ادامه روش سفارشی سازی WebApplicationFactory را مشاهده میکنید. برای مثال اگر خواستید سرویسها و تنظیمات پیشفرض برنامهی اصلی را تغییر دهید میتوانید متد CreateWebHostBuilder را بازنویسی کنید و یا اگر خواستید سرویس جدیدی را اضافه و یا حذف کنید، میتوان متد ConfigureWebHost را بازنویسی کرد.
استفاده از WebApplicationFactory سفارشی، جهت ایجاد یک HttpClient
هدف اصلی از ایجاد CustomWebApplicationFactory نه فقط راه اندازی یک هاست وب سفارشی است، بلکه توسط متد CreateClient آن میتوان به یک HttpClient دسترسی یافت که قابلیت ارسال اطلاعات را به برنامهی وبی که در پشت صحنه راه اندازی میشود، دارا است. کار CustomWebApplicationFactory شبیه به راه اندازی dotnet run در پشت صحنهاست. در اینجا دیگر نیازی نیست تا اینکار را به صورت دستی انجام داد. به همین جهت چون برنامهی وب اصلی به نحو متداولی در پشت صحنه اجرا میشود، عموما راه اندازی آن که شامل تنظیمات اولیه و یا حتی ایجاد بانک اطلاعاتی است، کمی کند است و اگر قرار باشد هربار اینکار صورت گیرد، به آزمونهای بسیار کندی خواهیم رسید. به همین جهت میتوان یک کلاس singleton را برای مدیریت تک وهلهی نهایی HttpClient آن به صورت زیر ایجاد کرد:
using System; using System.Threading; using System.Net.Http; namespace ASPNETCore2JwtAuthentication.IntegrationTests { public static class TestsHttpClient { private static readonly Lazy<HttpClient> _serviceProviderBuilder = new Lazy<HttpClient>(getHttpClient, LazyThreadSafetyMode.ExecutionAndPublication); /// <summary> /// A lazy loaded thread-safe singleton /// </summary> public static HttpClient Instance { get; } = _serviceProviderBuilder.Value; private static HttpClient getHttpClient() { var services = new CustomWebApplicationFactory(); return services.CreateClient(); //NOTE: This action is very time consuming, so it should be defined as a singleton. } } }
نوشتن اولین آزمون یکپارچگی
پس از تنظیم هاست وب آزمایشی و ایجاد یک HttpClient از پیش تنظیم شده که به آن اشاره میکند، اکنون میتوان اولین آزمون یکپارچگی را به صورت زیر نوشت:
using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace ASPNETCore2JwtAuthentication.IntegrationTests { [TestClass] public class JwtTests { [TestMethod] public async Task TestLoginWorks() { // Arrange var client = TestsHttpClient.Instance; // Act var token = await doLoginAsync(client); // Assert token.Should().NotBeNull(); token.AccessToken.Should().NotBeNullOrEmpty(); token.RefreshToken.Should().NotBeNullOrEmpty(); } [TestMethod] public async Task TestCallProtectedApiWorks() { // Arrange var client = TestsHttpClient.Instance; // Act var token = await doLoginAsync(client); // Assert token.Should().NotBeNull(); token.AccessToken.Should().NotBeNullOrEmpty(); token.RefreshToken.Should().NotBeNullOrEmpty(); // Act const string protectedApiUrl = "/api/MyProtectedApi"; client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken); var response = await client.GetAsync(protectedApiUrl); response.EnsureSuccessStatusCode(); // Assert var responseString = await response.Content.ReadAsStringAsync(); responseString.Should().NotBeNullOrEmpty(); var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var apiResponse = JsonSerializer.Deserialize<MyProtectedApiResponse>(responseString, options); apiResponse.Title.Should().NotBeNullOrEmpty(); apiResponse.Title.Should().Be("Hello from My Protected Controller! [Authorize]"); } private static async Task<Token> doLoginAsync(HttpClient client) { const string loginUrl = "/api/account/login"; var user = new { Username = "Vahid", Password = "1234" }; var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, loginUrl) { Content = new StringContent(JsonSerializer.Serialize(user), Encoding.UTF8, "application/json") }); response.EnsureSuccessStatusCode(); var responseString = await response.Content.ReadAsStringAsync(); responseString.Should().NotBeNullOrEmpty(); return JsonSerializer.Deserialize<Token>(responseString); } } }
- در هر آزمونی نیاز است در ابتدا به TestsHttpClient.Instance، که همان HttpClient ساخته شدهی توسط CustomWebApplicationFactory است، دسترسی یافت و همانطور که عنوان شد، دسترسی به وهلهای از HttpClient که به هاست وب آزمایشی برنامهی اصلی اشاره میکند، عموما بسیار زمانبراست و برای مثال در دو آزمایش نوشته شدهی در اینجا اگر قرا باشد هربار اینکار از صفر انجام شود، زمان به اتمام رسیدن این آزمایشها بسیار طولانی خواهد شد. به همین جهت طول عمر TestsHttpClient را singleton تعریف کردیم تا فقط یکبار کار برپایی وب سرور آزمایشی در پشت صحنه انجام شود.
- سپس مابقی کار، همان روش استاندارد کار با HttpClient است. در ابتدا درخواستی را به سمت سرور آزمایشی که در پشت صحنه در حال اجرا است، ارسال میکنیم. چون HttpClient دریافتی توسط CustomWebApplicationFactory تنظیم شدهاست، دیگر نیازی به ذکر آدرس پایهی وب سایت مانند https://localhost:5001 نیست و آدرسهای ذکر شدهی در اینجا، نسبی هستند. سپس محتوای Response دریافتی از سرور را جهت تکمیل آزمایشات، بررسی خواهیم کرد.
یک نکته: اگر OpenAPI را در برنامههای Web API فعال کنید، میتوان با استفاده از ابزارهای تولید کد، کدهای مرتبط با HttpClient را نیز به صورت خودکار تولید و سپس از آنها در اینجا استفاده کرد.
اجرای آزمونهای یکپارچگی نوشته شده
چون ظاهر این آزمونها با آزمونهای واحد MSTest یا هر فریم ورک مشابه دیگری یکسان است، میتوان از امکانات IDEها برای اجرای آنها استفاده کرد و یا حتی میتوان دستور dotnet test را نیز در ریشهی این پروژهی جدید برای اجرای تمام آزمونهای نوشته شده، اجرا کرد:
کدهای کامل این مطلب را در اینجا میتوانید مشاهده کنید.
سری کار با IdentityServer 5
- Creating an IdentityServer 5 Project
- Adding JWT Bearer Authentication to an ASP.NET Core 5 API
- Adding Policy-Based Authorisation to an ASP.NET Core 5 API
- Authenticating a .NET 5 Console Application using IdentityServer 5
- Adding API Resources to IdentityServer 5
- Containerising a PostgreSQL 13 Database
- Adding Entity Framework Core 5 to IdentityServer 5
- Adding an OAuth 2.0 Security Scheme to an ASP.NET Core 5 API
- Adding ASP.NET Core 5 Identity to IdentityServer 5
- Seeding an ASP.NET Core 5 Identity Database with Users
- Authenticating an ASP.NET Core 5 Web Application using IdentityServer 5
- Authenticating an Angular 11 Single-Page Application using IdentityServer 5
- Authenticating a Flutter Android Application using IdentityServer 5
- Containerising an IdentityServer 5 Project
- Containerising an IdentityServer 5 Project with API Resources and Clients
معرفی نگارش بعدی ASP.NET
ASP.NET MVC and Web API have been unified into a single programming model
No-compile developer experience
Dependency injection out of the box
Side by side - deploy the runtime and framework with your application
NuGet everything - even the runtime itself
All Open Source via the .NET Foundation and takes contributions
همچنین پشتیبانی رسمی از Mono توسط مایکروسافت:
- سری مباحث ASP.NET Identity 2,x را از اینجا شروع به مطالعه کنید. برای نمونه: «افزودن تصدیق ایمیل به ASP.NET Identity در MVC 5» و «Identity 2.0 : تایید حسابهای کاربری و احراز هویت دو مرحله ای » و «پیاده سازی یک سیستم دسترسی Role Based در Web API و AngularJs - بخش اول»
جهت اطلاع عموم
ASP.NET Identity 2.x یک پروژهی خاتمه یافته از دیدگاه مایکروسافت محسوب شده و دیگر پشتیبانی خاصی ندارد . نگارش بعدی آن ASP.NET Core Identity نام دارد.
- معماری لایه ای
- استفاده از جدیدترین نسخه ASP.NET Identity
- به روز رسانی لایه IocConfig برای یک برنامه ASP.NET Core با توجه به بخش روش تعویض IoC Container توکار ASP.NET Core با StructureMap و https://github.com/structuremap/StructureMap.Microsoft.DependencyInjection جهت استفاده از تنظیمات StructureMap در یک لایه مجزا (IocConfig) با کانفیگهای مختص برای آخرین نگارش MVC و Web API و SignalR و Entity Framework .
در این کتاب میخوانید:
- دلایل معرفی و استفاده از ASP.NET MVC
- اکشن ها، کنترلرها، Viewها، فیلترها، مسیریابی
- آشنایی با آزمایشهای واحد و استفاده از آنها
- سفارشی سازی اجزای ASP.NET MVC
- تزریق وابستگی
- Model Templates و Model Binding
- تعیین اعتبار داده ها
- استفاده از AJAX و jQuery
- امنیت
- ASP.NET Web API
- انتشار پروژه
- و بسیاری مطالب دیگر...
جزئیات بیشتر...
در این قسمت با نحوهی تناظر آدرسها توسط Web API به متدهای موجود در Controller آشنا میشوید.
در هر درخواستی که ارسال میشود، Web API، انتخاب Controller مناسب را با رجوع به جدولی با نام جدول مسیرها انجام میدهد. زمانی که یک پروژهی جدید با استفاده از ASP.NET MVC 4 ایجاد میکنید، یک route پیش فرض به صورت ذیل در متد RegisterRoutes قرار میگیرد.
routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } );
- {controller} به نام Controller تناظر پیدا میکند.
- نوع درخواست ارسالی (GET، POST، PUT، DELETE) به نام متد تناظر پیدا میکند.
- اگر قسمت {id} در آدرس وجود داشته باشد، به پارامتر id متد انتخاب شده پاس داده میشود.
- اگر آدرس دارای Query String باشد، به پارامترهای همنام خود در متد، تناظر پیدا میکنند.
در ذیل، مثال هایی را از چند آدرس درخواستی و نتیجهی حاصل از فراخوانی آنها مشاهده میکنید.
- آدرس api/products/ با نوع درخواست GET به متد ()GetAllProducts
- آدرس api/products/1/ با نوع درخواست GET به متد (1)GetProductById
- آدرس api/products?category=hardware/ با نوع درخواست GET به متد ("GetProductByCategory("hardware
در آدرس اول، عبارت "products" به ProductsController تطبیق پیدا میکند. درخواست نیز از نوع GET است، بنابراین Web API به دنبال متدی در Controller میگردد که نام آن با عبارت GET "آغاز" شده باشد. همچنین، آدرس شامل قسمت {id} نیز نیست. بنابراین، Web API متدی را انتخاب میکند که پارامتر ورودی ندارد. متد GetAllProducts در ProductsController، تمامی این شروط را دارد، پس انتخاب میشود.
در دومین آدرس، همان حالت قبل وجود دارد، با این تفاوت که در آدرس درخواستی، قسمت {id} وجود دارد. از آنجا که نوع قسمت {id} در متد int ،GetProductById تعریف شده است، باید یک عدد صحیح بعد از آدرس /api/products وجود داشته باشد تا متد GetProductById فراخوانی شود. این عدد به طور خودکار به نوع int تبدیل شده و در پارامتر اول متد GetProductById قرار میگیرد. در ذیل، برخی آدرسها را ملاحظه میکنید که معتبر نیستند و باعث بروز خطا میشوند.
- آدرس api/products/ با نوع درخواست POST، باعث خطای 405Method Not Allowed میشود.
- آدرس api/users/ با نوع درخواست GET، باعث خطای 404Not Found میشود.
- آدرس api/products/abc/ با نوع درخواست GET، باعث خطای 400Bad Request میشود.
در آدرس اول، Client یک درخواست از نوع POST ارسال کرده است. Web API به دنبال متدی میگردد که نام آن با عبارت Post آغاز میشود. اما متدی با این شرط در ProductsController وجود ندارد. بنابراین، پاسخی که دریافت میشود، عبارت "405 Method Not Allowed" است. درخواست برای آدرس /api/users/ نیز معتبر نیست، چون Controllerیی با نام UsersController وجود ندارد. و سومین آدرس نیز بدین دلیل نامعتبر است که قسمت abc نمیتواند به یک عدد صحیح تبدیل شود.
زمانی که با یک وب سرویس کار میکنید، مشاهدهی محتویات درخواست ارسالی و پاسخ دریافتی میتواند کاربرد زیادی در درک نحوهی تعامل بین Client و وب سرویس و کشف خطاهای احتمالی داشته باشد. در Firefox با استفاده از افزونهی Firebug و در Internet Explorer 9 به بالا با ابزار Developer Tools آن میتوان درخواستها و پاسخها را مشاهده کرد. در Internet Explorer، کلید F12 را برای اجرای Developer Tools فشار دهید. از قسمت Network بر روی دکمهی Start Capturing کلیک کنید. حال کلید F5 را برای بارگذاری مجدد صفحه فشار دهید. Internet Explorer، درخواست و پاسخ رد و بدل شده بین مرورگر و Web Server را مانیتور کرده و گزارشی را نشان میدهد (شکل ذیل).
از ستون URL، آدرس /api/products/ را انتخاب و بر روی دکمهی Go to detailed view کلیک کنید. در قسمتی که باز میشود، گزینه هایی برای مشاهدهی هدرهای درخواست، پاسخ و همچنین بدنهی هر یک وجود دارد. به عنوان مثال، اگر قسمت Request headers را انتخاب کنید، خواهید دید که Internet Explorer از طریق هدر Accept، تقاضای پاسخ در قالب JSON را کرده است (شکل ذیل).
اگر قسمت Response body را انتخاب کنید، پاسخ دریافت شده در قالب JSON را خواهید دید.
پیشنیازها
- مطالعهی سری کار با Angular CLI خصوصا قسمت نصب و قسمت ساخت برنامههای آن، پیش از مطالعهی این مطلب ضروری است.
- همچنین فرض بر این است که سری ASP.NET Core را نیز یکبار مرور کردهاید و با نحوهی برپایی یک برنامهی MVC آن و ارائهی فایلهای استاتیک توسط یک پروژهی ASP.NET Core آشنایی دارید.
ایجاد یک پروژهی جدید ASP.NET Core در VS 2017
در ابتدا یک پروژهی خالی ASP.NET Core را در VS 2017 ایجاد خواهیم کرد. برای این منظور:
- ابتدا از طریق منوی File -> New -> Project (Ctrl+Shift+N) گزینهی ایجاد یک ASP.NET Core Web application را انتخاب کنید.
- در صفحهی بعدی آن هم گزینهی «empty template» را انتخاب نمائید.
تنظیمات یک برنامهی ASP.NET Core خالی برای اجرای یک برنامهی Angular CLI
برای اجرای یک برنامهی مبتنی بر Angular CLI، نیاز است بر روی فایل csproj برنامهی ASP.NET Core کلیک راست کرده و گزینهی Edit آنرا انتخاب کنید.
سپس محتوای این فایل را به نحو ذیل تکمیل نمائید:
الف) درخواست عدم کامپایل فایلهای TypeScript
<PropertyGroup> <TargetFramework>netcoreapp1.1</TargetFramework> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> </PropertyGroup>
ب) مشخص کردن پوشههایی که باید الحاق و یا حذف شوند
<ItemGroup> <Folder Include="Controllers\" /> <Folder Include="wwwroot\" /> </ItemGroup> <ItemGroup> <Compile Remove="node_modules\**" /> <Content Remove="node_modules\**" /> <EmbeddedResource Remove="node_modules\**" /> <None Remove="node_modules\**" /> </ItemGroup> <ItemGroup> <Compile Remove="src\**" /> <Content Remove="src\**" /> <EmbeddedResource Remove="src\**" /> </ItemGroup>
سپس دو پوشهی node_modules و src واقع در ریشهی پروژه را نیز به طور کامل از سیستم ساخت و توزیع VS 2017 حذف کردهایم. پوشهی node_modules وابستگیهای Angular را به همراه دارد و src همان پوشهی برنامهی Angular ما خواهد بود.
ج) افزودن وابستگیهای سمت سرور مورد نیاز
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" /> <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" /> </ItemGroup> <ItemGroup> <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" /> </ItemGroup> <ItemGroup> <!-- extends watching group to include *.js files --> <Watch Include="**\*.js" Exclude="node_modules\**\*;**\*.js.map;obj\**\*;bin\**\*" /> </ItemGroup>
در اینجا Watcher.Tools هم به همراه تنظیمات آن اضافه شدهاند که در ادامهی بحث به آن اشاره خواهد شد.
افزودن یک کنترلر Web API جدید
با توجه به اینکه دیگر در اینجا قرار نیست با فایلهای cshtml و razor کار کنیم، کنترلرهای ما نیز از نوع Web API خواهند بود. البته در ASP.NET Core، کنترلرهای معمولی آن، توانایی ارائهی Web API و همچنین فایلهای Razor را دارند و از این لحاظ تفاوتی بین این دو نیست و یکپارچگی کاملی صورت گرفتهاست.
using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; namespace ASPNETCoreIntegrationWithAngularCLI.Controllers { [Route("api/[controller]")] public class ValuesController : Controller { // GET: api/values [HttpGet] [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)] public IEnumerable<string> Get() { return new string[] { "Hello", "DNT" }; } } }
تنظیمات فایل آغازین یک برنامهی ASP.NET Core جهت ارائهی برنامههای Angular
در ادامه به فایل Startup.cs برنامهی خالی جاری، مراجعه کرده و آنرا به نحو ذیل تغییر دهید:
using System; using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace ASPNETCoreIntegrationWithAngularCLI { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Use(async (context, next) => { await next(); if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value) && !context.Request.Path.Value.StartsWith("/api/", StringComparison.OrdinalIgnoreCase)) { context.Request.Path = "/index.html"; await next(); } }); app.UseMvcWithDefaultRoute(); app.UseDefaultFiles(); app.UseStaticFiles(); } } }
در قسمت app.Use آن، تنظیمات URL Rewriting مورد نیاز جهت کار با مسیریابی برنامههای Angular را مشاهده میکنید. برای نمونه اگر کاربری در ابتدای کار آدرس /products را درخواست کند، این درخواست به سمت سرور ارسال میشود و چون چنین صفحهای در سمت سرور وجود ندارد، خطای 404 بازگشت داده میشود و کار به پردازش برنامهی Angular نخواهد رسید. اینجا است که تنظیم میانافزار فوق، کار مدیریت خروجیهای 404 را بر عهده گرفته و کاربر را به فایل index.html برنامهی تک صفحهای وب، هدایت میکند. به علاوه در اینجا اگر درخواست وارد شده، دارای پسوند باشد (یک فایل باشد) و یا با api/ شروع شود (اشاره کنندهی به کنترلرهای Web API برنامه)، از این پردازش و هدایت به صفحهی index.html معاف خواهد شد.
ایجاد ساختار اولیهی برنامهی Angular CLI در داخل پروژهی جاری
اکنون از طریق خط فرمان به پوشهی ریشهی برنامهی ASP.NET Core، جائیکه فایل Startup.cs قرار دارد، وارد شده و دستور ذیل را اجرا کنید:
>ng new ClientApp --routing --skip-install --skip-git --skip-commit
پس از تولید ساختار برنامهی Angular CLI، به پوشهی آن وارد شده و تمام فایلهای آن را Cut کنید. سپس به پوشهی ریشهی برنامهی ASP.NET Core جاری، وارد شده و این فایلها را در آنجا paste نمائید. به این ترتیب به حداکثر سازگاری ساختار پروژههای Angular CLI و VS 2017 خواهیم رسید. زیرا اکثر فایلهای تنظیمات آنرا میشناسد و قابلیت پردازش آنها را دارد.
پس از این cut/paste، پوشهی خالی ClientApp را نیز حذف کنید.
تنظیم محل خروجی نهایی Angular CLI به پوشهی wwwroot
برای اینکه سیستم Build پروژهی Angular CLI جاری، خروجی خود را در پوشهی wwwroot قرار دهد، تنها کافی است فایل .angular-cli.json را گشوده و outDir آنرا به wwwroot تنظیم کنیم:
"apps": [ { "root": "src", "outDir": "wwwroot",
فراخوانی کنترلر Web API برنامه در برنامهی Angular CLI
در ادامه صرفا جهت آزمایش برنامه، فایل src\app\app.component.ts را گشوده و به نحو ذیل تکمیل کنید:
import { Component, OnInit } from '@angular/core'; import { Http } from '@angular/http'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { constructor(private _httpService: Http) { } apiValues: string[] = []; ngOnInit() { this._httpService.get('/api/values').subscribe(values => { this.apiValues = values.json() as string[]; }); } }
سپس این آرایه را در فایل قالب این کامپوننت (src\app\app.component.html) استفاده خواهیم کرد:
<h1>Application says:</h1> <ul *ngFor="let value of apiValues"> <li>{{value}}</li> </ul> <router-outlet></router-outlet>
نصب وابستگیهای برنامهی Angular CLI
در ابتدای ایجاد پوشهی ClientApp، از پرچم skip-install استفاده شد، تا صرفا ساختار پروژه، جهت cut/paste آن با سرعت هر چه تمامتر، ایجاد شود. اکنون برای نصب وابستگیهای آن یا میتوان در solution explorer به گره dependencies مراجعه کرده و npm را انتخاب کرد. در ادامه با کلیک راست بر روی آن، گزینهی restore packages ظاهر میشود. و یا میتوان به روش متداول این نوع پروژهها، از طریق خط فرمان به پوشهی ریشهی پروژه وارد شد و دستور npm install را صادر کرد. بهتر است اینکار را از طریق خط فرمان انجام دهید تا مطمئن شوید که از آخرین نگارشهای این ابزار که بر روی سیستم نصب شدهاست، استفاده میکنید.
روش اول اجرای برنامههای مبتنی بر ASP.NET Core و Angular CLI
تا اینجا اگر برنامه را از طریق VS 2017 اجرا کنید، خروجی را مشاهده نخواهید کرد. چون هنوز فایل index.html آن تولید نشدهاست.
بنابراین روش اول اجرای این نوع برنامهها، شامل مراحل ذیل است:
الف) ساخت پروژهی Angular CLI در حالت watch
> ng build --watch
ب) اجرای برنامه از طریق ویژوال استودیو
اکنون که کار ایجاد محتوای پوشهی wwwroot برنامه انجام شدهاست، میتوان برنامه را از طریق VS 2017 به روش متداولی اجرا کرد:
یک نکته: میتوان قسمت الف را تبدیل به یک Post Build Event هم کرد. برای این منظور باید فایل csproj را به نحو ذیل تکمیل کرد:
<Target Name="AngularBuild" AfterTargets="Build"> <Exec Command="ng build" /> </Target>
تنها مشکل روش Post Build Event، کند بودن آن است. زمانیکه از روش ng build --watch به صورت مستقل استفاده میشود، برای بار اول اجرا، اندکی زمان خواهد برد؛ اما اعمال تغییرات بعدی به آن بسیار سریع هستند. چون صرفا نیاز دارد این تغییرات اندک و تدریجی را کامپایل کند و نه کامپایل کل پروژه را از ابتدا.
روش دوم اجرای برنامههای مبتنی بر ASP.NET Core و Angular CLI
روش دومی که در اینجا بررسی خواهد شد، مستقل است از قسمت «ب» روش اول که توضیح داده شد. برنامههای NET Core. نیز به همراه CLI خاص خودشان هستند و نیازی نیست تا حتما از VS 2017 برای اجرای آنها استفاده کرد. به همین جهت وابستگی Microsoft.DotNet.Watcher.Tools را نیز در ابتدای کار به وابستگیهای برنامه اضافه کردیم.
الف) در ادامه، VS 2017 را به طور کامل ببندید؛ چون نیازی به آن نیست. سپس دستور ذیل را در خط فرمان، در ریشهی پروژه، صادر کنید:
> dotnet watch run
>dotnet watch run [90mwatch : [39mStarted Hosting environment: Production Now listening on: http://localhost:5000 Application started. Press Ctrl+C to shut down.
ب) در اینجا چون برنامه بر روی پورت 5000 ارائه شدهاست، بهتر است دستور ng serve -o را صادر کرد تا بتوان به نحو سادهتری از سرور وب ASP.NET Core استفاده نمود. در این حالت برنامهی Angular CLI بر روی پورت 4200 ارائه شده و بلافاصله در مرورگر نیز نمایش داده میشود.
مشکل! سرور وب ما بر روی پورت 5000 است و سرور آزمایشی Angular CLI بر روی پورت 4200. اکنون برنامهی Angular ما، یک چنین درخواستهایی را به سمت سرور، جهت دریافت اطلاعات ارسال میکند: localhost:4200/api
برای رفع این مشکل میتوان فایلی را به نام proxy.config.json با محتویات ذیل ایجاد کرد:
{ "/api": { "target": "http://localhost:5000", "secure": false } }
>ng serve --proxy-config proxy.config.json -o
مزیت این روش، به روز رسانی خودکار مرورگر با انجام هر تغییری در کدهای قسمت Angular برنامه است.
نکته 1: بدیهی است میتوان قسمت «ب» روش دوم را با قسمت «الف» روش اول نیز جایگزین کرد (ساخت پروژهی Angular CLI در حالت watch). اینبار گشودن مرورگر بر روی پورت 5000 (و یا آدرس http://localhost:5000) را باید به صورت دستی انجام دهید. همچنین هربار تغییر در کدهای Angular، سبب refresh خودکار مرورگر نیز نمیشود که آنرا نیز باید خودتان به صورت دستی انجام دهید (کلیک بر روی دکمهی refresh پس از هر بار پایان کار ng build).
نکته 2: میتوان قسمت «الف» روش دوم را حذف کرد (حذف dotnet run در حالت watch). یعنی میخواهیم هنوز هم ویژوال استودیو کار آغاز IIS Express را انجام دهد. به علاوه میخواهیم برنامه را توسط ng serve مشاهده کنیم (با همان پارامترهای قسمت «ب» روش دوم). در این حالت تنها موردی را که باید تغییر دهید، پورتی است که برای IIS Express تنظیم شدهاست. عدد این پورت را میتوان در فایل Properties\launchSettings.json مشاهده کرد و سپس به تنظیمات فایل proxy.config.json اعمال نمود.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: ASPNETCoreIntegrationWithAngularCLI.zip
به همراه این کدها تعدادی فایل bat نیز وجود دارند که جهت ساده سازی عملیات یاد شدهی در این مطلب، میتوان از آنها استفاده کرد:
- فایل restore.bat کار بازیابی و نصب وابستگیهای پروژهی دات نتی و همچنین Angular CLI را انجام میدهد.
- دو فایل ng-build-dev.bat و ng-build-prod.bat بیانگر قسمت «الف» روش اول هستند. فایل dev مخصوص حالت توسعه است و فایل prod مخصوص ارائهی نهایی.
- دو فایل dotnet_run.bat و ng-serve-proxy.bat خلاصه کنندهی قسمتهای «الف» و «ب» روش دوم هستند.