4

when I try to upload any kidn of file through my SlackApp(via c# using HttpClient), I allways get the following response:

{"ok":false,"error":"no_file_data"}

I checked my ByteArray (I stream the file to an array and then try to upload) and wrote my data back into a .txt and .jpg - I tried both types of data. When i write them back they are exact copies from the original, so I guess my streaming and writing to an ByteArrayworks fine. But something is off with my upload.

I'll show you my code: The Client and the method to upload:

using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Net.Http.Headers;


namespace SlackApp
{
    public class SlackClient
    {
        private readonly Uri _webhookUrl;
        private readonly HttpClient _httpClient = new HttpClient {};

        public SlackClient(Uri webhookUrl)
        {
            _webhookUrl = webhookUrl;
        }

        public async Task<HttpResponseMessage> UploadFile(byte[] file)
        {
            var requestContent = new MultipartFormDataContent();
            var fileContent = new ByteArrayContent(file);
            fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data");
            requestContent.Add(fileContent, "slack", "slack.txt");

            var response = await _httpClient.PostAsync(_webhookUrl, requestContent);
            return response;
        }
    }
}

the creation of the bytearray:

public class PostFile
{
    String path = @"C:\Users\f.held\Desktop\Held-Docs\dagged.jpg";

    public byte[] ReadImageFile()
    {            
        FileInfo fileInfo = new FileInfo(path);
        long imageFileLength = fileInfo.Length;
        FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
        BinaryReader br = new BinaryReader(fs);
        byte[] imageData = br.ReadBytes((int)imageFileLength);
        return imageData;
    }
}

the Main:

using System;
using System.Net.Http;
using System.Threading.Tasks;


namespace SlackApp
{
    class TestArea
    {
        public static void Main(string[] args)
        {    
            Task.WaitAll(IntegrateWithSlackAsync());
        }

        private static async Task IntegrateWithSlackAsync()
        {
            var webhookUrl = new Uri("https://slack.com/api/files.upload?token=xoxp-hereStandsMyToken&channel=MyChannel");  
            var slackClient = new SlackClient(webhookUrl);
            PostMessage PM = new PostMessage();
            PostFile PF = new PostFile();
            var testFile = PF.ReadImageFile();

            while (true)
            {
                var message = Console.ReadLine(); 
                FormUrlEncodedContent payload = PM.Content(message, "");
                var response = await slackClient.SendMessageAsync(payload);

                string content = await response.Content.ReadAsStringAsync();
                Console.WriteLine(content); //I build these two lines in here so I got the response from the method, and this is where it says "no_file_data"

                var isValid = response.IsSuccessStatusCode ? "valid" : "invalid";
                Console.WriteLine($"Received {isValid} response.");
                Console.WriteLine(response); //this puts out a "valid" response - oddly enough
            }
        }
    }
    }

Does anybody have an idea what is wrong here? Why isn't it taking the data?

Erik Kalkoken
  • 30,467
  • 8
  • 79
  • 114
Fabian Held
  • 373
  • 1
  • 6
  • 17

2 Answers2

1

You have two bugs in your code:

  • main(): The parameter to specify the channels is called channels, not channel
  • UploadFile(): When you add your file content to the multipart you need to include the correct API parameter for the file which is file, not slack. And also want to include a reasonable filename (instead of slack.txt).

Additional comments

  • UploadFile(): Its wrong to set the content type to multipart/form-data. The correct type for that content would be image/jpeg. However, the correct type seams to be detected automatically, so just remove the line.
  • main(): The Slack API will always return OK (http 200, unless there is a network problem), so you want to also look on the ok and error properties of the JSON response instead.

Here is an update version of your code. I changed your main() method to include a call to `UploadFile()?.

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;


namespace SlackApp
{
    public class PostFile
    {
        string path = @"C:\Users\Stratios_down.jpg";

        public byte[] ReadImageFile()
        {
            FileInfo fileInfo = new FileInfo(path);
            long imageFileLength = fileInfo.Length;
            FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
            BinaryReader br = new BinaryReader(fs);
            byte[] imageData = br.ReadBytes((int)imageFileLength);
            return imageData;
        }
    }

    public class SlackClient
    {
        private readonly Uri _webhookUrl;
        private readonly HttpClient _httpClient = new HttpClient { };

        public SlackClient(Uri webhookUrl)
        {
            _webhookUrl = webhookUrl;
        }

        public async Task<HttpResponseMessage> UploadFile(byte[] file)
        {
            var requestContent = new MultipartFormDataContent();
            var fileContent = new ByteArrayContent(file);            
            requestContent.Add(fileContent, "file", "stratios.jpg");

            var response = await _httpClient.PostAsync(_webhookUrl, requestContent);
            return response;
        }
    }

    class TestArea
    {
        public static void Main(string[] args)
        {
            Task.WaitAll(IntegrateWithSlackAsync());
        }

        private static async Task IntegrateWithSlackAsync()
        {
            var webhookUrl = new Uri(
                "https://slack.com/api/files.upload?token=xoxp-MY-TOKEN&channels=test"
            );
            var slackClient = new SlackClient(webhookUrl);

            PostFile PF = new PostFile();
            var testFile = PF.ReadImageFile();

            var response = await slackClient.UploadFile(testFile);

            string content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(content);
            Console.ReadKey();

        }
    }
}

In addition I would have a couple of suggestions to improve your code.

  • Instead of including the additional API parameters in the URL, I would send them in the POST request as recommended by the API documentation.
  • Including the file as FileStream instead of loading it yourself into a ByteArray is the better approach and recommended for larger files.
  • Not sure why you need an infinite loop in your main. Those are really bad and should be avoided.

Please also take also a look at my new async example for uploading a file to Slack where I applied those two ideas.

Erik Kalkoken
  • 30,467
  • 8
  • 79
  • 114
  • Good morning, as allways very nice answer! Okay, so you say its "channels" with an 's' - but why is it "channel" on the SlackAPI-website and "channel" does work with my standard messages? I had, in the beginning, the parameter "file" instead of "slack" - made no difference! Also I thought this just is a name, not a real parameter... But I'm still wrong about a lot of things :) I also had, in the beginning, no contenttype defined and then I tried `image/jpeg` instead of `multipart/form data` - all of this made no difference, it didn't work. I'll take a look at your approach now. Thanks – Fabian Held Nov 14 '18 at 07:16
  • 2nd answer: I don't want to include the API paramters in the URL directly, but I don't know how to put them in the request properly. probably with `request.Add`? I'll try the FileStream-approach instead of bytearray. And with the infinite loop... I can not even remember where that came from :D Anyways, I'll update you here on how things are going. Thank you so far! I appreciate the help, tipps and insights. BTW, "slack.txt" really is the name of a txt-file I created - it holds a lot information I gathered reagrding my code and the SlackApp. So, I might have bad naming habbits... – Fabian Held Nov 14 '18 at 07:26
  • 1
    I got it working! It was the combination of `file` instead of random name because, well it really is a parameter. And it had to be `channelS` with an 's' - what the hell? I swear it says "channel" without 's' on Slack's website... how should anyone know that? Thank you for your continuing help and efforts Erik. – Fabian Held Nov 14 '18 at 07:58
  • Happy to help. yes, you can add the other API parameters with `request.Add` e.g. `requestContent.Add(new StringContent("xoxp-TOKEN"), "token");` – Erik Kalkoken Nov 14 '18 at 14:33
  • Hey, I just wanted to give some more feedback. I had a great day, I got it all working with HttpClient, without using JSON :) , I cleaned up my code, made strict, clean classes and am working on making it all accessible now :) Thanks again, man! You were of great help! One question though. Why do you use JSON? I work with `Dicitonary<>` because I couldn't handle JSON correctly. Is there a benefit to JSON? – Fabian Held Nov 14 '18 at 15:16
  • All API methods responses are in JSON. Also some methods require parameter input in JSON (e.g. `attachments` for messages). Since C# can not work with JSON directly you want to convert all that to some C# data structure, which can be `Dictionary<>` in some cases. Otherwise: How do you access the response attributes, e.g. `error` in `{"ok":false,"error":"no_file_data"}` ?? – Erik Kalkoken Nov 14 '18 at 15:34
  • Ah, okay... well I use this: ` var response = await slackClient.UploadFileAsync(PM.SendFile()); string content = await response.Content.ReadAsStringAsync(); // with this line! it puts it out just like JSON, but of course doesn't put it in a nice object which one could deserialize... so yeah, I might have to get into JSON anyways :) Console.WriteLine(content);` – Fabian Held Nov 14 '18 at 15:58
  • yes, that will work great for testing. But if you want to react programmatically to the responses (e.g. log an error message based on the response) you will need to convert it to a c# data structure. Its not that hard though. Just add the Newtonsoft JSON library. – Erik Kalkoken Nov 14 '18 at 16:15
  • 1
    in my case the file was correct, but I had not supplied a filename so got no_file_data. Awful error message from slack, error should be no_file_name. – TimCO Apr 25 '21 at 15:57
0

I was running into the no_file_data error as well. I found out you the file needs to exist AND it needs actual content inside. Make sure to do a size check or content length check in addition to the file exists check before uploading

RasTheDestroyer
  • 1,756
  • 16
  • 22