Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Created (or CreatedAtAction / CreatedAtRoute) in an asp net core api

I'd like to inform consumers of my api about the location of the newly created object. I know there is Created() CreatedAtRoute() and CreatedAtAction() but I am unsure how to use it.

Here is what I've tried:

I have a Get resource to which I would like to point. It takes an ID as input:

    [HttpGet("/${id}", Name = "GetProduct")]     [ProducesResponseType(typeof(Produkt), 200)]     public IActionResult Get([FromRoute] int id)     {        // some code...         return Ok(...);     } 

When a product gets created via my POST route, I would like to point to this Resource via the Location header:

Attempt 1

    [HttpPost]     [ProducesResponseType(typeof(Produkt), 200)]     public IActionResult CreateNewProduct([FromBody] ProduktDtoForCreate productFromBody)     {         //...         return CreatedAtRoute("GetProduct", new { id = productToCreate.Id }, productToCreate);     } 

This returns a Location Header of: http://localhost:5000/$15003

Attempt 2

    [HttpPost]     [ProducesResponseType(typeof(Produkt), 200)]     public IActionResult CreateNewProduct([FromBody] ProduktDtoForCreate productFromBody)     {         //...         return Created(new Uri($"{Request.Path}/{productToCreate.Id}", UriKind.Relative), productToCreate);     } 

This one works and returns /api/v1.0/produkte/16004 but it seems like using the current request to point to the new location should not be needed. Also I am not sure if this is good practice?

like image 304
Riscie Avatar asked Dec 22 '17 10:12

Riscie


People also ask

What is the use of CreatedAtRoute?

The CreatedAtRoute method is intended to return a URI to the newly created resource when you invoke a POST method to store some new object. So if you POST an order item for instance, you might return a route like 'api/order/11' (11 being the id of the order obviously).

What is C# CreatedAtAction?

CreatedAtAction(String, Object, Object) Creates a CreatedAtActionResult object that produces a Status201Created response. CreatedAtAction(String, Object) Creates a CreatedAtActionResult object that produces a Status201Created response.


2 Answers

CreatedAtAction gives the best output in my opinion. The following controller code will do what you need:

[Route("api/products")] [ApiController] public class ProductsController : ControllerBase {     private readonly IProductRepository productRepository;      public ProductsController(IProductRepository productRepository)     {         this.productRepository = productRepository;     }      [HttpPost]     [Route("")]     [ProducesResponseType(StatusCodes.Status201Created)]     public ActionResult<Product> CreateProduct(ProductCreateDto product)     {         if (product is null)             return BadRequest(new ArgumentNullException());          var entity = productRepository.CreateProduct(product);          return CreatedAtAction(nameof(GetProduct), new { id = entity.ID }, entity);     }      [HttpGet]     [Route("{id}")]     public ActionResult<Product> GetProduct(int id)     {         return productRepository.GetProduct(id);     } } 

Issuing the following request:

POST http://localhost:5000/api/products HTTP/1.1  Host: localhost:5000 Connection: keep-alive  Content-Length: 25  Content-Type: application/json  { "name": "ACME Widget" } 

Will yield the following response:

HTTP/1.1 201 Created Date: Mon, 12 Oct 2020 09:50:00 GMT Content-Type: application/json; charset=utf-8 Server: Kestrel Content-Length: 29 Location: http://localhost:5000/api/products/1  {"id":1,"name":"ACME Widget"} 
like image 111
phil_rawlings Avatar answered Sep 22 '22 04:09

phil_rawlings


In your route for the Get method, take both the leading / and the $ out (i.e. it should just be "{id}"). Having the leading / in there means that the route will be relative to the base of the application; taking it out makes the route for the method relative to the controller's base path instead. The $ is being treated as a literal character in the route, hence why it was appearing in the Location header in Attempt 1. Once you've made the changes, you should find that your CreatedAtRoute call works as you would expect.

like image 27
Graham Watts Avatar answered Sep 24 '22 04:09

Graham Watts