5

I have a ProductsController and I want to create an action method that copies a product. It only needs one parameter which is an Id which temps me to want to use a simple GET Request. Something is telling me GET doesn't make sense here though. Do I really use POST and pass a single Id to this method?

Here is what I came up with:

// COPY: api/products/{id}/copy
[HttpPost("{id}/copy")]
public void Copy(int id)
{
    _productManager.Copy(sourceProductId: id);
}
Blake Rivell
  • 13,105
  • 31
  • 115
  • 231
  • Using the get verb here does seems semantically incorrect for this situation. – BlakeH May 23 '16 at 13:04
  • @BlakeH would you use post and pass a single Id like I am doing above? – Blake Rivell May 23 '16 at 13:07
  • Some opinions on HTTP verb usage here: http://stackoverflow.com/questions/18755220/what-is-the-restful-way-to-represent-a-resource-clone-operation-in-the-url – Martin Costello May 23 '16 at 13:16
  • If you want it to be strict REST, this answer seems to be generally accepted: http://stackoverflow.com/a/859518/1720077 but I also like http://stackoverflow.com/a/6709383/1720077 – BlakeH May 23 '16 at 13:17
  • @BlakeH Thanks for the useful links. It seems like this is purely preference. If I was passing the entire source object I would want to use POST or PUT without a doubt, but the fact that my copy function only needs an Id is leaning me towards just using a GET. – Blake Rivell May 23 '16 at 13:42

1 Answers1

11

I'd basically handle the situation using one of the following approaches:


1. COPY

Do as per the WebDav specification and just use COPY http method. This, I believe, is the most semantically correct way of cloning/copying a resource in a RESTful manner. Note that REST do not restricts you in using just the default verbs:

[AcceptVerbs("COPY")]
[Route("{id}")]
public void Copy(int id)
{
    _productManager.Copy(sourceProductId: id);
}

2. POST

Keep using the same method you are using for creating a new resource (because, after all, you are creating a new resource), but with an optional query string parameter that will determine if you are copying an existing resource.
You will then trigger the copy operation by sending a POST request with an empty body to a URI similar to the following one: http://myhost/api/products?sourceId=1. Here it is a code sample:

[HttpPost]
[Route("")]
public void CreateOrCopy([FromBody] ProductDTO dto, [FromUri] int? sourceId = null)
{
    if(dto == null && sourceId.HasValue)
        _productManager.Copy(sourceProductId: id); // copy behavior
    else
        // normal behavior
}

I think that both approaches are very usable from a client point of view, without breaking REST constraints or semantic meanings (absolutely avoid the use of GET for such a purpose).

Community
  • 1
  • 1
Federico Dipuma
  • 17,655
  • 4
  • 39
  • 56
  • Thanks!! I didn't even know there was a COPY HTTP Method, this is exactly what I need. Can you specify any Verbs that you want and the controller will route to that function? Additionally is there anything I need to do specifically when calling this method from a client or should it work just like DELETE? – Blake Rivell May 23 '16 at 14:31
  • Hmm just realized that my function all needs the client to pass the username so I guess I still need to do a post. – Blake Rivell May 23 '16 at 14:38
  • I decided to just send the entire object from the client even though I really only need the Username and Id. Since I am already using POST and PUT for my basic Create and Update functions will I need to edit the url to have /copy in it or can I use HttpVerbs? – Blake Rivell May 23 '16 at 14:54
  • 2
    Appending /copy to a URI is not restful at all. I suggest you to use the same POST method you are already using, and check for those parameter either in the query string or inside the body of the request. – Federico Dipuma May 23 '16 at 14:57
  • Alright one last question: lets say I just want to do a POST now since I want to pass the entire source object instead of just the Id since the object will have the InsertuserId set. How can I make sure that it gets routed to my Copy Action Method rather than Create or Update? – Blake Rivell May 23 '16 at 15:13
  • 1
    What I am saying is that you should use one single method for both Create and Copy (in a manner similar to what I described in the answer, but without the other parameters). Because you are practically just creating a new object (which is a copy of another, but this does not matter anymore). If you need more parameters then add them to your model (the `ProductDTO` object I declared in my code sample). – Federico Dipuma May 23 '16 at 15:17
  • Perfect! I fully understand now. – Blake Rivell May 23 '16 at 15:20