c# - 从显式类型化的 ASP.NET Core API Controller (不是 IActionResult)返回 404

标签 c# asp.net-core asp.net-core-mvc http-status-code-404 asp.net-core-webapi

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 questionthis answer )指向有用的 return NotFound(); Controller 的扩展方法,但所有这些都返回 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 上的所有返回类型都必须更改(即根本不使用显式类型)或者将混合在一起,其中一些操作处理显式类型而其他操作。此外,单元测试现在需要对序列化做出假设并显式反序列化 IActionResult 的内容。之前他们有一个具体的类型。

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

似乎可能的选择是:

  1. 将显式类型和 IActionResult 奇怪地(难以测试)混合在一起取决于预期的类型。
  2. 忘记显式类型,Core MVC 并不真正支持它们,始终使用 IActionResult (在这种情况下,他们为什么会出现?)
  3. 编写 HttpResponseException 的实现并像ArgumentOutOfRangeException一样使用它(参见 this answer 的实现)。然而,这确实需要为程序流使用异常,这通常是一个坏主意,也是 deprecated by the MVC Core team .
  4. 编写 HttpNoContentOutputFormatter 的实现返回 404对于 GET 请求。
  5. 在 Core MVC 应该如何工作方面我还遗漏了什么?
  6. 或者有什么原因 204是正确的 404失败的 GET 请求错误?

这些都涉及妥协和重构,会丢失一些东西或添加一些看似不必要的复杂性,这与 MVC Core 的设计不一致。哪种折衷方案是正确的,为什么?

最佳答案

这是 addressed in ASP.NET Core 2.1ActionResult<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>();

最后,操作可以抛出 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 的方法?添加如此基本的东西似乎需要做很多工作。

关于c# - 从显式类型化的 ASP.NET Core API Controller (不是 IActionResult)返回 404,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41464540/

相关文章:

异常文档的 C# 准则

c# - 静态属性和构造函数注入(inject)潜在的内存泄漏

asp.net-core - 如何在 ASP.NET Core 的 OpenIdConnectOptions 上设置 redirect_uri 参数

c# - 在 Application Insights 中查看 POST 请求正文

c# - 使用来自 MVC Controller 的复杂对象对 API 端点的 Http GET 调用

asp.net-mvc - ASP.NET Core TestServer 导致 Razor View 出现 HTTP 500

c# - 如何使用 C#、Visual Studio MS Test 获取测试用例的 TestCategory

c# - StreamWriter 中的特殊字符

C# ASCII 或 Unicode

javascript - 将 blob 文件发送到服务器