【问题标题】:.NET Core 2.0 Web API Controller routing (POST with optional [FromBody] Parameter) for receiving web hooks.NET Core 2.0 Web API 控制器路由(带有可选 [FromBody] 参数的 POST)用于接收 Web 挂钩
【发布时间】:2017-12-15 10:15:37
【问题描述】:

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

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

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

发送服务器做了两件事:

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

我实现了一个 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】:

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

    【讨论】:

    • 这很容易,但不起作用,因为两个调用(事件和启动)都是作为 POST 发送的。如果我将查找更改为 GET,则它不会被识别为有效端点。
    • 您是否尝试过使用[HttpPost("endpointname")] 进一步指定您的路线?
    • 不太清楚你现在的意思。如果我要定义 [HttpPost("lookup"] 和 [HttpPost("events"] 会导致控制器上出现两个不同的 URL,这是一个问题,因为我只能在服务器上定义一个端点 URL。我理解对了吗? ?
    • 啊,是的!我不确定你不能那样做。那么,您是否尝试过[ActionName("HookReceiverEvent")] 并使用相同的操作名定义它们?
    • 到目前为止,我还没有使用 ActionName,也无法让它工作。如果我将 ActionName 注释添加到 I 然后键入 localhost:50100/enpoint 或 localhost:50100/api/endpoint 或 localhost:50100/api/Hook/endpoint 我总是得到 404 作为响应。任何提示我做错了什么?
    猜你喜欢
    • 1970-01-01
    • 2017-08-30
    • 2022-08-15
    • 1970-01-01
    • 2018-11-06
    • 2020-04-08
    • 1970-01-01
    • 2019-03-11
    • 1970-01-01
    相关资源
    最近更新 更多