在ASP.net MVC 4 WebApi中嵌套的资源

在新的ASP.net MVC 4 WebApi中,是否有更好的方法来处理嵌套的资源,而不是为每一个设置一个特殊的路由? (类似于这里: ASP.Net MVC支持嵌套资源? – 这是2009年发布)。

比如我想处理:

/customers/1/products/10/

我已经看到了除Get()Post()等命名的ApiController动作的一些例子,例如在这里我看到一个名为GetOrder()的动作的例子。 我找不到任何文件。 这是达到这个目的的一种方法吗?

对不起,我已经更新了这个多次,因为我自己find一个解决scheme。

似乎有很多方法可以解决这个问题,但到目前为止,我发现的最有效的方法是:

在默认路由下添加:

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

然后,该路由将匹配URL中的任何控制器操作和匹配的段名称。 例如:

/ api / customers / 1 /订单将匹配:

 public IEnumerable<Order> Orders(int customerId) 

/ api / customers / 1 / orders / 123会匹配:

 public Order Orders(int customerId, int id) 

/ api / customers / 1 /产品将匹配:

 public IEnumerable<Product> Products(int customerId) 

/ api / customers / 1 / products / 123会匹配:

 public Product Products(int customerId, int id) 

方法名称必须与路由中指定的{action}段匹配。


重要的提示:

从评论

由于RC需要告诉每个动作可接受哪种动词,即[HttpGet]

编辑:虽然这个答案仍然适用于Web API 1,对于Web API 2,我强烈build议使用丹尼尔·哈兰的答案,因为它是最先进的映射子资源(其他niceties)。


有些人不喜欢在Web API中使用{action},因为他们认为这样做会打破REST的“意识形态”……我认为。 {action}仅仅是一个帮助路由的结构。 它在你的实现中是内部的,与用来访问资源的HTTP动词无关。

如果将HTTP动词约束放在动作上并相应地命名,则不会违反任何REST准则,并且将以更简单,更简洁的控制器而不是每个子资源的单个控制器为单位。 记住:这个动作只是一个路由机制,它是你实现的内部。 如果你对这个框架感到困惑,那么框架或者你的实现就会有一些问题。 只需使用HTTPMETHOD约束来映射路由,您就可以轻松完成任务:

 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, } ); 

你可以像这样在CustomersController中处理这些:

 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 } // ... } 

您也可以在相同的“资源”(订单)上路由创build操作:

 routes.MapHttpRoute( name: "OneLevelNested", routeTemplate: "api/customers/{customerId}/orders", constraints: new { httpMethod = new HttpMethodConstraint(new string[] { "POST" }) }, defaults: new { controller = "Customers", action = "CreateOrder", } ); 

并在客户控制器中相应处理:

 public class CustomersController { // ... public Order CreateOrder(long customerId) { // create and return the order just created (with the new order id) } // ... } 

是的,你仍然需要创build很多路由,只是因为Web API依然不能路由到不同的方法,但是我认为声明性地定义路由比清除自定义调度机制更清晰基于枚举或其他技巧。

对于您的API的消费者,它将看起来完美RESTful:

GET http://your.api/customers/1/orders (映射到GET http://your.api/customers/1/orders (长)返回客户的所有订单1)

GET http://your.api/customers/1/orders/22 (映射到GET http://your.api/customers/1/orders/22 (long,long)返回客户1的订单22

POST http://your.api/customers/1/orders (映射到CreateOrder(long),它将创build一个订单并将其返回给调用者(使用刚刚创build的新ID)

但是不要把我的话当成一个绝对的事实。 我仍在试验,我认为MS未能正确解决子资源访问问题。

我强烈build议你尝试http://www.servicestack.net/写一些不那么痛苦的经验,但是不要误解我的意思,我崇拜Web API,并将其用于我的大部分专业项目,主要是因为find那些已经“知道”它的程序员比较容易…对于我个人的项目,我更喜欢ServiceStack。

由于Web API 2可以使用路由属性为每个方法定义自定义路由,从而允许分层路由

 public class CustomersController : ApiController { [Route("api/customers/{id:guid}/products")] public IEnumerable<Product> GetCustomerProducts(Guid id) { return new Product[0]; } } 

您还需要在WebApiConfig.Register()中初始化属性映射,

  config.MapHttpAttributeRoutes(); 

我不喜欢在ASP.NET Web API的路由中使用“操作”的概念。 REST中的动作应该是HTTP动词。 我通过简单地使用父控制器的概念,以一种通用而有些优雅的方式实现了我的解决scheme。

https://stackoverflow.com/a/15341810/326110

下面是全部转载的答案,因为我不确定当一个post回答两个SO问题时该怎么办:(


我想以更一般的方式处理这个问题,而不是像Abhijit Kadam那样直接用controller = "Child"来连接ChildController。 我有几个孩子控制器,并不想为每个controller = "ChildX"controller = "ChildY"映射一个特定的路线。

我的WebApiConfig看起来像这样:

 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 } ); 

我的父母控制器是非常标准的,并匹配上面的默认路由。 示例子控制器如下所示:

 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 } 

我的实现的一些缺点

  • 正如你所看到的,我使用了一个enum ,所以我仍然需要在两个不同的地方pipe理父控制器。 它可以很容易地是一个string参数,但是我想阻止api/crazy-non-existent-parent/5/comment/122工作。
  • 可能有一种方法可以使用reflection或其他东西来实现这个动作,而不用单独进行pipe理,但现在这对我来说是有效的。
  • 它不支持儿童的孩子。

可能有更好的解决scheme更一般,但正如我所说,这对我来说是有效的。