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

Posted

技术标签:

【中文标题】ASP.net MVC 4 WebApi 中的嵌套资源【英文标题】:Nested resources in ASP.net MVC 4 WebApi 【发布时间】:2012-03-24 13:53:50 【问题描述】:

在新的 ASP.net MVC 4 WebApi 中是否有比为每个资源设置一个特殊路由更好的方法来处理嵌套资源? (类似于此处:ASP.Net MVC support for Nested Resources? - 这是 2009 年发布的)。

比如我要处理:

/customers/1/products/10/

我看到了一些ApiController 操作的示例,而不是Get()Post() 等,例如here 我看到了一个名为GetOrder() 的操作示例。不过,我找不到任何关于此的文档。这是实现这一目标的方法吗?

【问题讨论】:

Action Invoker 只是寻找 Get、Put、Post 方法作为前缀。该方法的其余部分可以是任何东西。 【参考方案1】:

对不起,我已经多次更新了这个,因为我自己正在寻找解决方案。

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

在默认路由下添加:

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

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

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

public IEnumerable<Order> Orders(int customerId)

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

public Order Orders(int customerId, int id)

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

public IEnumerable<Product> Products(int customerId)

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

public Product Products(int customerId, int id)

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


重要提示:

来自 cmets

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

【讨论】:

你刚刚让我头疼得厉害。谢谢! 从 RC 开始,您需要告诉每个动作哪种动词是可以接受的,即 [AcceptVerbs("GET","POST")]。 如果只支持一个动词,也可以使用 [HttpGet] 代替 [AcceptVerbs("GET")] ***.com/questions/4744990/… 我看到的唯一问题是 public void Put(int id) 将匹配 /controller/id 和 /controller/id/Put url。想法? @Adam 添加对动作的约束,拒绝值Get、POST、PUT【参考方案2】:

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


有些人不喜欢在 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,  
);

您可以像这样在 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
    
    // ...

您还可以在同一“资源”(订单)上路由创建操作:

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

是的,您仍然必须创建很多路由,因为 Web API 仍然无法根据路径路由到不同的方法……但我认为以声明方式定义路由比提出一个更简洁基于枚举或其他技巧的自定义调度机制。

对于您的 API 的使用者来说,它看起来完全是 RESTful:

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 api 的痛苦体验...但不要误会我的意思,我喜欢 Web API 并将它用于我的大多数专业项目,主要是因为它更容易找到已经“了解”它的程序员......对于我的个人项目,我更喜欢 ServiceStack。

【讨论】:

【参考方案3】:

从 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()中初始化Attribute Mapping,

  config.MapHttpAttributeRoutes();

【讨论】:

据我了解,这是使用 ASP.Net Web API 2 的最先进的解决方案。它也是其他框架(Servicestack 或几个 Java-REST-Frameworks)处理它的方式.更多信息可以在这里找到:asp.net/web-api/overview/web-api-routing-and-actions/… 刚找到这个答案,我不得不更新我自己的答案来参考你的答案。这比在 Web API 中使用“动作”要优雅得多,并且具有更大的灵活性。而且更容易实现启动!不错!【参考方案4】:

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

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

以下是完整复制的答案,因为我不确定当一篇帖子回答两个 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,所以我仍然需要在两个不同的地方管理父控制器。它可以很容易地成为一个字符串参数,但我想阻止 api/crazy-non-existent-parent/5/comment/122 工作。 可能有一种方法可以使用反射或其他方式即时执行此操作,而无需单独管理,但目前这对我有用。 不支持孩子的孩子。

可能有更好的解决方案,更通用,但就像我说的,这对我有用。

【讨论】:

以上是关于ASP.net MVC 4 WebApi 中的嵌套资源的主要内容,如果未能解决你的问题,请参考以下文章

ASP .NET MVC 4 WebApi:手动处理 OData 查询

ASP.Net MVC 4 Web API 控制器不适用于 Unity.WebApi

ASP.NET MVC 4 Web API

ASP.NET MVC 4,Web API - 404 未找到

从 ASP.NET MVC Web API 中的多个表单数据键接收文件

使用 ASP.NET 核心 MVC/Razor 站点和 WebAPI 进行授权