FormData in post to API

j3k01

Member
Joined
Sep 6, 2023
Messages
12
Programming Experience
1-3
Hi, I'm struggling with post to api on endpoint that requires formdata content-type. I dont have idea why json in postman works, but in my code not. I'll be glad for any type of help :)


post to api:
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json.Linq;
using System.Text;
using System.Net;
using Newtonsoft.Json;
using mesaCon;

namespace MesaConnection
{
    class Program
    {
        private const string MesaApiUrl = "url";
        private const string MesaApiUrlPost = "url2";

        static async Task Main(string[] args)
        {
            try
            {
                var authToken = await GetAuthToken();

                byte[] Blob64byte = Encoding.ASCII.GetBytes("qwerty");

                var requestData = new Root
                {
                    Metadata = new Metadata
                    {
                        Data = new Data
                        {
                            Name = "Najnowsza27",
                            Category = "5000025",
                            MainLang = "6",
                            UploadFile = new UploadFile
                            {
                                FileName = "Mesa n3.pdf",
                                Description = "Mesa n3",
                                Blob64 = Blob64byte
                            },
                            AREQ = "2",
                            SendNotification = "2",
                            S01_1 = "3000033",
                            S01_3 = "3000263",
                            Ambito = new List<string> { "3000756", "3000744" }
                        }
                    },
                    DeleteFile = new List<object>(),
                    Files = new List<object>()
                };

                string jsonRequest = JsonConvert.SerializeObject(requestData);


                Console.WriteLine("JSON Request:");
                Console.WriteLine(jsonRequest);

                var postMessage = await ApiPostRequest(authToken, jsonRequest);



                Console.WriteLine("Response from POST Request:");
                Console.WriteLine(postMessage);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception caught: " + ex.Message);
            }
        }


        private static async Task<string> GetAuthToken()
        {
            using (var client = new HttpClient())
            {
                var baseUri = "url";
                var username = "1";
                var password = "2!";
                var clientId = "3";

                var formData = new List<KeyValuePair<string, string>>
                {
                    new KeyValuePair<string, string>("an_login_type_id", "1"),
                    new KeyValuePair<string, string>("client_id", clientId),
                    new KeyValuePair<string, string>("grant_type", "password"),
                    new KeyValuePair<string, string>("password", password),
                    new KeyValuePair<string, string>("username", username),
                };

                var content = new FormUrlEncodedContent(formData);
                var response = await client.PostAsync(baseUri + "/auth/token", content);

                if (response.IsSuccessStatusCode)
                {
                    var responseContent = await response.Content.ReadAsStringAsync();
                    var jwtToken = JObject.Parse(responseContent)["access_token"].ToString();
                    return jwtToken;
                }

                throw new Exception("Nie udało się uzyskać tokena JWT.");
            }
        }

        private static async Task<string> ApiGetRequest(string authToken)
        {
            using (var client = new HttpClient())
            {
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);

                var endpoint = await client.GetAsync(MesaApiUrl);

                if (endpoint.StatusCode == System.Net.HttpStatusCode.Unauthorized)
                {
                    throw new Exception("Brak autoryzacji. Sprawdź swoje uwierzytelnienie.");
                }

                endpoint.EnsureSuccessStatusCode();
                string responseBody = await endpoint.Content.ReadAsStringAsync();
                return responseBody;
            }
        }

        private static async Task<string> ApiPostRequest(string authToken, string jsonRequest)
        {
            using (var client = new HttpClient())
            {
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);

                string boundary = "Test";

                JObject jsonDataObject = JObject.Parse(jsonRequest);

                var content = new MultipartFormDataContent(boundary);

                foreach (var property in jsonDataObject["Metadata"]["Data"].Children())
                {
                    content.Add(new StringContent(property.First.ToString()), property.Path);
                }

                string fileName = jsonDataObject["Metadata"]["Data"]["UploadFile"]["FileName"].ToString();
                byte[] fileData = Convert.FromBase64String(jsonDataObject["Metadata"]["Data"]["UploadFile"]["Blob64"].ToString());
                var fileContent = new ByteArrayContent(fileData);
                fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
                content.Add(fileContent, "file", fileName);

                content.Headers.Remove("Content-Type");
                content.Headers.TryAddWithoutValidation("Content-Type", $"multipart/form-data; boundary={boundary}");

                var response = await client.PostAsync(MesaApiUrlPost, content);

                if (response.StatusCode == HttpStatusCode.Unauthorized)
                {
                    throw new Exception("Brak autoryzacji. Sprawdź swoje uwierzytelnienie.");
                }

                return await response.Content.ReadAsStringAsync();
            }
        }

    }
}
 
Don't distract them too much; they might forget to answer the question I asked :D

Let's get the working request bytes and the broken request bytes, fix the broken request so it works, work out which part of the code generated the broken part of the request, and fix the code

No, sory. I'll answer to your question asap, I just have to get back home :)
 
Sure, this is also quite interesting, because this postman code don't work aswell, returns:
Code:
System.FormatException: „The format of value 'multipart/form-data; boundary=TestoLimite' is invalid.
    System.Net.Http.Headers.MediaTypeHeaderValue.CheckMediaTypeFormat(string, string) w MediaTypeHeaderValue.cs
    System.Net.Http.Headers.MediaTypeHeaderValue.MediaTypeHeaderValue(string) w MediaTypeHeaderValue.cs
    System.Net.Http.StringContent.StringContent(string, System.Text.Encoding, string) w StringContent.cs
    Program.Main(string[]) w Program.cs
    System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() w ExceptionDispatchInfo.cs
    System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task) w TaskAwaiter.cs
    System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task) w TaskAwaiter.cs
    System.Runtime.CompilerServices.TaskAwaiter.GetResult() w TaskAwaiter.cs
    Program.<Main>(string[])

”


PostmanCode:
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "url");
request.Headers.Add("Authorization", "Bearer token");
var content = new StringContent("--TestoLimite\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\"Metadata\":{\"Data\":{\"Name\":\"Najnowsza29\",\"Category\":\"5000025\",\"MainLang\":\"6\",\"AREQ\":\"2\",\"SendNotification\":\"2\",\"S01_1\":\"3000031\",\"S01_3\":\"3000263\",\"Ambito\":[\"3000756\",\"3000744\"]}},\"DeleteFile\":[],\"Files\":[]}\r\n--TestoLimite--", null, "multipart/form-data; boundary=TestoLimite");
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());

The generated code is broken because of:
C#:
new StringContent(..., "multipart/form-data; boundary=TestoLimite");
Looking at the reference source for MediaTypeHeaderValue.CheckMediaTypeFormat(), it looks like the constructor expects to only find "multipart/form-data" as the last parameter.
 
Can you post, in CODE tags so it doesn't turn into a mess, the request being emitted by C# that doesn't work and the postman request that does.

Request bytes, not c# code. Send a hello world text file in each so you don't have to snip a megabyte JPEG

Don't forget to include the request headers
Postman headers:
Content-Type: multipart/form-data; boundary=TestoLimite
Connection: keep-alive
Accept-Encoding : gzip, deflate, br
User-Agent: PostmanRuntime/7.32.3

Example from postman that returns 200:

Postman:
--TestoLimite
Content-Disposition: form-data; name="metadata"

{
  "Metadata": {
    "Data": {
      "Name": "Najnowsza27",
      "Category": "5000025",
      "MainLang": "6",
      "UploadFile": {
        "FileName": "Mesa.pdf",
        "Description": "Mesa",
        "Blob64": "JVB"
      },
      "AREQ": "2",
      "SendNotification": "2",
      "S01_1": "3000033",
      "S01_3": "3000263",
      "Ambito": [
        "3000756",
        "3000744"
      ]
    }
  },
  "DeleteFile": [],
  "Files": []
}
--TestoLimite--


And request from my code:


Request from code:
--TestoLimite
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=metadata
{
  "Metadata": {
    "Data": {
      "Name": "Najnowsza27",
      "Category": "5000025",
      "MainLang": "6",
      "UploadFile": {
        "FileName": "Mesa n3.pdf",
        "Description": "Mesa n3",
        "Blob64": "JVB"
      },
      "AREQ": "2",
      "SendNotification": "2",
      "S01_1": "3000033",
      "S01_3": "3000263",
      "Ambito": [
        "3000756",
        "3000744"
      ]
    }
  },
  "DeleteFile": [],
  "Files": []
}
--TestoLimite--

I hope this is what you asked about
 
The generated code is broken because of:
C#:
new StringContent(..., "multipart/form-data; boundary=TestoLimite");
Looking at the reference source for MediaTypeHeaderValue.CheckMediaTypeFormat(), it looks like the constructor expects to only find "multipart/form-data" as the last parameter.

I deleted boundary=TestoLimite and leave new StringContent(..., "multipart/form-data"); server returns me 400. Tried also new StringContent(..., "boundary=TestoLimite; multipart/form-data"), but error also
 
Well, 400 is better than 500. :)
 
Well, 400 is better than 500. :)

I tried python code from Postman and everything works fine. I'll try to write everything in python and test how it works in this language.
Then the problem may be in the API configuration for C# or sth else?
 
No, no problem with C#. It's every bit as capable at putting bytes down a socket as any other modern language is.. you're just putting the wrong bytes down the socket, that's all

The flurl library I usually recommend for these kind of works has methods for making your multi part form life easier - How can I upload a file and form data using Flurl?
 
Last edited:
No, no problem with C#. It's every bit as capable at putting bytes down a socket as any other modern language is.. you're just putting the wrong bytes down the socket, that's all

Okay, I'll try. I was just curious why postman-generated code in python works but in c# not. After testing this library I get you know if it works. Anyway, thank you for help guys.
 
Report it as a bug to postman team; postman generated c# code that doesn't work. Probably they'll say it's just a helper for you, rather than intended to do all your work for you, and some user debugging efforts may be required. Or it may be that the postman code would work against a different API so maybe not fully postman team's fault

All in, it's definitely not C#'s fault that postman's code doesn't work with this particular api
 
Back
Top Bottom