6

I am trying to post a file to an iManage server REST interface (Apache server, java backend?? not sure). Postman works fine, but when I try it from C# .NET CORE 3.1 I get a response like so:

{ "error": { "code": "FileUploadFailure", "message": "File upload failure" } }

Anyone have any ideas I can try? Thanks!

<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />


using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.IO;
using System.Text;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static async Task Main(string[] args)
        {
            Uri url = new Uri("https://iManageServer.net/");
            string filename = @"C:\Temp\temp.txt";
            string token = "E4vt1DzXcnkQTmOUspN6TG6KLR7TClCPPbjyvHsu9TRlKvND9gO4xTPYIEYy0+Lu";
            const string folderId = "MyFolderId";

            using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                using (var content = new MultipartFormDataContent($"{DateTime.Now.Ticks:x}"))
                {
                    var jsonString = JsonConvert.SerializeObject(new { warnings_for_required_and_disabled_fields = true, doc_profile = new { name = Path.GetFileNameWithoutExtension(filename), extension = Path.GetExtension(filename).TrimStart('.'), size = fs.Length } });
                    HttpContent httpContent = new StringContent(jsonString, Encoding.UTF8);
                    httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

                    var c1 = httpContent;
                    content.Add(c1, "\"json\"");

                    var c2 = new StreamContent(fs);
                    c2.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
                    content.Add(c2, "\"file\"");
                    c2.Headers.ContentDisposition.FileName = $"\"{filename}\"";
                    c2.Headers.ContentDisposition.FileNameStar = null;

                    var hch = new HttpClientHandler();
                    hch.ServerCertificateCustomValidationCallback += (sender, cert, chain, error) => true;

                    using (var httpClient = new HttpClient(hch) { BaseAddress = url })
                    {
                        httpClient.DefaultRequestHeaders.Add("User-Agent", "PostmanRuntime/7.26.5");
                        httpClient.DefaultRequestHeaders.Add("Accept", "*/*");
                        httpClient.DefaultRequestHeaders.Add("Connection", "keep-alive");

                        using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, $"folders/{folderId}/documents"))
                        {
                            requestMessage.Headers.Add("X-Auth-Token", token);

                            requestMessage.Content = content;
                            var response = await httpClient.SendAsync(requestMessage);

                            string jsonResponse = await response.Content.ReadAsStringAsync();
                            if (response.IsSuccessStatusCode)
                            {
                                //never hits
                            }
                            else
                            {
                                System.Diagnostics.Debug.WriteLine(jsonResponse);

                                //{
                                //  "error": {
                                //    "code": "FileUploadFailure", 
                                //    "message": "File upload failure"
                                //  }
                                //}
                            }
                        }
                    }
                }
            }
        }
    }
}

Postman works fine. Here is what the Wireshark trace looks like for both:

Postman is First then the C# result: Postman

C#

Corey
  • 226
  • 1
  • 13
  • Why do you set "PostmanRuntime" as UserAgent in your C# code? – Fildor Sep 28 '20 at 13:07
  • Yes, i was desperate and added that to the c# class as well. I was trying to make it look exact as much as possible – Corey Sep 28 '20 at 13:08
  • Perhaps unrelated, but `using (var httpClient = new HttpClient(hch)` => [You are using HttpClient wrong](https://visualstudiomagazine.com/blogs/tool-tracker/2019/09/using-http.aspx) – Fildor Sep 28 '20 at 13:09
  • This is purely a working example. I have it architected quite differently in my real app. The end result is the same however – Corey Sep 28 '20 at 13:12
  • Ah, ok. Hmm, See how the Content-Length differs? 518 for Postman and 361 in C#? Did you try different files or the same? – Fildor Sep 28 '20 at 13:18
  • Same file for both. the file contents does show up in both wireshark output. Actually I did just notice that as you responded. Where could that be coming from? The boundary lengths are a little different in each which could contribute a little bit – Corey Sep 28 '20 at 13:21
  • Not sure if they even explain the full 157 bytes difference... that would mean, it's unrelated. – Fildor Sep 28 '20 at 13:29
  • I see Accept-Encoding header is not present in the C# request that you made but is present from the Postman request. My suggestion would be to exactly mimic the request from Postman. (Assuming that works) Then take headers you don't think you need out one by one after that to get the minimalistic set. Then I would see if you can modify the value of ones that are there (like User-Agent) and see if it works. – Omar Abdel Bari Sep 28 '20 at 14:16
  • Couple of comments re the above: apparently with .NET Core you do not keep a static HttpClient as you used to with Framework. Also, the length issue is because of the difference in the whole http request NOT the length of the file. And the UserAgent value is irrelevant to the server, with iManage at least. – Rob Kent Oct 17 '20 at 16:27
  • Plus I would like to add that it would be nice if whoever wrote that file upload code on the server could produce an error message that explained what the error was. I even got access to the server logs and it was the same message. Why not just surface it at the point the code gave up parsing? – Rob Kent Oct 17 '20 at 16:35

1 Answers1

5

The Boundary on the MultipartFormDataContent was quoted. The iManage API did not like that. I had to add the following code right after the instantiation of the content:

var boundary = $"-------------------------{DateTime.Now.Ticks:x}";
content.Headers.Remove("Content-Type");
content.Headers.TryAddWithoutValidation("Content-Type", $"multipart/form-data; boundary={boundary}");
content.GetType().BaseType.GetField("_boundary", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(content, boundary);
Corey
  • 226
  • 1
  • 13
  • Je vous en prie. C'est remarquable que vous avez trouvé la solution de ce problème. Je pense que cette solution a sauvé mon weekend et, peut-être, mon projet. Maintenant, je vais rechercher pourquoi le serveur d'iManage fait une erreur s'il y a des marques de quotation autour le séparateur. Je me demande si ce soit l'Apache ou bien le code de iManage lui même. – Rob Kent Oct 18 '20 at 05:00
  • There is a general discussion of this issue here, without specific reference to iManage: https://stackoverflow.com/questions/21569770/wrong-content-type-header-generated-using-multipartformdatacontent – Rob Kent Oct 18 '20 at 07:30
  • The line where you set the private field is redundant. I commented it out and your solution still works. – Rob Kent Oct 18 '20 at 07:32
  • I'm using .NET Framework so the private field setter may be required on .Core. – Rob Kent Oct 18 '20 at 17:29
  • 1
    Yep this is .NET Core. I did try without the private setter but it blew up. – Corey Oct 20 '20 at 21:47