ASP.NET Core API Controller 通常返回显式类型(如果创建新项目,则默认情况下会这样做),例如:
[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)指向 Controller 的有用
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>
-显式类型会丢失,并且 Controller 上的所有返回类型都必须更改(即完全不使用显式类型),否则将会出现混合一些 Action 处理显式类型而其他 Action 。另外,单元测试现在需要对序列化进行假设,并在其具有具体类型之前,将IActionResult
的内容显式反序列化。有很多方法可以解决此问题,但这似乎是一个容易混淆的困惑,因此真正的问题是: ASP.NET Core设计人员打算使用的正确方法是什么?
似乎可能的选择是:
IActionResult
混合使用(测试很麻烦)。 IActionResult
(在这种情况下为什么要显示它们?)HttpResponseException
的实现,并将其像ArgumentOutOfRangeException
一样使用(有关实现,请参见this answer)。但是,这确实需要对程序流使用异常,这通常是一个坏主意,也是deprecated by the MVC Core team。 HttpNoContentOutputFormatter
的实现,该实现为GET请求返回404
。 204
正确而404
错误的原因? 所有这些都涉及到折衷和重构,这些折衷和重构会丢失某些东西或增加似乎不必要的复杂性,这与MVC Core的设计不一致。哪种妥协是正确的,为什么?
最佳答案
这是addressed in ASP.NET Core 2.1和ActionResult<T>
:
public ActionResult<Thing> Get(int id) {
Thing thing = GetThingFromDB();
if (thing == null)
return NotFound();
return thing;
}
甚至:
public ActionResult<Thing> Get(int id) =>
GetThingFromDB() ?? NotFound();
实现后,我将更详细地更新此答案。
原始答案
在ASP.NET Web API 5中,有一个
HttpResponseException
(由Hackerman指出),但是它已从Core中删除,并且没有中间件来处理它。我认为这种变化是由于.NET Core所致-ASP.NET尝试在其中进行开箱即用的所有操作,而ASP.NET Core只是按照您的特定要求进行操作(这是为什么它这么快和可移植的很大一部分) )。
我找不到一个可以做到这一点的现有库,因此我自己编写了它。首先,我们需要一个自定义异常来检查:
public class StatusCodeException : Exception
{
public StatusCodeException(HttpStatusCode statusCode)
{
StatusCode = statusCode;
}
public HttpStatusCode StatusCode { get; set; }
}
然后,我们需要一个
RequestDelegate
处理程序,该处理程序检查新异常并将其转换为HTTP响应状态代码:public class StatusCodeExceptionHandler
{
private readonly RequestDelegate request;
public StatusCodeExceptionHandler(RequestDelegate pipeline)
{
this.request = pipeline;
}
public Task Invoke(HttpContext context) => this.InvokeAsync(context); // Stops VS from nagging about async method without ...Async suffix.
async Task InvokeAsync(HttpContext context)
{
try
{
await this.request(context);
}
catch (StatusCodeException exception)
{
context.Response.StatusCode = (int)exception.StatusCode;
context.Response.Headers.Clear();
}
}
}
然后,我们在
Startup.Configure
中注册该中间件:public class Startup
{
...
public void Configure(IApplicationBuilder app)
{
...
app.UseMiddleware<StatusCodeExceptionHandler>();
最后, Action 可以引发HTTP状态代码异常,同时仍然返回一个显式类型,无需从
IActionResult
进行转换即可轻松进行单元测试:public Thing Get(int id) {
Thing thing = GetThingFromDB();
if (thing == null)
throw new StatusCodeException(HttpStatusCode.NotFound);
return thing;
}
这将保留返回值的显式类型,并允许轻松区分成功的空结果(
return null;
)和错误,因为找不到内容(我认为这类似于抛出ArgumentOutOfRangeException
)。尽管这是解决问题的方法,但仍然无法真正回答我的问题-Web API的设计人员为显式类型提供了支持,并期望使用它们,并为
return null;
添加了特定的处理方式,以便生成204而不是200,然后没有添加任何方法来处理404?添加如此基本的内容似乎需要大量工作。