.NET 提供了很多種API 介接模式:WCF、Web API、gRPC。現在.NET Core 又多了一種新架構: Minimal APIs。初步觀察,在結構上非常簡潔,馬上來研究一下。
第一個目標是:以Minimal API 架構提供RESTful API,好像有點饒口,總之就是提供一個不用身分驗證的 GET/POST/PUT/DELETE。
官方範例是將所有的API 端點都定義在Program.cs
裡面,如果要提供的API 只有兩三隻,直接照官方範例寫完就行了,本文結束!收工!!
沒有啦,基本上Minimal APIs 採用擴充方法定義要走哪一種HTTP Method、routing、lambda 方式傳入業務邏輯。
先照著官方教學走一遍可以得出以下的心得:
- 可幫routing 分group 方便管理。
- 可將lambda 的業務邏輯拉出去,改傳入獨立方法。
- 回傳TypeResults 而非Results 以便於程式碼閱讀及測試。
請直接閱讀程式及註解:
using Boren.MiniAPI.WebApplication;
using Microsoft.EntityFrameworkCore;
// builder 可以註冊DBContext,想當然也可以註冊Class
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();
// 一、定義完整routing
// API 設定domain/todoitems1 以HTTPGET 進行呼叫,回傳Todos 資料
app.MapGet("/todoitems1", async (TodoDb db) =>
await db.Todos.ToListAsync());
// 二、使用MapGroup,將routing 分類
var todoItems = app.MapGroup("/todoitems2");
// 等於domain/todoitems2/{id} 走HTTPGET
todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id) is Todo todo ? Results.Ok(todo) : Results.NotFound());
// 等於domain/ 走HTTPPOST
todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/{todo.Id}", todo);
});
// 三、從lambda 改成獨立方法
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
// Results 與TypedResults 差異
// Results 回傳IResult,TypedResults 回傳IResult 的實作
// 意即IResult 要做型別轉換才能知道實體類別
// 所以推薦使用TypedResults 以便於程式碼閱讀及測試
return TypedResults.NotFound();
}
todoItems.MapDelete("/{id}", DeleteTodo);
// return Results 需要呼叫Produces 以供OpenApi 產生文件
// return TypedResults 則不需要
app.MapGet("/hello", () => Results.Ok(new Todo() { Name = "Hello World!" }))
.Produces<Todo>();
app.MapGet("/hello2", () => TypedResults.Ok(new Todo() { Name = "Hello World!" }));
app.Run();
但由於太多程式放在Program.cs 是很明顯的Bad-smell,所以當面臨到API 複雜度提升時,可以可以採取以下作法將不同群組的Endpoint 註冊整理成擴充方法。當拉成方法後,下一步就可能要拉成獨立類別。此時就可以用擴充方法註冊同一個domain的邏輯。
// 擴充類別、方法,將Endpoints 註冊集中
public static class Endpoints
{
public static void MapClassEndpoints(this WebApplication app)
{
var group = app.MapGroup("/class");
group.MapGet("/", () => "Get classes list");
}
public static void MapStudentEndpoints(this WebApplication app)
{
var group = app.MapGroup("/students");
// 配合D.I.
group.MapGet("/", (IStudentService service) => service.ReadAll());
group.MapGet("/{Id}", (int id, IStudentService service) => service.Read(id));
group.MapPost("/", (Student student, IStudentService service) => service.Create(student));
}
}
public static class Program
{
static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddScoped<IStudentService, StudentService>();
var app = builder.Build();
// 利用擴充方法綁定Endpoints
app.MapClassEndpoints();
app.MapStudentEndpoints();
app.Run();
}
}
引入物件導向概念之後,程式碼就可以分類清楚、做更有效的管理啦。
以下是API 測試成功畫面
References:
https://www.tessferrandez.com/blog/2023/10/31/organizing-minimal-apis.html
https://blog.darkthread.net/blog/minimal-api/
https://www.youtube.com/watch?v=i-xJ97uJ9qY&ab_channel=IAmTimCorey