[英]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 { }
}
原始答案
這是因為它首先使用路由表中的路由來查找控制器,然后檢查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.