Generating Multipart form data body from text for RestSharp request - c#

I'm trying to get a basic login feature working using RestSharp. I have the login user and password as text that I need to convert somehow since the server accepts requests as multipart/form-data and my initial attempt was this:
RestRequest request = new RestRequest(resource, Method.POST);
request.AddParameter("login", config.Login, ParameterType.RequestBody);
request.AddParameter("pass", config.Password, ParameterType.RequestBody);
Only to find out I cannot add two params for request body, where it discards all but the first. So my next attempt was to build the requestBody:
MultipartFormDataContent formData = new MultipartFormDataContent();
formData.Add(new StringContent(config.Login), "login");
formData.Add(new StringContent(config.Password), "pass");
request.AddBody(formData);
This also didnt work and just returned the closed element </MultipartFormDataContent>
Last I tried to just pass them as post params rather than the body, which it seems to have just ignored considering it expects a form I guess:
request.AddParameter("login", JsonConvert.SerializeObject(config.Login), ParameterType.GetOrPost);
request.AddParameter("pass", JsonConvert.SerializeObject(config.Password), ParameterType.GetOrPost);
I'm looking for any tips on how I can somehow convert and send this text as valid form-data. It looks easier to do with HTTPWebRequest rather than RestSharp but it's bothering me that I'm sure it's possible.
Thanks so much!

It turns out that the default headers generated by RestSharp were sending the boundary parameter with quotes, where my endpoint was receiving them without quotes, so I needed a lower level interface and ended up being able to create a custom header for boundary with HttpWebRequest and MediaHeadervalue that fit my needs. Thanks everyone for looking!

Related

Why is postman sending form data in an HTTP GET?

I received a Postman json collection from an API vendor that works perfectly, but has something mystifying to me: The request is in a GET format, yet there is an x-www-form-urlencoded body.
URL: https://login.microsoftonline.com/d1e<secret>9563/oauth2/token
And when I look at the postman-generated c# code, the mystery continues:
var client = new RestClient("https://login.microsoftonline.com/d1e...d3/oauth2/token");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("client_id", "c06bb...79");
request.AddParameter("client_secret", "7~u...D");
request.AddParameter("resource", "https://vault.azure.net");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
Note the AddParameter constructions for a GET call. To me, this must be a slight-of-hand for merely adding those values to the querystring. But when I look at the postman console I see:
In the postman console I would have expected to see those params appended to the url as a querystring, and then everything would have made sense. But you can see that it's a bonafide Request Body.
When I make GET calls in my c# code I like to use the simple yet solid WebClient object to call the DownloadString() method. But this method is only for GETs and there's no way to send a form-post style body, understandably.
Is postman truly sending a GET with all those values being appended to the url as a querystring? And should I do the same in my DownloadString() call? Or is there something else going on here? Should I instead, in my c#, be calling the UploadString() method and sending a form post BODY as a GET??
Http protocol supports adding a body to a request, but the WebClient class you use doesn't. Presumably because it isn't considered the norm.
I'm sure there's good reasons for Microsoft using it in the OAuth flow though. Those guys normally do things right!
HTTP GET with request body
API is just an abstraction , you can send what ever you want to the API . It depends on the implementation , how the server handles these data.
Some services considers only what it requires and ignores other information
some services considers the entire requests and validates that it has only the allowed data. what should be allowed depends on the service
Postman is just a client that sends data to server , its upto you to decide what all information it should send . If you dont need any body then keep it as none. if you need some thing then add it.

Restsharp parameter behavior when setting ParameterType=GetOrPost

I face a weird intermittent issue with RestSharp. I use it to submit a POST authentication request to a server. But under certain conditions that I cannot isolate yet, this authentication call fails. By looking at the server log, I see a GET request instead of a POST. And I really wonder how the hell it is possible.
Here is the code to submit the authentication request :
var client = new RestClient(m_baseUrl);
var request = new RestRequest("https://dummyserver.com/api/auth", Method.POST);
request.AddParameter("client_id", apiCredentials.ApiKey, ParameterType.GetOrPost);
request.AddParameter("client_secret", apiCredentials.ApiSecret, ParameterType.GetOrPost);
request.AddHeader("Content-Type", "multipart/form-data");
IRestResponse response = await client.ExecutePostTaskAsync(request);
Since it is a distributed application, I don't have much info on the client request. Telemetry just confirms that the authentication failed.
Is it possible (known bug) that RestSharp transformed this request into a GET?
And, second question, is there any difference on the request being created with those two syntaxes:
request.AddParameter("client_id", apiCredentials.ApiKey, ParameterType.GetOrPost);
request.AddParameter("client_id", apiCredentials.ApiKey);
I need parameters to be submitted as form-data for security purposes.
Thanks for your help.
I don't think it's possible that a POST request gets executed as GET. Consider that you have some code (not the code from your question), which does that. Even using ExecutePost is redundant when you explicitly set the request type in the RestRequest constructor.
Concerning the second question, there's no real difference. The default parameter type is GetOrPost. I also believe that we use multipart/form-data for POST requests by default where there's no JSON or XML body.
You can easily find it by looking at the code of RestSharp.
Although weird, I've experienced a similar issue with you.
My solution is to use ExecuteAsPost():
var response = client.ExecuteAsPost(request, "POST");

Postman C# code snippet working on postman and not in app

I have an XML file that is sent as form-data to a remote server. When I use postman, I receive the response that I was supposed to receive.
Postman response
Then I use the code snippet generator to port the call to my C# app, and when I run on the app it does not work.
C# response
The code snippet is:
var client = new RestClient("http://172.20.27.1/xxxxxx/xxxxxx");
var request = new RestRequest(Method.POST);
request.AddHeader("Postman-Token", "1d9df75d-2c8f-4c92-b861-9f4291145846");
request.AddHeader("cache-control", "no-cache");
request.AddHeader("content-type", "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW");
request.AddParameter("multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW", "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"Metas\"; filename=\"C:\\Users\\agufprad\\Desktop\\Pepillo\\FLEX_REMAN_CfgRec_Final_00-00-00-4D_20190311-102223_1010039188_7000AHB.xml\"\r\nContent-Type: application/xml\r\n\r\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Is there anything I am not seeing here?
Can you export + upload the postman collection. That would help a lot.
A couple of things I see off the bat
1) You have a postman token header - why do you need this in C#?
2) Does none multi-part data work?
3) Are you sure content type is supposed to have the boundary data?
4) There is no file associated with the snippet. Pretty sure you can't just AddParameter multi-part data. Look for AddFile, which may be your issue.
5) Does an example off the web work? Currently this looks like it would be a malformed multi-part request
Thanks to everyone for the responses, gave an idea of where to look.
Finally i added a header on the post method request.AddHeader("Accept", "text/plain");
And added the file request.AddFile(fileName, Properties.Settings.Default.SaveRoute + fileName);
Thanks a lot!
I would advice you remove any content-length param, postman-token, cookie param generated from the code generator on Postman, and it should work as its own specific request.
Remove
request.AddHeader("Postman-Token", "1d9df75d-2c8f-4c92-b861-9f4291145846");

What is the equivalent of using StringContent (System.Net.Http) to make a post request in Python

I have some C# code that does the following post request:
string postData = newFormUrlEncodedContent(params).ReadAsStringAsync().Result;
var postContent = new StringContent(postData, UTF8Encoding.UTF8, "application/x-www-form-urlencoded");
var responseMessage = httpClient.PostAsync(url, postContent).Result;
I would like to do the equivalent in Python using the Requests library. This is what I have:
headers = {'content-type':'application/x-www-form-urlencoded'}
postContent = requests.post(url, params=params, headers=headers, cookies=previousResponse.cookies)
But postContent.status_code for the Python code is 404, whereas the C# request returns 200. It's possible that there's something wrong with the params since I retrieve those via some Regex matching from a previous request, but that seems to be working.
Edit: I think setting the params parameter is for get requests, not post requests. Also I believe Requests takes care of form encoding:
Typically, you want to send some form-encoded data — much like an HTML form. To do this, simply pass a dictionary to the data argument. Your dictionary of data will automatically be form-encoded when the request is made
So now I have:
postContent = requests.post(url, data = params, cookies = previousResponse.cookies)
Now postContent.status_code == 500. The stack trace says the data is invalid at the root level. I will look into it.

upload feed to walmart

I am breaking my head trying to upload a feed to walmart, after many times trying i used postman to generate C# restsharp code for me, in postman it works, but when using the c# restsharp code it returns a mysterious error. like this:
"No message body writer has been found for response class FeedAcknowledgement"
what does that mean?
here is my code:
string requestUrl = "";
requestUrl = string.Format("https://marketplace.walmartapis.com/v2/feeds?feedType=inventory");
string method = "POST";
// string[] sig = getSig(method, requestUrl).Replace("\r", "").Split('\n');
var mySig = new Signature(ConsumerID, SecretKEY, requestUrl, method);
var s = mySig.TimeStamp;
var returendSigniture = mySig.GetSignature(s);
var client = new RestClient("https://marketplace.walmartapis.com/v2/feeds?feedType=inventory");
var request = new RestRequest(Method.POST);
//request.AddHeader("postman-token", "c325ba5f-813a-f990-7899-6bfc4b14aa1b");
request.AddHeader("cache-control", "no-cache");
request.AddHeader("content-type", "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW");
request.AddHeader("accept", "application/xml");
request.AddHeader("wm_consumer.id", "--");
request.AddHeader("wm_sec.auth_signature", returendSigniture);
request.AddHeader("wm_sec.timestamp", mySig.TimeStamp);
request.AddHeader("wm_qos.correlation_id", "123456abcdef");
request.AddHeader("wm_svc.name", "Walmart Marketplace");
request.AddParameter("multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW", "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"BOUNDERY\"\r\n\r\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<wm:inventory xmlns:wm=\"http://walmart.com/\">\n <wm:sku>PP00500-2PC</wm:sku>\n <wm:quantity>\n <wm:unit>EACH</wm:unit>\n <wm:amount>120</wm:amount>\n </wm:quantity>\n <wm:fulfillmentLagTime>1</wm:fulfillmentLagTime>\n</wm:inventory>\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
I spent all day in figuring out how to request Walmart v3. I propose you the following two steps:
Use Walmart signer in order to generate signed token.
You will need to use HttpWebRequest for getting response from Walmart in a way similar to what is described here.
I have not been able to get this to work natively in C#, but I do have a work around.
The Java SDK can successfully submit multi-part requests to Walmart. I wrote a wrapper around the SDK functions that can accept basic command line input to read a text file and send the appropriate call with attached files. From here, you can just call the .jar file (I do it via dynamically generated batch file) from your C# program and receive responses back via text file. This is a sub-optimal system, but it works reliably and when the choice was between updating inventory on 2000 items every day and using some dirty code, I went with the Java wrapper method. This will be replaced as soon as the C# SDK comes out, but I believe this is one of the reasons why the C# SDK may be being delayed.
This solution was used, only after spending about a week trying to get boundaries / streams / attachments to work in C# and having zero success. Cases were also submitted to walmart and I was able to work with some of their top tier engineering support staff and this problem completely stumped them. I was able to trace the Java SDK execution all the way down to a built in Maven / Java function that constructed the web request so there's something under the hood that Java is doing with a multi-part request that isn't immediately clear in C#.

Categories