簡體   English   中英

具有相同URL路由但HTTP方法不同的多個控制器

[英]Multiple controllers with same URL routes but different HTTP methods

我有以下兩個控制器:

[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匹配的多種控制器類型。 如果多個控制器上的屬性路由與請求的URL匹配,則會發生這種情況。

此消息非常明顯。 在尋找合適的控制器/動作候選者時,WebApi似乎沒有考慮HTTP方法。

我怎樣才能達到預期的行為?


更新 :我已經深入研究了Web API的內部結構,並且我了解這是默認情況下的工作方式。 我的目標是分離代碼和邏輯-在實際情況下,這些控制器具有不同的依賴關系,並且更加復雜。 為了維護,可測試性,項目組織等,它們應該是不同的對象(SOLID和東西)。

我以為可以覆蓋某些WebAPI服務( IControllerSelector等),但是對於這種簡單的(我假設是這樣)常見的情況,這似乎有點冒險且不標准。

更新

根據您的評論,更新的問題和此處提供的答案

具有相同路由前綴的多個控制器類型ASP.NET Web Api

可以通過針對應用於控制器操作的HTTP方法的自定義路由約束來獲得所需的結果。

在檢查默認的Http {Verb}屬性(即[HttpGet][HttpPost]RouteAttribute ,我發現它們的功能可以組合到一個類中,類似於在Asp.Net中的實現方式-核心。

以下是針對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 { }
}

原始答案

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM