How to handle hierarchical routes in ASP.NET Web API?

24,182

Solution 1

Configure the routes as below. The {param} is optional (use if you need):

routes.MapHttpRoute(
           name: "childapi",
           routeTemplate: "api/Parent/{id}/Child/{param}",
           defaults: new { controller = "Child", param = RouteParameter.Optional }
  );

routes.MapHttpRoute(
         name: "DefaultApi",
         routeTemplate: "api/{controller}/{id}",
         defaults: new { id = RouteParameter.Optional }
  );

Then call the child APi as /api/Parent/1/child The parent can be called simple as /api/Parent/

The child controller:

    public class ChildController : ApiController
    {     
        public string Get(int id)
        {
          //the id is id between parent/{id}/child  
          return "value";
        }
        .......
    }

Solution 2

Since Web API 2 you can now use Route Attributes to define custom routing per Method,

[Route("api/customers/{id:guid}/orders")]
public IEnumerable<Order> GetCustomerOrders(Guid id) {
   return new Order[0];
}

You also need to add following line to WebApiConfig.Register() initialization method,

  config.MapHttpAttributeRoutes();

Full article, http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

Solution 3

I wanted to handle this in a more general way, instead of wiring up a ChildController directly with controller = "Child", as Abhijit Kadam did. I have several child controllers and didn't want to have to map a specific route for each one, with controller = "ChildX" and controller = "ChildY" over and over.

My WebApiConfig looks like this:

config.Routes.MapHttpRoute(
  name: "DefaultApi",
  routeTemplate: "api/{controller}/{id}",
  defaults: new { id = RouteParameter.Optional }
);
  config.Routes.MapHttpRoute(
  name: "ChildApi",
  routeTemplate: "api/{parentController}/{parentId}/{controller}/{id}",
  defaults: new { id = RouteParameter.Optional }
);

My parent controllers are very standard, and match the default route above. A sample child controller looks like this:

public class CommentController : ApiController
{
    // GET api/product/5/comment
    public string Get(ParentController parentController, string parentId)
    {
        return "This is the comment controller with parent of "
        + parentId + ", which is a " + parentController.ToString();
    }
    // GET api/product/5/comment/122
    public string Get(ParentController parentController, string parentId,
        string id)
    {
        return "You are looking for comment " + id + " under parent "
            + parentId + ", which is a "
            + parentController.ToString();
    }
}
public enum ParentController
{
    Product
}

Some drawbacks of my implementation

  • As you can see, I used an enum, so I'm still having to manage parent controllers in two separate places. It could have just as easily been a string parameter, but I wanted to prevent api/crazy-non-existent-parent/5/comment/122 from working.
  • There's probably a way to use reflection or something to do this on the fly without managing it separetly, but this works for me for now.
  • It doesn't support children of children.

There's probably a better solution that's even more general, but like I said, this works for me.

Solution 4

An option beyond using default mvc routing is to look at Attribute Routing - https://github.com/mccalltd/AttributeRouting. Although its more work, decorating individual action methods provides a ton of flexibility when you need to design complicated routes. You can also use it in conjunction with standard MVC routing.

Share:
24,182
Haris Hasan
Author by

Haris Hasan

https://www.linkedin.com/pub/haris-hasan/10/98a/391

Updated on November 14, 2020

Comments

  • Haris Hasan
    Haris Hasan over 3 years

    Currently I have two controllers

    1 - Parent Controller

    2 - Child Controller

    I access my Parent Controller like this

    someurl\parentcontroller
    

    Now I want to access my children controller like this

    someurl\parentcontroller\1\childcontroller
    

    This last url should return all the children of a particular parent.

    I have this route currently in my global.asax file

    routes.MapHttpRoute ("Route1", "{controller}/{id}", new { id = RouteParameter.Optional });

    I am not sure how can I achieve my parent\id\child hierarchy.. How should I configure my routes to achieve this? Ideas?

  • Haris Hasan
    Haris Hasan almost 12 years
    Not working.. can you elaborate how would the Child controller look in this case? How would I get the parent id?
  • Imran Qadir Baksh - Baloch
    Imran Qadir Baksh - Baloch almost 12 years
    @HarisHasan, is you seen the above link?
  • Haris Hasan
    Haris Hasan almost 12 years
    @user960567 Yes I have seen it. But the approach in this answer is much simpler.
  • Mikey Hogarth
    Mikey Hogarth almost 11 years
    Another drawback: Imagine you have a second type of child (e.g. here a product has many comments, what if a product also had many "countries" - your generalizing of "parent" prevents that from being possible)
  • mo.
    mo. almost 11 years
    I don't see why that would be the case. That's the very purpose of my answer. In fact my actual implementation where I use the above code has several different types of children. Just have a similar class, replacing the word "Comment" with "Country" everywhere. Then your URL is api/product/5/country/122... Am I misunderstanding your remark?
  • Jason Coyne
    Jason Coyne about 10 years
    The harder problem is that if the same child type could have multiple parents, you can't use a different controller for each relationship, the single child would have to fork its own logic internally to handle both parent cases
  • mo.
    mo. about 10 years
    In my production code, I have two child types, each of which can be nested under any of three different parent types. The controller doesn't care so much what the parent is, but at some point, yes, I had to have a switch somewhere in the depths of my dependencies. I don't see that as a significant drawback in practical application of this solution. You could totally design a powerful solution around IHttpControllerSelector that handles it more elegantly, but that's beyond the scope of my answer.