【问题标题】:Does ASP.NET Core WebAPI not support deferred execution / lazy evaluation when returning collections?返回集合时,ASP.NET Core WebAPI 是否不支持延迟执行/延迟评估?
【发布时间】:2018-08-30 17:07:11
【问题描述】:

我刚刚在编写 C# ASP.NET Core 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,但是我希望该框架足够智能,可以在将其提供的任何集合发布到世界之前对其进行迭代.这实际上是预期的行为还是错误?

至少在这种情况下它返回的响应代码不是很直观 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);
    }
}

【问题讨论】:

  • 如果您检查 Visual Studio 的输出窗口,您是否看到任何异常?我会想象一个未处理的异常被抛出,并且在某个地方被转换为 HTTP 406
  • 有趣。当我删除 ToList 并重新运行测试时,我在输出窗口中看到以下内容:warn: Microsoft.AspNetCore.Mvc.Internal.ObjectResultExecutor[1] No output formatter was found for content type 'application/xml' to write the response.
  • 你上面的操作方法是直接复制粘贴吗?因为那实际上不应该编译。 IEnumerable&lt;OutputData&gt; 不是 IActionResult(您的返回类型)。你应该返回类似return Ok(_repository.GetData(inputs));的东西。
  • @ChrisPratt 你是对的 - 我实际上在示例中省略了一个包装类,它将输出包装到 ObjectResult - 我的错。
  • 有趣。我几乎不再使用 XML,所以我没有意识到,但似乎 XmlSerializer 实际上 支持枚举,而它们与 JSON.NET 序列化之类的东西完全可以。建议的解决方案是在可枚举上调用ToList(),当然,您已经找到了工作。换句话说,这完全是由于返回了 XML。如果您返回 JSON,则不会有任何问题。据说DataContractSerializer 没有这个问题,所以你可能想用那个换掉XmlSerializer,或者继续使用ToList()

标签: c# asp.net-core dapper lazy-evaluation asp.net-core-webapi


【解决方案1】:

感谢以上评论者,将其标记为已回答。

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

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

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

在我的情况下,需要 XML 输出,因此作为一种解决方法,我根据我的原始帖子明确地将 IEnumerable&lt;T&gt; 转换为 List&lt;T&gt;。然后 XML 格式化程序能够正确地序列化集合。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-11-12
    • 2021-05-17
    • 2014-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-03
    • 1970-01-01
    相关资源
    最近更新 更多