【C#】ASP.NET Core Minimal APIs 研究

  • 74
  • 0
  • C#
  • 2024-05-31

.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 方式傳入業務邏輯。
先照著官方教學走一遍可以得出以下的心得:

  1. 可幫routing 分group 方便管理。
  2. 可將lambda 的業務邏輯拉出去,改傳入獨立方法。
  3. 回傳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