Is there a better way in the new ASP.net MVC 4 WebApi to handle nested resources than setting up a special route for each one? (similar to here: ASP.Net MVC support for Nested Resources? - this was posted in 2009).
For example I want to handle:
/customers/1/products/10/
I have seen some examples of ApiController
actions named other than Get()
, Post()
etc, for example here I see an example of an action called GetOrder()
. I can't find any documentation on this though. Is this a way to achieve this?
If you have used ASP.NET MVC, you are already familiar with controllers. Web API controllers are similar to MVC controllers, but inherit the ApiController class instead of the Controller class. In Solution Explorer, right-click the Controllers folder. Select Add and then select Controller.
Nesting resources provide REST API consumers an easy and efficient way to manage data by allowing the consumer to send and receive only the required object. The nested resource must be a business object, that is, it must still represent a complete business object.
Web. Http” assembly has all features of MVC but does not have routing, and model binding which are exclusive to Web API. MVC does not either support content negotiation or self-hosting. MVC controller is extremely heavy and we can see the number of interfaces the code uses.
Sorry, I have updated this one multiple times as I am myself finding a solution.
Seems there is many ways to tackle this one, but the most efficient I have found so far is:
Add this under default route:
routes.MapHttpRoute( name: "OneLevelNested", routeTemplate: "api/{controller}/{customerId}/{action}/{id}", defaults: new { id = RouteParameter.Optional } );
This route will then match any controller action and the matching segment name in the URL. For example:
/api/customers/1/orders will match:
public IEnumerable<Order> Orders(int customerId)
/api/customers/1/orders/123 will match:
public Order Orders(int customerId, int id)
/api/customers/1/products will match:
public IEnumerable<Product> Products(int customerId)
/api/customers/1/products/123 will match:
public Product Products(int customerId, int id)
The method name must match the {action} segment specified in the route.
From comments
Since the RC you'll need to tell each action which kind of verbs that are acceptable, ie [HttpGet]
, etc.
EDIT: Although this answer still applies for Web API 1, for Web API 2 I strongly advise using Daniel Halan's answer as it is the state of the art for mapping subresources (among other niceties).
Some people don't like to use {action} in Web API because they believe that in doing so they will be breaking the REST "ideology"... I contend that. {action} is merely a construct that helps in routing. It is internal to your implementation and has nothing to do with the HTTP verb used to access a resource.
If you put HTTP verb constraints on the actions and name them accordingly you're not breaking any RESTful guidelines and will end up with simpler, more concise controllers instead of tons of individual controllers for each sub-resource. Remember: the action is just a routing mechanism, and it is internal to your implementation. If you struggle against the framework, then something is amiss either with the framework or your implementation. Just map the route with an HTTPMETHOD constraint and you're good to go:
routes.MapHttpRoute( name: "OneLevelNested", routeTemplate: "api/customers/{customerId}/orders/{orderId}", constraints: new { httpMethod = new HttpMethodConstraint(new string[] { "GET" }) }, defaults: new { controller = "Customers", action = "GetOrders", orderId = RouteParameter.Optional, } );
You can handle these in the CustomersController like this:
public class CustomersController { // ... public IEnumerable<Order> GetOrders(long customerId) { // returns all orders for customerId! } public Order GetOrders(long customerId, long orderId) { // return the single order identified by orderId for the customerId supplied } // ... }
You can also route a Create action on the same "resource" (orders):
routes.MapHttpRoute( name: "OneLevelNested", routeTemplate: "api/customers/{customerId}/orders", constraints: new { httpMethod = new HttpMethodConstraint(new string[] { "POST" }) }, defaults: new { controller = "Customers", action = "CreateOrder", } );
And handle it accordingly in the Customer controller:
public class CustomersController { // ... public Order CreateOrder(long customerId) { // create and return the order just created (with the new order id) } // ... }
Yes, you still have to create a lot of routes just because Web API still can't route to different methods depending on the path... But I think it is cleaner to declaratively define the routes than to come up with a custom dispatching mechanisms based on enums or other tricks.
For the consumer of your API it will look perfectly RESTful:
GET http://your.api/customers/1/orders
(maps to GetOrders(long) returning all orders for customer 1)
GET http://your.api/customers/1/orders/22
(maps to GetOrders(long, long) returning the order 22 for customer 1
POST http://your.api/customers/1/orders
(maps to CreateOrder(long) which will create an order and return it to the caller (with the new ID just created)
But don't take my word as an absolute truth. I'm still experimenting with it and I think MS failed to address properly subresource access.
I urge you to try out http://www.servicestack.net/ for a less painful experience writing REST apis... But don't get me wrong, I adore Web API and use it for most of my professional projects, mainly because it is easier to find programmers out there that already "know" it... For my personal projects I prefer ServiceStack.
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