【发布时间】: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<OutputData>不是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