I\'ve come across a problem at work where I can\'t find information on the usual standard or practice for performing CRUD operations in a RESTful web service against a resource
I suggest:
/api/PartsProductsAssoc
: Create link between part and product. Include part and product ids in POST data./api/PartsProductsAssoc/<assoc_id>
: read/update/delete link with <assoc_id>
(not part or product id, yes, this means creating a new column in your PartsProductsAssoc table)./api/PartsProductsAssoc/Parts/<part_id>/Products
: get list of products associated with the given part./api/PartsProductsAssoc/Products/<product_id>/Parts
: get list of parts associated with the given product.Reasons to take this approach:
For more info, see https://www.youtube.com/watch?v=hdSrT4yjS1g at 56:30.
I too like the aesthetics of /api/Products/1/Parts/2
. You could also have multiple routes go to the same action, so you could double up and also offer /api/Parts/2/Products/1
as an alternate URL for the same resource.
As for POST, you already know the composite key. So why not eliminate the need for POST and just use PUT for both creation and updates? POST to a collection resource URL is great if your system generates the primary key, but in cases where you have a composite of already known primary keys, why do you need POST?
That said, I also like the idea of having a separate ProductPartAssocController
to contain the actions for these URL's. You would have to do a custom route mapping, but if you're using something like AttributeRouting.NET that is very easy to do.
For example we do this for managing users in roles:
PUT, GET, DELETE /api/users/1/roles/2
PUT, GET, DELETE /api/roles/2/users/1
6 URL's, but only 3 actions, all in the GrantsController
(we call the gerund between users and roles a "Grant"). Class ends up looking something like this, using 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
}
This seems a fairly intuitive approach to me. Keeping the actions in a separate controller also makes for leaner controllers.