简体   繁体   English

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

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

Is it possible to separate GETs and POSTs into separate API Controller types and accessing them using the same Route Prefix?是否可以将 GET 和 POST 分成单独的 API 控制器类型并使用相同的路由前缀访问它们?

Here are my controllers:这是我的控制器:

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

Web API (1.x-2.x) does not support multiple attribute routes with the same path on different controllers. Web API (1.x-2.x) 不支持在不同控制器上具有相同路径的多个属性路由。 The result is a 404, because all the route matches more than one controller and at that point Web API will consider the result ambiguous.结果是 404,因为所有路由都匹配多个控制器,此时 Web API 将认为结果不明确。

Note that MVC Core does support this scenario note: MVC Core serves as both MVC & Web API.请注意, MVC Core确实支持此场景说明:MVC Core 同时用作 MVC 和 Web API。

If you choose to use Web API 2.11 (or newer) you can create a route constraint for the http method per controller and use it instead of the built in Route Attribute.如果您选择使用 Web API 2.11(或更新版本),您可以为每个控制器的 http 方法创建路由约束,并使用它代替内置的路由属性。 The sample below shows that you can use RoutePrefix or directly Routes (like kmacdonald's answer).下面的示例显示您可以使用 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) { }
}

These two classes simplify the use of the constraint route attribute这两个类简化了约束路由属性的使用

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

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

This class allows adding constraints to the route generated此类允许向生成的路由添加约束

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

This is just a standard route constraint, nit: you may want to cache the constraints object to reduce allocations.这只是一个标准的路由约束,nit:您可能希望缓存约束对象以减少分配。

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

You don't always need to specify a RoutePrefix on your controller.您并不总是需要在控制器上指定RoutePrefix you could just put the route directly on the web methods:您可以将路由直接放在网络方法上:

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

However, I would imagine that your RoutePrefix would work fine on both controllers.但是,我认为您的RoutePrefix在两个控制器上都可以正常工作。 I think the attribute RoutePrefix is used in conjunction with the Route attribute which actually defines the route.我认为属性RoutePrefix与实际定义路由的Route属性结合使用。 This means that as long as you don't have any conflicting routes (this is a biggie) you should be fine.这意味着只要您没有任何冲突的路线(这是一个大问题),您应该没问题。

Take advantage of partial classes.利用部分类。 Partial Classes and Methods - C# MSDN 部分类和方法 - C# MSDN

Create two files: BooksController.Write.cs and BooksController.Read.cs Only RoutePrefix one file, since they are the same class it will give you an error saying you are prefixing the same class two times.创建两个文件: BooksController.Write.csBooksController.Read.cs只有 RoutePrefix 一个文件,因为它们是同一个类,它会给你一个错误,说你为同一个类添加了两次前缀。

Both files will compile as a single class (because it is a single class, but split in different files).两个文件都将编译为一个类(因为它是一个类,但拆分为不同的文件)。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM