【ASP.NET Core】體驗一下 Mini Web API

東邪獨孤發表於2021-11-16

在上一篇水文中,老周給大夥伴們簡單演示了通過 Socket 程式設計的方式控制 MPD (在樹莓派上)。按照計劃,老周還想給大夥伴們演示一下使用 Web API 來封裝對 MPD 控制。思路很 Easy,樹莓派上使用本地 Socket 來封裝一下,然後以 Web API 的方式對客戶端公開。這樣有一個好處:之後不管你打算把客戶端做成桌面視窗,還是 Web 頁面,或是做成手機 App,你都可以直接呼叫這套 Web API。這樣一來,很多程式碼就不必重複寫了,省時省力,減少腦細胞的大量死亡。

如果大家比較關心時事的話,應該知道 .NET 6 的 ASP.NET Core 有一個新特性—— Mini API,或 Mini Web API。有一個不錯的翻譯叫做“極簡 API”。其實,極簡的不只是 Web API,整個 ASP.NET Core 應用專案的結構都精簡了不少。最讓老周高興的就是沒有了 Startup 類(其實早期版本中也可以不使用 Startup,如果你以前看過老周的誤人子弟教程的話,你應該有印象),也不用費心地搞個什麼 ConfigureService 又要弄個 Configure 方法約定了。再配合 C# 9 的新功能,連 Main 方法都省了。所以初始化配置工作都可以在一個程式碼檔案中搞定。

使用這個簡化版的 Web API 來做 MPD 的封裝還真的不錯。不過,本文老周先不弄這個,先讓大夥伴們瞭解一下 Mini API 怎麼玩——權且當作預備知識。

.NET 6 還有個新功能也不錯,就是全域性的 using 指令。以前在 C 語言中,如果你寫一個 abc.h 標頭檔案,裡面放上這些程式碼:

#ifndef _ABC_H_
#define _ABC_H_

#include "nc.h"
#include "nt.h"
#include "zz.h"
#include "xb.h"
#include "sb.h"

#endif

然後在程式碼檔案中 include 一個這個 abc.h 就可以間接引用這些標頭檔案,但 C# 中沒有這種玩法,每個程式碼檔案要用到哪些名稱空間,都要寫一遍 using 指令。在隨同 .NET 6 一同釋出的 C# 10 中終於有全域性 using 了。只要你在其中一個程式碼檔案中寫上全域性 using 指令,然後其他程式碼檔案中就不必再 using 了。

方法是在 using 前加上 global 就行了。C# 雖然早有 global 關鍵字,但在過去這個並不是全域性 using 用的,而是專用來標識 .NET 框架中的名稱空間的,主要是防止名稱空間重名的。

 

好了,我們們現在就去嗨一下 Mini API吧。

此處假設你已經安裝好 VS 2022 和 .NET 6。在建立新專案時選擇空白 ASP.NET Core 應用程式。用空白專案方便稍後寫自己的 Code。

 

然後輸入專案名稱以及存放路徑。

 

選擇.net 版本號。

 

  最後,確定新建立專案。

---------------------------------------------------------------------------------

專案建立後你會發現,真TM簡潔了不少,Program.cs 檔案中只有這麼幾行。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

第一行:呼叫 CreateBuilder 方法建立一個builder ,這個 builder 隨後用於構建 Web 應用程式。

第二行:直接就用預設的引數構建了一個 app 物件。

第三行:配置 HTTP 管道——怎麼處理HTTP請求。此處配置表明只向客戶端返回“Hello World!”。

第四行:執行 app。

你,不用再寫 Startup 類了,也不用遵守方法簽約定去寫 ConfigureServices 等方法了。

要配置服務咋辦?直接 Services Add。例如

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddRazorPages();
builder.Services.AddAntiforgery()
                .AddWebEncoders()
                .AddSingleton<XXX, YYY>();
var app = builder.Build();
app.MapGet("/", () => "Hello World!");

app.Run();

看看是不是很整齊?注意這個過程是在 build 方法呼叫之前完成的。原因和以前 Startup 類中寫方法一樣,Add 服務是宣告我們們的應用程式中要使用哪些功能,哪些物件被用於依賴注入,一旦 build 了,程式的功能結構就確定下來了,所以應用程式構建後就不再修改其功能了。

以前在寫 Startup 時,我們知道,還有一個 Configure 方法用來配置中介軟體的,也就是剛剛說的配置HTTP管道。build 方法構建 app 後,就可以直接通過這個 app 例項來配置你要 Use 的東西。例如

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// 中介軟體配置
app.MapGet("/", () => "Hello World!");
app.UseRouting();
app.UseCookiePolicy();
app.Use(async (context, next) =>
{
    context.Response.Headers.Add("Server", "Big Bomb");
    await next();
});

 app.MapControllers();
 app.MapBlazorHub();
 app.MapRazorPages();

app.Run();

如果我們要編寫 Mini API,不需要 Add 什麼 Service,也不需要 Use 什麼元件,在 build 和 app.run 之間直接 MapXXX 就行了。XXX 指 HTTP 請求方法,比如 GET、POST、PUT 等。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//====================================
// Mini API 寫在這裡
//====================================
app.Run();

 

相當有意思的是:這些 MapXXX 擴充套件方法都有一個 handler 引數,型別是 Delegate。也就是說你在呼叫時可以傳遞任何型別的委託物件。於是,就可以這樣搞:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseHttpsRedirection();
//====================================
// Mini API 寫在這裡
app.MapGet("/greet", () =>
{
    return "What the fuck?";
});
//====================================
app.Run();

一個 Web API 就完成了。真的,可以用了,不信執行下看看。拿出祕密武器——Postman,測試一下。

 

 

用 Postman 還是有些不夠爽,而且這貨現在越做越複雜,還整天叫你註冊帳號,實屬無趣。我們改為用 swagger 來測試。開啟 Nuget 包管理器,搜尋 swagger。

 

 安裝這個包包。

 

把程式碼改一下。

var builder = WebApplication.CreateBuilder(args);
// 不要忘了加這兩個服務
builder.Services.AddSwaggerGen();
builder.Services.AddEndpointsApiExplorer();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
//====================================
// Mini API 寫在這裡
app.MapGet("/greet", () =>
{
    return "What the fuck?";
});
//====================================
app.Run();

這樣一來,我們們在開發測試階段就可以直接用這個元件在 Web 頁上測試 API 的呼叫了,不再啟動其他工具軟體了。

執行該專案,然後瀏覽器定位到 http(s)://root_url:port/swagger。

比如,我執行後得到的URL是 https://localhost:7189,那麼在瀏覽器中開啟 https://localhost:7189/swagger。

 

 

點選右邊的“try it out”,然後點 “Execute” 按鈕,就能看到執行結果了。

 

 

怎麼樣,好用吧?我們們再新增一個API,這次要帶引數的 POST 請求。

app.MapPost("/submit", (string name, int age) =>
{
    return $"你提交的內容:{name} - {age}";
});

這個 API 帶兩個引數,分別取名為 name,age。在呼叫時,是通過請求的 body 來提供的,格式為 JSON。

前面老周說過,這些MapXXX方法的委託引數是 Delegate 型別,所以你可以用任意型別的委託,有引數的沒引數的,有返回值的沒返回值的。

每次除錯時都要在瀏覽器地址輸入 http(s)://root/swagger 太TM不方便了,我們們可以配置一下,讓其自動定位到 swagger 下。開啟專案屬性視窗,轉到“除錯”標籤。

 

 點選頁面上的“開啟除錯啟動配置檔案 UI”連結。

 

 把滾動條往地獄方向拉,一直拉到看到“URL”標題,在文字中填上 swagger。搞定。

現在,你直接執行專案,就自動開啟 API 列表了。點選展開 submit,再點“try it out”。

 

 為 name 和 age 引數填上值,執行。

 

 

下面再舉一例,請求方式為 GET,引數來自 URL 查詢(即帶 ? 的URL,如 /abc?t=3000)。

app.MapGet("/md5", ([FromQuery(Name = "msg")] string data) =>
{
    byte[] buffer = Encoding.UTF8.GetBytes(data);
    using MD5 md5ec = MD5.Create();
    byte[] comres = md5ec.ComputeHash(buffer);
    return $"加密結果:{Convert.ToHexString(comres).ToLower()}";
});

這個 API 的功能:接收一個字串型別的物件,對其作 MD5 運算,然後返回結果。這個請求是從查詢字串中得到引數 data 的值的,所以要加上 FromQuery 特性,而且,實際傳值時查詢引數的名稱與API的引數名稱不同,故要用 Name = .... 明確指定,要從 msg 查詢引數中提取值。

綜上,此API的呼叫方式為 GET /md5?msg=呵呵哈哈呵呵哈

 

 

我們們玩這一步了,你心中一定有個高大上的疑問:這貨支援依賴注入乎?

很好,老周也有此疑問,要不,我們們搞搞看。

public interface IComputer
{
    int RunIt(int x, int y, int z);
}

internal class ComputerService : IComputer
{
    public int RunIt(int x, int y, int z)
    {
        return x - y - z;
    }
}

我定義了一個服務介面,裡面有個 RunIt 方法;然後俺實現之。邏輯很簡單,x、y、z 三數相減。

接下來改程式碼,在 build 方法呼叫之前註冊服務,我們們就註冊個單例項模式吧,全域性共享一個例項。

var builder = WebApplication.CreateBuilder(args);
……
builder.Services.AddSingleton<IComputer, ComputerService>();

然後,我們完成 API。

app.MapPost("/comp", (int n1, int n2, int n3, IComputer compsv) =>
{
    int r = compsv.RunIt(n1, n2, n3);
    return $"計算結果:{r}";
});

不要猶豫,執行它!

 

 得到結果:

 

 

在這個API中,n1,n2,n3 三個引數是從客戶端 POST 過來的,而最後一個引數是通過依賴注入得到引用物件的。那麼,把引數的位置調換一下,是否也可行呢?

app.MapPost("/comp", (IComputer compsv, int n1, int n2, int n3) =>
{
    int r = compsv.RunIt(n1, n2, n3);
    return $"計算結果:{r}";
});

然後再測,結果表明,是可行滴。

 

 

好了,今天的話題就聊到這兒了,下次我們們就用這個 Mini API 來封裝 MPD。

 

相關文章