บทความชุด .NET 6 แบบจับมือทำ — #11: API Project
ติดตั้ง Entity Framework Nuget Package
- ติดตั้ง Microsoft.EntityFrameworkCore.Design เพื่อให้เราสามารถที่จะรันคำสั่งสำหรับการสร้าง Migration File ได้ครับ
ติดตั้ง Entity Framework ใน Source Code
- ติดตั้ง EF Core / PostgreSQL ใน
Program.cs
ด้วย Script
builder.Services.AddDbContext<DataContext>(opt =>{
opt.UseNpgsql(builder.Configuration.GetConnectionString(
"DefaultConnection"));
});
หรือ Copy Code ได้จากที่นี้เลยครับ
- เพิ่ม Connection String ใน appsettings.Development.json เพราะทำที่ Local
"ConnectionStrings": {
"DefaultConnection":
"Host=localhost;Username=postgres;Password=postgres; Database=postgres"
}
สร้าง Migration Script
- ติดตั้ง
dotnet ef cli tool
เพื่อให้เราสามารถรันคำสั่งที่เกี่ยวข้องกับการสร้าง Migration File ได้ครับ
dotnet tool install --global dotnet-ef --version 6.0.8
- เพิ่ม Migration File ใหม่ โดยเก็บไว้ที่ Infra Project และใช้ Database Connection String จาก API Project
ให้อยู่ที่ Root Project ก่อนน้า
cd .. // Go to root project
dotnet ef migrations add FirstMigration -p 3.Infra -s 4.API --output-dir Database/Migrations
ติดตั้งการรัน Migration และ Seed Data
- ติดตั้ง Function Seed Data และสั่งรันใน ใน
Program.cs
app.MapControllers();
await SeedDatabase();
app.Run();
async Task SeedDatabase()
{
using (var scope = app.Services.CreateScope())
{
var dbcontext =
scope.ServiceProvider.GetRequiredService<DataContext>();
// Run migration scripts
await dbcontext.Database.MigrateAsync();
// Seed data to the project
await Infra.Seed.SeedData(dbcontext);
}
}
สั่งรัน Migration Script เพื่อสร้าง Tables
สั่งรัน API Project เพื่อรัน Migration เพื่อสร้าง Tables ลงไปยัง Database (ตัว CLI จะช่วยสร้าง Database ให้ถ้ายังไม่มี Database นี้อยู่) และรัน Seed Data ให้ด้วย ทีเดียวพร้อมใช้ฮะ สบายสุดๆๆๆ
dotnet run
รายละเอียดของ Tables
_EFMigrationsHistory
ใช้เพื่อเก็บรายละเอียดของ Migration Files ที่ถูกรันไปแล้ว
Provinces
ใช้เพื่อเก็บข้อมูลจังหวัดที่อ้างอิงไปยัง Domain Model Province ใน Domain Project
PointOfInterests
ใช้เพื่อเก็บข้อมูลจังหวัดที่อ้างอิงไปยัง Domain Model PointOfInterest ใน Domain Project
ติดตั้ง Email Provider
หลังจากที่เรากำหนดมาตราฐานของ Email Provider Interface ใน Core Project และทำการพัฒนาใน Infra Project ให้สามารถรองรับ Configuration File แล้ว ก็มาถึงการกำหนด Configuration ที่จะใช้ใน API Project นี้นะครับ
ขั้นตอนแรกให้ไปเพิ่ม Configuration Items เพิ่มเติมใน appsettings.Development.json ดังนี้ครับ
"SendgridEmailProvider": {
"Host": "smtp.sendgrid.net",
"UserName": "SendgridUser",
"Password": "SendgridPassword"
},
"MailGunEmailProvider": {
"APIURL": "https://api.mailgun.net",
"APIKey": "MailGunAPIKey"
}
หรือเอาจากลิ้งนี้เบย
เสร็จแล้วให้ไปทำการติดตั้ง Email Provider Configuration ใน Program.cs นะครับ เพื่อให้สามารถใช้ Option Pattern ในการดึงข้อมูลจาก Configuration Files ได้
builder.Services.Configure<MailGunEmailProviderOptions>(
builder.Configuration.GetSection(MailGunEmailProviderOptions.ConfigItem));
builder.Services.Configure<SendgridEmailProviderOptions>(
builder.Configuration.GetSection(SendgridEmailProviderOptions.ConfigItem));
หรือเอาจากลิ้งนี้เบย
แล้วต่อด้วยการติดตั้ง Service Lifetimes ให้กับ Email Provider ด้วยนะครับ เท่านี้ Email ก็พร้อมใช้งานใน API Project แล้ว
สามารถย้อนกลับไปศึกษาเรื่อง Service Lifetimes ได้ที่นี้นะครับ
// Try to switch between Sendgrid and Mailgun
builder.Services.AddTransient<IEmailProvider, SendgridEmailProvider>();
//builder.Services.AddTransient<IEmailProvider, MailGunEmailProvider>();
สร้าง Token Controller
ให้เราไปที่ Folder Controllers และสร้าง File ชื่อ TokenController.cs พร้อมกับ Copy Code ในลิ้งนี้
ให้เราทดลองรันและลองทดสอบเรียก API นี้ผ่าน Swagger ดูนะครับ
สร้าง Province Controller
ให้เราไปที่ Folder Controllers และสร้าง File ชื่อ ProvinceController.cs พร้อมกับ Copy Code ในลิ้งนี้ได้เลยนะครับ
โดยในการพัฒนา RESTFul API นี้เราจะทำการกำหนด HTTP Methods ตามมาตราฐานตารางข้างล่างเลยนะครับ กล่าวคือ
GET
ใช้สำหรับการดึง
ข้อมูลPOST
ใช้สำหรับการเพิ่ม
ข้อมูลPUT
ใช้สำหรับการแก้ไข
ข้อมูลDELETE
ใช้สำหรับการลบ
ข้อมูล
จาก Code เราจะสังเกตุได้ว่า ในมุม Web API นั้น Code จะสั้นลงมาก เพราะเราเอา Logic ส่วนใหญ่ไปใส่ไว้ใน Core Project หมดแล้วนั้นเอง เย้
ทำให้สมมติเราเอา Core Project ไปใช้กับ Client อื่น เช่น Web UI, Console App ก็สามารถ Reuse Logic กันได้เต็มที่เลยครับไม่ต้องเขียนเพิ่มเยอะ
using Microsoft.AspNetCore.Mvc;
using _2.Core;
namespace _4.API.Controllers;
[ApiController]
[Route("[controller]")]
public class ProvinceController : ControllerBase
{
private readonly IProvinceService _provinceService;
public ProvinceController(IProvinceService provinceService)
{
this._provinceService = provinceService;
}
[HttpPost]
public async Task<ActionResult<ProvinceServiceResponse>> CreateNewProvinceAsync(ProvinceServiceInput input)
{
try
{
return Ok(await _provinceService.CreateNewProvinceAsync(input));
}
catch(Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}
[HttpPut("{id}")]
public async Task<ActionResult<ProvinceServiceResponse>> UpdateProvinceAsync(Guid id, ProvinceServiceInput input)
{
try
{
return Ok(await _provinceService.UpdateProvinceAsync(id, input));
}
catch(ArgumentException ex)
{
return StatusCode(StatusCodes.Status404NotFound, ex.Message);
}
catch(Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}
[HttpDelete("{id}")]
public async Task<ActionResult<ProvinceServiceResponse>> DeleteProvinceAsync(Guid id)
{
try
{
return Ok(await _provinceService.DeleteProvinceAsync(id));
}
catch(ArgumentException ex)
{
return StatusCode(StatusCodes.Status404NotFound, ex.Message);
}
catch(Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}
[HttpGet("{id}")]
public async Task<ActionResult<ProvinceServiceResponse>> GetProvinceAsync(Guid id)
{
try
{
return Ok(await _provinceService.GetProvinceAsync(id));
}
catch(ArgumentException ex)
{
return StatusCode(StatusCodes.Status404NotFound, ex.Message);
}
catch(Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}
[HttpGet("GetProvinceAsyncWithPointOfInterestAsync")]
public async Task<ActionResult<ProvinceServiceResponseWithPointOfInterest>> GetProvinceAsyncWithPointOfInterestAsync(Guid id)
{
try
{
return Ok(await _provinceService.GetProvinceAsyncWithPointOfInterestAsync(id));
}
catch(ArgumentException ex)
{
return StatusCode(StatusCodes.Status404NotFound, ex.Message);
}
catch(Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
}
}
เมื่อรันโปรแกรมขึ้นมาเราจะพบว่าได้โครงสร้างของ API ใกล้เคียงกับมาตราฐานข้างบนเลยครับ
ทดลองสร้าง Province ใหม่ จะพบว่าได้ผลลัพธ์ออกมาเรียบร้อยพร้อม ID ให้เราเอาไปใช้ต่อ
ทดลองเอา Id ที่สร้างใหม่ไปแก้ไขข้อมูล
ทดลองดึงข้อมูลด้วย Id ใหม่
ทดลองลบข้อมูลด้วย Id ใหม่ เนื่องจากเป็น Soft Delete ไม่ได้ลบออกจากระบบจริงๆ ทำให้เราเห็น Flag IsActive = false
ครับ
ลุยกันครับบบบ
- #0: เกริ่นนำ
- #1: ติดตั้งโปรแกรม
- #2: รวม Cheat Sheet
- #3: Empty Solution in GitHub
- #4: พัฒนาโครงสร้างของ Source Code
- #5: VS Code Debugging
- #6: Configuration
- #7: Dependency Injection & Service Lifetime
- #8: Domain Project
- #9: Core Project
- #10: Infra Project
- #11: API Project
- #12: Unit Testing
- #13: Logging and Monitoring
- #14:เพิ่มความปลอดภัยด้วย OAuth2 & OpenID Connect
- #15: Background Process & Scheduler
- #16: Health Checks
- #17: The Twelve-Factor App
- #18: Deployment — Docker
- #19: Deployment — Azure App Service
- #20: Deployment — Azure Container Apps