វិធីពីរយ៉ាងក្នុងការបង្កើត Endpoint ក្នុង ASP.NET Core
បើអ្នកទើបតែបើក Project ASP.NET Core ថ្មីមួយ ដើម្បីសរសេរ Web API ប្រហែលជាអ្នកធ្លាប់ឆ្ងល់ថា៖ គួរសរសេរជា Controllers ដូចមុន ឬសាកល្បង Minimal APIs ដែលគេនិយាយគ្នាច្រើនពេលនេះ?
និយាយឲ្យត្រង់ទៅ ទាំងពីរធ្វើការងារបានដូចគ្នា។ វាដើរលើ Pipeline តែមួយរបស់ ASP.NET Core ដូច្នេះ Feature ដែលអ្នកប្រើនៅខាងមួយ ក៏ប្រើបាននៅខាងម្ខាងទៀតដែរ។ អ្វីដែលខុសគ្នាពិតប្រាកដ គឺរបៀបដែលអ្នករៀបចំកូដ ហើយចម្លើយថាមួយណាល្អជាង ភាគច្រើនអាស្រ័យលើទំហំ Project និង Team របស់អ្នក។
ក្នុងអត្ថបទនេះ យើងនឹងសរសេរ Endpoint តែមួយ តាមវិធីទាំងពីរ រួចប្រៀបធៀបគ្នា ហើយបញ្ចប់ដោយគន្លឹះខ្លះៗ ដែលខ្ញុំយល់ថាមានប្រយោជន៍ពេលប្រើជាក់ស្តែង។
1. សរសេរជា Controller
Controller ជាវិធីដែល Developer .NET ភាគច្រើនស្គាល់ច្បាស់ ព្រោះវាមានតាំងពីសម័យ ASP.NET MVC មកម្ល៉េះ។ គំនិតរបស់វាសាមញ្ញ៖ Endpoint នីមួយៗជា Method មួយនៅក្នុង Class ហើយយើងប្រើ Attributes ដើម្បីប្រាប់ថា Method នោះឆ្លើយតបនឹង Route និង HTTP Verb មួយណា។
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpPost]
public ActionResult<Product> CreateProduct(Product product)
{
return Created($"/api/products/{product.Id}", product);
}
}តោះមើលម្តងមួយបន្ទាត់។
[ApiController] ជួយយើងច្រើនជាងអ្វីដែលមើលទៅ។ វាធ្វើ Model Validation ឲ្យដោយស្វ័យប្រវត្តិ ពោលគឺបើ Data ដែលផ្ញើមកមិនត្រឹមត្រូវ វានឹងត្រឡប់ 400 Bad Request ភ្លាម ដោយយើងមិនចាំបាច់សរសេរ if ពិនិត្យខ្លួនឯង។ វាក៏ដឹងដែរថា Product គួរតែយកពី Request Body។
[Route("api/[controller]")] កំណត់ Route មូលដ្ឋាន។ ពាក្យ [controller] នឹងត្រូវជំនួសដោយឈ្មោះ Class ដោយកាត់ពាក្យ "Controller" ចេញ ដូច្នេះ ProductsController ក្លាយជា api/products។
ControllerBase ផ្តល់ Helper Methods ងាយៗ ដូចជា Ok(), NotFound() និង Created()។ ក្នុងករណីនេះ Created(...) ត្រឡប់ Status 201 Created ព្រមទាំងដាក់ Location Header ដែលចង្អុលទៅ Resource ថ្មី។
“ចំណាំ៖ កុំភ្លេចចុះឈ្មោះ Controllers នៅក្នុង
Program.csផង។ បើគ្មានពីរបន្ទាត់នេះទេ Endpoint របស់អ្នកនឹងមិនដំណើរការឡើយ៖csharpbuilder.Services.AddControllers(); // ... app.MapControllers();
2. សរសេរជា Minimal API
Minimal APIs ចេញមកជាមួយ .NET 6។ គំនិតគឺកាត់បន្ថយ "ពិធីការ" ទាំងអស់ចោល។ មិនចាំបាច់មាន Class មិនចាំបាច់មាន Attribute គ្រាន់តែប្រាប់ថា Route នេះ ធ្វើអ្វី តាមរយៈ Lambda មួយ។
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/api/products", (Product product) =>
{
return Results.Created($"/api/products/{product.Id}", product);
});
app.Run();អានហើយស្ទើរតែមិនចាំបាច់ពន្យល់។ app.MapPost(...) ភ្ជាប់ HTTP POST ទៅ /api/products។ Parameter product ក្នុង Lambda ត្រូវបាន Bind ពី Request Body ដោយស្វ័យប្រវត្តិ។ ហើយ Results.Created(...) ត្រឡប់ 201 Created ដូចគ្នានឹងកំណែ Controller ដែរ។
កូដទាំងមូលមានប្រហែលប្រាំបីបន្ទាត់ ហើយអ្នកអាច Run វាបានភ្លាម។
“កំណត់សម្គាល់៖ ក្នុងរូបភាពប្រៀបធៀបមួយចំនួនដែលចែកចាយតាមអ៊ីនធឺណិត អ្នកអាចឃើញ
using Microsoft.AspNetCore.Mvc;នៅលើកូដ Minimal API។ តាមពិតវាមិនចាំបាច់ទេ។ Web SDK មាន Implicit Usings ដែលនាំចូល Namespace ទាំងនោះឲ្យរួចហើយ។
3. ផ្លូវខុសគ្នា តែទៅដល់កន្លែងតែមួយ
ចំណុចដែលមនុស្សច្រើនមើលរំលង គឺថា Request មិនបានដឹងទេថាអ្នកសរសេរ Endpoint តាមរបៀបណា។ វាឆ្លងកាត់ Middleware ដូចគ្នា ទៅ Routing ដូចគ្នា ទើបបំបែកផ្លូវនៅចុងក្រោយបង្អស់៖
HTTP POST /api/products
│
▼
┌───────────────────────────┐
│ ASP.NET Core Pipeline │
│ (Middleware: Auth, CORS, │
│ Logging, Exception...) │
└─────────────┬─────────────┘
│
▼
┌─────────────┐
│ Routing │
└──────┬──────┘
┌──────────┴──────────┐
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ Controller │ │ Minimal API │
│ Action Method │ │ Lambda Handler │
│ + Filters + MVC │ │ + Endpoint Filters│
└─────────┬─────────┘ └─────────┬─────────┘
└──────────┬───────────┘
▼
201 Created + JSON Bodyនេះហើយជាមូលហេតុដែលយើងនិយាយថា "Same Power"។ Dependency Injection, Authentication, Authorization, Middleware, Logging និង OpenAPI ប្រើបានទាំងពីរខាង ដោយគ្មានការរឹតបន្តឹងអ្វីពិសេសឡើយ។
4. ប្រៀបធៀបក្បែរគ្នា
| លក្ខណៈ | Controllers | Minimal APIs |
|---|---|---|
| រចនាសម្ព័ន្ធ | ផ្អែកលើ Class មានរបៀបរៀបរយច្បាស់ | ផ្អែកលើ Function ខ្លី និងបត់បែនបាន |
| Boilerplate | ច្រើនជាង | តិចណាស់ |
| Startup | យឺតជាងបន្តិច ព្រោះត្រូវផ្ទុក MVC | លឿន និងស្រាលជាង |
| Native AOT | មិនគាំទ្រពេញលេញ | គាំទ្រ |
| Filters | Action / Result / Exception Filters | Endpoint Filters |
| Model Validation | មានស្រាប់តាមរយៈ [ApiController] |
មានស្រាប់តាំងពី .NET 10 (AddValidation()) ឬប្រើ FluentValidation |
| ការធ្វើ Test | Test តាម Class និង Method | Test តាម Handler ឬ Integration Test |
| ល្អសម្រាប់ | Application ធំ និង Team ធំ | Microservices, Prototype, API តូចៗ |
5. ដូច្នេះ គួរជ្រើសមួយណា?
ខ្ញុំនឹងជ្រើស Controllers ពេលដែល Project ធំ មាន Endpoint រាប់រយ ហើយមានមនុស្សច្រើននាក់ធ្វើការលើ Codebase តែមួយ។ ក្នុងស្ថានភាពបែបនេះ Convention ដែលច្បាស់លាស់របស់ Controllers ជួយឲ្យអ្នកថ្មីចូលមក ដឹងភ្លាមថាកូដនៅទីណា។ វាក៏សមរម្យដែរ ពេលអ្នកពឹងលើ Feature MVC ច្រើន ដូចជា Custom Model Binders ឬ Action Filters ស្មុគស្មាញ ឬពេលកំពុងថែទាំ Project ចាស់ដែលប្រើ Controllers ស្រាប់។ គ្មានហេតុផលអ្វីត្រូវសរសេរឡើងវិញទាំងអស់នោះទេ។
ផ្ទុយទៅវិញ Minimal APIs ពិតជាល្អសម្រាប់ Microservice ឬ API តូចដែលមាន Endpoint មិនច្រើន។ បើអ្នកខ្វល់ពី Performance និង Startup Time ចង់ Deploy ជាមួយ Native AOT ឬចង់បាន Container Image តូច វាគឺជាជម្រើសដ៏ល្អ។ ហើយពេលចង់សាកល្បងគំនិតអ្វីមួយឲ្យលឿន Minimal APIs អនុញ្ញាតឲ្យអ្នកផ្តោតលើ Logic ជាជាងរៀបចំ Structure។
បើនៅតែស្ទាក់ស្ទើរ ផែនទីតូចនេះអាចជួយបាន៖
ចាប់ផ្តើម Project ថ្មី
│
▼
API មាន Endpoint ច្រើន និង Team ធំ?
│ │
មែន ទេ
│ │
▼ ▼
ត្រូវការ MVC Features? Microservice ឬ API តូច?
│ │ │
មែន ទេ មែន
│ │ │
▼ ▼ ▼
Controllers Minimal APIs Minimal APIs
+ Route Groups6. គន្លឹះ៖ Minimal APIs មិនមែនមានន័យថាដាក់គ្រប់យ៉ាងក្នុង Program.cs ទេ
ការយល់ច្រឡំមួយដែលខ្ញុំឃើញញឹកញាប់ គឺគិតថាប្រើ Minimal APIs មានន័យថាត្រូវដាក់ Endpoint ទាំងអស់ក្នុង Program.cs ហើយចុងក្រោយ File នោះវែងរាប់ពាន់បន្ទាត់។ តាមពិត យើងអាចបំបែកវាជា File ដាច់ៗបាន ដោយប្រើ Route Groups (មានតាំងពី .NET 7) រួមជាមួយ Extension Methods៖
// ProductEndpoints.cs
public static class ProductEndpoints
{
public static RouteGroupBuilder MapProductEndpoints(this IEndpointRouteBuilder routes)
{
var group = routes.MapGroup("/api/products")
.WithTags("Products");
group.MapGet("/{id:int}", GetById);
group.MapPost("/", Create);
return group;
}
static async Task<IResult> GetById(int id, IProductService service)
{
var product = await service.GetByIdAsync(id);
return product is null ? TypedResults.NotFound() : TypedResults.Ok(product);
}
static async Task<IResult> Create(Product product, IProductService service)
{
var created = await service.CreateAsync(product);
return TypedResults.Created($"/api/products/{created.Id}", created);
}
}បន្ទាប់មក Program.cs នៅតែស្អាត ត្រឹមតែមួយបន្ទាត់៖
// Program.cs
app.MapProductEndpoints();សូមកត់សម្គាល់ថាខ្ញុំប្រើ TypedResults ជំនួស Results។ ភាពខុសគ្នាតូចមួយនេះ ជួយឲ្យ OpenAPI ដឹងពី Response Type ពិតប្រាកដ ហើយធ្វើឲ្យការសរសេរ Unit Test ស្រួលជាងមុនច្រើន។
សរុបមក
Controllers ផ្តល់រចនាសម្ព័ន្ធ ដែលសមស្របសម្រាប់ Application ធំ។ Minimal APIs ផ្តល់ភាពសាមញ្ញ និងល្បឿន ដែលសមស្របសម្រាប់ API តូច និង Microservices។ ហើយទាំងពីរប្រើ Pipeline និង Feature ដូចគ្នារបស់ ASP.NET Core។
គ្មានជម្រើសណាមួយ "ត្រូវ" ជានិច្ចនោះទេ។ អ្នកថែមទាំងអាចប្រើទាំងពីររួមគ្នាក្នុង Project តែមួយបានទៀតផង។ ឧទាហរណ៍ រក្សា Controllers ចាស់ៗទុក ហើយសរសេរ Endpoint ថ្មីៗជា Minimal APIs។
បើអ្នកចង់ដឹងថាមួយណាត្រូវនឹងរបៀបធ្វើការរបស់អ្នក វិធីល្អបំផុតគឺសាកសរសេរ CRUD API តូចមួយ តាមវិធីទាំងពីរ រួចមើលថាមួយណាអ្នកចូលចិត្តជាង។