I have written a REST service using Web API and after reading sections of this Web API Design from Brian Mulloy, was trying to figure out how I could implement associations with Web API.
Web API Design Extract:
Associations
Resources almost always have relationships to other resources. What's a simple way to express these relationships in aWebAPI?
Let's look again at the API we modeled in nouns are good, verbs are bad -theAPI that interacts with our dogs resource. Remember, we had two base URLs: /dogs and dogs/1234.
We're using HTTP verbs to operate on the resources and collections. Our dogs belong to owners. To get all the dogs belonging to a specific owner, or to create a new dog for that owner, do a GET or a POST:
GET /owners/5678/dogs
POST /owners/5678/dogsNow, the relationships can be complex. Owners have relationships with veterinarians, who have relationships with dogs, who have relationships with food, and so on. It's not uncommon to see people string these together making a URL 5 or 6 levels deep. Remember that once you have the primary key for one level, you usually don't need to include the levels above because you've already got your specific object. In other words, you shouldn't need too many cases where a URL is deeper than what we have above /resource/identifier/resource.
So I tried to add a controller method for the association like follows:
public class EventsController : ApiController
{
// GET api/events
public IEnumerable<Event> Get()
{
// get list code
}
// GET api/events/5
public Event Get(int id)
{
// get code
}
// POST api/events
public void Post([FromBody]Event evnt)
{
// add code
}
// POST api/events/5
public void Post(int id, [FromBody]Event evnt)
{
// update code
}
// DELETE api/events/5
public void Delete(int id)
{
// delete code
}
// GET api/events/5/guests
public IEnumerable<Guest> Guests(int id)
{
// association code
}
}
I also modified my route templates to the following:
config.Routes.MapHttpRoute("ApiWithAssociations",
"api/{controller}/{id}/{action}");
config.Routes.MapHttpRoute("DefaultApi",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });
Unfortunately, when I do an update/post of the event resource I now get a HTTP 500 Internal Server Error with a response body stating
Multiple actions were found that match the request
I've tried modifying the route templates in conjunction with adding System.Web.Http.HttpPostAttribute (and other HTTP verbs) as well but to no avail.
Has anyone tried this and got it working? Any help would be appreciated. If it is absolutely not possible to have multiples for an http verb then I guess I'll have to abandon associations with my REST service.
EDIT: SOLUTION
Using Radim Köhler's answer, I was able to get this working. Add the HttpGetAttribute to the Guests method like so:
// GET api/event/5/guests
[HttpGet]
public IEnumerable<Guest> Guests(int id)
{
// association code
}
And added an addition route to cater for the default GET action like follows:
config.Routes.MapHttpRoute("DefaultGet",
"api/{controller}/{id}",
new {action = "Get"},
new {httpMethod = new HttpMethodConstraint(HttpMethod.Get)});
config.Routes.MapHttpRoute("ApiWithAssociations",
"api/{controller}/{id}/{action}");
config.Routes.MapHttpRoute("DefaultApi",
"api/{controller}/{id}",
new {id = RouteParameter.Optional});