I am in a situation where I know I can connect to an endpoint (using Postman chrome app) but I get an authentication error when I attempt it through HttpClient executing as WebJob on Azure.
public string ScanEndPoint()
{
string result;
using (var client = new HttpClient())
{
var requestContent = new MultipartFormDataContent();
var url = $"{Host}/{Path}";
requestContent.Add(new StringContent("*"), Version);
requestContent.Add(new StringContent("***"), Reference);
requestContent.Add(new StringContent("********"), Password);
var response = client.PostAsync(url, requestContent).Result;
result = response.Content.ReadAsStringAsync().Result;
}
return result;
}
The MultipartFormData is because I have to post the credentials in the body and not as headers. Clicking on the code link in Postman shows:
POST /*************.php HTTP/1.1
Host: *****-*******.****.******
Cache-Control: no-cache
Postman-Token: b574e803-1873-d7dd-ff10-bfc509991342
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="*"
**
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="***"
****
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="*********"
********************************
------WebKitFormBoundary7MA4YWxkTrZu0gW--
What steps do I need to take to replicate that postman request so that it works in code?
This is what we had to do to get it working:
using (var client = new HttpClient(handler: clientHandler, disposeHandler: true))
{
client.BaseAddress = new Uri(Host);
var url = $"{Path}";
var parameters = new Dictionary<string, string> {
{ "V", Version },
{ "ref", Reference },
{ "password", Password }
};
var encodedContent = new FormUrlEncodedContent(parameters);
var response = client.PostAsync(url, encodedContent).Result;
result = response.Content.ReadAsStringAsync().Result;
}
return result;
The handler sets up a proxy for local debug which should be irrelevant on Azure but I can remove it if that proves to be wrong.
Most of the posts I read about this suggested the same approach as #Jayendran pointed to. Any ideas on what the differences might be?
Note: we also had to re-arrange the Host and path so that the Host ended with a "/"
Related
I am new to the C# world, and can't for the life of me figure out how to get around this error. I am trying to make a simple get request using a platform API key. I have built out the API connection in Google App Script on the same laptop, and it works fine, but when trying to build out the same API in C#, it is returning:
{StatusCode: 403, ReasonPhrase: 'Forbidden', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
Date: Sun, 13 Mar 2022 02:41:29 GMT
Transfer-Encoding: chunked
Connection: close
CF-Chl-Bypass: 1
Permissions-Policy: accelerometer=(),autoplay=(),camera=(),clipboard-read=(),clipboard-write=(),fullscreen=(),geolocation=(),gyroscope=(),hid=(),interest-cohort=(),magnetometer=(),microphone=(),payment=(),publickey-credentials-get=(),screen-wake-lock=(),serial=(),sync-xhr=(),usb=()
Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
X-Frame-Options: SAMEORIGIN
Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
Strict-Transport-Security: max-age=2592000
Server: cloudflare
CF-RAY: 6eb1692f8bd776c3-LHR
Content-Type: text/html; charset=utf-8
Expires: Thu, 01 Jan 1970 00:00:01 GMT
}}
The API documentation says:
"To authenticate against the API, include your API key in the 'Authorization' header, prefixed with 'Key ', in every request. Example: 'Authorization: Key yourapikey'"
And so, I have tried adding this to
a) the HttpClient via HttpClient.DefaultRequestHeaders.Authorization
b) the HttpClient via HttpClient.DefaultHeaders.Add
c) the HttpRequestMessage via HttpRequestMessage.Headers.Add
In each instance, the request URI looks good, as well as the headers, but still returning 403.
My current structure is:
// services
builder.Services.AddHttpClient("myplatform", c =>
{
c.BaseAddress = new Uri("https://seller-api.myplatform.com/v2/");
c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
});
// controller
// constructor uses IHttpClientFactory
this._httpClient = clientFactory.CreateClient("myplatform");
// service
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Key", platformKey);
string endPoint = "offers" + CreateQueryString(pageNumber, pageSize);
// example endPoint: offers?page_number=1&page_size=100
var requestMsg = new HttpRequestMessage()
{
Method = HttpMethod.Get,
RequestUri = new Uri(httpClient.BaseAddress + endPoint)
};
try
{
var result = await httpClient.SendAsync(requestMsg);
}
catch (Exception ex)
{
}
Again, the content of the call works when calling through GoogleAppScript. What am I doing wrong in C#, and how can I correct this?
Not sure if this is all the information needed - let me know otherwise! Important to note, the target framework is .NET 6.0.
EDIT
As suggested by a comment around duplicate clients, I have removed the client factory. I am now creating a new HttpClient in the controller constructor, and passing this client to my service to do the GET request.
this._httpClient = new HttpClient();
Again, the client and the request message look well formed at time of request but still returning 403 error. Is there an issue with my VS22 client, or web client etc.?
Also, the call I am making successfully via Google AppScript is using UrlFetchApp. Not sure what is the issue here with the C# side..
EDIT2
Adding current GAS code for reference:
var url = 'https://seller-api.platform.com/v2';
var end_point = '/offers?';
var header = {
'Authorization': api_key
}
var params = {
'method': 'GET',
'headers': header
}
// call API
var page_query = 'page_number=' + page + '&page_size=' + maxItemsPerPage;
var full_url = url + end_point + page_query;
var response = UrlFetchApp.fetch(full_url, params);
The HTTP 403 Forbidden response status code indicates that the server understands the request but refuses to authorize it.
There are two ways add request headers when using HttpClient:
Add headers for all requests using HttpClient.DefaultRequestHeaders.
HttpClient = new HttpClient();
HttpClient.DefaultRequestHeaders.Add("Key", platformKey);
var response = await HttpClient.GetAsync(GetRandomNumberUrl);
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync();
Add headers per request using HttpRequestMessage.Headers.
HttpClient = new HttpClient();
using (var request = new HttpRequestMessage(HttpMethod.Get, randomNumberUrl))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", platformKey);
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync();
}
Your problem is here in this line:
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Key", platformKey);
your _httpClient and Authorization httpClient is not same instance.
Try this way:
HttpClient HttpClient = new HttpClient();
string url = "https://seller-api.platform.com/v2";
string end_point = "/offers?";
string api_key = "key here";
string page_query = "page_number=" + 10 + "&page_size=" + 20;
string full_url = url + end_point + page_query;
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
{
request.Headers.Add("Authorization", api_key);
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var m = await response.Content.ReadAsStringAsync();
}
Solution 2:
Try calling it like a browser :
HttpClient httpClient = new HttpClient();
string url = "https://gatewayt.whatever.com/chkt/request/request.php";
string end_point = "/offers?";
string api_key = "key here";
string page_query = "page_number=" + 10 + "&page_size=" + 20;
string full_url = url + end_point + page_query;
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "text/html,application/xhtml+xml,application/xml");
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Encoding", "gzip, deflate");
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:19.0) Gecko/20100101 Firefox/19.0");
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Charset", "ISO-8859-1");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Key", api_key);
var response = await httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync();
source: Request like browser Link1 Link2 Link3
I am using C# 4.7.2 and am using a Console and not WinForms. I am trying to get an input of a user's image path then send a post request to a ShareX Image Hoster API.
How can I keep it plain and simple using void? EX:
public static void UploadImg(string ImagePath, string UploadAPI, string UploadKey) { }
ShareX Config:
{
"Version": "13.2.1",
"Name": "host",
"DestinationType": "ImageUploader",
"RequestMethod": "POST",
"RequestURL": "https://ADDRESS/upload",
"Headers": {
"token": "name_RANDOMSTRING",
"json": "true"
},
"Body": "MultipartFormData",
"Arguments": {
"imgfile": null
},
"FileFormName": "imgfile",
"URL": "$json:url$"
}
Capturing traffic with Fiddler I can use these headers:
POST https://IMAGEHOST/api/upload HTTP/1.1
token: SPECIALKEY
json: true
Content-Type: multipart/form-data; boundary=--------------------8d8ee229124e662
User-Agent: ShareX/13.4.0
Host: IMGHOSTER
Content-Length: 7518
Connection: Keep-Alive
----------------------8d8ee229124e662
Content-Disposition: form-data; name="imgfile"; filename="851TO25E8.png"
Content-Type: image/png
Then the rest after these headers is unknown ascii bytes nonsense.
The response is:
{"url":"https://FinalShortenedURL/"}
UPDATE - .Net 4.7.2
public static async Task UploadImg(string ImagePath, string UploadAPI, string UploadKey)
{
using (var client = new System.Net.Http.HttpClient())
{
// TODO: implement auth - this example works for bearer tokens:
// client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", UploadKey);
// Or you could use simple headers:
client.DefaultRequestHeaders.Add("token", UploadKey);
// inject the JSON header... and others if you need them
client.DefaultRequestHeaders.Add("json", "true");
var uri = new System.Uri(UploadAPI);
// Load the file:
var file = new System.IO.FileInfo(ImagePath);
if (!file.Exists)
throw new ArgumentException($"Unable to access file at: {ImagePath}", nameof(ImagePath));
using (var stream = file.OpenRead())
{
var multipartContent = new System.Net.Http.MultipartFormDataContent();
multipartContent.Add(
new System.Net.Http.StreamContent(stream),
"imgfile", // this is the name of FormData field
file.Name);
System.Net.Http.HttpRequestMessage request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, uri);
request.Content = multipartContent;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode(); // this throws an exception on non HTTP success codes
}
}
}
The following is the original posted solution for .Net Core to upload using multi-part:
public static async Task UploadImg(string ImagePath, string UploadAPI, string UploadKey)
{
using (var client = new Windows.Web.Http.HttpClient())
{
// TODO: implement auth - this example works for bearer tokens:
client.DefaultRequestHeaders.Authorization = new Windows.Web.Http.Headers.HttpCredentialsHeaderValue("Bearer", UploadKey);
// Or you could use simple headers:
client.DefaultRequestHeaders.Add("token", UploadKey);
// Load the file:
StorageFile file = await StorageFile.GetFileFromPathAsync(ImagePath);
var uri = new System.Uri(UploadAPI);
HttpMultipartFormDataContent multipartContent = new HttpMultipartFormDataContent();
multipartContent.Add(
new HttpStreamContent(stream),
"imgfile", // this is the name of FormData field
file.Name);
Windows.Web.Http.HttpRequestMessage request = new Windows.Web.Http.HttpRequestMessage(Windows.Web.Http.HttpMethod.Post, uri);
request.Content = multipartContent;
var response = await client.SendRequestAsync(request);
response.EnsureSuccessStatusCode(); // this throws an exception on non HTTP success codes
}
}
The process is similar in .Net framework, except that you can use System.IO for file operations.
More Information
Having a quick snoop around SO will find many similar questions with similar solutions or pratical advice. This answer is specifically provided to work with OPs ShareX configuration, but if you need further information have a read over these articles:
Http MultipartFormDataContent
C# HttpClient 4.5 multipart/form-data upload
c# MultipartFormDataContent Add methods (how to properly add a file)
I develop a Xamarin.Android app and I need to update an audio recorded file.
For this, my client has provided me an API like this:
URL: /api/recordings/
Method: POST
Data format: multipart/form-data
Fields: some text fields and the audio file to send
When I test it on Postman, it works fine. The generate code snippets are:
POST /api/recordings/ HTTP/1.1
Host: X.X.X.X
Cache-Control: no-cache
Postman-Token: xxxxx-xxxx-xxxx-xxxx-xxxxx
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="param1"
param1 name
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="param2"
param2 name
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="audio_file"; filename="xxx.wav"
Content-Type: audio/wav
But when I try from the app, I get an 400 - Bad Request error.
First attempt:
I first tried with the following basic code:
using (HttpClient client = new HttpClient())
{
try
{
MultipartFormDataContent formData = new MultipartFormDataContent();
formData.Add(new StringContent("param1 name"), "param1");
formData.Add(new StringContent("param2 name"), "param2");
var response = await client.PostAsync(url, formData);
Logger.Log("Response : " + responseObj.ToString());
}
catch (Exception ex)
{
Logger.Log("Exception : " + ex.ToString());
}
}
But there is a 400 - Bad Request error:
Response : StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Server: nginx/1.15.0
Date: Mon, 25 Jun 2018 23:25:41 GMT
Connection: keep-alive
Vary: Cookie
X-Frame-Options: SAMEORIGIN
Content-Type: application/json
Content-Length: 85
Allow: POST, OPTIONS
}
Second attempt:
Then I've tried the following snippet that I found there:
string servResp = "";
string boundary = "----CustomBoundary" + DateTime.Now.Ticks.ToString("x");
using (var content = new MultipartFormDataContent(boundary))
{
content.Headers.Remove("Content-Type");
content.Headers.TryAddWithoutValidation("Content-Type", "multipart/form-data; boundary=" + boundary);
content.Add(new StringContent("param1 name"), "param1");
content.Add(new StringContent("param2 name"), "param2");
HttpClientHandler handler = new HttpClientHandler();
var cookieContainer = new CookieContainer();
handler.CookieContainer = cookieContainer;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url);
request.Headers.ExpectContinue = false;
request.Content = content;
try
{
var httpClient = new HttpClient(handler);
HttpResponseMessage response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
servResp = await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
Logger.Log("PostTest() - Exception : " + ex.ToString());
}
}
return servResp;
But there is still a 400 - Bad Request error:
Exception : System.Net.Http.HttpRequestException: 400 (Bad Request)
at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode () [0x0002a] in <996a681f30a44cd685a4da54e11956e2>:0
=> Would you have any explanation? How could I fix this?
Edit : the audio file
I haven't specified the code of the audio file, I use a ByteArrayContent in both cases:
byte[] b = System.IO.File.ReadAllBytes(fullFilePath);
formData.Add(new ByteArrayContent(b, 0, b.Length), "audio_file", "audio.wav");
Conclusion:
I was able to resolve my problem by creating a console project that using the same service: this allows me to find and fix my error.
For an other issue, I followed the Fiddler instructions to parameter proxy on the device. However, it seems that the proxy doesn't work for apps but only for websites in the latest versions of Android...
Do you know an alternative to Fiddler that could work on Android devices?
I am creating a restsharp request in order to trigger a batch direct send push request off to Azure notification hub.
I am receiving a 400 Bad Request response, with the message; Could not find 'notifications' part in the multipart content supplied.
The request looks like such;
const string multipartContentType = "multipart/form-data; boundary=\"simple-boundary\"";
const string authSignature = "myvalidauthsignature";
const string url = "mynotificanhuburl";
const string message = "Some message";
var restClient = new RestClient
{
BaseUrl = new Uri(url),
Proxy = new WebProxy("127.0.0.1", 8888),
};
var request = new RestSharp.RestRequest(Method.POST)
{
RequestFormat = DataFormat.Json,
AlwaysMultipartFormData = true
};
request.AddHeader("Content-Type", multipartContentType);
request.AddHeader("Authorization", authSignature);
request.AddHeader("ServiceBusNotification-Format", "gcm");
request.AddParameter("notification", JsonConvert.SerializeObject(new { data = new { message } }), ParameterType.GetOrPost);
request.AddParameter("devices", JsonConvert.SerializeObject(new List<string> { "123", "456" }), ParameterType.GetOrPost);
var response = restClient.Execute(request);
I can see the raw request via Fiddler;
POST https://xxxxx.servicebus.windows.net/xxx/messages/$batch?direct&api-version=2015-04 HTTP/1.1
Authorization: [redacted]
ServiceBusNotification-Format: gcm
Accept: application/json, application/xml, text/json, text/x-json, text/javascript, text/xml
User-Agent: RestSharp/105.2.3.0
Content-Type: multipart/form-data; boundary=-----------------------------28947758029299
Host: [redacted]
Content-Length: 412
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
-------------------------------28947758029299
Content-Disposition: form-data; name="notification"
{"data":{"message":"Some message"}}
-------------------------------28947758029299
Content-Disposition: form-data; name="devices"
["123","456"]
-------------------------------28947758029299--
Which looks about right. If I copy this into postman with the headers etc, I can see the same error response. HOWEVER in postman when I remove the quote marks around the parameter names, it works and returns a 201 Created response.
So this works....
Content-Disposition: form-data; name=notification
This doesn't
Content-Disposition: form-data; name="notification"
Which seems really peculiar. As we are using restsharp however I don't think I have any direct control over the raw output for the request body. I am wondering;
Is there a restsharp setting to manage these quote, perhaps a formatting setting
Why would the Azure endpoint reject a parameter name with quotes
It is possible that the issue is elsewhere and this is a red herring, but this does seem to be responsible.
Appreciate any help...
According our documentation, request should look like this:
POST https://{Namespace}.servicebus.windows.net/{Notification Hub}/messages/$batch?direct&api-version=2015-08 HTTP/1.1
Content-Type: multipart/mixed; boundary="simple-boundary"
Authorization: SharedAccessSignature sr=https%3a%2f%2f{Namespace}.servicebus.windows.net%2f{Notification Hub}%2fmessages%2f%24batch%3fdirect%26api-version%3d2015-08&sig={Signature}&skn=DefaultFullSharedAccessSignature
ServiceBusNotification-Format: gcm
Host: {Namespace}.servicebus.windows.net
Content-Length: 431
Expect: 100-continue
Connection: Keep-Alive
--simple-boundary
Content-Type: application/json
Content-Disposition: inline; name=notification
{"data":{"message":"Hello via Direct Batch Send!!!"}}
--simple-boundary
Content-Type: application/json
Content-Disposition: inline; name=devices
["Device Token1","Device Token2","Device Token3"]
--simple-boundary--
So, the name parameter's value is not quoted (name=devices). I've not found any RFC which would explicitly specify requirements regarding the situation. However, in examples inside of RFCs a values appear quoted. And because of that I'm going to fix the service to support both options. Fix should come with next deployment in a week or so.
I was plagued by this for a few days and was diligently searching for a solution with RestSharp and was unable to find one as it always default the content type to "multipart/form-data". I know the OP was looking for a way to do this with RestSharp but I don't believe there is currently.My solution comes from a few different posts over a few days so I apologize for not linking to them. Below is a sample Function to perform a multipart/related POST with json body and base64 pdf string as the file.
public static void PostBase64PdfHttpClient(string recordID, string docName, string pdfB64)
{
string url = $"baseURL";
HttpClient client = new HttpClient();
var myBoundary = "------------ThIs_Is_tHe_bouNdaRY_";
string auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"UN:PW"));
client.DefaultRequestHeaders.Add("Authorization", $"Basic {auth}");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"{url}/api-endpoint");
request.Headers.Date = DateTime.UtcNow;
request.Headers.Add("Accept", "application/json; charset=utf-8");
MultipartContent mpContent = new MultipartContent("related", myBoundary);
mpContent.Headers.TryAddWithoutValidation("Content-Type", $"multipart/related; boundary={myBoundary}");
dynamic jObj = new Newtonsoft.Json.Linq.JObject(); jObj.ID = recordID; jObj.Name = docName;
var jsonSerializeSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
var json = JsonConvert.SerializeObject(jObj, jsonSerializeSettings);
mpContent.Add(new StringContent(json, Encoding.UTF8, "application/json"));
mpContent.Add(new StringContent(pdfB64, Encoding.UTF8, "application/pdf"));
request.Content = mpContent;
HttpResponseMessage response = client.SendAsync(request).Result;
}
I am trying to call Docusign REST API as is outlined in the "Step 3: Send signature request on behalf of User 2" Section in this link. I get the following error below. What is the boundary supposed set to? How do I correctly set it?
{
"errorCode": "INVALID_MULTI_PART_REQUEST",
"message": "An error was found while parsing the multipart request. Boundary terminator '--BOUNDARY; charset=utf-8--' was not found in the request."
}
public static string HttpRequest(string url, List<CELPHttpHeader> headerList, EnvelopeDefinition envelopeDefination)
{
string responseString = string.Empty;
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("accept", "application/json");
MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("multipart/form-data");
NameValueHeaderValue item = new NameValueHeaderValue("boundary", "BOUNDARY");
mediaType.Parameters.Add(item);
JsonMediaTypeFormatter formatter = new JsonMediaTypeFormatter();
HttpRequestMessage requestMessage = new HttpRequestMessage();
requestMessage.Method = HttpMethod.Post;
requestMessage.Content = new ObjectContent<EnvelopeDefinition>(envelopeDefination, formatter, mediaType);
foreach (CELPHttpHeader header in headerList)
{
client.DefaultRequestHeaders.Add(header.Name, header.Value);
}
try
{
Task<HttpResponseMessage> webTaskResult = client.PostAsync(url, requestMessage.Content);
webTaskResult.Wait();
HttpResponseMessage response = webTaskResult.Result;
}
catch (Exception ex)
{
}
return (responseString);
}
A snippet of what the API request should look like is below:
--BOUNDARY
Content-Type: application/json
Content-Disposition: form-data
{
<JSON request here>
}
--BOUNDARY
Content-Type: application/pdf
Content-Disposition: file; filename="test1.pdf"; documentid=1
Content-Transfer-Encoding: base64
JVBERi0xLjUNJeLjz9MNCjMwMDIgMCBvYmoNPDwvTGluZWFyaXplZCAxL0wgMTM1
<snipped>
V1sxIDMgMF0+PnN0cmVhbQ0KaN5iYhRZU8PEwCDsBCQY1wMJpicAAQYAHeIDMQ0K
ZW5kc3RyZWFtDWVuZG9iag1zdGFydHhyZWYNCjEzNjA0NjUNCiUlRU9GDQo=
--BOUNDARY--