Search code examples
.netasp.net-web-api-routingasp.net-apicontroller

Bitbucket POST Hooks with .Net ApiController setup?


I am trying to get my .Net WebApi 2.2 to accept posts from bitbucket. But BB doesn't post it's data as the body, but rather as a post parameter.

I have simulated what bitbucket sends using the PostMan here: http://www.posttestserver.com/data/2014/11/05/shea/22.05.091712543622

You can import the PostMan collection here: https://www.getpostman.com/collections/ec562c5141d85fe7b850

When I try to post to my ApiController, I get

{
    "Message": "The requested resource does not support http method 'POST'."
}

That happens when my post function signature looks like this:

public string Post([FromUri]string payload)
{
    var hookEvent = JsonConvert.DeserializeObject<HookEvent>(payload);
    ....

or public string Post(string payload) { var hookEvent = JsonConvert.DeserializeObject(payload); ....

When I do:

public string Post([FromUri]HookEvent payload)
{
    ....

payload isn't null but all the fields in it are null.

    public string Post(HookEvent payload)
    {

gives this error:

"Message": "The request contains an entity body but no Content-Type header. The inferred media type 'application/octet-stream' is not supported for this resource.",
"ExceptionMessage": "No MediaTypeFormatter is available to read an object of type 'HookEvent' from content with media type 'application/octet-stream'.",
"ExceptionType": "System.Net.Http.UnsupportedMediaTypeException",
"StackTrace": "   at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)\r\n   at System.Net.Http.HttpContentExtensions.ReadAsAsync(HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)\r\n   at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)"

I know my 'HookEvent' is deserializing correctly, because if change PostMan to send the payload as the request body, the object is correctly deserialized, with all fields set. Unfortunately BB doesn't send the payload that way, but as a post param.

My controller signature is:

public class BitbucketHookController : ApiController
{
    ....

Other routing bits:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        routes.MapMvcAttributeRoutes();
    }
}

And Application_Start() in Global.asax.cs

        DiConfig.Register();
        AreaRegistration.RegisterAllAreas();
        GlobalConfiguration.Configure(WebApiConfig.Register);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

What do I have to do to get my controller to accept the post payload?


Solution

  • The best solution I was able to file was to use a container model.

    public class HookEventContainer
    {
        public string payload { get; set; }
        public HookEvent HookEvent {
            get
            {
                if (string.IsNullOrEmpty(payload))
                {
                    return null;
                }
                return JsonConvert.DeserializeObject<HookEvent>(payload);
            }
        }
    }
    

    Then use this for my Post signature:

        // POST api/<controller>
        //
        public string Post(HookEventContainer eventData)
        {
            var hookEvent = eventData.HookEvent;
    

    Ugly but it works.

    I also sent a request to Bitbucket to find out why they choose to send the payload as a parameter instead of as the request body. Since Atlassian bought them, they aren't all that responsive though....