Convert curl --form to C# - c#

I'm working with an external api and want to upload file using multipart/form-data. The working solution in curl is: curl --form file=#./fileName.mp4 "http://page.com"
According to https://curl.olsh.me/. The code I'm looking for is:
using (var httpClient = new HttpClient())
{
using (var request = new HttpRequestMessage(new HttpMethod("POST"), "http://page.com/"))
{
var multipartContent = new MultipartFormDataContent();
multipartContent.Add(new ByteArrayContent(File.ReadAllBytes("./fileName.mp4")), "file", Path.GetFileName("./fileName.mp4"));
request.Content = multipartContent;
var response = await httpClient.SendAsync(request);
}
}
However it doesn't work. The headers and the file is different so I'm getting an error.
The code I've written and 'works', but the attachment is not valid, because encoding bytes to string is incorrect.
var webClient = new HttpClient();
webClient.DefaultRequestHeaders.ExpectContinue = true;
string boundary = "------------------------" + DateTime.Now.Ticks.ToString("x");
var fileData = Encoding.ASCII.GetString(System.IO.File.ReadAllBytes(pathToFile));
var package = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n{3}\r\n--{0}--\r\n", boundary, "fileName", "application/octet-stream", fileData);
var content = new StringContent(package);
content.Headers.Remove("Content-Type");
content.Headers.Add("Content-Type", "multipart/form-data; boundary=" + boundary);
content.Headers.Remove("Content-Length");
content.Headers.Add("Content-Length", package.Length.ToString());
await webClient.PostAsync(address, content);
Edit:
The headers from curl request:
POST http://page.com/ HTTP/1.1
Host: page.com
User-Agent: curl/7.55.1
Accept: */*
Connection: Keep-Alive
Content-Length: 1703578
Expect: 100-continue
Content-Type: multipart/form-data; boundary=------------------------1a538744c5619c1e
--------------------------1a538744c5619c1e
Content-Disposition: form-data; name="file"; filename="cst.mp4"
Content-Type: application/octet-stream
The headers from the 1st example code:
POST http://page.com/ HTTP/1.1
Content-Type: multipart/form-data; boundary="735b20d4-4de4-46fb-8293-2c2e996ce180"
Content-Length: 1703552
Host: page.com
--735b20d4-4de4-46fb-8293-2c2e996ce180
Content-Disposition: form-data; name=file; filename=cst.mp4; filename*=utf-8''cst.mp4

I think the file size difference is a red herring here.
A common practice for web servers is to deny requests that don't specify a user agent. Try adding a user agent (or even spoofing the curl agent) as below:
webClient.DefaultRequestHeaders.Add("User-Agent", "curl/7.55.1");

The problem with the first code is that the boundary, file headers and output headers differs. I had to add two Content-Type headers - one to my file and one to the output. I was unable to add the correct headers and remove the wrong one, beacuse I tried to add headers to multipart instead of multipart content.
If you want to add headers to your multipart content you need to do it on HttpContent.
The working solution is:
string boundary = "------------------------" + DateTime.Now.Ticks.ToString("x");
using (var httpClient = new HttpClient())
{
using (var request = new HttpRequestMessage(new HttpMethod("POST"), address))
{
httpClient.DefaultRequestHeaders.Add("User-Agent", userAgentName);
httpClient.DefaultRequestHeaders.Add("Connection", "Keep-Alive");
httpClient.DefaultRequestHeaders.ExpectContinue = true;
var multipartContent = new MultipartFormDataContent(boundary);
multipartContent.Headers.Remove("Content-Type");
multipartContent.Headers.Add("Content-Type", "multipart/form-data; boundary="+boundary);
var bcd = new ByteArrayContent(System.IO.File.ReadAllBytes(path));
bcd.Headers.Clear();
bcd.Headers.Add("Content-Disposition", "form-data; name=\"file\"; filename=\""+fileName+"\"");
bcd.Headers.Add("Content-Type", "application/octet-stream");
multipartContent.Add(bcd, "file", fileName);
request.Content = multipartContent;
var response = await httpClient.SendAsync(request);
}
}
Now the headers from curl and the HttpClient are almost the same(Accept header is missing, but I don't need it).

Related

Unable to make a Async Post call to Here Maps positioning endpoint

When testing the call through Postman and a C# WebRequest it works, but I am unable to do the same using an HttpClient with a PostAsync or PostJsonAsync call.
Error: Unsupported Media Type, although application/json is required and applied.
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(data, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync("https://pos.api.here.com/positioning/v1/locate?app_id={id}&app_code={code}", content);
return response;
StatusCode: 415, ReasonPhrase: 'Unsupported Media Type', Version: 1.1,
Content: System.Net.Http.HttpConnection+HttpConnectionResponseContent,
Headers:{ Date: Fri, 08 Nov 2019 13:38:37 GMT Server: nginx-clojure
Content-Type: application/json Content-Length: 114}
WebRequest
HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
if (!string.IsNullOrEmpty(data))
{
request.ContentType = "application/json";
request.Method = "POST";
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
streamWriter.Write(data);
streamWriter.Flush();
streamWriter.Close();
}
}
using (HttpWebResponse webresponse = request.GetResponse() as HttpWebResponse)
{
using (StreamReader reader = new StreamReader(webresponse.GetResponseStream()))
{
string response = reader.ReadToEnd();
return response;
}
}
There are two differences I see:
You are setting the Accept header in your HttpClient code, where you were not in your WebRequest code. This defines the type of data that you accept. If this API call does not return JSON, then it could be just saying "I have nothing to say to you then". You can try just deleting that whole line.
The Content-Type in your HttpClient code will be application/json; charset=utf-8, whereas you set it to just application/json in your WebRequest code. I don't see why the charset would make it choke, but if changing #1 doesn't work, you can try setting the Content-Type directly and see if it makes any difference:
var content = new StringContent("");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

use HttpClient to set the Content-Type to "application/json" and add object to the body

I'm trying to create the following post using HttpClient, using postman its working correctly but I cant seem to replicate it in code. I need to set the header Content-Type to application/json and have an object in the body of the post.
POST https://mycompanyurl.com/authenticate
HEADERS
Key: Content-Type, Value: application/json
BODY
{
"username": "someusername",
"password": "somepassword"
}
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://companyurl.com");
var serializedObject = JsonConvert.SerializeObject(
new {username = "username", password = "password" });
var request = new HttpRequestMessage(HttpMethod.Post, "authenticate");
request.Content = new StringContent(serializedObject, Encoding.UTF8,"application/json");
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
}
Using the reverse proxy in fidder I can capture the raw call from postman which works, the rest api returns a result as expected:
POST http://127.0.0.1:8888/v1/authenticate HTTP/1.1 Content-Type: application/json;charset=UTF-8 cache-control: no-cache Postman-Token: 4db8f2dd-cbf0-413c-ad5b-20af0543a31d User-Agent: PostmanRuntime/7.6.0 Accept: / Host: 127.0.0.1:8888 accept-encoding: gzip, deflate content-length: 87 Connection: keep-alive
{"username":"username","password":"password"}
My call from HttpClient and using fiddler is below, This does not work, returns 200 but its not working correctly, data is not being returned, I cant see anything differences in the payload that will make the rest api not respond as expected.
POST http://127.0.0.1:8888/v1/authenticate HTTP/1.1 Content-Type: application/json; charset=utf-8 Host: 127.0.0.1:8888 Content-Length: 87 Expect: 100-continue Connection: Keep-Alive
{"username":"username","password":"password"}
The logic below should generate the same working request signature provided in your example (which was posted as an Answer, please edit your Question instead), and therefore should work:
var clientHandler = new HttpClientHandler
{
AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate,
AllowAutoRedirect = false
};
using (var client = new HttpClient(clientHandler, true))
{
client.BaseAddress = new Uri("http://127.0.0.1:8888/v1/");
client.DefaultRequestHeaders.Add("cache-control", "no-cache");
client.DefaultRequestHeaders.Add("Postman-Token", "db8f2dd-cbf0-413c-ad5b-20af0543a31d");
client.DefaultRequestHeaders.Add("User-Agent", "PostmanRuntime/7.6.0");
client.DefaultRequestHeaders.Add("Accept", "*/*");
client.DefaultRequestHeaders.ExpectContinue = false;
var serializedObject = JsonConvert.SerializeObject(
new { username = "username", password = "password" }
);
var request = new HttpRequestMessage(HttpMethod.Post, "authenticate")
{
Content = new StringContent(serializedObject, Encoding.UTF8, "application/json")
};
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
}
It will create the following request:
POST http://127.0.0.1:8888/v1/authenticate HTTP/1.1
cache-control: no-cache
Postman-Token: db8f2dd-cbf0-413c-ad5b-20af0543a31d
User-Agent: PostmanRuntime/7.6.0
Accept: */*
Content-Type: application/json; charset=utf-8
Host: 127.0.0.1:8888
Content-Length: 45
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
{"username":"username","password":"password"}
Hope this helps.

Authentication error using token with c# httpclient

I have a HttpClient that I am using to use a REST API. However I am having trouble setting up the Authorization header. I need to set the header to the token I received from signin method, but when I use it in another method I get an authentication error.
var invoiceObj = new InvoiceUploadObj { dataFile = file, credential = "", domain = "" };
var invoiceObjSerialized = JsonConvert.SerializeObject(invoiceObj);
var data = new StringContent(invoiceObjSerialized, Encoding.UTF8, "application/json");
HttpClient clientDemoWS = new HttpClient();
clientDemoWS.BaseAddress = new Uri("https://www.nameservice.com");
clientDemoWS.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
clientDemoWS.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", String.Format("Bearer {0}", bearer_token));
HttpResponseMessage responseUpload = clientDemoWS.PostAsync("/services/invoice/upload", data).Result;
The status code is 200, but when I deserialize the server response I get an authentication error as error description.
Checking with Fiddler I see that this is the header:
POST /services/invoice/upload HTTP/1.1
Accept: application/json
Authorization: Bearer eyJ0eXAi......
Content-Type: application/json; charset=utf-8
Host: www.nameservice.com
Content-Length: 4623
Expect: 100-continue
Where am I wrong?

Multipart/form-data with additional parameters in .net

What is the best way to create post request like this one in .net ?
I try to use HttpClient and MultipartFormDataContent classes but so far I can only be able to upload image. How can i add additional parameter like api key?
POST /services/upload/ HTTP/1.1
Content-Type: multipart/form-data; boundary=---------------------------7d44e178b0434
Host: api.flickr.com
Content-Length: 35261
-----------------------------7d44e178b0434
Content-Disposition: form-data; name="api_key"
3632623532453245
-----------------------------7d44e178b0434
Content-Disposition: form-data; name="auth_token"
436436545
-----------------------------7d44e178b0434
Content-Disposition: form-data; name="api_sig"
43732850932746573245
-----------------------------7d44e178b0434
Content-Disposition: form-data; name="photo"; filename="C:\test.jpg"
Content-Type: image/jpeg
{RAW JFIF DATA}
-----------------------------7d44e178b0434--
Something along the lines of.. in C# + .NET 4.0
Uri uri = new Uri("api.flickr.com");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "POST";
//goes into the body. not header
NameValueCollection queryString = HttpUtility.ParseQueryString(string.Empty);
queryString["api_key"] = "7d44e178b0434";
...
object returnValue = null;
try
{
using (var writer = new StreamWriter(request.GetRequestStream()))
{
writer.WriteLine(queryString);
}
returnValue = request.GetResponse();
}
catch (Exception ex)
{
..
}
And if you wanted to add something to the header -
request.Headers.Add("HeaderKEY", somevalue);

Wrong Content-Type header generated using MultipartFormDataContent

I have the following code:
private static string boundary = "----CustomBoundary" + DateTime.Now.Ticks.ToString("x");
private static async Task<string> PostTest()
{
string servResp = "";
using (var content = new MultipartFormDataContent(boundary))
{
content.Add(new StringContent("105212"), "case-id");
content.Add(new StringContent("1/14/2014"), "dateFrom");
content.Add(new StringContent("1/15/2014"), "dateTo");
HttpClientHandler handler = new HttpClientHandler();
cookieContainer = new CookieContainer();
handler.CookieContainer = cookieContainer;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "http://somewebsite.com/form");
request.Headers.ExpectContinue = false;
request.Content = content;
httpClient = new HttpClient(handler);
HttpResponseMessage response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
servResp = await response.Content.ReadAsStringAsync();
}
return servResp;
}
When I run it, I see the Content-Type header in Fiddler:
Content-Type: multipart/form-data; boundary="----CustomBoundary8d0f01e6b3b5daf"
Because the boundary value is in quotes, the server ignores the request body. If I remove the quotes and run the request in Fiddler Composer, the request is being processed correctly.
I tried adding the content headers:
//request.Content.Headers.Add("Content-Type", "multipart/form-data; boundary=" + boundary);
//request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("multipart/form-data; boundary=" + boundary);
... but it didn't work, the error messages were: "Cannot add value because header 'Content-Type' does not support multiple values." and "The format of value 'multipart/form-data, boundary=----CustomBoundary8d0f024297b32d5' is invalid.", correspondingly.
How can I add the proper Content-Type header to the request so that the boundary value would not be enclosed in quotes?
Content-Type: multipart/form-data; boundary=----CustomBoundary8d0f01e6b3b5daf
Solved this by removing the header from MultipartFormDataContent and re-adding it back without validation:
content.Headers.Remove("Content-Type");
content.Headers.TryAddWithoutValidation("Content-Type", "multipart/form-data; boundary=" + boundary);
As Darrel Miller pointed out in the RFC:
( which I find here )
https://datatracker.ietf.org/doc/html/rfc2046#section-5.1.1
The quotes are not NORMALLY necessary. But MAY BE necessary if the boundary value has characters illegal in a header value. (E.g., colon, comma, space, ...)
ILLEGAL:
Content-Type: multipart/form-data, boundary=abc:def
LEGAL:
Content-Type: multipart/form-data, boundary="abc:def"
The multipart markers, with the lead and eventually trailing double hyphens, MUST NOT have the quotes:
ILLEGAL:
--"abc:def"
LEGAL:
--abc:def

Categories