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?
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).
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.
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"}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With