Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Web API get child list (hierarchical resources)

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
like image 871
Adam Levitt Avatar asked Feb 03 '13 15:02

Adam Levitt


2 Answers

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.

like image 91
Aliostad Avatar answered Nov 13 '22 05:11

Aliostad


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:

  • Hierarchical Resource Structure in ASP.NET Web API

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.

like image 24
tugberk Avatar answered Nov 13 '22 04:11

tugberk