具有相同URL路由但HTTP方法不同的多个控制器 [英] Multiple controllers with same URL routes but different HTTP methods

查看:107
本文介绍了具有相同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)
    {
        // ...
    }
}

所有这三个动作实际上具有三种不同的途径:

All three actions got in fact three different routes:

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

如果我将它们放在单个控制器中,则一切正常,但是,如果我将它们分开(如上所示),WebApi将引发以下异常:

If I put them into single controller everything works just fine, however if I separate them (as shown above) WebApi throws following exception:

找到与URL匹配的多种控制器类型.这个可以 如果多个控制器上的属性路由与请求的匹配,则会发生 网址.

Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.

此消息非常明显.在寻找合适的控制器/动作候选者时,WebApi似乎没有考虑HTTP方法.

This message is quite obvious. It seems WebApi does not take HTTP method into account when looking for a right candidate for controller/action.

我如何实现预期的行为?

How could I achieve the expected behavior?

更新:我已经对Web API的内部结构进行了一些深入研究,并且我了解这是默认情况下的工作方式.我的目标是分离代码和逻辑-在实际情况下,这些控制器具有不同的依赖关系,并且更加复杂.为了维护,可测试性,项目组织等,它们应该是不同的对象(SOLID和东西).

UPDATE: I've digged a little into Web API internals and I understand that's the way it works by default. My goal is to separate the code and logic - in real world case those controllers have different dependencies and are a bit more complex. For the sake of maintenance, testability, project organization etc. they should be different objects (SOLID and stuff).

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

I thought I could override some WebAPI services (IControllerSelector etc) however this seems to be a little bit risky and non-standard approach for this simple and - as I assumed - common case.

推荐答案

更新

根据您的评论,更新的问题和此处提供的答案

Based on your comments, updated question and the answer provided here

具有相同路由前缀ASP.NET Web API的多个控制器类型

可以通过对控制器方法应用HTTP方法的自定义路由约束来获得所需的结果.

Desired result can be achieved via custom route constraints for the HTTP method applied to controller actions.

检查默认的Http {Verb}属性,即 RouteAttribute ,通过密封,我意识到它们的功能可以组合为一个类,类似于它们在Asp.Net-中的实现方式-核心.

On inspection of the default Http{Verb} attributes ie [HttpGet], [HttpPost] and the RouteAttribute, which by the way are sealed, I realized that their functionality can be combine into one class similar to how they are implemented in Asp.Net-Core.

以下是关于GET和POST的内容,但为要应用于控制器的其他HTTP方法PUT, DELETE...etc创建约束并不难.

The following is for GET and POST, but it shouldn't be difficult to create constraints for the other HTTP methods PUT, DELETE...etc to be applied to the controllers.

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

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

重要的一类是路线工厂和约束本身.该框架已经具有负责大多数路由工厂工作的基类,并且还具有

The important class is the route factory and the constraint itself. The framework already has base classes that take care of most of the route factory work and also a HttpMethodConstraint so it is just a matter of applying the desired routing functionality.

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;
        }
    }
}

因此,以下控制器已应用了自定义路线约束...

So given the following controller with the custom route constraints applied...

[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();
    }
}

进行了内存中的单元测试以确认功能是否正常.

Did an in-memory unit test to confirm functionality and it worked.

[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 { }
}


原始答案

引用: ASP.NET Web API中的路由和动作选择

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

That's because it uses the routes in the route table to find the controller first and then checks for Http{Verb} to select an action. which is why it works when they are all in the same controller. if it finds the same route to two different controllers it doesn't know when one to select, hence the error.

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

If the goal is simple code organization then take advantage of partial classes

ResourcesController.cs

ResourcesController.cs

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

ResourcesController_Creation.cs

ResourcesController_Creation.cs

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

ResourcesController_Display.cs

ResourcesController_Display.cs

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

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

这篇关于具有相同URL路由但HTTP方法不同的多个控制器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆