原文:Formatting Response Data
作者:Steve Smith
翻譯:劉怡(AlexLEWIS)
校對:許登洋(Seay)
ASP.NET Core MVC 內建支持對相應數據(response data)的格式化,用來修正格式或生成客戶端指定的格式。
特定格式的操作結果
某些操作結果(Action result)的類型是指定的特定格式,比如 JsonResult 或 ContentResult。Action 可以返回格式化為特定方式的具體結果。比如返回 JsonResult 將返回 JSON 格式化數據,而不是客戶端要求的的格式。同樣地,返回 ContentResult 將返回純文本格式的字符串數據(就像是簡單第返回一個字符串那樣)。
提示
Action 並不強制要求返回一個特定的類型,MVC 支持任何對象作為返回值。如果 Action 返回的是IActionResult的某個實現並且控制器繼承自Controller,那么開發人員就可以使用很多輔助方法(對應地就會有很多選擇)。從 Action 返回的結果如果不是IActionResult類型的對象的話將使用適當的IOutputFormatter實現序列化。
若要從繼承了 Controller 基類的控制器返回指定格式的數據,可以使用內置的輔助方法 Json
來返回 JSON 格式,使用 Content 來返回純文本。此時你的 Action 方法的返回類型就必須是指定的結果類型(如 JsonResult)或 IActionResult。
返回 JSON 格式的數據:
// GET: api/authors
[HttpGet]
public JsonResult Get()
{
return Json(_authorRepository.List());
}
這個操作(Action)將返回的響應樣本:

注意,響應的內容類型是 application/json,它同時顯示在網絡請求列表中和響應的頭節點中。另外還要注意,由瀏覽器(比如 Microsoft Edge)提交的請求頭中 Accept 頭的選擇列表。當前的技術方案是忽略這個頭信息,下面將具體討論。
要返回純文本格式的數據,請使用 ContentResult 以及 Content 輔助方法:
// GET api/authors/about
[HttpGet("About")]
public ContentResult About()
{
return Content("An API listing authors of docs.asp.net.");
}
該 Action 的響應:

注意在此情況下, Content-Type 將返回 test/plain。你也可以通過一個字符串來實現響應這一相同行為:
// GET api/authors/version
[HttpGet("version")]
public string Version()
{
return "Version 1.0.0";
}
小技巧
對於將返回不同類型或選項的復雜操作(non-trivial actions)(比如根據操作的結果不同返回不同 HTTP 狀態碼),那么請使用IActionResult作為返回類型。
內容協商
內容協商(簡寫為 conneg)是指:當客戶端在 Accept 頭 中指定接受要求時會發生的過程。ASP.NET Core MVC 默認格式使用的是 JSON。內容協商由 ObjectResult 實現,它還內置了從輔助方法(它們盡數基於 ObjectResult)為指定的 Action 結果返回狀態碼的功能。你也可以返回一個模型類型(你自行定義的數據傳輸類),框架將自動為你將其包裝在 ObjectResult 內。
下面的 Action 方法使用 Ok 和 NotFound 兩種輔助方法:
// GET: api/authors/search?namelike=th
[HttpGet("Search")]
public IActionResult Search(string namelike)
{
var result = _authorRepository.GetByNameSubstring(namelike);
if (!result.Any())
{
return NotFound(namelike);
}
return Ok(result);
}
除非要求返回另一種服務器可以返回的格式,不然將返回 JSON 格式的響應。你可以使用像 Fiddler這樣的工具來創建包含 Accept 頭的、並且指定另一種格式的請求。在這種情況下,如果服務器具有創建請求中指定格式的 formatter,那么該結果將按客戶端所選的格式返回。

在上面的截圖中,Fiddler 的 Composer 標簽可用於生成請求,並指定 Accept: application/xml。默認情況下,ASP.NET Core MVC 只支持 JSON,所以即使是指定了另一種格式,返回的依舊是 JSON 格式。你可以在下一節中了解到如何增加其它格式。
控制器的 Action 可以返回 POCO(Plain Old CLR Objects),此時 ASP.NET Core MVC 會自動創建ObjectResult 並將該對象進行包裝。客戶端將獲得經序列化后的對象(默認是 JSON 格式,當然你也可以自己配置 XML 或其它格式)、如果對象返回的是 null,那么框架將返回 204 No Content 響應。
返回對象類型:
// GET api/authors/ardalis
[HttpGet("{alias}")]
public Author Get(string alias)
{
return _authorRepository.GetByAlias(alias);
}
在本例中,有效的作者別名的請求將收到 200 OK 響應(帶着作者的數據),而無效別名將收到 204 No Content 響應。截圖中分別顯示了 XML 和 JSON 的響應。
內容協商過程
內容協商只在當 Accept 頭出現在請求中時才會發生。當請求包含 Accept 頭時,框架將按優先級的順序枚舉媒體類型(media types)並嘗試尋找能生產出 Accept 頭中指定格式(中的一種)的格式化程序。如果找到可以滿足客戶端請求的格式化程序,框架將嘗試尋找第一個能生產響應的格式化程序(除非開發人員已經在 MvcOptions 中配置返回 406 Not Acceptable 響應)。如果請求指定的格式是 XML,但 XML 格式化程序並沒有被配置,那么將使用 JSON 格式化程序。更通常地來講,如果沒有配置任何可以提供所請求的格式的格式化程序,那么將使用第一個格式化程序來格式化對象。如果沒有給定頭,那么所返回的對象將使用第一個格式化程序來序列化。在這種情況下並沒有發生內容協商——其實是由服務器來決定使用哪種格式來格式化對象。
提示
如果 Accept 頭中包含/,那么 Header 將被忽略,除非MvcOptions的RespectBrowserAcceptHeader設置為 true。
瀏覽器和內容協商
與其它 API 客戶端不同,Web 瀏覽器一般都會在請求中包含 Accept 頭,其中使用通配符(wildcards)。默認情況下,當框架檢測到請求來自瀏覽器,它就會忽略 Accept 頭並返回應用程序配置的默認格式(如果沒有另行安排,則默認為 JSON 格式)。這樣一來,當使用不同的瀏覽器消費 API 時提供一致的體驗。
如果你希望你的應用程序優先考慮瀏覽器的 Accept 頭,你可以在 MVC 的配置中進行相關配置,具體來講是在 Startup.cs 的 ConfigureServices 方法中將 RespectBrowserAcceptHeader 設置為 true。
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true; // false by default
}
配置格式化程序
如果你的應用程序需要支持默認的 JSON 之外的格式,那么你需要在 project.json 文件中添加這些額外的依賴項,並配置 MVC 來支持它們。輸入和輸出的格式是可以隔離的。輸入格式通過使用模型綁定,輸出格式通過使用格式化響應。你也可以配置 自定義格式化。
添加對 XML 格式的支持
為增加對 XML 格式的支持,需要在 project.json 的 dependencies 列表中增加“Microsoft.AspNetCore.Mvc.Formatters.Xml”包。
在 Startup.cs 的 MVC 配置中添加 XmlSerializerFormatters:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddXmlSerializerFormatters();
services.AddScoped<IAuthorRepository, AuthorRepository>();
}
或者你可以只添加輸出格式:
services.AddMvc(options =>
{
options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
});
這兩種方法都將使用 System.Xml.Serialization.XmlSerializer 序列化結果。如果你願意,你可以通過添加其它相關格式來使用 System.Runtime.Serialization.DataContractSerializer 。
services.AddMvc(options =>
{
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
});
一旦你添加了對 XML 格式的支持,你的控制器方法就可以根據 Accept 頭信息來返回相應的格式了,就像下面這個 Fiddler 的演示:

你可以在 Inspectors 標簽下看到,原始 GET 請求設置了 Accept: application/xml 頭,在響應面板中顯示 Content-Type: application/xml 頭,並且有一個已經被序列化為 XML 格式的 Author 對象。
使用 Composer 面板可以修改請求,把 Accept 頭改為 application/json,然后執行這個請求,此時將獲得一個格式化為 JSON 格式的響應:

在這個截圖中,你可以發現請求報文中的 Accept: application/json 頭以及與之相同的響應 Content-Type 。同時 Author 對象也序列化為 JSON 格式后出現在響應報文中。
強制特定格式化
若是你想為某個 action 限制響應格式,可以使用 [Produces] 過濾器。[Produces] 過濾器可以為這個控制器或 Action 指定響應格式。就像大多數 過濾器 那樣,這一過濾器可應用於 Action、控制器,甚至全局范圍。
[Produces("application/json")]
public class AuthorsController
[Produces] 過濾器會強制要求所有 AuthorsController 內的 Action 返回 JSON 格式的響應,即使其它格式化程序已經在應用程序中配置了、且客戶端也通過 Accept 頭指明要返回 JSON 之外的其他可用格式。更多請查閱 過濾器 ,包括如何在全局范圍應用過濾器。
特例格式化程序
一些特殊情況下使用的是內建的格式化實現。默認情況下,返回類型為 string 將格式化為 text/plain (如果通過 Accept 頭的話則是 text/html )。這種行為可以通過移除TextOutputFormatter 來改變。如果你如下例代碼這般在 Startup.cs 的 Configure 方法中移除了HttpNoContentOutputFormatter,那么當你某個返回類型為模型對象的 Action 返回了 null 時將返回 204 No Content 響應。下列代碼移除了 TextOutputFormatter 和 HttpNoContentOutputFormatter 。
services.AddMvc(options =>
{
options.OutputFormatters.RemoveType<TextOutputFormatter>();
options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
});
如果沒有 TextOutputFormatter,返回類型為 string 的操作將返回 406 Not Acceptable。此時請注意,如果存在 XML 格式化程序,此時將格式化該響應結果。
如果沒有 HttpNoContentOutputFormatter,空對象將使用配置的格式進行格式化。比如 JSON 格式將簡單的返回主體信息為 null 的響應,而 XML 格式將返回一個空的帶有 xsi:nil="true" 特性集的 XML 元素。
響應格式 URL 映射
客戶端可以在 URL 中請求特定的格式,比如在請求字符串(query string)或路徑(path)中,或者索性使用特定格式的文件擴展名(比如 .xml 或 .json)。從請求路徑中進行映射的話需要在 API 所使用的路由中進行指定,比如:
[FormatFilter]
public class ProductsController
{
[Route("[controller]/[action]/{id}.{format?}")]
public Product GetById(int id)
這一路由配置將允許使用可選的文件擴展名來指定格式。[FormatFilter] 特性將在 RouteData 中檢查該格式的值是否存在,並在創建響應時將響應數據映射為適當的格式。
| 路由 | 格式化 |
|---|---|
/products/GetById/5 |
默認輸出格式 |
/products/GetById/5.json |
JSON 格式(如果配置了的話) |
/products/GetById/5.xml |
XML 格式(如果配置了的話) |
