.Net Web Api Accepted vs AcceptedAtAction

Make your code simpler using AcceptedAtAction instead of Accepted

.Net Web Api Accepted vs AcceptedAtAction
Photo by Douglas Lopes / Unsplash

In the past week I came across some web api code where the Action returned an Accepted result. You know an HTPP 202 response. The code was as follows:

[HttpPost]
public async Task<IActionResult> DoSomething(CancellationToken cancellationToken){

    int resultId = await SomeStuff() ///Code does something;
    var location = $"{Request.Path}/{resultId}";
    return Accepted(location,, new {id=$"{resultId}"});
}

[HttpGet("{id}")]
public async Task<IActionResult> GetSomething(int id, CancellationToken cancellationToken){
    
    var something = await FetchSomething(id);
    if(something == null) return NotFound();
    return Ok(something);
}

As you can see above what this will do is return an HTTP 202 with a Location HTTP Header set to the path of the request appended with the id. The idea is that when you perform this post you can use the Location header to check for the final result since "accepted" only tells you :"Okay we got your info and we will do something with it, please call back to see if it ready". This calling back is done by looking at the Location header address. If you perform a GET request you might get a result depending if the processing has completed or not.

Mind the location header: http://localhost:5176/123

Even though the code above is not wrong and will work just fine there are 2 subtle improvements which will make it less work and cleaner to read:

  1. Use the AcceptedAtAction instead of Accepted
  2. There is no need for string interpolation in de routeData parameter, asp.net will infer the correct type just fine (or do a .ToString())
[HttpPost]
public async Task<IActionResult> DoSomething(CancellationToken cancellationToken){

    int resultId = await SomeStuff(); ///Code does something
    return AcceptedAtAction(nameof(GetSomething), new { id= resultId });
}

[HttpGet("{id}")]
public async Task<IActionResult> GetSomething(int id, CancellationToken cancellationToken){
    
    var something = await FetchSomething(id);
    if(something == null) return NotFound();
    return Ok(something);
}

The AcceptedAtAction will automatically construct the required url for the Location header for you.

You do need to make sure the property in the routedata parameter has the same name as the parameter in the GET route. Here it is "id", but if it was "[HttpGet("{somethingId}")] you also need to pass it like "new { somethingId= resultId }".

There you saved yourself the headache of constructing the routes yourself, you've got compile time checks should it ever change by using the nameof() and your code is simpeler and cleaner.

☝️
You can also pass the id in the body of the result by changing the return line as follows: return AcceptedAtAction(nameof(GetSomething), new { id = resultId}, resultId)
Notice there is now a Response body as well