在编写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
时延迟执行/延迟评估的概念,但是我希望框架足够智能,可以在将它们分散到世界之前迭代它提供的任何集合。这实际上是预期的行为,还是一个错误?
至少它在这种情况下返回的响应代码不是非常直观的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输出格式化程序;他们似乎根本不支持产量支持的IEnumerable<T>
对象的序列化,至少在.NET Core中是这样。
我尝试使用XmlSerializerOutputFormatter
和XmlDataContractSerializerOutputFormatter
作为输出格式化程序,结果相同。
用XML输出格式化程序替换标准JSON格式化器可以解决问题。
在我的例子中,XML输出是必需的,所以作为一种解决方法,我根据我的原始帖子明确地将IEnumerable<T>
转换为List<T>
。然后,XML格式化程序可以正确地序列化集合。