26

我在工作中遇到了一个问题,我找不到有关在 RESTful Web 服务中针对主键是其他资源 ID 组合的资源执行 CRUD 操作的常用标准或实践的信息。我们正在使用 MVC WebApi 创建控制器。例如,我们有三个表:

  • Product: PK=产品编号
  • Part: PK=PartId
  • ProductPartAssoc: PK=(ProductId, PartId)

一个产品可以有很多部分,一个部分可以是很多产品的组成部分。关联表还包含与关联本身相关的附加信息,而不是需要可编辑的。

我们有ProductsControllerPartsController类使用定义为的路由模板处理通常的 GET/PUT/POST/DELETE 操作:{controller}/{id}/{action}这样以下 IRI 可以工作:

  • GET,POST /api/Products- 返回所有产品,创建一个新产品
  • GET,PUT,DELETE /api/Products/1- 检索/更新/删除产品 1
  • GET,POST /api/Parts- 返回所有部分,创建一个新部分
  • GET,PUT,DELETE /api/Parts/2- 检索/更新/删除第 2 部分
  • GET /api/Products/1/Parts- 获取产品 1 的所有部件
  • GET /api/Parts/2/Products- 获取第 2 部分是组件的所有产品

我遇到的问题是如何为 ProductPartAssoc 资源定义路由模板。获取关联数据的路由模板和 IRI 应该是什么样的?遵守惯例,我希望是这样的:

  • GET,POST /api/ProductPartAssoc- 返回所有关联,创建关联
  • GET,PUT,DELETE /api/ProductPartAssoc/[1,2]- 检索/更新/删除产品 1 和第 2 部分之间的关​​联

我的同事发现这在美学上令人不快,并且似乎认为完全没有ProductPartAssocController课程会更好,而是ProductsController向管理关联数据添加额外的方法:

  • GET,PUT,DELETE /api/Products/1/Parts/2- 获取产品 1 和第 2 部分之间关联的数据,而不是作为第 1 部分的成员的第 2 部分的数据,这通常是基于其他示例(例如/Book/5/Chapter/3我在其他地方看到的)的情况。
  • POST 这里不知道他们期望 IRI 是什么样子。不幸的是,他们是决策者。

归根结底,我想我正在寻找的不是验证,就是我可以指出并说“看,这就是其他人所做的”的方向。

处理由复合键标识的资源的典型做法是什么?

4

2 回答 2

21

我也很喜欢它的美学/api/Products/1/Parts/2。您还可以让多个路由执行相同的操作,因此您可以加倍并提供/api/Parts/2/Products/1同一资源的备用 URL。

至于 POST,您已经知道复合键。那么为什么不消除对 POST 的需求,只使用 PUT 来创建和更新呢?如果您的系统生成主键,则 POST 到集合资源 URL 非常好,但是如果您有已知主键的组合,为什么需要 POST?

也就是说,我也喜欢单独ProductPartAssocController包含这些 URL 的操作的想法。您必须进行自定义路由映射,但如果您使用的是AttributeRouting.NET之类的东西,这很容易做到。

例如,我们这样做是为了管理角色中的用户:

PUT, GET, DELETE /api/users/1/roles/2
PUT, GET, DELETE /api/roles/2/users/1

6 个 URL,但只有 3 个操作,都在GrantsController(我们将用户和角色之间的动名词称为“授予”)。类最终看起来像这样,使用AttributeRouting.NET

[RoutePrefix("api")]
[Authorize(Roles = RoleName.RoleGrantors)]
public class GrantsController : ApiController
{
    [PUT("users/{userId}/roles/{roleId}", ActionPrecedence = 1)]
    [PUT("roles/{roleId}/users/{userId}", ActionPrecedence = 2)]
    public HttpResponseMessage PutInRole(int userId, int roleId)
    {
        ...
    }

    [DELETE("users/{userId}/roles/{roleId}", ActionPrecedence = 1)]
    [DELETE("roles/{roleId}/users/{userId}", ActionPrecedence = 2)]
    public HttpResponseMessage DeleteFromRole(int userId, int roleId)
    {
        ...
    }

    ...etc
}

这对我来说似乎是一种相当直观的方法。将动作保存在单独的控制器中也有助于更精简的控制器。

于 2013-04-24T17:35:47.613 回答
1

我建议:

  • POST /api/PartsProductsAssoc:在零件和产品之间创建链接。在 POST 数据中包含部件和产品 ID。
  • GET、PUT、DELETE /api/PartsProductsAssoc/<assoc_id>:读取/更新/删除链接<assoc_id>(不是零件或产品 ID,是的,这意味着在您的 PartsProductsAssoc 表中创建一个新列)。
  • GET /api/PartsProductsAssoc/Parts/<part_id>/Products:获取与给定部件相关的产品列表。
  • GET /api/PartsProductsAssoc/Products/<product_id>/Parts:获取与给定产品相关的部件列表。

采用这种方法的原因:

  • 每个链接的单一、完全限定的 URI。
  • 修改链接会修改单个 REST 资源。

有关详细信息,请参阅56:30 的https://www.youtube.com/watch?v=hdSrT4yjS1g

于 2015-02-19T21:13:20.657 回答