.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"
发送服务器做两件事:
- 启动时它会向定义的端点发送一个空的 post 以检查它是否存在以及支持哪些媒体类型
- 稍后事件在 post 正文中作为普通 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)
- 如果我有这两种方法,接收网络 api 将不会以异常开始
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);
}
}
目前我正在尝试实现 .NET Core 2.0 Web API web hook。
发送服务器上的 webhook 可以简单地配置为:
"endpoint": "http://localhost:50100/api/Hook"
发送服务器做两件事:
- 启动时它会向定义的端点发送一个空的 post 以检查它是否存在以及支持哪些媒体类型
- 稍后事件在 post 正文中作为普通 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)
- 如果我有这两种方法,接收网络 api 将不会以异常开始
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);
}
}