NET Core API 控制器通常返回显式类型(如果创建新项目,则默认返回显式类型) ,类似于:
[Route("api/[controller]")]
public class ThingsController : Controller
{
// GET api/things
[HttpGet]
public async Task<IEnumerable<Thing>> GetAsync()
{
//...
}
// GET api/things/5
[HttpGet("{id}")]
public async Task<Thing> GetAsync(int id)
{
Thing thingFromDB = await GetThingFromDBAsync();
if(thingFromDB == null)
return null; // This returns HTTP 204
// Process thingFromDB, blah blah blah
return thing;
}
// POST api/things
[HttpPost]
public void Post([FromBody]Thing thing)
{
//..
}
//... and so on...
}
问题是,return null;
-它返回一个 HTTP 204
: 成功,没有内容。
这被很多客户端 Javascript 组件认为是成功的,所以有这样的代码:
const response = await fetch('.../api/things/5', {method: 'GET' ...});
if(response.ok)
return await response.json(); // Error, no content!
在线搜索(例如 这个问题和 这个答案)指向有用的控制器 return NotFound();
扩展方法,但是所有这些返回 IActionResult
,这与我的 Task<Thing>
返回类型不兼容。这个设计模式看起来像这样:
// GET api/things/5
[HttpGet("{id}")]
public async Task<IActionResult> GetAsync(int id)
{
var thingFromDB = await GetThingFromDBAsync();
if (thingFromDB == null)
return NotFound();
// Process thingFromDB, blah blah blah
return Ok(thing);
}
这是可行的,但使用它的返回类型 GetAsync
必须改为 Task<IActionResult>
-显式类型丢失,并且要么所有的返回类型控制器必须改变(即不使用显式类型)或将有一个混合,一些操作处理显式类型,而其他。此外,单元测试现在需要对序列化做出假设,并明确反序列化 IActionResult
的内容,而在此之前,它们有一个具体的类型。
有很多方法可以解决这个问题,但是这似乎是一个容易被设计出来的混杂问题,所以真正的问题是: ASP.NET 核心设计者想要的正确方式是什么?
似乎可能的选择是:
IActionResult
取决于预期的类型。IActionResult
(在这种情况下,为什么它们会出现呢?)HttpResponseException
的实现,并像使用 ArgumentOutOfRangeException
一样使用它(有关实现,请参阅 这个答案)。然而,这确实需要对程序流使用异常,这通常是一个坏主意,也是 MVC 核心团队不赞成。404
的 HttpNoContentOutputFormatter
实现。204
是正确的,而 404
是错误的?这些都涉及到妥协和重构,这些妥协和重构会丢失一些东西,或者增加一些似乎不必要的复杂性,这些复杂性与 MVC 核心的设计不一致。哪种妥协是正确的? 为什么?