简体   繁体   English

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

[英]Nested resources in ASP.net MVC 4 WebApi

Is there a better way in the new ASP.net MVC 4 WebApi to handle nested resources than setting up a special route for each one? 在新的ASP.net MVC 4 WebApi中,是否有比为每个嵌套资源设置一条特殊路由更好的方法? (similar to here: ASP.Net MVC support for Nested Resources? - this was posted in 2009). (类似于此处: ASP.Net MVC对嵌套资源的支持? -发布于2009年)。

For example I want to handle: 例如,我要处理:

/customers/1/products/10/

I have seen some examples of ApiController actions named other than Get() , Post() etc, for example here I see an example of an action called GetOrder() . 我已经看到了一些名为Get()Post()等的ApiController动作示例,例如, 在这里,我看到了一个名为GetOrder()的动作示例。 I can't find any documentation on this though. 我找不到关于此的任何文档。 Is this a way to achieve this? 这是实现这一目标的方法吗?

Sorry, I have updated this one multiple times as I am myself finding a solution. 抱歉,我已经多次更新了此文件,因为我自己正在寻找解决方案。

Seems there is many ways to tackle this one, but the most efficient I have found so far is: 似乎有很多方法可以解决此问题,但到目前为止,我发现的最有效的方法是:

Add this under default route: 在默认路由下添加:

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

This route will then match any controller action and the matching segment name in the URL. 然后,此路由将匹配任何控制器操作以及URL中匹配的段名称。 For example: 例如:

/api/customers/1/orders will match: / api / customers / 1 /订单将匹配:

public IEnumerable<Order> Orders(int customerId)

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

public Order Orders(int customerId, int id)

/api/customers/1/products will match: / api / customers / 1 /产品将匹配:

public IEnumerable<Product> Products(int customerId)

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

public Product Products(int customerId, int id)

The method name must match the {action} segment specified in the route. 方法名称必须与路由中指定的{action}段匹配。


Important Note: 重要的提示:

From comments 来自评论

Since the RC you'll need to tell each action which kind of verbs that are acceptable, ie [HttpGet] , etc. 从RC开始,您需要告诉每个动作哪种可接受的动词,例如[HttpGet]等。

EDIT: Although this answer still applies for Web API 1, for Web API 2 I strongly advise using Daniel Halan's answer as it is the state of the art for mapping subresources (among other niceties). 编辑:虽然此答案仍适用于Web API 1,但对于Web API 2,我强烈建议使用Daniel Halan的答案,因为它是映射子资源(以及其他优点)的最新技术。


Some people don't like to use {action} in Web API because they believe that in doing so they will be breaking the REST "ideology"... I contend that. 有些人不喜欢在Web API中使用{action},因为他们相信这样做会破坏REST的“意识形态”……我主张这一点。 {action} is merely a construct that helps in routing. {action}仅仅是有助于路由的结构。 It is internal to your implementation and has nothing to do with the HTTP verb used to access a resource . 它是实现的内部功能,与用于访问资源的HTTP动词无关。

If you put HTTP verb constraints on the actions and name them accordingly you're not breaking any RESTful guidelines and will end up with simpler, more concise controllers instead of tons of individual controllers for each sub-resource. 如果将HTTP动词约束放在操作上并相应地命名,则不会违反任何RESTful准则,并且最终将得到更简单,更简洁的控制器,而不是每个子资源大量的单个控制器。 Remember: the action is just a routing mechanism, and it is internal to your implementation. 请记住:操作只是路由机制,它是实现的内部功能。 If you struggle against the framework, then something is amiss either with the framework or your implementation. 如果您与框架抗争,那么框架或您的实现将有毛病。 Just map the route with an HTTPMETHOD constraint and you're good to go: 只需使用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,  }
);

You can handle these in the CustomersController like this: 您可以像这样在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
    }
    // ...
}

You can also route a Create action on the same "resource" (orders): 您还可以在同一“资源”(订单)上路由“创建”操作:

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

And handle it accordingly in the Customer controller: 并在客户控制器中进行相应处理:

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

Yes, you still have to create a lot of routes just because Web API still can't route to different methods depending on the path... But I think it is cleaner to declaratively define the routes than to come up with a custom dispatching mechanisms based on enums or other tricks. 是的,您仍然必须创建很多路由,只是因为Web API仍然无法根据路径来路由到不同的方法...但是,我认为声明性地定义路由比使用自定义分派机制更干净基于枚举或其他技巧。

For the consumer of your API it will look perfectly RESTful: 对于您的API使用者,它将看起来完全是RESTful的:

GET http://your.api/customers/1/orders (maps to GetOrders(long) returning all orders for customer 1) GET http://your.api/customers/1/orders (映射到GET http://your.api/customers/1/orders (long),返回客户1的所有订单)

GET http://your.api/customers/1/orders/22 (maps to GetOrders(long, long) returning the order 22 for customer 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 (maps to CreateOrder(long) which will create an order and return it to the caller (with the new ID just created) POST http://your.api/customers/1/orders (映射到CreateOrder(long),它将创建一个订单并将其返回给调用者(具有刚刚创建的新ID)

But don't take my word as an absolute truth. 但不要把我的话当作绝对真理。 I'm still experimenting with it and I think MS failed to address properly subresource access. 我仍在尝试它,我认为MS无法正确解决子资源访问问题。

I urge you to try out http://www.servicestack.net/ for a less painful experience writing REST apis... But don't get me wrong, I adore Web API and use it for most of my professional projects, mainly because it is easier to find programmers out there that already "know" it... For my personal projects I prefer ServiceStack. 我敦促您尝试一下http://www.servicestack.net/ ,以减少编写REST api的痛苦。但是请不要误会我的意思,我喜欢Web API并将其用于我的大多数专业项目,主要是因为更容易找到已经“知道”它的程序员...对于我的个人项目,我更喜欢ServiceStack。

Since Web API 2 you can use Route Attributes to define custom routing per Method, allowing for hierarchical routing 从Web API 2开始,您可以使用Route Attributes为每个Method定义自定义路由,从而实现分层路由

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

You also need to initialize Attribute Mapping in WebApiConfig.Register(), 您还需要在WebApiConfig.Register()中初始化属性映射,

  config.MapHttpAttributeRoutes();

I don't like using the concept of "actions" in the route of an ASP.NET Web API. 我不喜欢在ASP.NET Web API的路由中使用“动作”的概念。 The action in REST is supposed to be the HTTP Verb. REST中的操作应该是HTTP Verb。 I implemented my solution in a somewhat generic and somewhat elegant way by simply using the concept of a parent controller. 我仅通过使用父控制器的概念就以某种通用且优雅的方式实现了我的解决方案。

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

Below is that answer reproduced in full because I'm not sure what to do when one post answers two SO questions :( 下面是完整复制的答案,因为我不确定当一个帖子回答两个SO问题时该怎么做:(


I wanted to handle this in a more general way, instead of wiring up a ChildController directly with controller = "Child" , as Abhijit Kadam did. 我想以一种更通用的方式来处理此问题,而不是像Abhijit Kadam那样直接用controller = "Child"来连接ChildController。 I have several child controllers and didn't want to have to map a specific route for each one, with controller = "ChildX" and controller = "ChildY" over and over. 我有几个子控制器,并且不想controller = "ChildX"使用controller = "ChildX"controller = "ChildY"来为每个路由映射一条特定的路由。

My WebApiConfig looks like this: 我的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 }
);

My parent controllers are very standard, and match the default route above. 我的父控制器非常标准,并且与上面的默认路由匹配。 A sample child controller looks like this: 一个示例子控制器如下所示:

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
}

Some drawbacks of my implementation 我的实现的一些缺点

  • As you can see, I used an enum , so I'm still having to manage parent controllers in two separate places. 如您所见,我使用了enum ,所以我仍然必须在两个不同的地方管理父控制器。 It could have just as easily been a string parameter, but I wanted to prevent api/crazy-non-existent-parent/5/comment/122 from working. 它本来可以是一个字符串参数,但我想阻止api/crazy-non-existent-parent/5/comment/122正常工作。
  • There's probably a way to use reflection or something to do this on the fly without managing it separetly, but this works for me for now. 可能有一种方法可以即时使用反射或某种方式来执行此操作,而无需单独管理它,但这暂时对我有效。
  • It doesn't support children of children. 它不支持孩子的孩子。

There's probably a better solution that's even more general, but like I said, this works for me. 可能有一个更通用的更好的解决方案,但是就像我说的那样,这对我有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM