【问题标题】:Should Odata(V4) actions be invoked only with POST?是否应该仅使用 POST 调用 Odata(V4) 操作?
【发布时间】:2020-08-14 09:03:12
【问题描述】:

Odata V4 规范说操作可能有可观察到的副作用,应该使用 HTTP POST 调用。但我们确实有需要使用仅修改某些状态的操作的场景。

例如:
1.
您可能希望将由 id 标识的文档的状态标记为已锁定
端点 - .../Documents({id})/lock()。
由于我在这里进行部分更新,因此我认为 PATCH 更合适。

2.
您可能希望提供两种删除文档的方法
a) 只是隐藏
端点 - ...../Documents({id})
这是 HTTP DELETE(无争议)

b) 永久删除
端点 - ...../Documents({id})/permanentDelete()
这是一个 ODATA 操作。
在我看来,HTTP 删除在这里更合适,而不是HTTP POST。

从 Odata 的角度来看,推荐的方法是什么?非常感谢这里的任何帮助。

以下是来自 SPEC 的信息。

规格 11.5.4 操作

操作是由 OData 服务公开的操作,在调用时可能会产生副作用。动作可以返回数据,但不能进一步由额外的路径段组成。 11.5.4.1 调用操作

为了调用绑定到资源的操作,客户端向操作 URL 发出 POST 请求。动作 URL 可以从先前返回的实体表示中获得,或者通过将命名空间或别名限定的动作名称附加到标识资源的 URL 来构造,该资源的类型与绑定参数的类型相同或派生自那个行动。绑定参数的值是在附加动作名称之前由 URL 标识的资源的值,并且任何非绑定参数值都根据特定格式在请求正文中传递。

提前致谢
--ksp

【问题讨论】:

    标签: rest asp.net-web-api odata


    【解决方案1】:

    从 OData 的角度来看,您始终必须使用 POST 来调用操作,而使用任何其他动词都行不通。

    您的两个示例都是我个人认为不太适合操作的事情,因为已经有使用 OData 执行这些操作的方法,并且它们使用您提到的动词。更新属性支持使用 DELETE 支持 PATCH 和删除对象。您提到您有两种不同类型的删除操作,这比较困难,但您可以使用自定义标头来区分它们。

    一个动作往往不适合正常的 CRUD 操作,因此并不总是清楚应该使用哪个 HTTP 动词。

    在您的示例中,感觉想要使用 DELETE 和 PATCH 是正确的。但是,问题来了,因为我们需要遵循一个标准,请记住 OData 操作都可以通过元数据发现,因此客户端可以在不知道操作实际执行的情况下使用,在这种情况下,我们需要有定义的东西。由于我们知道操作会以某种方式影响服务器,因此在我看来,POST 似乎是对所有操作都一致的最不坏的选项。

    【讨论】:

    • 但 Microsoft.AspNet.OData 的最新实现在操作不带参数时似乎不能很好地与 POST 动词配合使用。
    • @Eniola POST 可能是 OData 中最常用的动词,我从未遇到过没有参数的操作的任何问题,它们非常适合切换标志或其他流程逻辑。我有很多动作,例如StartNow()CheckIn()CheckOut()。我重申这是一条旧评论,但如果您遇到问题,请在参考中链接或发布您自己的问题,以便我们让您恢复运行。通常,像 Swashbuckle 这样的第 3 方组件可能会导致空参数出现问题,但这些组件有快速修复,问题不在 OData 运行时
    • @ChrisSchaller,谢谢。当时我只是围绕着 OData 的 ASP.NET Core 实现而思考,我得到了很多东西。我有一个建议。为什么必须从 ODataParameters 字典中读取所有进入操作的参数?为什么不允许我们使用映射到 HTTP 消息正文中的元素的 [FromBody] 参数,以便 OData 在幕后完成的验证在实现方法中更具可读性/可发现性,并且从一开始就具有强类型。
    • @Eniola 我不能肯定,除了从设计的角度来看,使用[FromBody] 仅在整个主体是单个对象时才有效,单一类型。 OData 的一个关键原则是减少歧义,所有内容都必须在元数据模型中正式定义,定义中没有“或”。虽然我记得刚开始时遇到过同样的问题,但我真的认为我想要类型化参数访问,但这意味着对于几乎每个请求我都可以有一个唯一的类定义,这会变成很多需要管理的冗余类型
    • 支持可选参数或仅根据需要强制执行某些参数对于类表示会产生问题,将输入视为键值数组绕过冗余类定义并为我们提供一些难以表达的灵活性在使用[FromBody] 的其他实现中使用的序列化场景中。对我来说,我很快发现这是一个强大的功能,但它太不稳定了,不能让团队中的任何人添加他们自己的端点,他们通常使用错误的语法或期望。
    【解决方案2】:

    归根结底,作为作者,您的 API 由您来指挥。正如我们不应该在 C# 类设计中使用会在其他属性中引起副作用的属性,这并不意味着我们不能,而且它可能很常见。

    从 OData 的角度来看,PATCH 类似于使用属性访问器,而 actionsPOST 用于访问方法。

    PATCHPOST 之间做出决定以影响更改因此在使用属性mutators(使它们可写)或强制调用者通过相关方法。

    1. 您可能希望将由 id 标识的文档的状态标记为已锁定 端点 - .../Documents({id})/lock()。 由于我这里是做部分更新,所以我认为PATCH更合适。

    如果您的资源有一个名为Locked属性,而您当前的lock() Action 仅将Locked 属性设置为true,那么您可以简单地使用 PATCH 来更新 Locked 字段。

    Action 专门用于 lock 进程的一些常见原因:

    1. 您希望在调用 lock() 时执行特定逻辑,并且希望在自己的方法中维护此逻辑,而不是在补丁处理程序中包含复杂的条件逻辑。
    2. 您不希望反向逻辑unlock()每个人 都可用,当资源锁定 时,大概只有锁定它的用户可以释放需要满足锁或其他条件。
      • 与第一点一样,这种逻辑通常更容易在自己的方法中维护,因此Action
    3. 您希望使用安全属性将lock() 的访问权限限制为特定安全组,但您希望所有用户都拥有Locked 状态字段的读取权限。
    4. 当资源被锁定时,其他字段也会被设置,例如LockedByDateLocked

    虽然所有这些逻辑可以在控制器的单个 PATCH 端点逻辑中进行管理,但我不能强调这如何容易使您的解决方案随着时间的推移或复杂性变得难以管理你的资源增加了。

    更重要的是:记录并接受的约定是 PATCH 不会有副作用,因此不需要在之后执行 GET PATCH 因为客户端上的相同更改已在服务器上接受。相反,因为POST可能有副作用,如果来自POST的响应不包含更新的资源。

    DeletePermanentlyDelete 的情况下,现在事情变得个人化/固执己见...... 按照惯例,DELETE 的期望是两倍:

    1. 删除后,资源不应再出现在来自GET 的集合查询结果中
    2. 删除后,资源不应再通过GET 出现在项目请求中

    从服务器的角度来看,如果我的资源有一个 soft 删除,它只是设置一个标志,以及一个 permanent 删除,它从底层存储中删除记录,那么按照惯例,我会使用一个名为 Delete()Action 来进行 soft 删除,而 permanent 删除应该使用 DELETE http 动词。

    • 对此的一般推理与关于locked() 的讨论相同。

    但是,如果 softpermanent 删除的目的是拦截客户端的标准删除工作流程,这样永久删除的概念就会被隐藏或以某种方式抽象它不是通常工作流程的一部分(用户必须去其他地方,例如 回收站 才能查看已删除的记录并恢复或永久删除),在这种情况下,使用 HTTP 动词 @987654353 @ 用于软删除,并进行特定的、可能是集合绑定的 Action 以接受永久删除的请求

    • 此操作可能必须取消绑定或绑定到集合,具体取决于您实现过滤的方式,如果我们删除了记录“xyz”,例如GET: ~/document('xyz') 返回NOT-FOUND,那么我们真的不能指望@987654356 @ 执行...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多