Отладка веб-API ASP.NET с помощью инструмента Route Debugger

Введя запрос «asp.net web api routing» на сайте stackoverflow, вы сможете найти ответы на многие другие вопросы. Как работает маршрутизация в веб-API? Почему не работает мой маршрут? Почему не вызывается эта операция? Зачастую, отладка маршрутов представляет собой нелегкую задачу.

Чтобы разобраться с этим вопросом, я написал инструмент «ASP.NET Web API Route Debugger». Надеюсь, он поможет облегчить жизнь многим разработчикам.

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

Начало работ с Route Debugger (Отладчик маршрутов)

Route Debugger можно загрузить с сайта NuGet (http://www.nuget.org/packages/WebApiRouteDebugger/)

  1: PM> Install-Package WebApiRouteDebugger

В приложении будет создан новый каталог для ваших проектов. На картинке ниже показаны новые файлы, загруженные в проект. (Значком «+» обозначаются новые файлы, а красным цветом — измененные файлы).

clip_image002

Чтобы выполнить компиляцию, нажмите F5, а потом перейдите на страницу отладчика http:// localhost:xxx/rd.

clip_image004

Введите URL-адрес, который вы хотели бы проверить, и нажмите Send(Отправить). Ниже показана страница с результатами.

clip_image006

Далее я объясню, как интерпретировать полученные результаты.

Как работает маршрутизация в веб-API ASP.NET

Механизм маршрутизации в веб-API ASP.NET состоит из трех шагов: 1. Поиск совпадающего маршрута и получение его данных; 2. Поиск контроллера; 3. Поиск операции. Если на одном из шагов совпадение не обнаруживается, все последующие шаги не будут выполняться. Например, если не найден контроллер, поиск операции будет отменен.

Первый шаг — поиск маршрута. Любой маршрут можно описать с помощью шаблона маршрута, постоянных, ограничений, маркеров данных и обработчика. По умолчанию маршруты настраиваются в App_Start\WebApiConfig.cs. После того, как совпадение найдено, запрошенный URL-адрес преобразуется в данные маршрута с использованием шаблона. Данные маршрута представляют собой набор сопоставлений строк и объектов.

Сопоставление с контроллером зависит от значения его ключа в данных маршрута. Если в данных маршрута нет ключа, выбор контроллера выполнить не удастся.

После того, как контроллер подобран, все открытые методы, применяемые к нему, будут найдены через отражение. Для поиска операции применяется следующий алгоритм.

clip_image008

    1. Если данные маршрута содержат ключ «action», будет произведен поиск операции по ее названию. В отличие от ASP.NET MVC, в маршрутах веб-API обычно не используются названия операций.
      1. Поиск всех операций с названием «action» в данных маршрутов;
      2. Каждая операция поддерживает не менее одной HTTP-команды (GET, POST, PUT и т. д.). Удаление операций, не поддерживающих команду текущего HTTP-запроса.
    2. Если данные маршрута не содержат ключ «action», будет произведен поиск операции по поддерживаемому методу запроса.
    3. Проверка параметров метода для выбранных операций из двух предыдущих шагов. Удаление операций, не соответствующих всем параметрам в данных маршрута.
    4. Удаление операций, обозначенных атрибутом NonAction.
      1. При совпадении с более чем одной операцией выдается ошибка HTTP 500 (и внутренняя ошибка HttpResponseException).
      2. Если подходящих операций нет, выдается ошибка HTTP 404.

 

Использование отладчика Route bugger

Пример 1: Недостающее значение контроллера

Источник: http://stackoverflow.com/questions/13876816/web-api-routing

Описание проблемы

Ниже показаны примеры контроллера и маршрутов. URL-адрес не совпадает с маршрутом MachineApi.

  1: localhost/api/machine/somecode/all

 

Чтобы открыть пример, загрузите Образец 1 и установите отладчик маршрутов NuGet.

Контроллер

  1: public class MachineController : ApiController
  2: {
  3:     public IEnumerable<Machine> Get()
  4:     {
  5:         return new List<Machine>{
  6:             new Machine    {
  7:                 LastPlayed = DateTime.UtcNow,
  8:                 MachineAlertCount = 1,
  9:                 MachineId = "122",
  10:                 MachineName = "test",
  11:                 MachinePosition = "12",
  12:                 MachineStatus = "test"
  13:             }
  14:         };
  15:     }
  16:  
  17:     public IEnumerable<Machine> All(string code)
  18:     {
  19:         return new List<Machine>
  20:         {
  21:          new Machine
  22:             {
  23:                 LastPlayed = DateTime.UtcNow,
  24:                 MachineAlertCount = 1,
  25:                 MachineId = "122",
  26:                 MachineName = "test",
  27:                 MachinePosition = "12",
  28:                 MachineStatus = "test"
  29:             }
  30:         };
  31:     }
  32: }

Маршрут

  1: config.Routes.MapHttpRoute(
  2:     name: "MachineApi",
  3:     routeTemplate: "api/machine/{code}/all"
  4: );
  5:  
  6: config.Routes.MapHttpRoute(
  7:     name: "DefaultApi",
  8:     routeTemplate: "api/{controller}/{id}",
  9:     defaults: new { id = RouteParameter.Optional }

Тест

Проверьте ссылку http://localhost/api/machine/somecode/all в отладчике маршрутов:

clip_image010

Комментарии

  1. Статусный код HTTP 404 (источник не найден).
  2. Данные маршрута содержат только одну пару ключ-значение, сопоставляющую «Somecode» и «Code».
  3. Поскольку шаблон подходит для URL-адреса, выбран маршрут «Api/Machine/{Code}/All». Однако, для маршрута не заданы значения по умолчанию (default values).
  4. Нет совпадений с контроллерами (не выделено ни одной строки в таблице «Controller Selecting»).

Анализ

Отчет отладчика маршрутов показывает, что значение «controller» в данных маршрута или в его постоянных не найдено. Способ выбора контроллера основан на подборе по значению «controller».

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

Решение

Добавить в первый маршрут значение по умолчанию, определяющее контроллер machine.

  1: config.Routes.MapHttpRoute(
  2:     name: "MachineApi",
  3:     routeTemplate: "api/machine/{code}/all",
  4:     defaults: new { controller = "Machine" });

После внесенных изменений будет возвращено значение HTTP 200, указывающее на то, что контроллер и операция были найдены. В отчете отладчика совпадающие маршрут, контроллер и операция выделены зеленым цветом (см. пример ниже).

clip_image012

Похожие проблемы

Источник: http://stackoverflow.com/questions/13869541/why-is-my-message-handler-running-even-when-it-is-not-defined-in-webapi

Пример 2. Двусмысленное значение по умолчанию

Источник: http://stackoverflow.com/questions/14058228/asp-net-web-api-no-action-was-found-on-the-controller

Контроллер

  1: public class ValuesController : ApiController
  2: {
  3:     // GET api/values
  4:     public IEnumerable<string> Get()
  5:     {
  6:         return new string[] { "value1", "value2" };
  7:     }
  8:     // GET api/values/5
  9:     public string Get(int id)
  10:     {
  11:         return "value";
  12:     }
  13:     // POST api/values
  14:     public void Post([FromBody]string value)
  15:     {
  16:     }
  17:     // PUT api/values/5
  18:     public void Put(int id, [FromBody]string value)
  19:     {
  20:     }
  21:     // DELETE api/values/5
  22:     public void Delete(int id)
  23:     {
  24:     }
  25:  
  26:     [HttpGet]
  27:     public void Machines()
  28:     {
  29:     }
  30:     public void Machines(int id)
  31:     {
  32:     }
  33: }

Определение маршрута

  1: config.Routes.MapHttpRoute(
  2:     name: "DefaultApi",
  3:     routeTemplate: "api/{controller}/{action}/{id}",
  4:     defaults: new { action = "get", id = RouteParameter.Optional });

Тест

Маршруты, перечисленные ниже, работают корректно.

  • /api/Values
  • /api/Values/Machines
  • /api/Values/Machine/100

Однако URL /api/Values/1 возвращает ошибку 404.

clip_image014

Комментарии

  1. В разделе «Route Data» ключу «Action» сопоставлено значение «1» (третий сегмент URL-адреса). Для выбранного маршрута api/{controller}/{action}/{id} данное условие является ограничением.
  2. Несмотря на то, что для ключа «action» значением по умолчанию является «get», этому ключу присвоено значение «1».
  3. Строка «Values» отмечена в разделе «Controller selecting».
  4. В разделе «Action selecting» не отмечено ни одного совпадения. Везде в колонке «By Action Name» проставлены значения «False». Это означает, что операции были отклонены, поскольку их названия не совпадали со значением «action» в данных маршрута.

Анализ

Важно отметить два следующих момента.

  1. В данных маршрута предпочтение всегда отдается значениям URL-адреса, а не значению по умолчанию (если значение URL-адреса может быть обнаружено). Ни один из четырех URL-адресов, перечисленных выше, не содержит значения «action», заданного по умолчанию. Во всех четырех URL-адресах данные маршрута содержат как ключ, так и значение операции.
  2. Поскольку значение «action» содержится в данных маршрута, оно будет выбрано при подборе операции.

В отладчике маршрутов показано, что в URL-адресе http://localhost:xxx/api/values/1 «1» — это имя операции, но такая операция не существует.

Решение

Использовать только одну стратегию для подбора операций: либо по названию операции, либо по команде. Не включайте название и команду вместе в один контроллер и один маршрут.

Пример 3. Двусмысленная операция

Источник: Почему для моих маршрутов невозможно подобрать операции? http://stackoverflow.com/questions/14614516/my-web-api-route-map-is-returning-multiple-actions

Описание проблемы

URL-адрес http://localhost/api/access/blob возвращает ошибку 500.

Контроллер

  1: public class AccessController : ApiController
  2: {
  3:     // GET api/access/blob
  4:     [HttpGet]
  5:     public string Blob()
  6:     {
  7:         return "blob shared access signature";
  8:     }
  9:  
  10:     // GET api/access/queue
  11:     [HttpGet]
  12:     public string Queue()
  13:     {
  14:         return "queue shared access signature";
  15:     }
  16: }

Определение маршрута

  1: config.Routes.MapHttpRoute(
  2:     name: "DefaultApi",
  3:     routeTemplate: "api/{controller}/{id}",
  4:     defaults: new { id = RouteParameter.Optional }
  5: );
  6:  
  7: config.Routes.MapHttpRoute(
  8:     name: "AccessApi",
  9:     routeTemplate: "api/{controller}/{action}"
  10: );

clip_image016

Комментарии

  1. URL-адрес соответствует обоим указанным маршрутам. Первый маршрут был включен в отчет, поскольку алгоритмом веб-API был выбран первый же маршрут, удовлетворяющий условиям (т. н. «жадный» алгоритм).
  2. Шаблон первого маршрута не содержит элемента {action}. Значение «action» также отсутствует в данных маршрута. Поэтому операция подбирается на основе команды HTTP.
  3. В качестве контроллера был выбран AccessСontroller.
  4. Обе операции выбраны на основе единственного подходящего критерия: HTTP-команды GET.

Анализ

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

Решение

Существует два возможных решения:

  1. Переместить маршрут по умолчанию в конец очереди.
  2. Удалить маршрут по умолчанию.

В результате применения «жадного» алгоритма могут возникнуть сложно исправимые ошибки (особенно, когда подбирается неверный маршрут). Отладчик маршрутов поможет устранить эту проблему, указав выбранный шаблон маршрута.

Заключение

Проблемы маршрутизации в веб-API зачастую сложно идентифицировать и устранить. Инструмент Route Debugger позволяет выявить эти проблемы, а также описать применяемый алгоритм маршрутизации. С помощью маршрутизации атрибутов в веб-API в ближайшем будущем мы сможем устранять многие проблемы маршрутизации.

Исходный код

Исходный код инструмента можно загрузить с сайта http://aspnet.codeplex.com. Нажмите вкладку «Source code» и перейдите в раздел Tools\WebApi\RouteDebugger.

clip_image017

Ресурсы