開春首文,今天老周就跟各位大夥伴們聊一個很簡單的話題:怎麼設定API響應的資料格式。
說本質一點,就是設定所返回內容的 MIME 型別(Content-Type 頭)。當然了,我們們不會使用在HTTP管道中插入中介軟體的方式來解決,因為:
A、這樣做會導致所有傳入傳出的HTTP訊息都被修改;
B、這樣會毀壞API應用的設計規範,弄得不倫不類、禮崩樂壞、不堪入目。
所以,今天的主角是一個特性類(Attribute),它的大名叫 ProducesAttribute,位於 Microsoft.AspNetCore.Mvc 名稱空間下。這麼一介紹,你肯定能找到它。
根據定義,該特性類可用於:類、方法。說得再直接一點,就是用於 Controller類 和 Action方法。
這個特性類用於 設定API返回資料的MIME型別,嗯,也就是所謂的格式了。最人性化最簡單的使用方法就是這樣:
[Produces("text/html")] [Produces("audio/wav")] [Produces("image/png")] [Produces("application/octet-stream")]
就是這樣,你希望返回的是啥東西,就用 Content-Type 字串來指定。
馬上,立刻,現在,就給大夥兒演示一個例子,讓 API 返回 text/json 型別的資料。因為預設情況下,API 返回資料使用 application/json 格式,所以,我們們要改為 text/json,就得用 Produces 特性。
首先,新建一個空的 ASP.NET Core 應用專案。老周喜歡空模板,易於 DIY,可折騰性強。
然後,在 Program.cs 檔案中註冊與 MVC 控制器有關的服務,以及Map一下相關中介軟體。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); //這一句不要忘了 app.Run();
接著,新建一個類,或者在“新建項”中選擇空的 API 控制器。
[Route("api/[controller]")] [ApiController] public class Demo : ControllerBase { …… }
Route 特性指定訪問這個控制器的 URL,[controller] 是個佔位符,訪問時用實際的控制器名來替換。比如,這裡的控制器的名字是 Demo,訪問時的URL就是 http://somehost/api/demo/xxxx。不過,這裡老周的命名不太規範,規範的命名應該是 DemoController。只是老周嫌它的字尾太長。
其實 API 和 MVC 的控制器實現起來一樣,但 API 沒有檢視,所以類繼承時,基類可以用 ControllerBase 類而不是 Controller 類。另外,在類上面加一個 ApiController 特性,表明這個 Demo 類是作為 API 控制器用的,並且它的派生類都作為 API 控制器。
好,我們先實現兩個 Action。
[Route("getbt")] [HttpGet] public string GetWTF() => "What the bitch"; [Route("getak")] [HttpGet] public IDictionary<string, int> GetAK() { // 返回一個類例項和返回字典物件 // 其JSON結構差不多 // 此處為了簡單,直接用字典 return new Dictionary<string, int> { ["item1"] = 10, ["item2"] = 49 }; }
Action 方法上指定的 Route 是相對於控制器類的 Route 的,即 /api/demo/getbt、/api/demo/getak。
這個相信各位看得懂,不用過多解釋,看不懂的肯定是因為你太謙虛了。
執行一下這個示例,直接通過瀏覽器的開發人員工具檢視,得知:
第一個 action 返回的 string 型別,因此預設選用 text/plain 格式(普通文字)。
第二個 action 返回的是字典物件,預設選擇 application/json 格式。
現在,把 Produces 特性用上,使其返回的資料變為 text/json 格式。
[Route("api/[controller]")] [ApiController] [Produces("text/json")] public class Demo : ControllerBase { …… }
再次執行,從瀏覽器的開發人員工具中檢視HTTP訊息。
不過,你得小心!如果你指定的格式與 API 所返回的物件無法相容,就會崩盤。比如,把上面的 getak 改成這樣:
[Route("getak")] [HttpGet] [Produces("text/plain")] public IDictionary<string, int> GetAK() { …… }
雖然 Demo 控制器類上應用了 Produces 特性指定了 text/json 格式,但這個方法上也應用了此特性,依據就近原則,程式會優先選用 text/plain 格式。在內部的處理機制中,這是不匹配的,除非方法的返回值型別是 string。
一旦執行,就會得到錯誤狀態碼。
下面演示一個返回 jpg 影像格式(即 image/jpeg)的例子。在剛才的 Demo 控制器類上增加一個方法,名為 GetImage。
[Route("getpic")] [HttpGet] [Produces("image/jpeg")] public Stream GetImage() { // 因為應用程式目錄和內容目錄相同 // 所以直接獲取Current即可 string dirpath = Directory.GetCurrentDirectory(); // 直接返回檔案流 return System.IO.File.OpenRead(Path.Combine(dirpath, "505.jpg")); }
方法的返回型別為 Stream 物件,套用 image/jpeg 格式是沒問題的,畢竟影像是以二進位制的方式響應的。
老周事先從網上找了一張圖片,命名為 505.jpg,放在專案根目錄下。你在測試時可以隨便找個圖片,或者拍一張妹子的照片(前提是妹子不會報警),放到專案目錄下即可,檔名自己改。
在瀏覽器中訪問後得到結果如下圖所示。
--------------------------------------------------- 異次元分界線 -----------------------------------------------------------
既然資料可以以 JSON 格式返回,那能不能返回 XML 格式呢?當然是可以的。
public class 帥哥 { public string Name { get; set; } public int Age { get; set; } public decimal Weight { get; set; } }
----------------------------------------------------------- [Route("getxml")] [HttpGet] [Produces("application/xml")] public 帥哥 GetXML() { return new 帥哥 { Name = "老周", Age = 93, Weight = 203.77M }; }
先是定義了一個新類,叫“帥哥”,接著,GetXML 方法返回一個“帥哥”型別的例項。注意此方法應用了 Produces 特性,指定返回的資料格式為 application/xml。
Web API 控制器預設是不啟用 XML 輸出支援的,所以在 Program.cs 檔案中,在註冊MVC功能到服務容器時,需要手動開啟對XML輸出的支援。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers().AddXmlSerializerFormatters(); var app = builder.Build(); ……
這樣一來,訪問 /api/demo/getxml 就能得到 XML 資料了。
好了,今天的文章就水到這裡了,下一篇我們們聊聊 FormatFilter 特性類。