从 Web API 方法返回 IQueryable 时,如何确保错误的错误状态代码?
How do I ensure an error status code for errors when returning an IQueryable from a Web API method?
我正在通过 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>
的 returns 第一个结果之前抛出异常的情况。这将涵盖最严重的错误,其中良好的异常消息对调试非常有帮助:数据库脱机、数据库模式与预期不符等。
我该怎么做?
您可以直接在控制器中调用查询选项,而不是使用 [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
我可以创建一个自定义 IEnumerable<T>
实现,它包装另一个 IEnumerable<T>
,并在构造时立即调用 .GetEnumerator().MoveNext()
,注意保存枚举器和 MoveNext()
响应。然后,当第一次调用 my GetEnumerator()
时,我 return 对保存的枚举器进行包装。当它的 MoveNext()
第一次被调用时,我 return 保存的布尔值。其他用途只是转向包装可枚举和包装枚举器。我将保留代码,因为实现几乎是微不足道的。
棘手的部分是找到包装可查询对象的正确点。它需要在应用 URL 中的任何查询修饰符之后。这里主要有两个选项:
忘记 EnableQueryAttribute
。相反,正如 Ihar Yakimush 回答的那样,我可以使用 ODataQueryOptions<Document>
参数并直接应用查询修饰符。由于在修改查询后我仍然处于控制之中,因此我可以将其包装在我的自定义可枚举中。
Subclass EnableQueryAttribute
并覆盖其 OnResultExecuting(ResultExecutingContext context)
方法。然后,我可以检查是否 context.Result is ObjectResult
,如果是,如果 objectResult.Value
是 IEnumerable<T>
。如果两者都为真,那么我可以包装结果。
这种方法的一个变体是将 OnResultExecuting
放在一个单独的 ActionFilterAttribute
导数中,但对我来说将它们放在同一个 class.[=32 中更有意义=]
在这两种情况下,我都必须处理没有得到 IQuerable<Document>
的情况:我可能会得到 IQueryable<SomeWrapperAround<Document>>
。处理起来很容易。 dynamic
看起来它会变得异常简单:只需创建 object Wrap(object o) => o
和 object Wrap<T>(IQueryable<T> queryable) => new EnumerableWrapper<T>(queryable)
重载并让运行时确定调用哪个方法,但不幸的是 SomeWrapperAround
可能是一个OData-internal class,dynamic
不喜欢。手动反射还是不太难的
我正在通过 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>
的 returns 第一个结果之前抛出异常的情况。这将涵盖最严重的错误,其中良好的异常消息对调试非常有帮助:数据库脱机、数据库模式与预期不符等。
我该怎么做?
您可以直接在控制器中调用查询选项,而不是使用 [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
我可以创建一个自定义 IEnumerable<T>
实现,它包装另一个 IEnumerable<T>
,并在构造时立即调用 .GetEnumerator().MoveNext()
,注意保存枚举器和 MoveNext()
响应。然后,当第一次调用 my GetEnumerator()
时,我 return 对保存的枚举器进行包装。当它的 MoveNext()
第一次被调用时,我 return 保存的布尔值。其他用途只是转向包装可枚举和包装枚举器。我将保留代码,因为实现几乎是微不足道的。
棘手的部分是找到包装可查询对象的正确点。它需要在应用 URL 中的任何查询修饰符之后。这里主要有两个选项:
忘记
EnableQueryAttribute
。相反,正如 Ihar Yakimush 回答的那样,我可以使用ODataQueryOptions<Document>
参数并直接应用查询修饰符。由于在修改查询后我仍然处于控制之中,因此我可以将其包装在我的自定义可枚举中。Subclass
EnableQueryAttribute
并覆盖其OnResultExecuting(ResultExecutingContext context)
方法。然后,我可以检查是否context.Result is ObjectResult
,如果是,如果objectResult.Value
是IEnumerable<T>
。如果两者都为真,那么我可以包装结果。这种方法的一个变体是将
OnResultExecuting
放在一个单独的ActionFilterAttribute
导数中,但对我来说将它们放在同一个 class.[=32 中更有意义=]
在这两种情况下,我都必须处理没有得到 IQuerable<Document>
的情况:我可能会得到 IQueryable<SomeWrapperAround<Document>>
。处理起来很容易。 dynamic
看起来它会变得异常简单:只需创建 object Wrap(object o) => o
和 object Wrap<T>(IQueryable<T> queryable) => new EnumerableWrapper<T>(queryable)
重载并让运行时确定调用哪个方法,但不幸的是 SomeWrapperAround
可能是一个OData-internal class,dynamic
不喜欢。手动反射还是不太难的