ASP.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();
了控制器有用的扩展方法,但是所有这些returnIActionResult
都与我的Task<Thing>
return类型不兼容。该设计模式如下所示:
// 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
在它们具有具体类型之前显式地反序列化where的内容。
有很多解决方法,但是似乎很容易混淆,很容易设计出来,所以真正的问题是:ASP.NET Core设计者打算使用的正确方法是什么?
似乎可能的选择是:
- 混合使用显式类型和
IActionResult
预期类型,这很奇怪(要测试)。 - 忘记显式类型,Core MVC并不真正支持它们,而是始终使用
IActionResult
(在这种情况下,为什么它们全部存在?) - 编写的实现,
HttpResponseException
然后像使用它ArgumentOutOfRangeException
(有关实现,请参见此答案)。但是,这确实需要在程序流中使用异常,这通常是一个坏主意,并且MVC Core团队也不建议使用。 - 为GET请求编写
HttpNoContentOutputFormatter
该返回的实现404
。 - 在Core MVC应该如何工作的过程中,我还缺少其他东西?
- 还是有一个原因导致失败的GET请求
204
正确与404
错误?
所有这些都涉及到折衷和重构,这些折衷和重构会丢失某些东西或增加似乎不必要的复杂性,这与MVC Core的设计不一致。哪种妥协是正确的,为什么?
StatusCode(500)
它只能与return一起使用IActionResult
,然后再详细介绍。