Неоднозначные имена контроллеров с атрибутами маршрутизации: контроллеры с таким же именем и другим пространством имен для управления версиями

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

Controllers
 |
 |---Version0
 |      |
 |      |----- ProjectController.cs
 |      |----- HomeController.cs
 |
 |---Version1
       |
       |----- ProjectController.cs
       |----- HomeController.cs

Я использую RoutingAttribute для маршрутов. Таким образом, ProjectController в Version0 имеет функцию с маршрутом как

namespace MyProject.Controllers.Version0
{
   class ProjectController : BaseController
   {
     ...

     [Route(api/users/project/getProjects/{projectId})]
     public async GetProjects(string projectId) 
     {
       ...
     }
  }
}

и ProjectController в Версии 1 имеет функцию с маршрутом как

namespace MyProject.Controllers.Version1
{
   class ProjectController : BaseController
   {
     ...

     [Route(api/v1/users/project/getProjects/{projectId})]
     public async GetProjects(string projectId) 
     {
      ...
     }
  }
}

Но я получаю 404-NotFound, когда пытаюсь попасть в службу.

Если я переименую контроллеры на уникальное имя (Project1Controller и Project2Controller), то маршрутизация будет работать. Но я стараюсь избегать переименования для простоты.

Я воспользовался этой ссылкой, чтобы решить эту проблему, но это не помогло. Я создал области, но до сих пор не добился успеха. Добавление логики маршрутизации в файл global.aspx не помогает. Пространство имен также не работает. http://haacked.com/archive/2010/01/12/ambiguous-controller-names.aspx/

В приведенной выше ссылке предлагается создать области, но маршрутизация атрибутов не поддерживает области по ссылке: http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

Есть ли другое решение? Ошибка с атрибутами RoutingAttributes?

Спасибо!

Ответ 1

Во-первых, маршрутизация веб-API и маршрутизация MVC работают не точно так же.

Ваша первая ссылка указывает на маршрутизацию MVC с областями. Области официально не поддерживаются для веб-API, хотя вы можете попытаться сделать что-то похожее на них. Однако, даже если вы попытаетесь сделать что-то подобное, вы получите ту же ошибку, потому что способ, с помощью которого Web API ищет контроллер, не учитывает пространство имен контроллера.

Итак, из коробки это никогда не сработает.

Однако вы можете изменить большинство поведения веб-API, и это не исключение.

Web API использует контроллер, чтобы получить желаемый контроллер. Поведение, описанное выше, - это поведение DefaultHttpControllerSelector, которое поставляется с веб-интерфейсом, но вы можете реализовать свой собственный селектор, чтобы заменить стандартный, и поддерживать новые формы поведения.

Если вы google для "настраиваемого селектора контроллеров веб-api", вы найдете много образцов, но я считаю это наиболее интересным для вашей проблемы:

Эта реализация также интересна:

Как вы видите, в основном вам нужно:

  • реализовать свой собственный IHttpControllerSelector, который учитывает пространства имен, чтобы найти контроллеры и переменную маршрута пространств имен, чтобы выбрать один из них.
  • замените исходный селектор на это с помощью конфигурации веб-API.

Ответ 2

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

Когда я попробовал это, я обнаружил, что на самом деле отсутствует информация о маршрутизации, которая должна была быть сгенерирована, путем вызова метода MapHttpAttributeRoutes HttpConfiguration класса HttpConfiguration:

config.MapHttpAttributeRoutes();

Это означало, что метод SelectController из замещающей реализации IHttpControllerSelector фактически никогда не вызывался, и именно поэтому запрос выдает ответ http 404.

Проблема вызвана внутренним классом под названием HttpControllerTypeCache, который является внутренним классом в System.Web.Http сборке под System.Web.Http.Dispatcher пространства имен. Код, о котором идет речь, следующий:

    private Dictionary<string, ILookup<string, Type>> InitializeCache()
    {
      return this._configuration.Services.GetHttpControllerTypeResolver().GetControllerTypes(this._configuration.Services.GetAssembliesResolver()).GroupBy<Type, string>((Func<Type, string>) (t => t.Name.Substring(0, t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length)), (IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase).ToDictionary<IGrouping<string, Type>, string, ILookup<string, Type>>((Func<IGrouping<string, Type>, string>) (g => g.Key), (Func<IGrouping<string, Type>, ILookup<string, Type>>) (g => g.ToLookup<Type, string>((Func<Type, string>) (t => t.Namespace ?? string.Empty), (IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase)), (IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase);
    }

В этом коде вы увидите, что он группируется по имени типа без пространства имен. Класс DefaultHttpControllerSelector использует эту функцию при создании внутреннего кэша HttpControllerDescriptor для каждого контроллера. При использовании метода MapHttpAttributeRoutes он использует другой внутренний класс с именем AttributeRoutingMapper который является частью System.Web.Http.Routing имен System.Web.Http.Routing. Этот класс использует метод GetControllerMapping IHttpControllerSelector для настройки маршрутов.

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