从Web API方法返回IQueryable时,如何确保错误的错误状态代码?

时间:2018-07-27 10:13:54

标签: .net asp.net-core odata asp.net-core-webapi iqueryable

我正在通过Microsoft.AspNetCore.OData包开始使用OData。成功的案例是可行的,但是我注意到当成功返回IQueryable<T>但却无法执行时,我没有收到正确的错误消息。相反,我得到了一个被截断的结果:

{"@odata.context":"https://localhost:44300/odata/$metadata#Documents","value":[

我的控制器没有什么特别之处:

public class DocumentsController : ODataController
{
    Db.Context Context { get; }

    public DocumentsController(Db.Context context)
    {
        Context = context;
    }

    [EnableQuery]
    public ActionResult<IQueryable<Document>> Get()
    {
        return Context.Documents;
    }
}

问题不是特定于OData的:返回ControllerBase的普通IQueryable<Document>派生的控制器的行为方式相同(除了截断结果仅为[以外)。但是,OData的上下文可能会排除一些可能的解决方案。

我知道为什么会这样:发送序列化结果已经开始;没有办法退回到原先的状态,而是发送错误响应。

我还理解,在通常情况下,这不是一个真正的选择:发送许多项目时,除了最后一个项目之外的所有项目都可以毫无问题地发送,并且在最后一个项目上可能会抛出一些异常,因此通常情况下,要防止出现这种情况,就需要缓冲整个结果。

但是,应该至少可以处理甚至在IQueryable<T>返回其第一个结果之前引发异常的情况。这将涵盖最严重的错误,在这些错误中,良好的异常消息对于调试非常有帮助:数据库脱机,数据库架构不符合预期等。

我该怎么做?

2 个答案:

答案 0 :(得分:0)

您可以直接在控制器中调用查询选项,而不必使用[Queryable]属性。它将允许您执行所需的任何异常处理,并在需要时构建自定义响应。

为此,请将ODataQueryOptions参数添加到控制器方法。在这种情况下,您不需要[Queryable]属性。有关更多详细信息,请访问https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options#invoking-query-options-directly

答案 1 :(得分:0)

我可以创建一个自定义的IEnumerable<T>实现,该实现包装另一个IEnumerable<T>,并在构造后立即调用.GetEnumerator().MoveNext(),注意保存枚举数和MoveNext()的响应。然后,当第一次调用 my GetEnumerator()时,我返回保存的枚举数周围的包装器。首次调用其MoveNext()时,我返回保存的布尔值。其他用途仅用于包装枚举和包装枚举器。我将保留代码,因为实现几乎是微不足道的。

棘手的部分是找到包装可查询对象的正确点。必须在应用URL中的任何查询修饰符之后。这里有两个主要选项:

  1. 忘记EnableQueryAttribute。相反,正如Ihar Yakimush回答的那样,我可以采用ODataQueryOptions<Document>参数并直接应用查询修饰符。由于修改查询后仍处于控制状态,因此可以将其包装在自定义枚举中。
  2. 子类EnableQueryAttribute并覆盖其OnResultExecuting(ResultExecutingContext context)方法。然后,我可以检查是否context.Result is ObjectResult,如果可以,则检查objectResult.Value是否为IEnumerable<T>。如果两者都正确,那么我可以包装结果。

    此方法的一种变体是将OnResultExecuting放在单独的ActionFilterAttribute派生词中,但对我来说将它们放在同一类中更有意义。

在两种情况下,我都必须照顾没有得到IQuerable<Document>的情况:我可能得到IQueryable<SomeWrapperAround<Document>>。处理起来很容易。 dynamic似乎异常简单:只需创建object Wrap(object o) => oobject Wrap<T>(IQueryable<T> queryable) => new EnumerableWrapper<T>(queryable)重载,然后让运行时找出要调用的方法,但不幸的是SomeWrapperAround可以OData内部类,dynamic不喜欢。手动反射仍然不太困难。