Выбор контроллера маршрутизации атрибутов Web API 2

Я использую маршрутизацию атрибутов Web API 2 в своем проекте, чтобы предоставить интерфейс JSON для моих данных. Столкнулся со странным поведением при выборе контроллера, пока не решил, баг это или фича :) Опишу свой подход.

Я хотел бы имитировать синтаксис OData с помощью маршрутизации атрибутов (прямое использование OData было отклонено из-за принципов проектирования). Например, чтобы получить сущность с id=5, я использую HTTP-запрос GET к URI http://mydomain.com/api/Entity(5) . Я ожидаю использовать тот же URI с глаголом HTTP PUT для обновления объекта. Здесь начинается путешествие...

Я хотел бы иметь отдельный контроллер для получения сущностей (FirstController в приведенном ниже примере) и еще один для изменения сущностей (SecondController). Оба контроллера обрабатывают один и тот же URI (например, http://mydomain.com/api/Entity(5)) единственная разница заключается в том, что HTTP-глагол используется с URI - GET должен обрабатываться FirstController, PUT должен обрабатываться SecondController. Но URI не обрабатывается ни одним из них; вместо этого возвращается ошибка HTTP 404. Когда я «объединяю» действия GET и PUT только с одним контроллером (закомментировано в FirstController), оба глагола обрабатываются правильно. Я использую IIS Express, и все обычные маршруты отключены, отвечает только маршрутизация атрибутов.

Похоже, что процесс выбора контроллера не работает с глаголом HTTP. Другими словами, атрибуты HttpGet и HttpPut просто ограничивают использование действий, но они не служат критериями при выборе контроллера. Я не очень хорошо знаком с основами MVC/Web API, поэтому позвольте мне задать вам большой вопрос:

Является ли поведение, описанное здесь ранее, функцией, намеренно реализованной MVC/Web API 2, или ошибкой, которую необходимо исправить?

Если это рассматривать как особенность, это мешает мне следовать принципам дизайна. Я могу жить с «объединенными» контроллерами, но все же считаю это плохой практикой... Или я что-то упускаю из виду?

Моя настройка среды:

  • Windows 7 (виртуальная машина с использованием Oracle VirtualBox)
  • Визуальная студия 2013
  • .NET 4.5.1
  • Веб-API 2

Ниже приведена реализация класса FirstController:

public class FirstController : ApiController
{
  [HttpGet]
  [Route("api/Entity({id:int})")]
  public Output GetEntity(int id)
  {
    Output output = new Output() { Id = id, Name = "foo" };

    return output;
  }

  //[HttpPut]
  //[Route("api/Entity({id:int})")]
  //public Output UpdateEntity(int id, UpdateEntity command)
  //{
  //  Output output = new Output() { Id = id, Name = command.Name };

  //  return output;
  //}
}

Ниже приведена реализация класса SecondController:

public class SecondController : ApiController
{
  [HttpPut]
  [Route("api/Entity({id:int})")]
  public Output UpdateEntity(int id, UpdateEntity command)
  {
    Output output = new Output() { Id = id, Name = command.Name };

    return output;
  }
}

Ниже приведена реализация консольного приложения для проверки описанного поведения:

class Program
{
  static void Main(string[] args)
  {
    // HTTP client initialization
    HttpClient httpClient = new HttpClient();
    httpClient.BaseAddress = new Uri("http://localhost:1567");
    httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    // HTTP GET - FirstController.GetEntity
    HttpResponseMessage getEntityResponse = httpClient.GetAsync("/api/Entity(5)").Result;
    Output getOutput = getEntityResponse.Content.ReadAsAsync<Output>().Result;

    // HTTP PUT - SecondController.UpdateEntity
    UpdateEntity updateCommand = new UpdateEntity() { Name = "newEntityname" };
    HttpResponseMessage updateEntityResponse = httpClient.PutAsJsonAsync("/api/Entity(10)", updateCommand).Result;
    Output updateOutput = updateEntityResponse.Content.ReadAsAsync<Output>().Result;
  }
}

Для завершения используются следующие DTO:

public class UpdateEntity
{
  public string Name { get; set; }
}


public class Output
{
  public int Id { get; set; }
  public string Name { get; set; }
}

Заранее спасибо за ваши ответы,

Ян Качина


person Jan Kacina    schedule 24.01.2014    source источник
comment
Для завершения поиска в Google вот ошибка, которая у вас есть в этом случае: найдено несколько типов контроллеров, которые соответствуют URL-адресу. Это может произойти, если маршруты атрибутов на нескольких контроллерах соответствуют запрошенному URL-адресу.   -  person Christophe Blin    schedule 04.04.2016
comment
Один из самых хорошо продуманных вопросов, которые я видел на SO, хорошо сделано :)   -  person Scott    schedule 14.10.2016


Ответы (1)


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

Кроме того, если мы не будем использовать атрибутивную маршрутизацию, как это будет работать с обычной маршрутизацией? Давайте представим, что у нас есть 2 обычных маршрута, первый из которых предназначен для FirstController, а второй — для SecondController. Теперь, если URL-адрес запроса похож на api/Entity(5), тогда веб-API всегда будет соответствовать 1-му маршруту в таблице маршрутов, который всегда попадет в FirstController и никогда не достигнет SecondController. Помните, что как только веб-API соответствует маршруту, он пытается перейти к процессу выбора действия, и если процесс выбора действия не приводит к выбору действия, то клиенту отправляется ответ об ошибке. Вероятно, вы предполагаете, что если действие не выбрано в одном контроллере, то веб-API перенаправит его на следующий в конфигурации маршрута. Это неправильно.

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

person Kiran Challa    schedule 24.01.2014
comment
Спасибо за объяснение. Я предполагал это, просто хотел, чтобы предположение подтвердилось. - person Jan Kacina; 25.01.2014
comment
У меня такая же проблема, и я хотел бы знать, изменится ли это в будущем? Моя проблема заключается в том, что объединенный контроллер может быть довольно сложно тестировать, когда у вас очень разные зависимости между маршрутами... - person Christophe Blin; 04.04.2016