Маршрутизация веб-API - api / {controller} / {action} / {id} «dysfunctions» api / {controller} / {id}

94

У меня есть маршрут по умолчанию в Global.asax:

 RouteTable.Routes.MapHttpRoute(
         name: "DefaultApi",
         routeTemplate: "api/{controller}/{id}",
         defaults: new { id = System.Web.Http.RouteParameter.Optional }
         );

Я хотел иметь возможность настроить таргетинг на конкретную функцию, поэтому создал другой маршрут:

RouteTable.Routes.MapHttpRoute(
         name: "WithActionApi",
         routeTemplate: "api/{controller}/{action}/{id}",
         defaults: new { id = System.Web.Http.RouteParameter.Optional }
         );

Итак, в моем контроллере у меня есть:

    public string Get(int id)
    {
        return "object of id id";
    }        

    [HttpGet]
    public IEnumerable<string> ByCategoryId(int id)
    {
        return new string[] { "byCategory1", "byCategory2" };
    }

Звонок .../api/records/bycategoryid/5даст мне то, что я хочу. Однако звонок .../api/records/1даст мне ошибку

Было обнаружено несколько действий, соответствующих запросу: ...

Я понимаю, почему это так - маршруты просто определяют, какие URL-адреса действительны, но когда дело доходит до сопоставления функций, оба Get(int id)и ByCategoryId(int id)сопоставление api/{controller}/{id}, что сбивает структуру.

Что мне нужно сделать, чтобы маршрут API по умолчанию снова заработал и сохранил прежний {action}? Я подумал о создании другого контроллера, названного RecordByCategoryIdControllerв соответствии с маршрутом API по умолчанию, который я бы запросил .../api/recordbycategoryid/5. Однако я считаю это «грязным» (а значит, неудовлетворительным) решением. Я искал ответы на этот вопрос, и ни один учебник по использованию маршрута {action}даже не упоминает эту проблему.

Микаэль Карузо
источник

Ответы:

104

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

Итак, вам следует:

  1. Поместите свои конкретные правила перед общими правилами (например, по умолчанию), что означает использование RouteTable.Routes.MapHttpRouteдля сопоставления сначала «WithActionApi», а затем «DefaultApi».

  2. Удалите defaults: new { id = System.Web.Http.RouteParameter.Optional }параметр вашего правила WithActionApi, потому что, если идентификатор является необязательным, URL-адрес типа «/ api / {part1} / {part2}» никогда не перейдет в «DefaultApi».

  3. Добавьте именованное действие в свой «DefaultApi», чтобы указать движку маршрута, какое действие нужно ввести. В противном случае, если у вас есть более одного действия в вашем контроллере, движок не будет знать, какое из них использовать, и выдает сообщение «Было обнаружено несколько действий, соответствующих запросу: ...». Затем, чтобы он соответствовал вашему методу Get, используйте ActionNameAttribute .

Итак, ваш маршрут должен быть таким:

// Map this rule first
RouteTable.Routes.MapRoute(
     "WithActionApi",
     "api/{controller}/{action}/{id}"
 );

RouteTable.Routes.MapRoute(
    "DefaultApi",
    "api/{controller}/{id}",
    new { action="DefaultAction", id = System.Web.Http.RouteParameter.Optional }
);

И ваш контроллер:

[ActionName("DefaultAction")] //Map Action and you can name your method with any text
public string Get(int id)
{
    return "object of id id";
}        

[HttpGet]
public IEnumerable<string> ByCategoryId(int id)
{
    return new string[] { "byCategory1", "byCategory2" };
}
Крис Ли
источник
1
Я попробовал совет выше, и все работает, как ожидалось. Большое спасибо за этот "секрет".
Mickael Caruso
В пункте 2 вашего ответа, если idэто необязательно, то URL-адрес, например, /api/{part1}/{part2}может все еще перейти в DefaultApiмаршрут, если для маршрута не найдено соответствующее действие WithActionApi. Пожалуйста, поправьте меня, если я ошибаюсь.
orad
что произойдет, если я хочу иметь только api / {controller} / {action}. без идентификатора. Если кто-то еще сталкивается с такой же проблемой. забудьте про WebApiConfig. прочтите о маршрутизации атрибутов.
ahsant
40

Решить проблему можно с помощью Attribute routing.

Контроллер

[Route("api/category/{categoryId}")]
public IEnumerable<Order> GetCategoryId(int categoryId) { ... }

URI в jquery

api/category/1

Конфигурация маршрута

using System.Web.Http;

namespace WebApplication
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API routes
            config.MapHttpAttributeRoutes();

            // Other Web API configuration not shown.
        }
    }
}

и ваша маршрутизация по умолчанию работает как маршрутизация по умолчанию на основе соглашений

Контроллер

public string Get(int id)
    {
        return "object of id id";
    }   

URI в JQuery

/api/records/1 

Конфигурация маршрута

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Attribute routing.
        config.MapHttpAttributeRoutes();

        // Convention-based routing.
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

Обзорная статья для получения дополнительной информации маршрутизации атрибутов и ОНВЕНЦИИ основы маршрутизации здесь и это

MSTdev
источник
Бог ответ. но нельзя ли добавить для всех api / {controller} / {action} / {id} вместе с api / {controller} / {id}?
karim
Маршрутизация по атрибутам однозначно решает проблему. Один важный момент: до Web API 2 шаблоны проектов Web API генерировали такой код: protected void Application_Start () {WebApiConfig.Register (GlobalConfiguration.Configuration); } Если маршрутизация атрибутов включена, этот код вызовет исключение. Если вы обновляете существующий проект веб-API для использования маршрутизации атрибутов, обязательно обновите этот код конфигурации до следующего: protected void Application_Start () {GlobalConfiguration.Configure (WebApiConfig.Register); }
Deeb
0

Попробуй это.

public class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        var json = config.Formatters.JsonFormatter;
        json.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
        config.Formatters.Remove(config.Formatters.XmlFormatter);

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional , Action =RouteParameter.Optional }

        );
    }
}
Амит
источник
0

Возможная причина также может заключаться в том, что вы не унаследовали контроллер от ApiController. Со мной случилось время, чтобы понять то же самое.

Паритош Тивари
источник
0

Чтобы различать маршруты, попробуйте добавить ограничение, что id должен быть числовым:

RouteTable.Routes.MapHttpRoute(
         name: "DefaultApi",
         routeTemplate: "api/{controller}/{id}",
         constraints: new { id = @"\d+" }, // Only matches if "id" is one or more digits.
         defaults: new { id = System.Web.Http.RouteParameter.Optional }
         );  
Дерек Уэйд
источник