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

Gro*_*kys 35 rest asp.net-mvc asp.net-web-api

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

例如,我想处理:

/customers/1/products/10/

我见过一些例子ApiController命名为比其他行动Get(),Post()等等,比如在这里我看到一个名为动作的一个例子GetOrder().我找不到任何关于此的文档.这是实现这一目标的方法吗?

Tet*_*eto 37

对不起,我已经多次更新了这个,因为我自己找到了解决方案.

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

在默认路由下添加:

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

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

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

public IEnumerable<Order> Orders(int customerId)
Run Code Online (Sandbox Code Playgroud)

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

public Order Orders(int customerId, int id)
Run Code Online (Sandbox Code Playgroud)

/ api/customers/1/products将匹配:

public IEnumerable<Product> Products(int customerId)
Run Code Online (Sandbox Code Playgroud)

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

public Product Products(int customerId, int id)
Run Code Online (Sandbox Code Playgroud)

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


重要的提示:

来自评论

自RC以来,您需要告诉每个动作哪种动词是可接受的,即[HttpGet]等等.

  • 由于RC你需要告诉每个动作哪种动词是可以接受的,即[AcceptVerbs("GET","POST")]. (3认同)

Lou*_*ier 10

编辑:虽然这个答案仍适用于Web API 1,但对于Web API 2,我强烈建议使用Daniel Halan的答案,因为它是映射子资源(以及其他细节)的最新技术.


有些人不喜欢在Web API中使用{action},因为他们认为这样做会打破REST"意识形态"......我认为这一点.{action}仅仅是一个有助于路由的构造.它是您的实现的内部,与用于访问资源的HTTP谓词无关.

如果您对操作设置了HTTP谓词约束并相应地命名它们,那么您不会破坏任何RESTful准则,最终会得到更简单,更简洁的控制器,而不是每个子资源的大量单独控制器.请记住:操作只是一种路由机制,它是您实现的内部机制.如果你反对框架,那么框架或你的实现就会出现问题.只需使用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,  }
);
Run Code Online (Sandbox Code Playgroud)

您可以在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
    }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

您还可以在相同的"资源"(订单)上路由"创建"操作:

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

并在Customer控制器中相应地处理它:

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

是的,你仍然需要创建很多路由,因为Web API仍然无法根据路径路由到不同的方法......但我认为声明性地定义路由比提出自定义调度机制更清晰基于枚举或其他技巧.

对于API的消费者来说,它看起来非常完美:

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

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

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

但是不要把我的话当作绝对真理.我还在尝试它,我认为MS无法正确处理子资源访问.

我敦促你尝试http://www.servicestack.net/来减少编写REST apis的痛苦经历......但是不要误会我的意思,我喜欢Web API并将其用于我的大多数专业项目,主要是因为更容易找到已经"知道"它的程序员......对于我的个人项目,我更喜欢ServiceStack.


小智 8

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

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

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

  config.MapHttpAttributeRoutes();
Run Code Online (Sandbox Code Playgroud)


mo.*_*mo. 5

我不喜欢在ASP.NET Web API的路径中使用"actions"的概念.REST中的操作应该是HTTP Verb.我通过简单地使用父控制器的概念,以一种通用且有点优雅的方式实现了我的解决方案.

/sf/answers/1073926731/

以下是完整的回答,因为当一篇文章回答两个SO问题时,我不知道该怎么做:(


我希望以更一般的方式处理这个问题,而不是controller = "Child"像Abhijit Kadam那样直接连接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 }
);
Run Code Online (Sandbox Code Playgroud)

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

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
}
Run Code Online (Sandbox Code Playgroud)

我实现的一些缺点

  • 如你所见,我使用了一个enum,所以我仍然需要在两个不同的地方管理父控制器.它可以很容易地成为一个字符串参数,但我想阻止api/crazy-non-existent-parent/5/comment/122工作.
  • 可能有一种方法可以使用反射或其他东西来实现这一点,而无需进行单独管理,但这对我来说现在很有用.
  • 它不支持儿童的孩子.

可能有更好的解决方案甚至更普遍,但就像我说的,这对我有用.