【问题标题】:Multiple controllers with same URL routes but different HTTP methods具有相同 URL 路由但不同 HTTP 方法的多个控制器
【发布时间】:2017-04-15 00:40:57
【问题描述】:

我有以下两个控制器:

[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 方法。

我怎样才能达到预期的行为?


更新:我已经深入研究了 Web API 的内部结构,我知道这是默认的工作方式。我的目标是分离代码和逻辑——在现实世界中,这些控制器具有不同的依赖关系并且更复杂一些。为了维护、可测试性、项目组织等。它们应该是不同的对象(SOLID 和其他东西)。

我认为我可以覆盖一些 WebAPI 服务(IControllerSelector 等),但是对于这种简单且(正如我假设的)常见情况的方法来说,这似乎有点冒险和非标准。

【问题讨论】:

  • 那是因为它使用路由表中的路由先找到控制器,然后检查Http{Verb}来选择一个动作。这就是为什么当它们都在同一个控制器中时它可以工作的原因。如果它找到到两个不同控制器的相同路由,它不知道何时选择一个,因此会出错。
  • 您的路由逻辑与默认路由逻辑不同,因此您必须忍受它(例如,将所有逻辑从控制器移动到两个单独的非控制器类并使用单个控制器中的这些类),或者使用您自己的路由逻辑为 IHttpControllerSelector 提供自定义实现。
  • 可能不是路由前缀,你可以添加"/some-resources"到单个动作路由。
  • 正如开发人员所说,您可以改为使用Route 属性装饰方法并在您的路由前加上/some-resources 以绕过URL 的控制器部分。如果您有大量方法,这并不完全理想,这就是为什么我没有将其发布为答案。

标签: c# asp.net-web-api asp.net-web-api2 asp.net-web-api-routing attributerouting


【解决方案1】:

更新

根据您的 cmets、更新的问题和此处提供的答案

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

可以通过应用于控制器操作的 HTTP 方法的自定义路由约束来实现所需的结果。

检查默认的 Http{Verb} 属性,即 [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 { }
}

原始答案

参考:Routing and Action Selection in ASP.NET Web API

那是因为它首先使用路由表中的路由找到控制器,然后检查 Http{Verb} 来选择一个动作。这就是为什么当它们都在同一个控制器中时它可以工作的原因。如果它找到到两个不同控制器的相同路由,它不知道何时选择一个,因此会出错。

如果目标是简单的代码组织,那么利用部分类

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) {
        // ...
    }
}

【讨论】:

  • 抱歉耽搁了。一切似乎都完美无瑕。谢谢!
  • 我想你忘了在So given the following controller with the custom route constraints applied 部分实际使用[MethodConstraintedRoute(..)]
  • @quetzalcoatl 不,我没有。如果您查看上面的代码 sn-ps,您将看到 MethodConstraintedRouteHttpGetAttributeHttpPostAttribute 使用的基类
  • 哦,那是真的。我忽略了那部分,并认为 HttpGet/HttpPost 是标准框架代码。谢谢!
猜你喜欢
  • 2011-12-14
  • 1970-01-01
  • 2014-10-07
  • 2017-04-05
  • 2017-03-10
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多