Вложенные ресурсы в ASP.net MVC 4 WebApi - PullRequest
35 голосов
/ 07 марта 2012

Есть ли лучший способ в новом ASP.net MVC 4 WebApi для обработки вложенных ресурсов, чем настройка специального маршрута для каждого из них? (аналогично: Поддержка ASP.Net MVC для вложенных ресурсов? - это было опубликовано в 2009 г.).

Например, я хочу обработать:

/customers/1/products/10/

Я видел несколько примеров ApiController действий с именами, отличными от Get(), Post() и т. Д., Например здесь Я вижу пример действия с именем GetOrder(). Я не могу найти документацию по этому вопросу, хотя. Это способ достичь этого?

Ответы [ 4 ]

37 голосов
/ 05 апреля 2012

Извините, я обновлял это несколько раз, так как сам нахожу решение.

Кажется, есть много способов справиться с этим, но самый эффективный, который я нашел до сих пор:

Добавьте этот маршрут по умолчанию:

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

Этот маршрут будет соответствовать любому действию контроллера и соответствующему имени сегмента в URL.Например:

/ api / клиентов / 1 / заказов будет соответствовать:

public IEnumerable<Order> Orders(int customerId)

/ API / клиентов / 1 / заказов / 123 будет соответствовать:

public Order Orders(int customerId, int id)

/ api / клиенты / 1 / продукты будут соответствовать:

public IEnumerable<Product> Products(int customerId)

/ API / клиенты / 1 / продукты / 123 будут соответствовать:

public Product Products(int customerId, int id)

Имя метода должно соответствовать {действие} сегмент, указанный в маршруте.


Важное примечание:

Из комментариев

Начиная с RC вам нужно будет указывать каждому действию, какие глаголы являются приемлемымит. е. [HttpGet] и т. д.

10 голосов
/ 21 июня 2013

РЕДАКТИРОВАТЬ: Хотя этот ответ по-прежнему применяется для веб-API 1, для веб-API 2 я настоятельно рекомендую использовать ответ Даниэля Халана , поскольку это современный уровень для отображения подресурсов ( среди прочих тонкостей).


Некоторые люди не любят использовать {action} в Web API, потому что они верят, что при этом они нарушат "идеологию" REST ... Я утверждаю это. {действие} - это просто конструкция, которая помогает в маршрутизации. Он является внутренним для вашей реализации и не имеет ничего общего с 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 он будет отлично выглядеть:

GET http://your.api/customers/1/orders (отображается на GetOrders (long), возвращая все заказы для клиента 1)

GET http://your.api/customers/1/orders/22 (сопоставляется с GetOrders (long, long), возвращая заказ 22 для клиента 1

POST http://your.api/customers/1/orders (отображается на CreateOrder (long), который создаст заказ и вернет его вызывающей стороне (с только что созданным новым идентификатором)

Но не принимайте мое слово как абсолютную правду. Я все еще экспериментирую с этим, и я думаю, что MS не удалось должным образом обратиться к доступу субресурса.

Я призываю вас попробовать http://www.servicestack.net/ для менее болезненного опыта написания REST API ... Но не поймите меня неправильно, я обожаю Web API и использую его для большинства моих профессиональных проектов, главным образом потому, что легче найти программистов, которые уже «знают» это ... Для моих личных проектов я предпочитаю ServiceStack.

8 голосов
/ 13 ноября 2014

Начиная с 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();
5 голосов
/ 19 апреля 2013

Мне не нравится использовать понятие «действия» в маршруте ASP.NET Web API. Действие в REST должно быть HTTP-глаголом. Я реализовал свое решение несколько общим и несколько элегантным способом, просто используя концепцию родительского контроллера.

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

Ниже этот ответ воспроизводится полностью, потому что я не уверен, что делать, когда один пост отвечает на два SO вопроса: (


Я хотел бы обработать это в более общем смысле, вместо того, чтобы напрямую связывать ChildController с controller = "Child", как это делал Абхиджит Кадам. У меня есть несколько дочерних контроллеров, и я не хотел отображать конкретный маршрут для каждого с 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 не работал.
  • Возможно, есть способ использовать отражение или что-то еще, чтобы сделать это на лету, без отдельного управления этим, но сейчас это работает для меня.
  • Не поддерживает детей детей.

Возможно, есть лучшее решение, даже более общее, но, как я уже сказал, это работает для меня.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...