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

问题描述 投票:1回答:1

目前,我尝试实现.NET Core 2.0 Web API Web挂钩。

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

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

发送服务器做两件事:

  • 在启动时,它会向定义的端点发送一个空帖子,以检查它是否存在以及支持哪些媒体类型
  • 稍后将事件作为普通json在post主体中发送到定义的端点

我实现了一个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();
  }

}

问题很简单:

  • 如果我只有HookReceiverLookUp方法,服务器在启动时会识别出有效的端点,但很明显这种方法无法接收数据
  • 如果我只有HookReceiverEvent方法,它可以从POST主体接收json,但它不会被识别为有效端点(即使使用[FromBody] JObject res = null)
  • 如果我有这两种方法,接收web api将不会以异常开头

AmbiguousActionException:匹配多个动作。以下操作匹配路由数据并满足所有约束:


那么这两种方法之间有什么区别(web api说没有),我如何将它们组合成一种方法(因为服务器配置让我只定义一种方法)?


我的最终解决方案

基于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给予您极大的帮助和耐心。

asp.net-mvc-routing asp.net-core-webapi .net-core-2.0
1个回答
1
投票

好吧你可以定义为一个不同的类型,使用[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();
    }
}

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

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);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.