繁体   English   中英

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

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

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

例如,我要处理:

/customers/1/products/10/

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

抱歉,我已经多次更新了此文件,因为我自己正在寻找解决方案。

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

在默认路由下添加:

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,我强烈建议使用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,  }
);

您可以像这样在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 (映射到GET http://your.api/customers/1/orders (long),返回客户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),它将创建一个订单并将其返回给调用者(具有刚刚创建的新ID)

但不要把我的话当作绝对真理。 我仍在尝试它,我认为MS无法正确解决子资源访问问题。

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

从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];
    }
}

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

  config.MapHttpAttributeRoutes();

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

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

下面是完整复制的答案,因为我不确定当一个帖子回答两个SO问题时该怎么做:(


我想以一种更通用的方式来处理此问题,而不是像Abhijit Kadam那样直接用controller = "Child"来连接ChildController。 我有几个子控制器,并且不想controller = "ChildX"使用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正常工作。
  • 可能有一种方法可以即时使用反射或某种方式来执行此操作,而无需单独管理它,但这暂时对我有效。
  • 它不支持孩子的孩子。

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

暂无
暂无

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

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