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) {
// ...
}
}
Это потому, что он использует маршруты в таблице маршрутов, чтобы сначала найти контроллер, а затем проверить Http {Verb}, чтобы выбрать действие. поэтому он работает, когда все они находятся в одном контроллере. если он найдет один и тот же маршрут к двум различным контроллерам, он не знает, когда нужно выбрать, следовательно, ошибку. – Nkosi
Логика маршрутизации отличается от логики маршрутизации по умолчанию, поэтому вам нужно либо жить с ней (например, переместить всю логику с контроллеров на два отдельных класса неконтроллеров и использовать эти классы из одного контроллера), либо предоставить пользовательскую реализацию для IHttpControllerSelector с вашей собственной логикой маршрутизации. – Evk
Возможно, вместо префикса маршрута вы можете добавить '/ some-resources '' к индивидуальному маршруту действий. – Developer