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

查看:80
本文介绍了从显式类型的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!

在线搜索(例如这个问题这个答案)指向了控制器的有用的return NotFound();扩展方法,但是所有这些返回IActionResult,与我的Task<Thing>返回类型不兼容.该设计模式如下所示:

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>-显式类型丢失,并且控制器上的所有返回类型都必须更改(即,在以下位置不使用显式类型)全部),否则某些操作将处理显式类型,而其他操作则将混合使用.另外,单元测试现在需要对序列化进行假设,并在它们具有具体类型之前明确地反序列化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一样使用它(有关实现,请参见此答案).但是,这确实需要对程序流使用异常,这通常是一个坏主意,并且由MVC Core团队弃用..
  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?

推荐答案

这是

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 指出),但已将其从中删除.核心,没有中间件可以处理它.

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天全站免登陆