【译】在ASP.NET Core Web API中格式化响应数据
原文链接:传送门。
ASP.NET Core Web API支持格式化返回数据。返回数据可以以指定的格式进行格式化,或者对客户端请求的格式进行响应。
指定格式的Action结果
一些Action的结果类型会被限定于特定的格式,比如 JsonResult 和 ContentResult。Action可以返回以特定的格式进行格式化的结果,而不管客户端的参数选项。举个例子,返回JsonResult会返回基于JSON格式的数据。而返回ContentResult
或者string则会返回纯文本格式的字符串数据。
一个Action不要求返回指定的类型。ASP.NET Core支持任何对象返回值。如果一个Action的返回结果不是一个 IActionResult,那么这个返回结果便会用合适的IOutputFormatter 实现来进行序列化。更多信息,请参考Controller action return types in ASP.NET Core web API.。
内置的帮助器方法Ok 返回JSON格式的数据。
// GET: api/authors [HttpGet] public ActionResult Get() { return Ok(_authors.List()); }
上述示例代码返回了一个作者的列表。使用F12浏览器开发者工具或者Postman来观察对上述Action的请求。我们可以看到:
- 包含content-type:
application/json; charset=utf-8的响应头信息被展示出来。
- 请求头信息同样也被展示出来。比如Accept 头信息,而上述代码会忽略Accept 头信息。
为了返回纯文本格式的数据,请使用ContentResult 返回类型及 Content 帮助器方法。
// GET api/authors/about [HttpGet("About")] public ContentResult About() { return Content("An API listing authors of docs.asp.net."); }
在如上的代码中,返回的Content-Type 是 text/plain。返回一个字符串提供的Content-Type为
text/plain
:。
// GET api/authors/version [HttpGet("version")] public string Version() { return "Version 1.0.0"; }
对于具有多个返回类型的Action,我们可以返回一个IActionResult。比如,基于执行的操作的结果返回不同的HTTP状态码的情况。
内容协商
当客户端指定了一个Accept header时便会发生内容协商。ASP.NET Core默认使用的格式是JSON。内容协商是:
- 被 ObjectResult 实现
- 内置于从帮助器方法返回的指定状态码的Action结果中。Action结果帮助器方法是基于ObjectResult 的
当返回一个模型类型时,其返回结果便是ObjectResult。
如下的Action方法使用Ok及NotFound帮助器方法。
// GET: api/authors/search?namelike=th [HttpGet("Search")] public IActionResult Search(string namelike) { var result = _authors.GetByNameSubstring(namelike); if (!result.Any()) { return NotFound(namelike); } return Ok(result); }
默认的,ASP.NET Core支持 application/json,
text/json,
和text/plain 媒体类型。例如Fiddler 或者 Postman这样的工具可以设置Accept请求头以指定返回格式。当
Accept
头信息包含服务器支持的类型时,便会返回那个类型。下一章节将会展示给你如何添加额外的格式化器。
控制器Action可以返回一个POCO。当返回一个POCO时,运行时会自动创建一个封装了此对象的ObjectResult。客户端得到了格式化后的序列化对象。如果返回的对象是null,便会返回一个204 No Content 响应。
如下代码返回一个对象类型;
// GET api/authors/RickAndMSFT [HttpGet("{alias}")] public Author Get(string alias) { return _authors.GetByAlias(alias); }
在如上代码中,请求一个有效的作者别名会返回一个200 OK响应以及作者数据。请求一个无效的别名将返回一个204 No Content 响应。
Accept 头信息
当Accept头信息出现在一个请求中时,便会发生内容协商。当一个请求包含一个accept头信息时,ASP.NET Core便会:
- 以特定的顺序枚举accept头中的媒体类型
- 以指定的格式之一,尝试找到一个可以产生响应的格式化器
如果没有找到格式化器来满足客户端的请求,ASP.NET Core将会:
- 返回406 Not Acceptable,如果设置了MvcOptions,则返回 -
- 尝试找到可以生成响应的第一个格式化器
如果对于请求的格式没有配置相应的格式化器,便会使用可以格式化对象的第一个格式化器。如果请求中没有出现Accept
头信息:
- 将会使用可以处理此对象的第一个格式化器来序列化响应
- 不会发生内容协商。服务器会决定返回何种格式
如果Accept头包含*/*,便会忽略头信息,除非在MvcOptions 中 RespectBrowserAcceptHeader 属性被设置为 true。
浏览器和内容协商
与典型的API客户端不同的是,Web浏览器支持Accept头。Web浏览器会指定许多格式,包括wildcards。默认的,当框架检测出来一个请求是来自浏览器时,其会:
- 忽略
Accept
头 - 将以JSON格式返回内容,除非配置了其他选项
当消费API时,这提供了更加一致的跨浏览器体验。
为了配置一个App来支持浏览器Accept头,请将RespectBrowserAcceptHeader 设置为 true。
public void ConfigureServices(IServiceCollection services) { services.AddControllers(options => { options.RespectBrowserAcceptHeader = true; // false by default }); }
配置格式化器
需要支持其他格式的app可以添加合适的Nuget包并进行配置使其支持相应的格式。输入内容和输出内容的格式化器是分开的。输入格式化器被Model Binding 来使用。输出格式化器用来格式化响应。关于创建自定义格式化器的信息,请参考 Custom Formatters。
添加XML格式支持
使用 XmlSerializer 实现的XML格式化器可以通过调用AddXmlSerializerFormatters 来进行配置。
public void ConfigureServices(IServiceCollection services) { services.AddControllers() .AddXmlSerializerFormatters(); }
如上的代码使用XmlSerializer来序列化结果。
当使用如上的代码时,控制器Action将基于请求的Accept头来返回合适的格式。
配置基于System.Text.Json的格式化器
基于System.Text.Json的格式化器特性可通过使用Microsoft.AspNetCore.Mvc.JsonOptions.SerializerOptions来进行配置。
services.AddControllers().AddJsonOptions(options => { // Use the default property (Pascal) casing. options.JsonSerializerOptions.PropertyNamingPolicy = null; // Configure a custom converter. options.JsonSerializerOptions.Converters.Add(new MyCustomJsonConverter()); });
基于一个单独Action基元的输出序列化选项,可以使用JsonResult进行配置,比如:
public IActionResult Get() { return Json(model, new JsonSerializerOptions { WriteIndented = true, }); }
在ASP.NET Core 3.0之前,默认下使用基于Newtonsoft.Json包的JSON格式化器。在ASP.NET Core 3.0及之后的版本中,默认的JSON格式化器是基于System.Text.Json的。通过安装 Microsoft.AspNetCore.Mvc.NewtonsoftJson 并在Startup.ConfigureServices中对其进行配置,我们仍然可以支持基于
Newtonsoft.Json的格式化器。
public void ConfigureServices(IServiceCollection services) { services.AddControllers() .AddNewtonsoftJson(); }
在如上的代码中,对于AddNewtonsoftJson
的调用将配置如下特性:Web API, MVC, and Razor Pages 来使用Newtonsoft.Json。
- 读写JSON的输入输出格式化器
- JsonResult
- JSON Patch
- IJsonHelper
- TempData
某些特性可能不能与基于System.Text.Json的格式化器工作良好,其需要一个基于Newtonsoft.Json的格式化器。如果app具有如下几种情况,请继续使用基于Newtonsoft.Json的格式化器:
- 使用了Newtonsoft.Json属性,比如
[JsonProperty]
或者[JsonIgnore]
- 自定义了序列化设置
- 依赖于Newtonsoft.Json提供的特性
- 配置了Microsoft.AspNetCore.Mvc.JsonResult.SerializerSettings。在ASP.NET Core 3.0以前,JsonResult.SerializerSettings接收一个
JsonSerializerSettings
的实例,其被指定为Newtonsoft.Json。
- 生成一个 OpenAPI 文档
基于Newtonsoft.Json的格式化器特性可以通过Microsoft.AspNetCore.Mvc.MvcNewtonsoftJsonOptions.SerializerSettings来进行配置。
services.AddControllers().AddNewtonsoftJson(options => { // Use the default property (Pascal) casing options.SerializerSettings.ContractResolver = new DefaultContractResolver(); // Configure a custom converter options.SerializerSettings.Converters.Add(new MyCustomJsonConverter()); });
基于一个Action基元的输出序列化选择可以用JsonResult进行配置,比如:
public IActionResult Get() { return Json(model, new JsonSerializerSettings { Formatting = Formatting.Indented, }); }
指定一个格式
为了限定响应格式,可以应用 [Produces]
过滤器。如同大部分过滤器一样,[Priduces]可用于控制器,Action,以及全局域中。
[ApiController] [Route("[controller]")] [Produces("application/json")] public class WeatherForecastController : ControllerBase {
上述 [Produces]过滤器产生了如下效果:
- 使得控制器内的所有Action均返回基于JSON格式的响应
- 如果配置了其他格式化器并且客户端指定了一个不同的格式,那么也会返回JSON
更多信息,请参考 Filters。
特殊例子格式化器
一些特殊的场景使用内置的格式化器来实现。默认情况下,string返回类型被格式化为一个text/plain (如果通过Accept
来请求,则格式化为 text/html)。通过移除 StringOutputFormatter可以删除此行为。可以在ConfigureServices
方法中移除格式化器。返回一个对象的Action在返回null时候其会返回一个204 No Content。通过移除 HttpNoContentOutputFormatter 可删除此行为。如下代码移除了StringOutputFormatter
和 HttpNoContentOutputFormatter。
public void ConfigureServices(IServiceCollection services) { services.AddControllers(options => { // requires using Microsoft.AspNetCore.Mvc.Formatters; options.OutputFormatters.RemoveType<StringOutputFormatter>(); options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>(); }); }
如果没有StringOutputFormatter,内置的JSON格式化器会格式化string返回类型。如果内置的JSON格式化器被移除并且有一个XML格式化器是可用的,那么XML格式化器将会格式化string返回类型。否则,string返回类型返回一个406 Not Acceptable。
如果没有HttpNoContentOutputFormatter,将使用配置的格式化器来格式化null对象。比如:
- JSON格式化器会返回一个响应体为null的响应
- XML格式化器会返回一个空的XML元素,并设置了属性:xsi:nil="true"
响应格式URL映射
客户端可以作为URL的一部分来请求一个特殊的格式。比如:
- 在查询字符串或者路径的一部分
- 通过使用一个指定格式的文件扩展名,比如.xml or .json
来自于请求路径的映射应该指定在API使用的路由中。比如:
[Route("api/[controller]")] [ApiController] [FormatFilter] public class ProductsController : ControllerBase { [HttpGet("{id}.{format?}")] public Product Get(int id) {
上述路由将请求的格式指定为可选的文件扩展名。[FormatFilter] 特性将会检查RouteData中格式值的存在,并在创建响应时,将响应格式映射为合适的格式化器。
路由 | 格式化器 |
---|---|
/api/products/5 |
默认的输出格式化器 |
/api/products/5.json |
JSON格式化器(如果已配置) |
/api/products/5.xml |
XML格式化器(如果已配置) |