.NET Core 2.0 Web API 控制器路由(POST 带有可选的 [FromBody] 参数)用于接收网络挂钩

.NET Core 2.0 Web API Controller routing (POST with optional [FromBody] Parameter) for receiving web hooks

目前我正在尝试实现 .NET Core 2.0 Web API web hook。

发送服务器上的 webhook 可以简单地配置为:

"endpoint": "http://localhost:50100/api/Hook"

发送服务器做两件事:

我实现了一个 HookController 并且知道我正面临一个(对我来说)奇怪的路由问题。

控制器实现如下:

    [Route("api/Hook")]
public class HookController : Controller
{
  protected ILogger<HookController> Logger { get; set; }

  public HookController(ILogger<HookController> logger)
  {
    Logger = logger ?? throw new ArgumentNullException(nameof(logger));
  }

  [HttpPost]
  [Produces("application/json")]
  [Consumes("application/json")]
  public IActionResult HookReceiverLookUp()
  {
    Logger.LogInformation("Hook Receiver POST lookup called");
    return Ok();
  }

  [HttpPost]
  [Produces("application/json")]
  [Consumes("application/json")]
  public IActionResult HookReceiverEvent([FromBody] JObject res)
  {
    if (res == null)
    {
      Logger.LogInformation("Lookup called (most likely because post body was null)");
      return Ok();
    }
    Logger.LogInformation("Hook Receiver event");
    return Ok();
  }

}

问题很简单:

AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:


那么这两种方法有什么区别(网络 api 说 none)以及如何将它们组合成一种方法(因为服务器配置让我只定义一种方法)?


我的最终解决方案

根据 Lennarts 的意见,我得到了一个工作实施草案和 运行。

public class HookController : Controller
{
  protected ILogger<HookController> Logger { get; set; }

  public HookController(ILogger<HookController> logger)
  {
    Logger = logger ?? throw new ArgumentNullException(nameof(logger));
  }

  //configuration url: http://localhost:[port]/api/Hook/endpoint
  [HttpPost("Endpoint")]
  public IActionResult HookReceiver([FromHeader(Name = "Content-Type")] string type)  //do not bind JObject here
  {

    if (type == null) return HookReceiverLookUp();   //check if it is the initial look up call / ignore body

    //avoiding built in / default model binder and read the body yourself if necessary
    string body;

    using (var reader = new System.IO.StreamReader(Request.Body, System.Text.Encoding.UTF8, true, 1024, true))
    {
      body = reader.ReadToEnd();
    }

    //optionally convert it to Json if you want to work with json
    JObject obj = Newtonsoft.Json.JsonConvert.DeserializeObject<JObject>(body);

    if (obj == null) return Ok(); //what else??

    return HookReceiverEvent(obj);
  }

  private IActionResult HookReceiverLookUp()
  {
    Logger.LogInformation("Hook Receiver POST lookup called");
    return Ok();
  }

  private IActionResult HookReceiverEvent(JObject res)
  {
    Logger.LogInformation("Hook Receiver POST lookup called\r\n" + res.ToString());
    return Ok();
  }

}

非常感谢 Lennart 的大力帮助和耐心等待。

您可以将两者定义为不同的类型,使用 [HttpGet] 表示 HookReceiverLookUp(),使用 [HttpPost] 表示 HookReceiverEvent([FromBody] JObject res)

编辑

然后尝试这样的事情:

[Route("api/Hook")]
[Produces("application/json")]
[Consumes("application/json")]
public class HookController : Controller
{
    private ILogger<HookController> Logger { get; set; }

    public HookController(ILogger<HookController> logger)
    {
        Logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    return Request.Headers.Any(
            header => header.Key == "HeaderContentType" && header.Value == "application/json")
            ? HookReceiverEvent(Request.GetBody())
            : HookReceiverLookUp();

    private IActionResult HookReceiverLookUp()
    {
        Logger.LogInformation("Hook Receiver POST lookup called");
        return Ok();
    }

    private IActionResult HookReceiverEvent(JObject res)
    {
        Logger.LogInformation("Hook Receiver event");
        return Ok();
    }
}

然后你还需要一个 class 有扩展方法的地方。我的看起来像这样:

public static class Extensions
{
    public static JObject GetBody(this HttpRequest request)
    {
        string body;
        request.EnableRewind();

        using (var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true))
        {
            body = reader.ReadToEnd();
        }

        request.Body.Position = 0;
        return JsonConvert.DeserializeObject<JObject>(body);
    }
}