Slack-Integration in ASP.NET Web-Api 2

1.1k views Asked by At

I want to know exactly why this is not working:

[HttpPost]
public IHttpActionResult Post(Slack_Webhook json)
{
    return Ok(json.challenge);
}

public class Slack_Webhook
{
    public string type { get; set; }
    public string token { get; set; }
    public string challenge { get; set; }
}

The Official Documentation says:

We’ll send HTTP POST requests to this URL when events occur. As soon as you enter a URL, we’ll send a request with a challenge parameter, and your endpoint must respond with the challenge value.

This is an example object (JSON) sent by Slack:

{
    "token": "Jhj5dZrVaK7ZwHHjRyZWjbDl",
    "challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
    "type": "url_verification"
}

EDIT: I could write a book on code that does not work in this issue... here's another example that did not work - still no idea what is wrong:

[HttpPost]
public IHttpActionResult Post()
{
    var pairs = Request.GetQueryNameValuePairs();
    bool isValidToken = false;
    string c = "This does not work.";
    foreach(var pair in pairs)
    {
        if (pair.Key == "token")
        {
            if (pair.Value == "<UNIQUETOKEN>")
            {
                isValidToken = true;
            }
        }
        if (pair.Key == "challenge")
        {
            c = pair.Value;
        }
    }
    if (isValidToken == true)
    {
        return Json(new {challenge = c });
    }
    else
    {
        return BadRequest();
    }
}

EDIT2: Very interesting that I get NULL as a response from below code - that means the body of the received POST is empty.. Could anyone with a working Slack-Integration try that out? So their site is wrong, stating the challenge is sent in the body - where else could it be?

// POST: api/Slack
[HttpPost]
public IHttpActionResult Post([FromBody]string json)
{
    return Json(json);
}

EDIT3: This function is used to get the raw request, but there is nothing inside the body - I am out of solutions.. the support of Slack said, they have no idea about ASP.NET and I should ask here on SO for a solution. Here we are again! ;-)

[HttpPost]
public async Task<IHttpActionResult> ReceivePostAsync()
{
    string rawpostdata = await RawContentReader.Read(this.Request);
    return Json(new StringContent( rawpostdata));
}
public class RawContentReader
{
    public static async Task<string> Read(HttpRequestMessage req)
    {
        using (var contentStream = await req.Content.ReadAsStreamAsync())
        {
            contentStream.Seek(0, SeekOrigin.Begin);
            using (var sr = new StreamReader(contentStream))
            {
                return sr.ReadToEnd();
            }
        }
    }
}

The result ( as expected ) looks like this:

Our Request:
POST
"body": { 
     "type": "url_verification",
     "token": "<token>",
     "challenge": "<challenge>"
}
Your Response:
"code": 200
"error": "challenge_failed"
"body": {
 {"Headers":[{"Key":"Content-Type","Value":["text/plain; charset=utf-8"]}]} 
}

I think I'm missing something - is there another way to get the body of the POST-Request? I mean, I can get everything else - except the body ( or it says it is empty).

EDIT4: I tried to read the body with another function I found - without success, returns empty string - but to let you know what I already tried, here it is:

[HttpPost]
public IHttpActionResult ReceivePost()
{
    var bodyStream = new 
    StreamReader(HttpContext.Current.Request.InputStream);
    bodyStream.BaseStream.Seek(0, SeekOrigin.Begin);
    var bodyText = bodyStream.ReadToEnd();
    return Json(bodyText);
}

While trying to solve this I learnt a lot - but this one seems to be so impossible, that I think I will never solve it alone. Thousands of tries with thousands of different functions - I have tried hundreds of parameters and functions in all of WebApi / ASP.NET / MVC / whatever - why is there no BODY? Does it exist? What's his/her name? Where does it live? I really wanna hang out with that parameter if I ever find it, must be hidden at the end of the rainbow under a pot of gold.

3

There are 3 answers

3
Nkosi On

According to the official documentation linked to in the OP you have to format your response depending on the content type you return.

It is possible you are not returning the value (challenge) in one of the expected formats.

Once you receive the event, respond in plaintext with the challenge attribute value. In this example, that might be:

HTTP 200 OK
Content-type: text/plain
3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P

To do the above you would have needed to return your request differently

[HttpPost]
public IHttpActionResult Post([FromBody]Slack_Webhook json) {
    //Please verify that the token value found in the payload 
    //matches your application's configured Slack token.
    if (ModelState.IsValid && json != null && ValidToken(json.token)) {
        var response = Request.CreateResponse(HttpStatusCode.OK, json.challenge, "text/plain");
        return ResponseMessage(response);
    }
    return BadRequest();
}

Documentation also shows

Or even JSON:

HTTP 200 OK
Content-type: application/json
{"challenge":"3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P"}

Which again would have to be formatted a little differently

[HttpPost]
public IHttpActionResult Post([FromBody]Slack_Webhook json) {
    //Please verify that the token value found in the payload 
    //matches your application's configured Slack token.
    if (ModelState.IsValid && json != null && ValidToken(json.token)) {
        var model = new { challenge = json.challenge };
        return Ok(model);
    }
    return BadRequest();
}
0
not-matthias On

Here's how you can access the data:

[HttpPost]  
[Route("something")]
public JsonResult DoSomething()
{
    var token = HttpContext.Request.Form["token"];

    // Is the same as:
    // var token = Request.Form["token"];

    return new JsonResult(token);
}

I suggest using a Request Bin for further debugging.

0
Leon V On

If you can use ASP.NET Core 2, this will do the trick:

public async Task<ActionResult> HandleEvent([FromBody] dynamic data)
    => new ContentResult {Content = data.challenge};