5

I have the following rest schema that I'd like to implement using the ASP.NET Web Api:

http://mydomain/api/students
http://mydomain/api/students/s123
http://mydomain/api/students/s123/classes
http://mydomain/api/students/s123/classes/c456

I've got the first two links working properly using the ApiController and the following two methods:

public class StudentsController : ApiController {
  // GET api/students
  public IEnumerable<Student> GetStudents() {  
  }

  // GET api/students/5
  public IEnumerable<Student> GetStudent(string id) {  
  }
}

In this same controller, (or do I need a different controller called ClassesController?), how would I implement the last two links? Also, what would the routing for the 'classes' part look like (if necessary)?

Here's my WebApiConfig (which I'd like to keep as dynamic, rather than hard-coding the route to the /classes if possible:

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

// EDIT - I'm getting 404's when trying to use this
context.Routes.MapHttpRoute(
  name: "JobsApi",
  routeTemplate: this.AreaName + "/Students/{id}/Classes/{classId}",
  defaults: new { classId = RouteParameter.Optional }
);   

EDIT Here's my newly created ClassesController:

public class ClassesController : ApiController {
  // GET api/classes
  public IEnumerable<TheClass> Get(string id) {    
      return null;
  }
}

I'm getting 404 Errors when attempting to go to this URL:

http://mydomain/api/students/s123/classes
Adam Levitt
  • 10,316
  • 26
  • 84
  • 145
  • This could help you: http://stackoverflow.com/questions/13893323/asp-net-custom-routing – cheesemacfly Feb 03 '13 at 15:57
  • As far as the routing aspect of this problem goes, this may be along the lines of what I'm looking for, except I'd like to dynamically be able to do this so I don't have to do it for each controller – Adam Levitt Feb 03 '13 at 15:59

2 Answers2

7

Routing in ASP.NET can express these more complex rules but needed to be explicitly set up. For example in this case you would have to define 2 routes:

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

config.Routes.MapHttpRoute(
  name: "DefaultApi",
  routeTemplate: "api/students/{studentId}/{controller}/{classId}",
  defaults: new { classId = RouteParameter.Optional }
);   

And you would have a controller for it:

public class ClassesController
{
   public TheClass Get(int studentId, int classId)
   {
       ....
   }
}

This is perhaps not ideal but the main option.

I was working on a hierarchical routing which was not possible due to an implementation issue in Web API but this issue has been fixed now so I might start working on it again.

Aliostad
  • 80,612
  • 21
  • 160
  • 208
7

With this nice hierarchical approach, you have more concerns that routing internally. There is a good sample application which adopts the hierarchical resource structure: PingYourPackage. Check that out.

Note: I have a blog post about this issue which explains the below concerns and gives solutions to those with a few code samples. You can check it out for more details:

Let me explain the concerns here briefly by setting up a sample scenario. This may not be the desired approach for these type of situations but lays out the concerns very well. Let's say you have the below two affiliates inside your database for a shipment company:

  • Affiliate1 (Id: 100)
  • Affiliate2 (Id: 101)

And then assume that these affiliates has some shipments attached to them:

  • Affiliate1 (Key: 100)
    • Shipment1 (Key: 100)
    • Shipment2 (Key: 102)
    • Shipment4 (Key: 104)
  • Affiliate2 (Key: 101)
    • Shipment3 (Key: 103)
    • Shipment5 (Key: 105)

Finally, we want to have the following resource structure:

GET api/affiliates/{key}/shipments
GET api/affiliates/{key}/shipments/{shipmentKey}
POST api/affiliates/{key}/shipments
PUT api/affiliates/{key}/shipments/{shipmentKey}
DELETE api/affiliates/{key}/shipments/{shipmentKey}

Routing Concerns

@Ali already explained it but I've a different approach here. Assume that we are sending a GET request against /api/affiliates/105/shipments/102. Notice that the affiliate key is 105 here which doesn't exist. So, we would want to terminate the request here ASAP. We can achieve this with a per-route message handler.

Authorization Concerns

If you have some type of authentication in place, you would want to make sure (in our scenario here) that the authenticated user and the requested affiliate resource is related. For example, assume that Affiliate1 is authenticated under the Affiliate role and you have the AuthorizeAttribute registered to check the "Affiliate" role authorization. In this case, you will fail miserably because this means that Affiliate1 can get to the following resource: /api/affiliates/101/shipments which belongs to Affiliate2. We can eliminate this problem with a custom AuthorizeAttribute.

Ownership Concerns

Now, the following URI should get me the correct data:

GET /api/affiliates/100/shipments/102

However, what would happen for the below URI:

GET /api/affiliates/100/shipments/103

This should get you "404 Not Found" HTTP response because affiliate whose Id is 100 doesn't own the shipment whose id is 103.

tugberk
  • 57,477
  • 67
  • 243
  • 335
  • Thanks for this response/post. I'm wondering, is there a way to dynamically route to the secondary controller? – Adam Levitt Feb 04 '13 at 18:43
  • @AdamLevitt what exactly do u mean by that? – tugberk Feb 04 '13 at 20:03
  • I mean, I'm looking at the routing in the answers on this page and the routing is hard coded, and I'm wondering if it could be dynamic? Something like this: "api/{controller1}/{id}/{controller2} – Adam Levitt Feb 04 '13 at 21:18
  • 1
    @AdamLevitt I see. Not with the current implementation. You can play with the controller selector, implement your own and replace it with the default one to enable this type of selection. Also, have a look at this post: http://www.strathweb.com/2013/01/magical-web-api-action-selector-http-verb-and-action-name-dispatching-in-a-single-controller/ it might be what u are looking for. – tugberk Feb 04 '13 at 21:46
  • Thanks for that. Right now I'm trying the hardcoded attempt, and my routing is not working properly because I'm getting 404 errors. Perhaps I'm doing something wrong? I'll put my routing in the original post. – Adam Levitt Feb 04 '13 at 21:47
  • @tugberk sir, what if I have a route [GET] api/classes/{classID}/coaches, which represent the coaches who run the class with id=classID, but what if i want to see(GET) all the coaches, should i have different controller or should it be under same controller with some other action? what should be the route? I am using API using .net core – GKhedekar Jan 18 '22 at 11:05