【问题标题】:ASP.NET WebAPI 2 Versioning POST with id带有 id 的 ASP.NET WebAPI 2 版本控制 POST
【发布时间】:2020-06-27 09:54:21
【问题描述】:

我正在尝试在现有的生产性 WebAPI 2 应用程序中实现“Microsoft.AspNet.WebApi.Versioning”nuget,以支持新设计的“v2”控制器。

到目前为止,大多数控制器都可以正常工作,但现在我遇到了一些 POST 控制器的问题。

示例代码:

using Microsoft.Web.Http;
using System.Web.Http;

namespace ApplicationApi.Controllers
{
    [ApiVersion("1.0")]
    [Route("api/version")]
    public class VersionController : ApiController
    {
        //Works
        [HttpGet]
        public IHttpActionResult Get()
        {
            return Ok(true);
        }

        //Throws  HTTP 405 - The requested resource with API version '1.0' does not support HTTP method 'POST'.
        [HttpPost]
        public IHttpActionResult Post(int id, [FromBody]dynamic value)
        {
            return Ok(true);
        }

        //Works
        [HttpPost]
        public IHttpActionResult Post([FromBody]dynamic value)
        {
            return Ok(true);
        }
    }
}

所有没有附加 {id} 的 POST 方法都有效。但是由于我们有大量的旧客户端很长一段时间,我必须保持旧版本正常运行,并且在旧控制器中删除 {id} 不是一个选项。在新的控制器中,不需要 {id}。

WebAPI 配置

var constraintResolver = new DefaultInlineConstraintResolver()
{
    ConstraintMap =
    {
        ["apiVersion"] = typeof( ApiVersionRouteConstraint )
    }
};
config.MapHttpAttributeRoutes(constraintResolver);

config.AddApiVersioning(options =>
{
    options.AssumeDefaultVersionWhenUnspecified = true;
});

config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

知道我做错了什么或如何解决这个问题吗?

【问题讨论】:

  • 你能澄清一下这两种 Post 方法中的哪一种是 v2 吗?收到 405 错误时的请求 URL 是什么样的?
  • 这个问题与 API 版本无关。看我的回答。

标签: c# asp.net-web-api asp.net-web-api2 asp.net-routing aspnet-api-versioning


【解决方案1】:

问题与版本无关。

带有id 参数的方法不起作用的原因是您有冲突路由

[Route("api/version")]

config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

对于名为Version 且具有Route("api/version") 属性的控制器,您只能拥有属性路由, 常规路由(MVC / WebAPI 路由),但不能同时拥有。

在您的情况下,属性路由会覆盖 MVC,因此 api/version/{id} 不再存在。但是api/version?id 仍然有效,因为它是通过属性路由寻址的。

要解决该问题,您可以将控制器上的属性路由更改为 RoutePrefix 而不是 Route,并在必要时在每个操作上使用带有相对路径的 Route。 p>

必要表示您有常规路由无法解决的路由。

[ApiVersion("1.0")]
[RoutePrefix("api/version")]
public class VersionController : ApiController
{
    [HttpPost]
    public IHttpActionResult Post([FromBody] dynamic value)
    {
        return Ok();
    }

    [HttpPost]
    public IHttpActionResult Post(int id, [FromBody] dynamic value)
    {
        return Ok(1);
    }
}

【讨论】:

  • 感谢您对我的回答的认识,我认为您的回答更具描述性,我的眼睛没有抓住路线部分。我删除了我的答案。干得好:)
  • @maytham-ɯɐɥʇʎɐɯ 谢谢,但实际上没有必要删除您的答案,相反,您可以更新它。您的回答提供了除我之外的其他有用/有用的信息,我认为这对人们阅读也有好处。
  • 这是一个可行的解决方案,但它也强调了混合路由样式会给您带来麻烦(并使您眼花缭乱)。我强烈建议反对混合风格。选择直接路由(又名属性路由)或基于约定的路由
  • 小程序@weichch,这里根本不涉及MVC。虽然它们看起来相同,但路由在所有实现方面都不同。这由Routes.MapRouteRoutes.MapHttpRoute 表示。 RoutePrefixRoute 使用相同的属性名称,但在 MVC 和 Web API 的不同命名空间中。大多数 Web API 类型都有 Http 前缀(例如:IActionResultIHttpActionResult)。
  • @ChrisMartinez 感谢您的建议。我修改了使用常规路由的答案。
【解决方案2】:

试试

[ApiVersion("1.0")]
public class VersionController : ApiController
{
    //Works
    [HttpGet]
    [Route("api/version")]
    public IHttpActionResult Get()
    {
        return Ok(true);
    }

    //Throws  HTTP 405 - The requested resource with API version '1.0' does not support HTTP method 'POST'.
    [HttpPost]
    [Route("api/version/{id}")]
    public IHttpActionResult Post([FromUri]int id, [FromBody]dynamic value)
    {
        return Ok(true);
    }

    //Works
    [HttpPost]
    [Route("api/version")]
    public IHttpActionResult Post([FromBody]dynamic value)
    {
        return Ok(true);
    }
}

[ApiVersion("1.0")]
[RoutePrefix("api/version/")]
public class VersionController : ApiController
{
    //Works
    [HttpGet]
    public IHttpActionResult Get()
    {
        return Ok(true);
    }

    //Throws  HTTP 405 - The requested resource with API version '1.0' does not support HTTP method 'POST'.
    [HttpPost]
    [Route("{id}")]
    public IHttpActionResult Post([FromUri]int id, [FromBody]dynamic value)
    {
        return Ok(true);
    }

    //Works
    [HttpPost]
    public IHttpActionResult Post([FromBody]dynamic value)
    {
        return Ok(true);
    }
}

【讨论】:

  • 感谢您的回答。我把赏金给了weichch,因为他的答案相同,而且更早了一点。
【解决方案3】:

问题是你有 2 个 post 方法,而 api 不知道如何识别/生成资源名称。

您需要做的是添加重复方法名称的自定义资源名称。

因此,如果我们以您的示例为例,您可以将资源添加到引发异常的那个并保留另一个,或者给它们两个唯一的资源名称。

例如[HttpPost("id")]

为了确认这一点,我用你的代码做了一个测试项目,为此我使用了 swagger,但它失败了。现在,当我将id 添加到这样的帖子方法之一时:

[HttpPost("id")]
public IActionResult Post(int id, [FromBody]dynamic value)
{
    return Ok(true);
}

[HttpPost]
public IActionResult Post([FromBody]dynamic value)
{
    return Ok(true);
}

然后它与以下输出一起工作:


主要答案的补充 在您的代码中,您允许通过在方法中添加 int id 来获取参数

 Post(int id, [FromBody]dynamic value)

如果您真的不想使用参数,那么您可以传递您自定义的对象类并执行类似的操作,而无需对其进行测试:

 Post([FromBody]MyCustomObjectClass data)

还有你MyCustomerObjectClass:

class MyCustomObjectClass {
    public int? Id { get; set; }
    public dynamic Value { get; set; }
}

你不需要2个post方法,你可以只有一个,ID可以是可选的。

只是为了不让你感到困惑。我在帖子中使用了[HttpPost("id")] 之类的 id 作为名称约定,这与您的方法 Id 参数无关,这是我的错。 id 在这种情况下只是资源的名称,它对操作没有影响。所以你可以把它叫做任何符合你逻辑的名字[HttpPost("post1")][HttpPost("post2")]

最后一个建议,使用动态进行测试并在短时间内启动 api 很好,但我总是使用静态类型的对象。

我强烈建议您查看this

【讨论】:

  • HttpPost 属性不支持参数
  • 检查我对答案的补充。
  • 这个答案只是部分正确。 MVC 可以找出具有不同参数的Post 方法。在这种特殊情况下它不能的原因是 OP 的控制器名称与属性路由指定的名称相同。属性路由覆盖{controller}/{id} 路由。
猜你喜欢
  • 2016-06-15
  • 2018-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-19
  • 2021-06-19
  • 2016-07-17
相关资源
最近更新 更多