I'm creating a new webapi using attribute routing to create a nested route as so:
// PUT: api/Channels/5/Messages [ResponseType(typeof(void))] [Route("api/channels/{id}/messages")] public async Task<IHttpActionResult> PostChannelMessage(int id, Message message) { if (!ModelState.IsValid) { return BadRequest(ModelState); } if (id != message.ChannelId) { return BadRequest(); } db.Messages.Add(message); await db.SaveChangesAsync(); return CreatedAtRoute("DefaultApi", new { id = message.Id }, message); }
I however want to return a route that isn't nested i.e.:
/api/Messages/{id}
which is defined on the messages controller. However The CreatedAtRoute call above is not resolving this route and instead throwing. Have I done something wrong, or does it not support routing to different api controller? n.b. the route I am trying to hit is not an attribute route, just a default one.
The exception is:
Message: "An error has occurred." ExceptionMessage: "UrlHelper.Link must not return null." ExceptionType: "System.InvalidOperationException" StackTrace: " at System.Web.Http.Results.CreatedAtRouteNegotiatedContentResult
1.Execute() at System.Web.Http.Results.CreatedAtRouteNegotiatedContentResult
1.ExecuteAsync(CancellationToken cancellationToken) at System.Web.Http.Controllers.ApiControllerActionInvoker.d__0.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter
1.GetResult() at System.Web.Http.Dispatcher.HttpControllerDispatcher.d__0.MoveNext()"
If it doesn't support this, what is the canonical way to return a 201 and can I do it in a refactor safe way?
Oh dear, this may be a new record for answering my own question.
return CreatedAtRoute("DefaultApi", new { controller = "messages", id = message.Id }, message);
does the trick. i.e. explicitly specifying the controller. I worked this our by seeing that the exception was related to the UrlHelper and reading its docs...
Late to the party but an alternative answer. If the action you are routing to also uses attribute routing, you can give the route a name and pass that in to the CreatedAtRoute method. This is done by setting a Name
property on the Route
. Following your post example, consider the following action.
// GET: api/Messages/5 [Route("api/messages/{id}", Name="GetMessage")] public async Task<IHttpActionResult> GetMessage(int id) { // get the message }
Note that the Name
property on the route attribute, [Route("api/messages/{id}", Name="GetMessage")]
, is set to "GetMessage"
. By doing this we can call the CreatedAtRoute
method from the PostChannelMessage
action and pass in the route name like so:
return CreatedAtRoute("GetMessage", new { id = message.Id }, message);
This is a scenario I encountered and my searching led here so thought I would post this alternative answer in case it helps anyone else.
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