4

У меня есть следующие два контроллера:несколько контроллеров с одинаковыми URL маршрутов, но разные методы HTTP

[RoutePrefix("/some-resources") 
class CreationController : ApiController 
{ 
    [HttpPost, Route] 
    public ... CreateResource(CreateData input) 
    { 
     // ... 
    } 
} 

[RoutePrefix("/some-resources") 
class DisplayController : ApiController 
{ 
    [HttpGet, Route] 
    public ... ListAllResources() 
    { 
     // ... 
    } 

    [HttpGet, Route("{publicKey:guid}"] 
    public ... ShowSingleResource(Guid publicKey) 
    { 
     // ... 
    } 
} 

Все три действия получили фактически три различных маршрута:

  • GET /some-resources
  • POST /some-resources
  • GET /some-resources/aaaaa-bbb-ccc-dddd

Если я ставлю их в один контроллер все работает нормально, но если я разделить их (как показано выше) WebAPI бросками следующее исключение:

Различные типы контроллеров были найдены, которые соответствуют URL. Это может произойти, если маршруты атрибутов на нескольких контроллерах соответствуют запрошенному адресу .

Это сообщение совершенно очевидно. Кажется, WebApi не учитывает метод HTTP при поиске подходящего кандидата для контроллера/действия.

Как я мог достичь ожидаемого поведения?


UPDATE: Я выкопал немного в внутренностей Web API, и я понимаю, что так оно и работает по умолчанию. Моя цель - отделить код и логику - в реальном мире эти контроллеры имеют разные зависимости и немного сложнее. Для поддержания, проверки, организации проекта и т. Д. Они должны быть разными объектами (SOLID и т. Д.) должен.

Я думал, что могу переопределить некоторые службы WebAPI (IControllerSelector и т. Д.), Однако это кажется немного рискованным и нестандартным подходом для этого простого и, как я предполагал, обычного случая.

+0

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

+3

Логика маршрутизации отличается от логики маршрутизации по умолчанию, поэтому вам нужно либо жить с ней (например, переместить всю логику с контроллеров на два отдельных класса неконтроллеров и использовать эти классы из одного контроллера), либо предоставить пользовательскую реализацию для IHttpControllerSelector с вашей собственной логикой маршрутизации. – Evk

+0

Возможно, вместо префикса маршрута вы можете добавить '/ some-resources '' к индивидуальному маршруту действий. – Developer

ответ

7

UPDATE

На основании ваших комментариев, обновленный вопрос и ответ, представленная здесь

Multiple Controller Types with same Route prefix ASP.NET Web Api

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

При осмотре Http по умолчанию {глагол} атрибуты т.е. [HttpGet][HttpPost], и RouteAttribute, которые, кстати, уплотнены, я понял, что их функциональность можно объединить в один класс подобно тому, как они реализуются в Asp.Net -CORE.

Ниже приведены для GET и POST, но не должно быть сложно создать ограничения для других методов HTTP PUT, DELETE...etc для применения к контроллерам.

class HttpGetAttribute : MethodConstraintedRouteAttribute { 
    public HttpGetAttribute(string template) : base(template, HttpMethod.Get) { } 
} 

class HttpPostAttribute : MethodConstraintedRouteAttribute { 
    public HttpPostAttribute(string template) : base(template, HttpMethod.Post) { } 
} 

Важным классом является фабрика маршрутов и само ограничение.Рамки уже имеют базовые классы, которые занимаются большей частью работы фабрики маршрутов, а также HttpMethodConstraint, поэтому это всего лишь вопрос применения желаемой функциональности маршрутизации.

class MethodConstraintedRouteAttribute 
    : RouteFactoryAttribute, IActionHttpMethodProvider, IHttpRouteInfoProvider { 
    public MethodConstraintedRouteAttribute(string template, HttpMethod method) 
     : base(template) { 
     HttpMethods = new Collection<HttpMethod>(){ 
      method 
     }; 
    } 

    public Collection<HttpMethod> HttpMethods { get; private set; } 

    public override IDictionary<string, object> Constraints { 
     get { 
      var constraints = new HttpRouteValueDictionary(); 
      constraints.Add("method", new HttpMethodConstraint(HttpMethods.ToArray())); 
      return constraints; 
     } 
    } 
} 

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

[RoutePrefix("api/some-resources")] 
public class CreationController : ApiController { 
    [HttpPost("")] 
    public IHttpActionResult CreateResource(CreateData input) { 
     return Ok(); 
    } 
} 

[RoutePrefix("api/some-resources")] 
public class DisplayController : ApiController { 
    [HttpGet("")] 
    public IHttpActionResult ListAllResources() { 
     return Ok(); 
    } 

    [HttpGet("{publicKey:guid}")] 
    public IHttpActionResult ShowSingleResource(Guid publicKey) { 
     return Ok(); 
    } 
} 

ли тест на блок в памяти для подтверждения функциональности, и она работала.

[TestClass] 
public class WebApiRouteTests { 
    [TestMethod] 
    public async Task Multiple_controllers_with_same_URL_routes_but_different_HTTP_methods() { 
     var config = new HttpConfiguration(); 
     config.MapHttpAttributeRoutes(); 
     var errorHandler = config.Services.GetExceptionHandler(); 

     var handlerMock = new Mock<IExceptionHandler>(); 
     handlerMock 
      .Setup(m => m.HandleAsync(It.IsAny<ExceptionHandlerContext>(), It.IsAny<System.Threading.CancellationToken>())) 
      .Callback<ExceptionHandlerContext, CancellationToken>((context, token) => { 
       var innerException = context.ExceptionContext.Exception; 

       Assert.Fail(innerException.Message); 
      }); 
     config.Services.Replace(typeof(IExceptionHandler), handlerMock.Object); 


     using (var server = new HttpTestServer(config)) { 
      string url = "http://localhost/api/some-resources/"; 

      var client = server.CreateClient(); 
      client.BaseAddress = new Uri(url); 

      using (var response = await client.GetAsync("")) { 
       Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); 
      } 

      using (var response = await client.GetAsync("3D6BDC0A-B539-4EBF-83AD-2FF5E958AFC3")) { 
       Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); 
      } 

      using (var response = await client.PostAsJsonAsync("", new CreateData())) { 
       Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); 
      } 
     } 
    } 

    public class CreateData { } 
} 

ОРИГИНАЛЬНЫЙ ОТВЕТ

Referencing: Routing and Action Selection in ASP.NET Web API

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

Если цель, то простой код организации воспользоваться частичных классов

ResourcesController.cs

[RoutePrefix("/some-resources")] 
partial class ResourcesController : ApiController { } 

ResourcesController_Creation.cs

partial class ResourcesController { 
    [HttpPost, Route] 
    public ... CreateResource(CreateData input) { 
     // ... 
    } 
} 

ResourcesController_Display.cs

partial class ResourcesController { 
    [HttpGet, Route] 
    public ... ListAllResources() { 
     // ... 
    } 

    [HttpGet, Route("{publicKey:guid}"] 
    public ... ShowSingleResource(Guid publicKey) { 
     // ... 
    } 
} 
+0

Извините за задержку. Все кажется безупречным. Спасибо! – Crozin

+0

Я думаю, что вы забыли на самом деле использовать '[MethodConstraintedRoute (..)]' в 'Так что задан следующий контроллер с использованием настраиваемых ограничений маршрута ' – quetzalcoatl

+0

@quetzalcoatl no У меня нет. Если вы посмотрите на фрагменты кода выше, вы увидите, что 'MethodConstraintedRoute' - это базовый класс, используемый атрибутами HttpGetAttribute' и HttpPostAttribute' – Nkosi

 Смежные вопросы

  • Нет связанных вопросов^_^