معرفی ASP.NET Core 8، Web API و Minimal APIs
منبع: Coding Clean, Reliable, and Safe REST APIs with ASP.NET Core 8 — Anthony Giretti
اعتبار ترجمه: ترجمه با کمک هوش مصنوعی
فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.
فصل ۲: معرفی ASP.NET Core 8
Microsoft نخستین Framework کامل خود برای توسعهٔ برنامههای وب را در سال ۲۰۰۲ با ASP.NET Web Forms منتشر کرد. سالهای بعد با فناوریهایی مانند ASP.NET Model-View-Controller (MVC)، ASP.NET Web API و SignalR همراه بود. Framework با سرعت زیادی قابلیتهای جدید دریافت کرد، درحالیکه هستهٔ آن—بهویژه Assembly معروف System.Web—تغییر بنیادینی نکرد. خیلی زود نیازهای تازهای مطرح شدند: Performance (کارایی) بهتر، امکان اجرای ASP.NET روی سرورهایی غیر از IIS که وبسرور Windows-only شرکت Microsoft است، سازگاری بیشتر با Cloud برای Deployment سادهتر و Configuration بسیار انعطافپذیرتر. نتیجه، تولد ASP.NET Core بود.
ASP.NET Core بازطراحی کامل ASP.NET است و امکان ساخت انواع برنامهها را فراهم میکند:
- برنامههای وب مانند MVC، Razor Pages یا Single-Page Applicationها با Blazor.
- APIها، شامل REST API، Remote Procedure Call و ارتباط Real-Time.
- Background Taskهایی که بهصورت Windows Service، Unix Daemon یا درون برنامهٔ ASP.NET Core اجرا میشوند.
doi.org/10.1007/978-1-4842-9979-1_2
در زمان نگارش کتاب، ASP.NET Core 8 که همراه .NET 8 ارائه شده جدیدترین نسخه است و در سراسر کتاب از آن استفاده میکنیم. ASP.NET Core 8 دیگر ASP.NET Web Forms و Windows Communication Foundation (WCF)، یعنی Framework سرویس وب مبتنی بر SOAP، را پشتیبانی نمیکند؛ بااینحال پروژهای به نام CoreWCF در اوایل ۲۰۲۲ منتشر شد.
در این فصل Fundamentals (مبانی) ASP.NET Core و دو نوع برنامهای را که برای ساخت REST API استفاده میکنیم بررسی خواهیم کرد:
- ASP.NET Core Web API
- ASP.NET Core Minimal APIs
مبانی ASP.NET Core
پیش از ورود به جزئیات، ابتدا Fundamentals را بررسی میکنیم. وقتی مبانی ASP.NET Core را بدانیم، میتوانیم همین دانش را برای ساخت انواع برنامههای وب، حتی gRPC، به کار ببریم.
نقطهٔ ورود یک برنامهٔ ASP.NET Core فایل Program.cs است. در این فایل، برنامه با ساخت یک WebApplicationBuilder توسط متد Static یعنی WebApplication.CreateBuilder آغاز میشود. WebApplicationBuilder اجازه میدهد Componentهای موردنظر را به Configuration اضافه کنیم و سپس آنها را فعال کنیم.
Listing 2-1. نمونهٔ فایل Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
فایل Program.cs دو بخش مشخص دارد:
- Services Configuration: نوع برنامه، Libraryهای Third-Party، Authentication، Authorization و ثبت Serviceها در Dependency Injection را شامل میشود.
- Services Activation: Middleware Pipeline در ASP.NET Core را تعریف میکند. Middleware یک Component است که پس از قرار گرفتن در Pipeline با ترتیب مشخص، Request و Response را مدیریت میکند و میتواند قبل و بعد از Component بعدی عملیات انجام دهد؛ شکل ۲-۱ این زنجیره را نمایش میدهد.
شکل ۲-۱. Middleware Pipeline در ASP.NET Core
Services Configuration پیش از ساخت برنامه با متد builder.Build() انجام میشود و Services Activation پس از آن، ولی پیش از app.Run() قرار میگیرد. Listing 2-2 نمونهٔ Configuration یک برنامهٔ Razor Pages است.
Listing 2-2. نمونهٔ Program.cs پیکربندیشده
var builder = WebApplication.CreateBuilder(args);
// Services configuration
builder.Services.AddRazorPages();
var app = builder.Build();
// Services activation
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
ممکن است معماری هنوز کمی مبهم باشد؛ شکل ۲-۲ نمای کلی معماری ASP.NET Core را خلاصه میکند.
شکل ۲-۲. معماری ASP.NET Core
Dependency Injection و Lifetimeها
Dependency Injection در مرکز ASP.NET Core قرار دارد. این تکنیک باعث Loose Coupling میان Objectها، Service Classها و Dependencyهای آنها میشود. بهجای آنکه Serviceها مستقیماً در Methodها یا Constructorها ساخته شوند، Class اعلام میکند به چه Dependencyهایی نیاز دارد. در این کتاب Serviceها معمولاً با Interface پیادهسازیشدهٔ خود ثبت میشوند و Interface در Constructor کلاس مصرفکننده Inject خواهد شد. این Decoupling، Code را Abstractتر و Testability را بهتر میکند.
Lifetime سرویسهای Injectشده بسیار مهم است. بعضی Serviceها باید در هر Request یک Instance تازه داشته باشند، بعضی فقط یک بار در Scope همان Request ساخته شوند و بعضی در کل عمر Application یک Instance مشترک داشته باشند. ASP.NET Core سه Lifetime ارائه میکند:
- Transient: Instance جدید Service برای هر بار Resolution ساخته میشود؛ در یک HTTP Request هم ممکن است برای هر مصرف، Instance جدیدی از همان Service ایجاد شود.
- Scoped: Service در هر HTTP Request فقط یک بار ساخته میشود. این Lifetime بسیار رایج است و یک Instance یکتا در Scope همان Request فراهم میکند.
- Singleton: Service فقط یک بار در کل عمر Application ساخته میشود و همهٔ کاربران همان Instance را به اشتراک میگذارند. ساخت Singleton در Container بهصورت Thread-safe مدیریت میشود، اما اگر Propertyهای Mutable مانند Dictionary دارید، باید از Structureهای Thread-safe مانند
ConcurrentDictionary استفاده کنید.
Listing 2-3 ثبت این سه Lifetime را نشان میدهد. پارامتر سمت چپ Interface و پارامتر سمت راست Concrete Class پیادهساز آن است. اگر Class آن Interface را پیادهسازی نکند، Compilation Error رخ میدهد.
Listing 2-3. پیکربندی Lifetimeهای مختلف
var builder = WebApplication.CreateBuilder(args);
services.AddControllers();
services.AddSingleton<ISingletonService, SingletonService>();
services.AddScoped<IScopedService, ScopedService>();
services.AddTransient<ITransientService, TransientService>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Listing 2-4 نحوهٔ Inject کردن Serviceها در Constructor یک MVC Controller را پس از ثبت Mapping آنها در Program.cs نشان میدهد.
Listing 2-4. MVC Controller با Constructor Injection
public class DemoController : Controller
{
private readonly ISingletonService _singletonService;
private readonly IScopedService _scopedService;
private readonly ITransientService _transientService;
public DemoController(ISingletonService singletonService,
IScopedService scopedService,
ITransientService transientService)
{
_singletonService = singletonService;
_scopedService = scopedService;
_transientService = transientService;
}
}
بسته به نیاز ممکن است از Singleton، Scoped یا Transient استفاده کنید، اما باید Hierarchy آنها را بشناسید. یک Transient میتواند مستقیماً به Scoped یا Singleton وابسته باشد و یک Scoped میتواند به Singleton وابسته باشد. جهت عکس مجاز نیست، زیرا Object با Lifetime طولانیتر نباید مستقیماً به Object کوتاهعمرتر وابسته شود. شکل ۲-۳ این Hierarchy را خلاصه میکند.
شکل ۲-۳. Hierarchy مربوط به Scope و Lifetime
Configuration و Options Pattern
ASP.NET Core امکان افزودن Configuration اضافی را فراهم میکند تا از طریق Dependency Injection در هر بخش برنامه مصرف شود. تنظیمات را میتوان در فایل appsettings.json ذخیره و برای Environmentهای مختلف سفارشی کرد. برای مثال appsettings.development.json میتواند Configuration مخصوص Development را داشته باشد. اگر یک Key در هر دو فایل باشد، فایل اختصاصیتر مقدار فایل اصلی را Override میکند.
میتوان Objectای از نوع Options ساخت و Configuration را داخل آن Bind کرد؛ این روش Options Pattern نام دارد. Listingهای 2-5 تا 2-8 نمونهٔ Configuration مربوط به SMTP، Mapping آن به SmtpConfiguration، ثبت در Dependency Injection و Inject کردن IOptions<TOptions> در Controller را نشان میدهند.
Listing 2-5. SMTP configuration در appsettings.json
{
"SmtpConfiguration": {
"Domain": "smtp.gmail.com",
"Port": 465
}
}
Listing 2-6. شیء SmtpConfiguration
public record class SmtpConfiguration
{
public string Domain { get; init; }
public int Port { get; init; }
}
Listing 2-7. Bind و Register کردن SmtpConfiguration در Dependency Injection
var builder = WebApplication.CreateBuilder(args); services.
Configure<SmtpConfiguration>(Configuration.GetSection("SmtpConf
iguration"));
....
Listing 2-8. Inject کردن Options در DemoController
public class DemoController : Controller
{
private readonly SmtpConfiguration _smtpConfiguration;
public DemoController(IOptions<SmtpConfiguration>
smtpConfigurationOptions)
{
_smtpConfiguration = smtpConfigurationOptions.Value;
}
}
این سادهترین روش استفاده از Options در ASP.NET Core است. بسته به نیاز میتوان از IOptionsSnapshot<TOptions> و IOptionsMonitor<TOptions> نیز استفاده کرد.
Development Environment
موضوع مهم دیگر امکان فعالکردن Development Mode از طریق Configuration است. Development Mode اجازه میدهد رفتار برنامه در محیط توسعه با Production فرق کند؛ برای مثال Connection String در Production رمزنگاری شود ولی در Development چنین نباشد. همچنین اطلاعات بسیار جزئیتری دربارهٔ Unhandled Error نمایش داده میشود. به همین دلیل Development Mode نباید در Production فعال باشد.
برای فعالکردن آن، Environment Variable با نام ASPNETCORE_ENVIRONMENT در launchSettings.json یا Project Properties روی Development قرار میگیرد. Listing 2-9 نمونهای از تنظیم Development Mode برای IIS و Self-hosting است.
Listing 2-9. Development Mode در launchSettings.json
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:57090",
"sslPort": 44366
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"MVCDemo": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
ASP.NET Core Web API
ASP.NET Core Web API همانطور که از نامش پیداست برای ساخت Web API بهکار میرود. Web API یک Application Programming Interface است که همراه HTTP استفاده میشود. امروزه Web APIها معمولاً از REST و قالب تبادل JSON استفاده میکنند؛ XML نیز قابل استفاده است اما کمتر رایج است.
APIها از قابلیتهای HTTP مانند URI بهره میبرند. با توجه به تنوع زیاد دستگاهها و Terminalهایی که کاربران نهایی استفاده میکنند، نیاز داریم داده را سریع و امن به مرورگرها یا Applicationهای جدید ارائه کنیم. ASP.NET Core Web API یک Framework مناسب و Performant برای ساخت سرویسهایی است که تعداد زیادی کاربر بتوانند از آنها استفاده کنند.
ASP.NET Core Web API از Pattern معروف Model-View-Controller (MVC) پیروی میکند. در برنامهٔ وب سنتی، V یا View همان صفحهٔ وب است؛ در Web API، View عملاً Response در قالب JSON، XML یا قالب دیگر است. شکل ۲-۴ نمای این Architecture را نشان میدهد.
شکل ۲-۴. معماری ASP.NET Core Web API
ایجاد Web API در Visual Studio 2022
برای ساخت پروژه، در Visual Studio 2022 فیلتر Web را انتخاب کنید تا Project Type با نام ASP.NET Core Web API پیدا شود.
شکل ۲-۵. پیدا کردن Project Type مربوط به ASP.NET Core Web API
پس از انتخاب Template، Project Name، محل ذخیره و Solution Name را مشخص میکنید.
شکل ۲-۶. ایجاد پروژهٔ جدید ASP.NET Core Web API
در مرحلهٔ بعد گزینههای مختلف Application قابل تنظیماند. Runtime را روی جدیدترین گزینه، یعنی .NET 8، قرار دهید؛ ASP.NET Core 8 فقط روی .NET 8 اجرا میشود. میتوان Authentication Type را انتخاب کرد—Windows، Microsoft Identity Platform یا بدون Authentication—و HTTPS، Docker، OpenAPI و استفاده یا عدم استفاده از Controllerها را نیز تنظیم کرد. اگر Controller را غیرفعال کنید، پروژه بر Minimal API استوار خواهد بود.
شکل ۲-۷. پیکربندی ASP.NET Core Web API
Docker یک Open Source Containerization Platform است که اجازه میدهد Application Source Code به همراه Libraryها و Dependencyهای سیستمعامل مورد نیاز برای اجرا، در Container قرار گیرد و در Environmentهای مختلف اجرا شود.
OpenAPI Specification یک Interface استاندارد و مستقل از زبان برای RESTful APIها تعریف میکند تا انسان و ماشین بدون خواندن Source Code بتوانند قابلیتهای سرویس را کشف و درک کنند. Swagger مجموعه ابزارهایی است که روی OpenAPI ساخته شدهاند.
پس از کلیک روی Create، Visual Studio یک پروژهٔ پیشفرض با Model و Controller مربوط به WeatherForecast میسازد.
شکل ۲-۸. Template پیشفرض WeatherForecast در ASP.NET Core Web API
شکل ۲-۹ Controller مربوط به WeatherForecastController را نشان میدهد.
شکل ۲-۹. کلاس WeatherForecastController
در فایل Program.cs، OpenAPI فعال شده است و Swagger UI از همین Configuration استفاده میکند.
شکل ۲-۱۰. فایل Program.cs پیکربندیشده با OpenAPI (Swagger)
اگر Application را اجرا کنید، Visual Studio مرورگر را با صفحهٔ OpenAPI باز میکند و Endpointهای موجود را نمایش میدهد. در Template اولیه فقط Endpoint مربوط به GET WeatherForecast وجود دارد. میتوانید Execute را بزنید و دادهٔ برگشتی را در بخش Response ببینید.
شکل ۲-۱۱. صفحهٔ Swagger UI
Swagger UI بهطور پیشفرض باز میشود، زیرا هنگام فعالکردن OpenAPI، Visual Studio مقدار launchUrl در launchSettings.json را روی swagger تنظیم میکند.
شکل ۲-۱۲. پارامتر launchUrl با مقدار swagger
HttpRepl
علاوه بر Swagger میتوان از ابزار Command-line با نام HttpRepl، مخفف HTTP Read-Eval-Print Loop، استفاده کرد. این ابزار سبک و Cross-platform است و با ASP.NET Core API و APIهای دیگر کار میکند. HttpRepl درخواست HTTP میفرستد و نتیجه را فارغ از محل Hosting نمایش میدهد. Verbهای DELETE، GET، HEAD، OPTIONS، PATCH، POST و PUT را پشتیبانی میکند.
Listing 2-10. فرمان نصب HttpRepl
dotnet tool install -g Microsoft.dotnet-httprepl
Listing 2-11. فرمان Help
شکل ۲-۱۳. فرمانهای موجود در HttpRepl
HttpRepl با خواندن فایل swagger.json میتواند Endpointها را کشف کند، میان آنها Navigate کند و درخواست اجرا کند. برای اتصال به Base URL محلی از فرمان زیر استفاده میشود.
Listing 2-12. اتصال به Base URL محلی API
httprepl https://localhost:5001
نحوهٔ Explore و Navigation آن جالب است، زیرا از فرمانهایی شبیه MS-DOS استفاده میکند؛ ls برای فهرست Endpointها و cd برای رفتن به Endpoint.
شکل ۲-۱۴. Explore، Navigation و اجرای Endpointهای API با HttpRepl
Postman
اگر نخواهید از Swagger UI یا HttpRepl استفاده کنید، Postman گزینهٔ دیگری است. Postman یک GUI برای ساخت Requestهای HTTP و آزمودن Endpointهای API است. در آن میتوان URL، Header، Verb، Query String، Body و سایر پارامترهای Request را تنظیم کرد.
شکل ۲-۱۵. رابط گرافیکی Postman
از میان این سه ابزار، Postman محبوبترین است. بااینحال HttpRepl برای علاقهمندان ابزارهای Command-line، بهویژه کاربران Linux، تجربهٔ جالبی ارائه میکند.
ASP.NET Core Minimal APIs
ASP.NET Core 8 قابلیتهای Minimal API را گسترش داده و فاصلهٔ آنها را با Web APIهای مبتنی بر Controller کمتر کرده است. در ادامهٔ کتاب بارها از Minimal API استفاده خواهیم کرد.
دلیل علاقهٔ من به آنها این است که گاهی فقط یک API بسیار کوچک با یک یا دو Endpoint لازم دارم و Data Manipulation ساده است. در این حالت نیازی به Controller نیست و یک فایل Program.cs کافی است. همانطور که میدانید این فایل میتواند Application را با Configuration حداقلی راهاندازی کند.
با وجود حداقل بودن Syntax، کل Pipeline اصلی ASP.NET Core همچنان پابرجا است: Dependency Injection و Middlewareها همچنان Request و Responseهای HTTP را مدیریت میکنند.
برای شروع میتوان Project Type با نام ASP.NET Core Empty ایجاد کرد.
شکل ۲-۱۶. ایجاد پروژهٔ ASP.NET Core Empty
پس از نامگذاری Project، Visual Studio 2022 یک پروژهٔ Minimal با Endpoint پیشفرض Hello World! میسازد.
شکل ۲-۱۷. پروژهٔ Minimal در ASP.NET Core
شکل ۲-۱۸ Minimal APIای را نشان میدهد که Swagger Documentation را برای Endpoint مربوط به Hello ارائه میکند. IHelloService در بالای فایل بهصورت Scoped در Dependency Injection ثبت شده است. C# همچنین اجازه میدهد Lambda Expressionها با Attributeهایی مانند FromRoute Decorate شوند تا Route Attribute با نام name به Parameter رشتهای name Map شود.
شکل ۲-۱۸. Minimal API با Dependency Injection، Attribute روی Lambda و مستندات Swagger
شکل ۲-۱۹. Swagger UI برای Minimal API نمونه
من این روش توسعهٔ API را دوست دارم و در عمل تقریباً بهصورت پیشفرض آن را به ASP.NET Core Web API مبتنی بر Controller ترجیح میدهم.
جمعبندی
در این فصل با ASP.NET Core، مبانی آن و برخی Frameworkهایی که پشتیبانی میکند آشنا شدید. ASP.NET Core یک Framework پویا است؛ فناوریهای متنوع وب را پشتیبانی میکند و Microsoft برای آن مستندات مناسبی ارائه کرده است. به همین دلیل Framework محبوب من برای توسعهٔ برنامههای وب است و امیدوارم شما نیز از کار با آن لذت ببرید.
در ادامهٔ کتاب با ساخت Minimal APIها خواهیم دید ASP.NET Core 8 تا چه اندازه Framework قدرتمندی است.