- สร้าง REST API ด้วย ASP.NET Core แบบทีละขั้นตอน
- สิ่งที่จะสร้าง
- 1. เตรียมเครื่องมือ
- 2. สร้างโครงการ Web API
- 3. โครงสร้างที่เราจะใช้
- 4. ติดตั้ง Entity Framework Core และ SQLite
- 5. สร้าง Product Model
- 6. สร้าง DTO
- 7. สร้าง DbContext
- 8. กำหนด Connection String
- 9. ลงทะเบียน Service ใน Program.cs
- 10. สร้าง Migration
- 11. สร้าง ProductsController
- 12. ทำความเข้าใจ Routing
- 13. HTTP Status Code ที่ควรเข้าใจ
- 14. ทดสอบ POST
- 15. ทดสอบ GET
- 16. ทดสอบ PUT
- 17. ทดสอบ DELETE
- 18. OpenAPI
- 19. เหตุใดจึงใช้ AsNoTracking()
- 20. Async/Await ใน Web API
- 21. Controller vs Minimal API
- 22. Dependency Injection
- 23. แนวทางจัดโครงสร้างเมื่อระบบใหญ่ขึ้น
- 24. Configuration และ Secret
- 25. Logging
- 26. Production API ควรเพิ่มอะไรอีก
- 27. ลำดับการทำงานของ POST
- 28. สรุป
- คำสั่งสรุป
- หัวข้อ Workshop ต่อเนื่อง
#สร้าง REST API ด้วย ASP.NET Core แบบทีละขั้นตอน
ASP.NET Core เป็น Web Framework ของ .NET ที่เหมาะสำหรับพัฒนา Web API และระบบ Backend ที่ต้องการประสิทธิภาพสูง รองรับ Dependency Injection, Configuration, Logging, Authentication และ OpenAPI โดยตรงใน ecosystem ของ .NET
บทความนี้เป็น Workshop สร้าง Product API ตั้งแต่สร้างโครงการ ไปจนถึง CRUD ด้วย Entity Framework Core และ SQLite โดยเลือกใช้แนวทาง Controller-based API เพื่อให้เห็นการแยกความรับผิดชอบของแต่ละส่วนอย่างชัดเจน
หมายเหตุ: คำสั่งและ package version ควรปรับให้ตรงกับ .NET SDK ที่ติดตั้งอยู่ในเครื่อง โดยตรวจสอบด้วย
dotnet --version
#สิ่งที่จะสร้าง
API ตัวอย่างมี endpoint หลักดังนี้
Method Endpoint หน้าที่
GET /api/products อ่านสินค้าทั้งหมด
GET /api/products/{id} อ่านสินค้าตาม ID
POST /api/products เพิ่มสินค้า
PUT /api/products/{id} แก้ไขสินค้า
DELETE /api/products/{id} ลบสินค้า
ภาพรวมสถาปัตยกรรม:
Client / Frontend / Mobile
|
| HTTP + JSON
v
ASP.NET Core Web API
|
+-- Controller
+-- DTO / Validation
+-- Dependency Injection
+-- Entity Framework Core
|
v
SQLite
#1. เตรียมเครื่องมือ
ติดตั้ง .NET SDK จากเว็บไซต์ .NET แล้วตรวจสอบ:
dotnet --version
dotnet --info
สามารถใช้ Visual Studio, Visual Studio Code, JetBrains Rider หรือ editor ที่รองรับ C# ได้
ตรวจสอบ template:
dotnet new list
#2. สร้างโครงการ Web API
สร้างโปรเจกต์:
dotnet new webapi -n ProductApi --use-controllers
cd ProductApi
ทดลอง build:
dotnet build
รัน:
dotnet run
หรือให้ reload เมื่อ source code เปลี่ยน:
dotnet watch run
Terminal จะแสดง URL เช่น:
http://localhost:5000
https://localhost:7000
เลข port จริงขึ้นอยู่กับ configuration ของโปรเจกต์
#3. โครงสร้างที่เราจะใช้
จัดโครงสร้างประมาณนี้:
ProductApi/
├── Controllers/
│ └── ProductsController.cs
├── Data/
│ └── AppDbContext.cs
├── DTOs/
│ ├── CreateProductDto.cs
│ └── UpdateProductDto.cs
├── Models/
│ └── Product.cs
├── Program.cs
├── appsettings.json
└── ProductApi.csproj
แนวคิดคือไม่ให้ Controller ทำทุกอย่างในไฟล์เดียว และไม่รับ Entity จาก request โดยตรงเมื่อ API เริ่มมีความซับซ้อน
#4. ติดตั้ง Entity Framework Core และ SQLite
ติดตั้ง provider:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
ติดตั้ง dotnet-ef หากเครื่องยังไม่มี:
dotnet tool install --global dotnet-ef
หากมีอยู่แล้วและต้องการอัปเดต:
dotnet tool update --global dotnet-ef
ตรวจสอบ:
dotnet ef --version
ควรใช้ major version ของ EF Core ให้สอดคล้องกับ target framework/SDK ของโครงการ
#5. สร้าง Product Model
สร้าง Models/Product.cs
namespace ProductApi.Models;
public class Product
{
public int Id { get; set; }
public required string Name { get; set; }
public decimal Price { get; set; }
public int Stock { get; set; }
}
Id จะใช้เป็น Primary Key ตาม convention ของ EF Core
#6. สร้าง DTO
การแยก DTO ช่วยควบคุมข้อมูลที่ client สามารถส่งเข้า API และลด coupling ระหว่าง API contract กับ database entity
สร้าง DTOs/CreateProductDto.cs
using System.ComponentModel.DataAnnotations;
namespace ProductApi.DTOs;
public class CreateProductDto
{
[Required]
[StringLength(200)]
public string Name { get; set; } = string.Empty;
[Range(0, double.MaxValue)]
public decimal Price { get; set; }
[Range(0, int.MaxValue)]
public int Stock { get; set; }
}
สร้าง DTOs/UpdateProductDto.cs
using System.ComponentModel.DataAnnotations;
namespace ProductApi.DTOs;
public class UpdateProductDto
{
[Required]
[StringLength(200)]
public string Name { get; set; } = string.Empty;
[Range(0, double.MaxValue)]
public decimal Price { get; set; }
[Range(0, int.MaxValue)]
public int Stock { get; set; }
}
เมื่อใช้ [ApiController] validation error สามารถถูกแปลงเป็น HTTP 400 โดย
framework ได้อัตโนมัติ
#7. สร้าง DbContext
สร้าง Data/AppDbContext.cs
using Microsoft.EntityFrameworkCore;
using ProductApi.Models;
namespace ProductApi.Data;
public class AppDbContext(DbContextOptions<AppDbContext> options)
: DbContext(options)
{
public DbSet<Product> Products => Set<Product>();
}
DbContext เป็นจุดกลางที่ EF Core ใช้ติดตาม entity และติดต่อฐานข้อมูล
#8. กำหนด Connection String
แก้ appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=products.db"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
SQLite จะเก็บข้อมูลไว้ในไฟล์ products.db
#9. ลงทะเบียน Service ใน Program.cs
แก้ Program.cs
using Microsoft.EntityFrameworkCore;
using ProductApi.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(
builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddOpenApi();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
app.MapControllers();
app.Run();
หัวใจสำคัญคือ Dependency Injection:
builder.Services.AddDbContext<AppDbContext>(...);
หลังจากนั้น Controller สามารถรับ AppDbContext ผ่าน constructor ได้
Template และ OpenAPI setup อาจแตกต่างกันเล็กน้อยตาม .NET SDK version ที่ใช้งาน
#10. สร้าง Migration
สร้าง migration แรก:
dotnet ef migrations add InitialCreate
นำ migration ไปใช้:
dotnet ef database update
จะได้ไฟล์ฐานข้อมูล:
products.db
เมื่อแก้ schema ในอนาคตสามารถสร้าง migration ใหม่ เช่น:
dotnet ef migrations add AddProductDescription
dotnet ef database update
#11. สร้าง ProductsController
สร้าง Controllers/ProductsController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ProductApi.Data;
using ProductApi.DTOs;
using ProductApi.Models;
namespace ProductApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class ProductsController(AppDbContext context) : ControllerBase
{
[HttpGet]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
{
return await context.Products
.AsNoTracking()
.ToListAsync();
}
[HttpGet("{id:int}")]
public async Task<ActionResult<Product>> GetProduct(int id)
{
var product = await context.Products
.AsNoTracking()
.FirstOrDefaultAsync(p => p.Id == id);
if (product is null)
{
return NotFound();
}
return product;
}
[HttpPost]
public async Task<ActionResult<Product>> CreateProduct(
CreateProductDto dto)
{
var product = new Product
{
Name = dto.Name,
Price = dto.Price,
Stock = dto.Stock
};
context.Products.Add(product);
await context.SaveChangesAsync();
return CreatedAtAction(
nameof(GetProduct),
new { id = product.Id },
product);
}
[HttpPut("{id:int}")]
public async Task<IActionResult> UpdateProduct(
int id,
UpdateProductDto dto)
{
var product = await context.Products.FindAsync(id);
if (product is null)
{
return NotFound();
}
product.Name = dto.Name;
product.Price = dto.Price;
product.Stock = dto.Stock;
await context.SaveChangesAsync();
return NoContent();
}
[HttpDelete("{id:int}")]
public async Task<IActionResult> DeleteProduct(int id)
{
var product = await context.Products.FindAsync(id);
if (product is null)
{
return NotFound();
}
context.Products.Remove(product);
await context.SaveChangesAsync();
return NoContent();
}
}
#12. ทำความเข้าใจ Routing
ส่วนนี้:
[Route("api/[controller]")]
เมื่อชื่อ class คือ:
ProductsController
route จะเป็น:
/api/products
จากนั้น attribute แต่ละ method กำหนด HTTP method เช่น:
[HttpGet]
[HttpGet("{id:int}")]
[HttpPost]
[HttpPut("{id:int}")]
[HttpDelete("{id:int}")]
#13. HTTP Status Code ที่ควรเข้าใจ
API ตัวอย่างใช้ status code ตาม semantics ของ HTTP:
Status ความหมาย ตัวอย่าง
200 OK GET สำเร็จ 201 Created POST สร้าง resource สำเร็จ 204 No Content PUT/DELETE สำเร็จโดยไม่ต้องส่ง body 400 Bad Request request/validation ไม่ถูกต้อง 404 Not Found ไม่พบ resource 500 Internal Server Error เกิดข้อผิดพลาดฝั่ง server
การเลือก status code ที่ถูกต้องช่วยให้ client เข้าใจผลของ request โดยไม่ต้องพึ่งข้อความเฉพาะระบบ
#14. ทดสอบ POST
รัน API:
dotnet run
ตัวอย่างด้วย curl:
curl -X POST "http://localhost:5000/api/products" \
-H "Content-Type: application/json" \
-d '{
"name": "Mechanical Keyboard",
"price": 2490,
"stock": 10
}'
Windows PowerShell สามารถใช้:
$body = @{
name = "Mechanical Keyboard"
price = 2490
stock = 10
} | ConvertTo-Json
Invoke-RestMethod `
-Uri "http://localhost:5000/api/products" `
-Method Post `
-ContentType "application/json" `
-Body $body
เปลี่ยน port ให้ตรงกับ URL ที่ dotnet run แสดง
#15. ทดสอบ GET
อ่านทั้งหมด:
curl "http://localhost:5000/api/products"
ผลลัพธ์ตัวอย่าง:
[
{
"id": 1,
"name": "Mechanical Keyboard",
"price": 2490,
"stock": 10
}
]
อ่านตาม ID:
curl "http://localhost:5000/api/products/1"
#16. ทดสอบ PUT
curl -X PUT "http://localhost:5000/api/products/1" \
-H "Content-Type: application/json" \
-d '{
"name": "Mechanical Keyboard Pro",
"price": 2990,
"stock": 8
}'
เมื่อสำเร็จ API จะตอบ 204 No Content
#17. ทดสอบ DELETE
curl -X DELETE "http://localhost:5000/api/products/1"
จากนั้นลอง:
curl "http://localhost:5000/api/products/1"
ควรได้รับ 404 Not Found
#18. OpenAPI
ใน Development environment ตัวอย่างนี้เปิด OpenAPI document ผ่าน:
builder.Services.AddOpenApi();
และ:
app.MapOpenApi();
โดยทั่วไปเอกสาร JSON จะเข้าถึงได้จาก endpoint เช่น:
/openapi/v1.json
OpenAPI เป็น machine-readable API contract ซึ่งสามารถนำไปใช้กับ API client, documentation UI และ code generator ได้
หากต้องการ Swagger UI แบบ interactive สามารถเพิ่ม package/tool ที่รองรับ Swagger/OpenAPI UI ตาม version ของ ASP.NET Core ที่ใช้งาน
#19. เหตุใดจึงใช้ AsNoTracking()
ใน GET:
context.Products.AsNoTracking()
ใช้เมื่อข้อมูลถูกอ่านอย่างเดียว EF Core ไม่จำเป็นต้องติดตามการเปลี่ยนแปลงของ entity เหล่านั้น จึงเหมาะกับ read-only query ของ Web API
#20. Async/Await ใน Web API
ตัวอย่างใช้:
await context.Products.ToListAsync();
await context.SaveChangesAsync();
Database I/O เป็นงานที่ต้องรอ การใช้ asynchronous API ช่วยไม่ให้ request thread ถูก block ระหว่างรอ I/O และเหมาะกับ server ที่รองรับหลาย request พร้อมกัน
#21. Controller vs Minimal API
ASP.NET Core รองรับทั้งสองแนวทาง
#Controller-based
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
}
เหมาะกับระบบที่ต้องการจัดกลุ่ม endpoint, filter, conventions และโครงสร้างที่ชัดเจน
#Minimal API
ตัวอย่าง:
app.MapGet("/api/hello", () =>
{
return Results.Ok(new { message = "Hello ASP.NET Core" });
});
เหมาะกับ API ขนาดเล็ก, prototype, microservice หรือ endpoint ที่ไม่ต้องการ Controller structure มากนัก
ทั้งสองแนวทางสามารถใช้งานใน ASP.NET Core application ได้ตามความเหมาะสม
#22. Dependency Injection
ASP.NET Core มี DI container ในตัว
เรา register:
builder.Services.AddDbContext<AppDbContext>(...);
และ inject:
public class ProductsController(AppDbContext context)
: ControllerBase
ในระบบจริงสามารถแยก business logic เป็น service:
ProductsController
|
v
IProductService
|
v
ProductService
|
v
AppDbContext
|
v
Database
ทำให้ Controller บางลงและทดสอบ business logic ได้ง่ายขึ้น
#23. แนวทางจัดโครงสร้างเมื่อระบบใหญ่ขึ้น
ตัวอย่าง:
ProductApi/
├── Controllers/
├── Data/
├── DTOs/
├── Models/
├── Services/
│ ├── IProductService.cs
│ └── ProductService.cs
├── Migrations/
├── Program.cs
└── appsettings.json
ระบบขนาดใหญ่กว่านี้อาจแยกเป็นหลาย project เช่น:
src/
├── ProductApi.Api
├── ProductApi.Application
├── ProductApi.Domain
└── ProductApi.Infrastructure
แต่ไม่จำเป็นต้องเริ่มด้วย architecture ที่ซับซ้อน หาก application ยังมีขนาดเล็ก
#24. Configuration และ Secret
ค่าที่ไม่เป็นความลับสามารถเก็บใน:
appsettings.json
appsettings.Development.json
แต่ password, token, API key หรือ production connection string ไม่ควร commit ลง Git repository
ใน development สามารถใช้ User Secrets:
dotnet user-secrets init
ตัวอย่าง:
dotnet user-secrets set "ApiSettings:ApiKey" "your-secret"
Production ควรใช้ secret/configuration mechanism ของ deployment platform
#25. Logging
สามารถ inject ILogger<T>:
private readonly ILogger<ProductsController> _logger;
public ProductsController(
AppDbContext context,
ILogger<ProductsController> logger)
{
_context = context;
_logger = logger;
}
แล้วบันทึก:
_logger.LogInformation(
"Reading product {ProductId}",
id);
ควรใช้ structured logging แทนการต่อ string เพื่อให้ระบบ observability ค้นหา field ได้ง่าย
#26. Production API ควรเพิ่มอะไรอีก
Workshop นี้เน้น CRUD พื้นฐาน ก่อนนำไป production ควรพิจารณา:
- Authentication และ Authorization เช่น JWT/OIDC
- HTTPS
- Global exception handling
- Problem Details
- Pagination / filtering / sorting
- DTO mapping
- API versioning เมื่อมีความจำเป็น
- CORS policy
- Rate limiting
- Health checks
- Structured logging และ observability
- Unit/Integration tests
- Database indexes
- Secrets management
- Docker
- CI/CD
- Security headers และ dependency scanning
#27. ลำดับการทำงานของ POST
เมื่อ client ส่ง:
POST /api/products
Content-Type: application/json
พร้อม:
{
"name": "Mouse",
"price": 990,
"stock": 20
}
flow โดยย่อ:
Client
|
| POST JSON
v
ASP.NET Core Routing
|
v
ProductsController
|
v
Model Binding + Validation
|
v
CreateProductDto
|
v
Product Entity
|
v
AppDbContext
|
v
EF Core
|
v
SQLite
|
v
201 Created
#28. สรุป
การสร้าง REST API ด้วย ASP.NET Core มีองค์ประกอบสำคัญที่ควรเข้าใจมากกว่าการเขียน CRUD ได้แก่ routing, model binding, validation, Dependency Injection, asynchronous I/O, HTTP semantics และการแยก API contract ด้วย DTO
Workshop นี้มี flow หลัก:
.NET SDK
↓
ASP.NET Core Web API
↓
Controller
↓
DTO + Validation
↓
Entity Framework Core
↓
SQLite
↓
REST API
เมื่อเข้าใจพื้นฐานแล้ว สามารถต่อยอดเป็นระบบจริงด้วย JWT/OIDC, PostgreSQL/SQL Server, Service Layer, Integration Testing, Docker, GitHub Actions, Kubernetes และ observability ได้
#คำสั่งสรุป
dotnet new webapi -n ProductApi --use-controllers
cd ProductApi
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet tool install --global dotnet-ef
dotnet ef migrations add InitialCreate
dotnet ef database update
dotnet build
dotnet run
#หัวข้อ Workshop ต่อเนื่อง
บทความนี้สามารถใช้เป็นพื้นฐานสำหรับ:
ASP.NET Core REST API
|
+-- EF Core + PostgreSQL
+-- JWT Authentication
+-- Role/Policy Authorization
+-- Unit Testing
+-- Integration Testing
+-- Docker
+-- GitHub Actions
+-- Kubernetes
+-- OpenTelemetry
+-- Clean Architecture