บทความชุด .NET 6 แบบจับมือทำ — #11: API Project

Ponggun
T. T. Software Solution
5 min readFeb 18, 2023

ติดตั้ง 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
List ของ Migration Files และรายละเอียดของการเพิ่ม Tables

ติดตั้งการรัน 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);
}
}
บรรทัดที่ 43 คือการสั่งให้ API มีการรัน Migration ที่ยังค้างอยู่ ส่วนบรรทัดที่ 44 คือการนำ Seed Data Script จาก Infra Project มารันเพื่อเพิ่มข้อมูลที่เกี่ยวข้องเบื้องต้น

สั่งรัน Migration Script เพื่อสร้าง Tables

สั่งรัน API Project เพื่อรัน Migration เพื่อสร้าง Tables ลงไปยัง Database (ตัว CLI จะช่วยสร้าง Database ให้ถ้ายังไม่มี Database นี้อยู่) และรัน Seed Data ให้ด้วย ทีเดียวพร้อมใช้ฮะ สบายสุดๆๆๆ

dotnet run
ใช้ Dbeaver สร้าง Database Diagram เพื่อดูความสัมพันธ์ที่เกิดขึ้น

รายละเอียดของ Tables

  • _EFMigrationsHistory ใช้เพื่อเก็บรายละเอียดของ Migration Files ที่ถูกรันไปแล้ว
  • Provinces ใช้เพื่อเก็บข้อมูลจังหวัดที่อ้างอิงไปยัง Domain Model Province ใน Domain Project
มี Seed Data มาให้เลย เย้
  • 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"
}
เตรียมช่องทางสำหรับการ Config Email Provider ทั้ง 2 เจ้า

หรือเอาจากลิ้งนี้เบย

เสร็จแล้วให้ไปทำการติดตั้ง 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));
เท่านี้ Configuration Items สำหรับ Email Provider ก็พร้อมแล้วคร้าบ

หรือเอาจากลิ้งนี้เบย

แล้วต่อด้วยการติดตั้ง 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 ในลิ้งนี้

Web API นี้จะทำการทดลองนำ Token Server ที่มีการเรียกใช้ IEmailProvider มาทดลองส่ง Token Email กันนะครับ

ให้เราทดลองรันและลองทดสอบเรียก API นี้ผ่าน Swagger ดูนะครับ

เราจะพบว่าถ้าเราติดตั้ง IEmailProvider ด้วย SendgridEmailProvider แล้วระบบจะสมมติว่าทำการส่งเมล์ด้วย Sendgrid นะครับ
แต่ถ้าถ้าเราติดตั้ง IEmailProvider ด้วย MailGunEmailProvider แล้วระบบจะสมมติว่าทำการส่งเมล์ด้วย MailGun แทนนะครับ ซึ่งเราจะพบว่าเราไม่ได้ต้องย้อนกลับไปแก้ไข Code ที่ Core Project เลยนะครับ แค่เราเปลี่ยนการติดตั้งที่ API Project ก็ได้ผลลัพธ์ที่แตกต่างกันแล้ว สบายเลย

สร้าง Province Controller

ให้เราไปที่ Folder Controllers และสร้าง File ชื่อ ProvinceController.cs พร้อมกับ Copy Code ในลิ้งนี้ได้เลยนะครับ

โดยในการพัฒนา RESTFul API นี้เราจะทำการกำหนด HTTP Methods ตามมาตราฐานตารางข้างล่างเลยนะครับ กล่าวคือ

  • GET ใช้สำหรับการ ดึง ข้อมูล
  • POST ใช้สำหรับการ เพิ่ม ข้อมูล
  • PUT ใช้สำหรับการ แก้ไข ข้อมูล
  • DELETE ใช้สำหรับการ ลบ ข้อมูล
learn.microsoft.com/first-web-api

จาก 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 ครับ

--

--

Ponggun
T. T. Software Solution

Development Manager, Web Developer with ASP.Net, ASP.net Core, Azure and Microsoft Technologies