在上一篇水文中,老周給大伙伴們簡單演示了通過 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。