Подтвердить что ты не робот

Вложенные ресурсы в ASP.net MVC 4 WebApi

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

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

/customers/1/products/10/

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

4b9b3361

Ответ 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}, указанному в маршруте.


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

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

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

Ответ 2

EDIT:. Хотя этот ответ по-прежнему применяется для Web API 1, для Web API 2 я настоятельно рекомендую использовать ответ Daniel Halan, поскольку он современное состояние для отображения подресурсов (среди других тонкостей).


Некоторые люди не любят использовать {действие} в веб-API, потому что считают, что при этом они будут нарушать идеологию 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,  }
);

Вы можете обрабатывать их в CustomerController следующим образом:

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

Вы также можете направить действие Create на один и тот же "ресурс" (заказы):

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

Да, вам все равно придется создавать множество маршрутов только потому, что веб-API по-прежнему не может маршрутизировать различные методы в зависимости от пути... Но я думаю, что более чисто декларативно определять маршруты, чем придумывать настраиваемые механизмы диспетчеризации на основе перечислений или других трюков.

Для потребителя вашего API он будет выглядеть отлично RESTful:

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

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

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

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

Я призываю вас попробовать http://www.servicestack.net/ для менее болезненного опыта написания REST apis... Но не поймите меня неправильно, Я обожаю 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(),

  config.MapHttpAttributeRoutes();

Ответ 4

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

fooobar.com/info/150885/...

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


Я хотел бы обработать это более общим способом, вместо того, чтобы подключать 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.
  • Вероятно, есть способ использовать отражение или что-то сделать это на лету, не управляя им отдельно, но это работает для меня пока.
  • Он не поддерживает детей детей.

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