179

From the template for Web API 2, a post method is always like this:

[ResponseType(typeof(MyDTO))]
public IHttpActionResult PostmyObject(MyDTO myObject)
{
    ...
    return CreatedAtRoute("DefaultApi", new { id = myObject.Id }, myObject);
}

I don't understand this CreatedAtRoute() method. Can anyone explain it to me?

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
martial
  • 3,773
  • 8
  • 33
  • 43

3 Answers3

186

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).

BTW I agree that the MSDN article is of no use in understanding this. The route you actually return will naturally depend on your routing setup.

Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
see sharper
  • 11,505
  • 8
  • 46
  • 65
  • Thank you. This is very helpful. But when I tested on Postman, I saw what returned is myObject, not the route? – martial Aug 05 '14 at 19:41
  • 15
    What it returns is actually a CreatedAtRouteNegotiatedContentResult object! That's what you'll see if you run a unit test on your action. However when run in the context of http, it will return the serialized object in the body, but you should see a header in the response with the link to the resource. BTW if you think I answered the question, could you mark as answer? Cheers. – see sharper Aug 07 '14 at 22:48
  • 2
    The route that you supplied appears as a Location header in the response. This is fairly typical REST behavior – Jeff Martin May 13 '15 at 21:54
  • 1
    Why do I have to return MyObject in the CreatedAtRoute, its already on the client... Only the Id is new which is already in the anonymous type?! – Elisabeth Jun 18 '15 at 21:30
  • 1
    @Elisa: MyObject is not returned by CreatedAtRoute - just the URI of the new resource. This will include the Id, which the client won't know yet (at least in a typical scenario where the db generates the id), and that is the main purpose of the method. It's standard with REST to return that when POSTing a new resource, but it's not compulsory or anything. If you don't need it, you could just return a 201. – see sharper Jun 19 '15 at 01:58
  • 5
    @seesharper When the MyObject is not returned BUT... why do I have to pass it to the CreatedAtRoute? What is the method doing with it? – Elisabeth Jun 19 '15 at 15:43
  • 1
    Ok this sort of answers the question for the simple case, but how about a more complex case? How would I call CreatedAtRoute when the URI I want to return is /api/Customers/{customerId}/Order/{orderId}? – Greg Veres May 22 '16 at 01:27
  • 1
    Here is [another link](http://stackoverflow.com/questions/29636095/web-api-2-post-urlhelper-link-must-not-return-null) to a related question about CreatedAtRoute that talks about naming routes when you use attributes (i.e. for complicated routes like the customer example I gave.) It solved my problem with filling out both parameters. – Greg Veres May 22 '16 at 01:43
  • 7
    Is there a way to use current route? For example, if I create an object in the files controller using `[Route("[controller]")]` on controller, what do I return (so that the adjacent GET action can be invoked with the URL, for instance)? – Shimmy Weitzhandler Dec 22 '17 at 07:31
  • @seesharper, When replying to Elisabeth, you say that MyObject is not returned by CreatedAtRoute. Though in a comment you made previously in this thread you say that it is returned in serialized form in the body. Could you clarify on this? Am I correct in thinking that both a URI to the resource, and the resource itself (in the body) is passed back to the client from the CreatedAtRoute call? If so, what is the point of including both? – Evan Sevy Jul 31 '19 at 22:40
  • 2
    @RuneStar good question. It's a long time since I wrote this answer and I haven't worked with Web API for a while, but yes, CreatedAtRoute returns the location as a header and the object itself in the body. So that comment to Elisabeth was wrong, it seems. As to the purpose, my guess is that with a POST request (as opposed to a PUT) it may be that the server creates an object that is not merely what the client sent, and the client may be interested in that, and not want to have to make a second request to the returned location URI. – see sharper Jul 31 '19 at 23:57
  • Am I right to understand that the CreatedAtRoute returns the created object and a link to the Get method. I originaly thought that the returned object is produced by a call to the Get method. – variable Jun 06 '20 at 18:23
  • @ShimmyWeitzhandler See [this answer](https://stackoverflow.com/questions/18248547/get-controller-and-action-name-from-within-controller) for how to get the controller name. I don't believe the `"[controller]"` idea works outside of `Route()` :( – Lucas Mar 27 '21 at 15:05
35

When you use CreatedAtRoute, the first argument is the route name of the GET to the resource. The trick that is not so obvious is that, even with the correct method name specified, you must thus use the Name param on the HttpGet attribute for it to work.

So if the return in your POST is this:

return CreatedAtRoute("Get", routeValues: new { id = model.Id }, value: model);

Then your Get method attribute should look like this even if your method is named Get:

[HttpGet("{id}", Name = "Get")]

Calls to your Post method will not only return the new object (normally as JSON), it will set the Location header on the response to the URI that would get that resource.

NOTE the field names in the routeValues field names need to match the binding names in the target route, i.e. there needs to be a field named id to match the {id} in HttpGet("{id}"

Finally, in some cases, it should be mentioned that the CreatedAtAction helper can be a more direct solution.

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
Scott Blasingame
  • 1,821
  • 2
  • 15
  • 7
  • "This will not only return the new object (normally as JSON), it will set the Location header on the response to the URI that would get that resource." By "This", do you mean HttpGet or HttpPost? Also, what do you mean by "it will set the Location header on the response to the URI that would get that resource."? – Tran Anh Minh Apr 27 '20 at 17:50
  • "This" was referring to the HttpPost method (edit the answer). As to your question regarding Location header, that is an Http Header that the client can decide to do something with like automatically redirect to it. It's a standard Http Response Header (https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Standard_response_fields). – Scott Blasingame Apr 30 '20 at 12:26
  • Thank you so very much for posting this... I just spent legit 2 hours trying to get rid of "No route matches the supplied values" trying every possible combination of values I could think of only to find out that I had to make sure to new-up and assign username to user.UserName which I didn't think would be a problem since I already had the string value available but in UserName and it turned out to be the cause all along... it was so frustrating. My return looks like this: return CreatedAtRoute("GetUser", routeValues: new {username = user.UserName}, _mapper.Map(photo)); – Andrey Vasilyev Oct 12 '22 at 07:54
20

In .net core WebAPI, you use this method to return a 201 code, which means that the object was created.

[Microsoft.AspNetCore.Mvc.NonAction]
public virtual Microsoft.AspNetCore.Mvc.CreatedAtRouteResult CreatedAtRoute (string routeName, object routeValues, object content);

As you can see above, the CreatedAtRoute can receive 3 parameters:

routeName Is the name that you must put on the method that will be the URI that would get that resource after created.

routeValues It's the object containing the values that will be passed to the GET method at the named route. It will be used to return the created object

content It's the object that was created.

The above example shows the implementation of two methods of a simple controller with a simple GET method with the bonded name and the POST method that creates a new object.

[Route("api/[controller]")]
[ApiController]
public class CompanyController : Controller
{
    private ICompanyRepository _companyRepository;

    public CompanyController(ICompanyRepository companyRepository)
    {
        _companyRepository = companyRepository;
    }

    [HttpGet("{id}", Name="GetCompany")]
    public IActionResult GetById(int id)
    {
        Company company = _companyRepository.Find(id);

        if (company == null)
        {
            return NotFound();
        }
        
        return new ObjectResult(company);
    }

    [HttpPost]
    public IActionResult Create([FromBody] Company company)
    {
        if (company == null)
        {
            return BadRequest();
        }

        _companyRepository.Add(company);

        return CreatedAtRoute(
            "GetCompany",
            new { id = company.CompanyID },
            company);
    }
}

IMPORTANT

  1. Notice that the first parameter at CreatedAtRoute (routeName), must be the same at the definition of the Name at the Get method.

  2. The object on the second parameter will need to have the necessary fields that you use to retrieve the resource on the Get method, you can say that it's a subset of the object created itself

  3. The last parameter is the company object received in the body request in it's full form.

FINALY

As final result, when the Post to create a new company got made to this API, you will you return a route like 'api/company/{id}' that will return to you the newly created resource

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
Bruno Bastos
  • 786
  • 6
  • 6