ASP.NET Core WebAPI 在返回集合时是否不支持延迟执行/延迟评估?

Does ASP.NET Core WebAPI not support deferred execution / lazy evaluation when returning collections?

我刚刚 运行 在编写 C# ASP.NET 核心 WebAPI 控制器时我认为有点奇怪的行为。

考虑以下控制器方法,它接受一个 JSON 主体并产生一个 XML 输出(由于我不会深入探讨的原因):

[Produces("application/xml")]
[Route("api/route/to/data"]
[HttpPost]
public IActionResult GetData([FromBody]IEnumerable<InputData> inputs)
{
    return HandleResponse(() => _repository.GetData(inputs));
}

以及以下存储库方法:

public IEnumerable<OutputData> GetData(IEnumerable<InputData> inputs)
{
    foreach(var input in inputs)
    {
        string field1 = input.Field1;
        int field2 = input.Field2;

        const string sql = @"
            SELECT foo.bar, foo.baz
            FROM my_table foo
            WHERE foo.wubble = :field1
            AND foo.flob = :field2";

        yield return _repoBase.GetSingleValue<OutputData>(sql, new {field1, field2});
    }
}

(_repoBase.GetSingleValue 方法只是 Dapper 的 QueryFirstOrDefault 方法的一个愚蠢的通用包装器,并没有做任何花哨或复杂的事情。我也知道以这种方式使用循环并不是最佳的,但就此示例而言,它会起作用。)

如果在控制器中放置断点,然后从 Postman 调用此端点,在正文中提供简单的 JSON 数据,例如:

[{"field1":"wibble", "field2":123456}]

然后命中断点,repo 按预期拉回数据,执行似乎失败了 - 但是 Postman 报告返回了 HTTP 406 Not Acceptable 状态代码作为响应,实际上没有包含任何数据.

但是,如果我强制 IEnumerable 在返回之前枚举到具体集合中:

[Produces("application/xml")]
[Route("api/route/to/data"]
[HttpPost]
public IActionResult GetData([FromBody]IEnumerable<InputData> inputs)
{
    return HandleResponse(() => _repository.GetData(inputs).ToList());
}

然后我得到 200 OK 以及我预期的数据。

我当然知道使用 IEnumerables 时延迟执行/惰性评估的概念,尤其是 yield return,但是我希望框架足够智能,可以在吐出它们之前迭代它提供的任何集合进入世界。这是实际预期的行为还是错误?

至少它 returns 在这种情况下的响应代码在 IMO 中不是很直观 - 它让我检查了我的中间件设置,以为我忘记添加输出格式化程序或其他东西。

编辑 1: 是的,我将输出包装到 ObjectResult 中。

编辑 2:ObjectResult 包装实际上是在此方法中完成的 - 没什么特别的:

protected IActionResult HandleResponse<T>(Func<T> resultProvider)
{
    try
    {
        return new ObjectResult(resultProvider());
    }
    catch (OracleException oex)
    {
        // DB error handling - omitted for brevity.
        // Nothing is done to the collection here.
        int errorCode = 500 // This is actually mapped to whatever Oracle error code is returned, e.g. incorrect Oracle password = 403.
        return new StatusCodeResult(errorCode);
    }
    catch (Exception e)
    {
        _logger.LogError("Unhandled Exception", e);
        return new StatusCodeResult(500);
    }
}

迟来将此标记为已回答,感谢上面的评论者。

事实证明,此行为特定于 XML 输出格式化程序;他们似乎根本不支持 yield-backed IEnumerable<T> 对象的序列化,至少在 .NET Core 中是这样。

我试过同时使用 XmlSerializerOutputFormatterXmlDataContractSerializerOutputFormatter 作为输出格式化程序,结果相同。

用 XML 输出格式化程序替换标准 JSON 格式化程序可以解决问题。

在我的例子中,需要 XML 输出,因此作为一种变通方法,我根据我原来的 post 明确地将 IEnumerable<T> 转换为 List<T>。 XML 格式化程序随后能够正确序列化集合。