Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.net Core RC2 Web API POST - When to use Create, CreatedAtAction, vs. CreatedAtRoute?

What are the fundamental differences of those functions? All I know is all three result in a 201, which is appropriate for a successful POST request.

I only follow examples I see online, but they don't really explain why they're doing what they're doing.

We're supposed to provide a name for our GET (1 record by id):

[HttpGet("{id}", Name="MyStuff")]
public async Task<IActionResult> GetAsync(int id)
{
     return new ObjectResult(new MyStuff(id));
}

What is the purpose of naming this get function, besides that it's "probably" required for the POST function below:

[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{
     // actual insertion code left out

     return CreatedAtRoute("MyStuff", new { id = myStuff.Id }, myStuff);
}

I notice that CreatedAtRoute also has an overload that does not take in the route name.

There is also CreatedAtAction that takes in similar parameters. Why does this variant exist?

There is also Created which expects a URL and the object we want to return. Can I just use this variant and provide a bogus URL and return the object I want and get it done and over with?

I'm not sure why there are so many variants just to be able to return a 201 to the client. In most cases, all I want to do is to return the "app-assigned" (most likely from a database) unique id or a version of my entity that has minimal information.

I think that ultimately, a 201 response "should" create a location header which has the URL of the newly-created resource, which I believe all 3 and their overloads end up doing. Why should I always return a location header? My JavaScript clients, native mobile, and desktop apps never use it. If I issue an HTTP POST, for example, to create billing statements and send them out to users, what would such a location URL be? (My apologies for not digging deeper into the history of the Internet to find an answer for this.)

Why create names for actions and routes? What's the difference between action names and route names?

I'm confused about this, so I resorted to returning the Ok(), which returns 200, which is inappropriate for POST.

like image 680
Mickael Caruso Avatar asked Jun 15 '16 15:06

Mickael Caruso


1 Answers

There's a few different questions here which should probably be split out, but I think this covers the bulk of your issues.

Why create names for actions and routes? What's the difference between action names and route names?

First of all, actions and routes are very different.

An Action lives on a controller. A route specifies a complete end point that consists of a Controller, and Action and potentially additional other route parameters.

You can give a name to a route, which allows you to reference it in your application. for example

routes.MapRoute(
  name: "MyRouteName",
  url: "SomePrefix/{action}/{id}",
  defaults: new { controller = "Section", action = "Index" }
);

The reason for action names are covered in this question: Purpose of ActionName

It allows you to start your action with a number or include any character that .net does not allow in an identifier. - The most common reason is it allows you have two Actions with the same signature (see the GET/POST Delete actions of any scaffolded controller)

What are the fundamental differences of those functions?

These 3 functions all perform essentially the same function - returning a 201 Created response, with a Location header pointing to the url for the newly created response, and the object itself in the body. The url should be the url at which a GET request would return the object url. This would be considered the 'Correct' behaviour in a RESTful system.

For the example Post code in your question, you would actually want to use CreatedAtAction.

[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{  
   // actual insertion code left out

   return CreatedAtAction("MyStuff", new { id = myStuff.Id }, myStuff);
}

Assuming you have the default route configured, this will add a Location header pointing to the MyStuff action on the same controller.

If you wanted the location url to point to a specific route (as we defined earlier, you could use e.g.

[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{  
   // actual insertion code left out

   return CreatedAtRoute("MyRouteName", new { id = myStuff.Id }, myStuff);
}

Can I just use this variant and provide a bogus URL and return the object I want and get it done and over with?

If you really don't want to use a CreatedResult, you can use a simple StatusCodeResult, which will return a 201, without the Location Header or body.

[HttpPost]
public async Task<IActionResult> PostAsync([FromBody]MyStuff myStuff)
{  
  // actual insertion code left out

  return StatusCode(201);
}
like image 69
Sock Avatar answered Nov 13 '22 11:11

Sock