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

4

38

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?

Cyclopropane answered 28/5, 2012 at 11:29 Comment(2)
forums.asp.net/t/1806436.aspxFeatherstitch
This looks like a duplicate of #9595171Doridoria
C
32

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";
        }
        .......
    }
Cannady answered 28/5, 2012 at 12:11 Comment(3)
Not working.. can you elaborate how would the Child controller look in this case? How would I get the parent id?Cyclopropane
@HarisHasan, is you seen the above link?Featherstitch
@user960567 Yes I have seen it. But the approach in this answer is much simpler.Cyclopropane
W
20

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

Whereas answered 13/11, 2014 at 11:19 Comment(0)
D
10

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.

Doridoria answered 11/3, 2013 at 15:0 Comment(4)
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)Dm
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?Doridoria
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 casesSedgewick
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.Doridoria
S
5

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.

Sambo answered 28/5, 2012 at 23:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.