从显式类型的 ASP.NET Core API 控制器(不是 IActionResult)返回 404 [英] Returning a 404 from an explicitly typed ASP.NET Core API controller (not IActionResult)

查看:39
本文介绍了从显式类型的 ASP.NET Core API 控制器(不是 IActionResult)返回 404的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

ASP.NET Core API 控制器通常返回显式类型(如果您创建新项目,则默认返回),例如:

ASP.NET Core API controllers typically return explicit types (and do so by default if you create a new project), something like:

[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:成功,没有内容.

The problem is that return null; - it returns an HTTP 204: success, no content.

然后很多客户端Javascript组件认为这是成功的,所以有这样的代码:

This is then regarded by a lot of client side Javascript components as success, so there's code like:

const response = await fetch('.../api/things/5', {method: 'GET' ...});
if(response.ok)
    return await response.json(); // Error, no content!

在线搜索(例如this questionthis answer) 指向有用的 return NotFound(); 扩展方法对于控制器,但所有这些都返回 IActionResult,这与我的 Task 返回类型不兼容.该设计模式如下所示:

A search online (such as this question and this answer) points to helpful return NotFound(); extension methods for the controller, but all these return IActionResult, which isn't compatible with my Task<Thing> return type. That design pattern looks like this:

// 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 的内容,而在它们具有具体类型之前.

That works, but to use it the return type of GetAsync must be changed to Task<IActionResult> - the explicit typing is lost, and either all the return types on the controller have to change (i.e. not use explicit typing at all) or there will be a mix where some actions deal with explicit types while others. In addition unit tests now need to make assumptions about the serialisation and explicitly deserialise the content of the IActionResult where before they had a concrete type.

有很多方法可以解决这个问题,但它似乎是一个很容易设计出来的令人困惑的大杂烩,所以真正的问题是:ASP.NET Core 设计人员打算采用的正确方法是什么?

There are loads of ways around this, but it appears to be a confusing mishmash that could easily be designed out, so the real question is: what is the correct way intended by the ASP.NET Core designers?

似乎可能的选项是:

  1. 根据预期的类型,将显式类型和 IActionResult 混合在一起.
  2. 忘记显式类型吧,Core MVC 并不真正支持它们,总是使用 IActionResult(在这种情况下,它们为什么存在?)
  3. 编写 HttpResponseException 的实现并像 ArgumentOutOfRangeException 一样使用它(参见 this回答 实现).但是,这确实需要对程序流使用异常,这通常是一个坏主意,而且 MVC 核心团队不推荐使用.
  4. 编写 HttpNoContentOutputFormatter 的实现,为 GET 请求返回 404.
  5. Core MVC 应该如何工作,我还缺少什么?
  6. 或者,对于失败的 GET 请求,204 正确而 404 错误是否有原因?
  1. Have a weird (messy to test) mix of explicit types and IActionResult depending on expected type.
  2. Forget about explicit types, they're not really supported by Core MVC, always use IActionResult (in which case why are they present at all?)
  3. Write an implementation of HttpResponseException and use it like ArgumentOutOfRangeException (see this answer for an implementation). However, that does require using exceptions for program flow, which is generally a bad idea and also deprecated by the MVC Core team.
  4. Write an implementation of HttpNoContentOutputFormatter that returns 404 for GET requests.
  5. Something else I'm missing in how Core MVC is supposed to work?
  6. Or is there a reason why 204 is correct and 404 wrong for a failed GET request?

这些都涉及妥协和重构,这些妥协和重构会丢失某些东西或增加与 MVC Core 设计不符的看似不必要的复杂性.哪种妥协是正确的,为什么?

These all involve compromises and refactoring that lose something or add what seems to be unnecessary complexity at odds with the design of MVC Core. Which compromise is the correct one and why?

推荐答案

这是 在 ASP.NET Core 2.1 中使用 ActionResult 解决:

This is addressed in ASP.NET Core 2.1 with 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();

一旦我实施了它,我会用更多细节更新这个答案.

I'll update this answer with more detail once I've implemented it.

在 ASP.NET Web API 5 中有一个 HttpResponseException(正如 Hackerman 指出的那样)但它已从 Core 中移除,并且没有中间件来处理它.

In ASP.NET Web API 5 there was an HttpResponseException (as pointed out by Hackerman) but it's been removed from Core and there's no middleware to handle it.

我认为这种变化是由于 .NET Core - ASP.NET 尝试开箱即用,ASP.NET Core 只做你特别告诉它的事情(这是为什么它如此之多的很大一部分原因)更快更便携).

I think this change is due to .NET Core - where ASP.NET tries to do everything out of the box, ASP.NET Core only does what you specifically tell it to (which is a big part of why it's so much quicker and portable).

我找不到可以执行此操作的现有库,因此我自己编写了它.首先,我们需要一个自定义异常来检查:

I can't find a an existing library that does this, so I've written it myself. First we need a custom exception to check for:

public class StatusCodeException : Exception
{
    public StatusCodeException(HttpStatusCode statusCode)
    {
        StatusCode = statusCode;
    }

    public HttpStatusCode StatusCode { get; set; }
}

然后我们需要一个 RequestDelegate 处理程序来检查新的异常并将其转换为 HTTP 响应状态代码:

Then we need a RequestDelegate handler that checks for the new exception and converts it to the HTTP response status code:

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中注册这个中间件:

Then we register this middleware in our Startup.Configure:

public class Startup
{
    ...

    public void Configure(IApplicationBuilder app)
    {
        ...
        app.UseMiddleware<StatusCodeExceptionHandler>();

最后,动作可以抛出 HTTP 状态代码异常,同时仍然返回一个显式类型,无需从 IActionResult 转换即可轻松进行单元测试:

Finally actions can throw the HTTP status code exception, while still returning an explicit type that can easily be unit tested without conversion from IActionResult:

public Thing Get(int id) {
    Thing thing = GetThingFromDB();

    if (thing == null)
        throw new StatusCodeException(HttpStatusCode.NotFound);

    return thing;
}

这保留了返回值的显式类型,并允许轻松区分成功的空结果 (return null;) 和错误,因为找不到某些东西(我认为它就像抛出一个ArgumentOutOfRangeException).

This keeps the explicit types for the return values and allows easy distinction between successful empty results (return null;) and an error because something can't be found (I think of it like throwing an ArgumentOutOfRangeException).

虽然这是问题的解决方案,但它仍然没有真正回答我的问题 - Web API 的设计者构建了对显式类型的支持,期望它们会被使用,添加了对 return null 的特定处理; 这样它就会产生204而不是200,然后没有添加任何处理404的方法?添加如此基本的东西似乎需要做很多工作.

While this is a solution to the problem it still doesn't really answer my question - the designers of the Web API build support for explicit types with the expectation that they would be used, added specific handling for return null; so that it would produce a 204 rather than a 200, and then didn't add any way to deal with 404? It seems like a lot of work to add something so basic.

这篇关于从显式类型的 ASP.NET Core API 控制器(不是 IActionResult)返回 404的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆