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!
在线搜索(例如this question和this answer)指向控制器的有用 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
的内容 .
有很多方法可以解决这个问题,但它似乎很容易被设计出来,所以真正的问题是: what is the correct way intended by the ASP.NET Core designers?
似乎可能的选择是:
-
根据预期的类型,有一个奇怪的(混乱的测试)混合的显式类型和
IActionResult
. -
忘记显式类型,Core MVC并不真正支持它们,总是使用
IActionResult
(在这种情况下它们为什么会出现?) -
编写
HttpResponseException
的实现并将其用作ArgumentOutOfRangeException
(有关实现,请参阅this answer) . 但是,这确实需要使用程序流的异常,这通常是一个坏主意,也是deprecated by the MVC Core team . -
编写
HttpNoContentOutputFormatter
的实现,为GET请求返回404
. -
我还缺少核心MVC的工作原理吗?
-
或者
204
是否正确且_2599973_错误导致GET请求失败?
这些都涉及妥协和重构,这些都会丢失一些东西,或者添加一些看似不必要的复杂性,这与MVC Core的设计不一致 . 哪个妥协是正确的,为什么?
4 回答
这是addressed in ASP.NET Core 2.1与
ActionResult<T>
:甚至:
一旦我实现了它,我会更详细地更新这个答案 .
原始答案
在ASP.NET Web API 5中有一个
HttpResponseException
(由Hackerman指出)但它没有中间件来处理它 .我认为这种变化是由.NET Core引起的 - ASP.NET试图完成所有开箱即用的工作,ASP.NET Core只做你特别指出的事情(这是它为什么它如此快速和便携的重要部分) ) .
我找不到一个现有的库,所以我自己写了 . 首先,我们需要一个自定义异常来检查:
然后我们需要一个
RequestDelegate
处理程序来检查新异常并将其转换为HTTP响应状态代码:然后我们在
Startup.Configure
注册这个中间件:最后,操作可以抛出HTTP状态代码异常,同时仍然返回一个显式类型,可以轻松地进行单元测试而无需转换
IActionResult
:这样可以保留返回值的显式类型,并且可以轻松区分成功的空结果(
return null;
)和错误,因为无法找到某些内容(我认为它就像抛出一个ArgumentOutOfRangeException
) .虽然这是问题的解决方案,但它仍然没有真正回答我的问题 - Web API的设计者构建对显式类型的支持,期望它们将被使用,为
return null;
添加了特定的处理,以便它将产生204而不是200,然后没有添加任何方式来处理404?添加如此基本的东西似乎需要付出很多努力 .您实际上可以使用
IActionResult
或Task<IActionResult>
而不是Thing
或Task<Thing>
或甚至Task<IEnumerable<Thing>>
. 如果您有一个返回 JSON 的API,那么您只需执行以下操作:Update
似乎关注的是,在API的返回中显式是有帮助的,而有可能是明确的,它实际上并不是非常有用 . 如果您正在编写执行请求/响应管道的单元测试,您通常会验证原始返回(最有可能是JSON,即 . 字符串 C# ) . 您可以简单地获取返回的字符串并将其转换回强类型的等效项,以便使用
Assert
进行比较 .这似乎是使用
IActionResult
或Task<IActionResult>
的唯一缺点 . 如果你真的,真的想要明确并且仍然想要设置状态代码,有几种方法可以做到这一点 - 但是由于框架已经有了内置的机制,所以不赞成,即 . 在Controller
类中使用IActionResult
返回方法包装器 . 但是,你可以写一些custom middleware来处理这个问题 .最后,我想指出,如果API调用根据 W3 返回
null
,则状态代码204实际上是准确的 . 为什么你想要404?204
我认为第二段的第一句话说得最好,"If the client is a user agent, it SHOULD NOT change its document view from that which caused the request to be sent" . 这是API的情况 . 与 404 相比:
主要区别之一是更适用于API而另一个更适用于文档视图,即;显示的页面 .
为了完成这样的事情(仍然,我认为最好的方法应该是使用
IActionResult
),你可以按照,如果Thing
是null
,你可以在哪里throw
HttpResponseException
:你从控制器返回 "Explicit Types" 所做的是不能配合你明确处理自己的 response code 的要求 . 最简单的解决方案是使用
IActionResult
(与其他人建议的那样);但是,您也可以使用[Produces]
过滤器显式控制返回类型 .Using IActionResult.
控制状态结果的方法是,您需要返回一个
IActionResult
,然后您可以使用StatusCodeResult
类型 . 但是,现在您已经遇到了想要强制执行部分格式的问题......以下内容取自Microsoft文档:Formatting Response Data -Forcing a Particular Format
把它们放在一起
除了控制"explicit return type"之外,这里还有一个控制
StatusCodeResult
的示例 .我'm not a big supporter of this design pattern, but I have put those concerns in some additional answers to other people'的问题 . 您需要注意
[Produces]
过滤器将要求您将其映射到相应的Formatter / Serializer / Explicit Type . 您可以查看this answer以获取更多创意,或者将此视图用于implementing a more granular control over your ASP.NET Core Web API .