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

本文关键字:Api 类型 控制器 Web NET 路由 ASP 前缀 | 更新日期: 2023-09-27 17:58:25

是否可以将GET和POST分离为单独的API控制器类型,并使用相同的路由前缀访问它们?

这是我的控制器:

[RoutePrefix("api/Books")]
public class BooksWriteController : EventStoreApiController
{
    [Route("")]
    public void Post([FromBody] CommandWrapper commandWrapper){...}
}
[RoutePrefix("api/Books")]
public class BooksReadController : MongoDbApiController
{
    [Route("")]
    public Book[] Get() {...}
    [Route("{id:int}")]
    public Book Get(int id) {...}
}

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

Web API(1.x-2.x)不支持不同控制器上具有相同路径的多个属性路由。结果是404,因为所有路由都与一个以上的控制器匹配,此时Web API将认为结果不明确。

请注意,MVC Core确实支持这种场景注意:MVC Core同时充当MVC&Web API。

如果选择使用Web API 2.11(或更高版本),则可以为每个控制器的http方法创建路由约束,并使用它而不是内置的路由属性。下面的示例显示您可以使用RoutePrefix或直接使用Routes(如kmacdonald的答案)。

using System.Collections.Generic;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Routing;
public class BooksWriteController : ApiController
{
    [PostRoute("api/Books")]
    public void Post() { }
}
[RoutePrefix("api/books")]
public class BooksReadController : ApiController
{
    [GetRoute]
    public void Get() { }
    [GetRoute("{id:int}")]
    public void Get(int id) { }
}

这两个类简化了约束路由属性的使用

class GetRouteAttribute : MethodConstraintedRouteAttribute
{
    public GetRouteAttribute(string template) : base(template ?? "", HttpMethod.Get) { }
}
class PostRouteAttribute : MethodConstraintedRouteAttribute
{
    public PostRouteAttribute(string template) : base(template ?? "", HttpMethod.Post) { }
}

此类允许将约束添加到路由生成的

class MethodConstraintedRouteAttribute : RouteFactoryAttribute
{
    public MethodConstraintedRouteAttribute(string template, HttpMethod method)
        : base(template)
    {
        Method = method;
    }
    public HttpMethod Method
    {
        get;
        private set;
    }
    public override IDictionary<string, object> Constraints
    {
        get
        {
            var constraints = new HttpRouteValueDictionary();
            constraints.Add("method", new MethodConstraint(Method));
            return constraints;
        }
    }
}

这只是一个标准的路由约束nit:您可能想要缓存constraints对象以减少分配。

class MethodConstraint : IHttpRouteConstraint
{
    public HttpMethod Method { get; private set; }
    public MethodConstraint(HttpMethod method)
    {
        Method = method;
    }
    public bool Match(HttpRequestMessage request,
                      IHttpRoute route,
                      string parameterName,
                      IDictionary<string, object> values,
                      HttpRouteDirection routeDirection)
    {
        return request.Method == Method;
    }
}

您并不总是需要在控制器上指定RoutePrefix。你可以直接把路线放在网上方法:

public class BooksWriteController : EventStoreApiController
{
    [Route("api/Books")]
    public void Post([FromBody] CommandWrapper commandWrapper){...}
}
public class BooksReadController : MongoDbApiController
{
    [Route("api/Books")]
    public TaskTypeInfo[] Get() {...}

    [Route("api/Books/{id:int}")]
    public TaskTypeInfo Get(int id) {...}
}

但是,我想您的RoutePrefix在两个控制器上都能正常工作。我认为属性RoutePrefix与实际上定义路由的Route属性一起使用。这意味着,只要你没有任何冲突的路线(这是一件大事),你就应该没事。

利用分部类。分部类和方法-C#MSDN

创建两个文件:BooksController.Write.csBooksController.cs

两个文件都将编译为一个类(因为它是一个单独的类,但拆分为不同的文件)。

// File BooksController.Write.cs
[RoutePrefix("api/Books")]
public partial class BooksController : EventStoreApiController
{
    [Route("")]
    public void Post([FromBody] CommandWrapper commandWrapper){...}
}
// File BooksController.Read.cs
public partial class BooksController : MongoDbApiController
{
    [Route("")]
    public Book[] Get() {...}

    [Route("{id:int}")]
    public Book Get(int id) {...}
}