How to send Ajax request, using HttpClient - c#

I have a website that send an Ajax request to check validation of a number. I want send that request in my WPF application, using HttpClient.
It use post method, and here is the request url: http://sim.mci.ir/chk-number-availability
Here is the headers:
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.5
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 18
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Cookie: _ga=GA1.2.589400539.1410935105; JSESSIONID=33719B6BDA36C7C23A96B5E73F602B08; _gat=1
Host: sim.mci.ir
Pragma: no-cache
Referer: http://sim.mci.ir/first-step
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:32.0) Gecko/20100101 Firefox/32.0
X-Requested-With: XMLHttpRequest
And here is the only value:
number=09122175567
I used SendAsync() and PostAsync() methods in common ways, but it doesn't work. I think the problem is in request headers.

HttpClient client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>() {
{ "number", "09129457000" }
});
client.DefaultRequestHeaders.Referrer = new Uri("http://sim.mci.ir/first-step");
var resp = await client.PostAsync("http://sim.mci.ir/chk-number-availability", content);
var repsStr = await resp.Content.ReadAsStringAsync();
This will return false string

Well, Actually you can't send real AJAX request using HttpClient, but you can simulate it by adding XMLHttpRequest to request header like this:
client.DefaultRequestHeaders.Add("X-Requested-With", "XMLHttpRequest");
Here is a Helper method which requires Microsoft.AspNet.WebApi.Client nuget package:
private async TResponse CreateHttpRequestAsync<TRequest, TResponse>(Uri uri, TRequest request)
where TRequest : class
where TResponse : class, new()
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("X-Requested-With", "XMLHttpRequest");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _bearerToken);
var response = await client.PostAsJsonAsync(uri, request);
if (response.StatusCode == HttpStatusCode.OK)
{
var json = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TResponse>(json);
}
else if(response.StatusCode == HttpStatusCode.Unauthorized)
{
throw new UnauthorizedException();
}
else
{
throw new InvalidOperationException();
}
}
}

You can't send an AJAX request using HttpClient. You can send an asynchronous HTTP request if you want.
AJAX is asynchronous JavaScript + XML (it has since become broader to mean Asynchronous JavaScript + other formats e.g. Json), but it's still fundamentally a browser technology involving JavaScript.
HttpClient doesn't know anything about JavaScript, you'd have to use a WebBrowser control for executing JavaScript.
Update:
Looking back at this question, I find it rather surprising that so many seemingly experienced devs don't understand the term AJAX and confuse it with a simple asynchronous HTTP request, so I think it'd be helpful to provide more info here.
Ajax is short for Asynchronous JAvaScript and XML, it allows web pages to send/receive asynchronous data serialized as XML via XMLHttpRequests, and then use that data to dynamically update the page without the need to reload the entire page which is all done using JavaScript.
Now over the last few years, XML has lost its luster as the de facto standard of formatting data and JSON is taking its place, and that has affected Ajax too, you typically see data formatted as JSON instead of XML when communicating with servers. So the acronym AJAX wasn't so precise anymore; AJAJ was proposed but its use did not become widespread, so people still use the term Ajax when in fact they are not sending/receiving XML, but that's not a very big deal because the two are essentially very similar other than the way the data is formatted:
it still involves a JavaScript.
a web page/application sending/receiving asynchronous requests.
a web browser that supports JavaScript and XMLHttpRequest.
With the above context, I hope its clear that what the OP is trying to do here, and the accepted answer for that matter, has very little to do with Ajax as it doesn't involve any of the above points. It is in fact, just a simple HTTP request.

Related

How to send unencoded form data with C# HttpClient

I'm trying to "repurpose" a third-party API used by a desktop application. I've found that the below code gets me very close to matching the packets sent by the app:
var formData = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>(JsonConvert.SerializeObject(myPayload), "")
});
var response = Client.PostAsync(myURL, formData).Result;
var json = response.Content.ReadAsStringAsync().Result;
This gets me almost exactly the same payload sent by the application, except it encodes the data (I know, "encoded" is right there in the name). I need to get the exact same request but without the data being encoded, but I can't quite find the right object(s) to pull it off. How do I keep this payload from being URL encoded?
Edit:
This is a login request I pulled from Wireshark emanating from the application:
POST /Login HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: 1.1.1.1
Content-Length: 161
Expect: 100-continue
Connection: Close
{"username":"myuser","auth-id":"0a0a140f81a2ce0c303386e93cec41bf04660c22a881be9a"}
This is what the above will generate:
POST /Login HTTP/1.1
Expect: 100-continue
Connection: Close
Content-Type: application/x-www-form-urlencoded
Content-Length: 221
Host: 1.1.1.1
%7B%22user-name%22%3A%22myuser%22%2C%22auth-id%22%3A%220a0a140f81a2ce0c303386e93cec41bf04660c22a881be9a%22%7D=
I've edited them for brevity so the Content-Length is wrong. I realize it might not be the best way to send this data, but I have no control over how it's consumed.
Since you're actually trying to send JSON, I think you need to wrap the JSON in a StringContent object rather than a FormUrlEncoded object. Form-encoded data and JSON data are two different ways of formatting a payload (another commonly used format would be XML, for example). Using them both together doesn't make any sense.
I think something like this should work:
var content = new StringContent(JsonConvert.SerializeObject(myPayload), Encoding.UTF8, "application/json");
var response = Client.PostAsync(myURL, content).Result;
var json = response.Content.ReadAsStringAsync().Result;
(P.S. the Content-Type: application/x-www-form-urlencoded header sent by the application appears to be misleading, since the request body clearly contains JSON. Presumably the receiving server is tolerant of this nonsense, or just ignores it because it's always expecting JSON.)

How do you set the Content-Type header for an HttpClient request with MultipartFormDataContent?

I looked at the MS Source code according to their interpretation the HttpClient itself does not have "Content-Type" only the content should have content-type. Seems logical except when you're dealing with MultipartFormDataContent.
MultipartFormDataContent completely ignores the following code:
string boundary = "--" + GenerateRandomString();
using (var content = new MultipartFormDataContent(boundary))
{
content.Headers.ContentType = new MediaTypeHeaderValue($"multipart/form-data");
content.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("boundry", boundary));
...
}
No "Content-Type" is present in the request.
And also ignores:
string boundary = "--" + GenerateRandomString();
using (var content = new MultipartFormDataContent(boundary))
{
content.Headers.Remove("Content-Type");
content.Headers.TryAddWithoutValidation("Content-Type", "multipart/form-data; boundary=" + boundary);
...
}
Attemting to set it at on the HttpClient
client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "multipart/form-data; boundary=" + boundary);
throws the following error:
System.InvalidOperationException: 'Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects.'
I can find plenty of examples of how to do this using StringContent but none with MultipartFormDataContent. MultipartFormDataContent allows setting the Content-Type and Content-Disposition with each field, I need this more at the client level. I need a header that looks something like this:
accept: application/json
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Authorization: Basic ZXlKbGRDSTZJakUxTWpZNU9UTXpOTnpkMjl5WkNJNklqa3haRGxtWkdKa1lUazRaVEJqWmpsalpUaGxNV1V3TXpOalxuWmpCbE1tVXhJaXdpZFhObGNpSTZJbUZrYldsdUluMD1cbjo=
Cache-Control: no-cache
Many non-Microsoft APIs require the "boundary" tag so it can distinguish the individual fields of data being sent. The validation here on the request seems a little over the top. Even TryAddWithoutValidation doesn't work (maybe a bug?). I realize that it may be possible to interpret RFC7578 in a way that says it shouldn't be required but flat out not allowing it doesn't seem right to me either. Anyone else ever run into this issue and solve it.
Initially I thought this was an HttpClient bug. I added logging to capture the request and the response. That logging was missing headers which lead me to believe that the missing "multi-part/form-data" content header was the issue and the reason the API I'm using kept telling me it couldn't find a required field. It turns out to be an issue with how the API handles the data it's sent when it's multi-part/form-data. After comparing both my HttpWebRequest and the HttpClient request in fiddler I discovered the following difference in the data being sent:
HttpWebRequest
----vekhftkcthxr
Content-Disposition: form-data; name="name";
d30-20180524
HttpClient
----bcgifxyjkmkw
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=name
d30-20180524
I build the HttpWebRequest manually so I included the quotes and ending semi-colons. The HttpClient request is built for me and does not include the extra quotes and semi-colon. So the API I am using does not play well with request being generated by the HttpClient even though the request is technically correct.
Thanks to Panagiotis Kanavos for showing me my error.

Why is the entire HttpResponseMessage serialized?

Why does this Web API implementation
[HttpGet("hello")]
public HttpResponseMessage Hello()
{
var res = new HttpResponseMessage(HttpStatusCode.OK);
res.Content = new StringContent("hello", Encoding.UTF8, "text/plain");
return res;
}
return
{
"Version":{
"Major":1,
"Minor":1,
"Build":-1,
"Revision":-1,
"MajorRevision":-1,
"MinorRevision":-1
},
"Content":{
"Headers":[{
"Key":"Content-Type",
"Value":["text/plain; charset=utf-8"]
}]
},
"StatusCode":200,
"ReasonPhrase":"OK",
"Headers":[],
"RequestMessage":null,
"IsSuccessStatusCode":true
}
instead of
hello
?
How can I make the Web API return an HTTP response like below?
200 OK
Content-Type: text/plain
hello
What I want to do finally is return JSON and other formats with various status codes, so the following code wouldn't help me as an answer.
[HttpGet("hello")]
public string Hello()
{
return "hello";
}
(I'm new to ASP.NET and other Microsoft technologies.)
I had the same happen to me recently. The reason was that, for some reason, there was two references to the System.Net.Http.dll assembly: one from the GAC and one local copy from my project.
It results in an interesting case where the type of HttpResponseMessage you send isn't the same type of HttpResponseMessage ASP.NET expects, and that's why instead on processing the message, it just serializes it as JSON.
The solution I found for this is to install the System.Net.Http package from NuGet, and ensure that the binding redirect is generated correctly on the Web.config, so that only one copy of the dependency is used.
Intriguing, if I try your code on ASP.NET 4
public HttpResponseMessage Hello()
{
var res = new HttpResponseMessage(HttpStatusCode.OK);
res.Content = new StringContent("hello", Encoding.UTF8, "text/plain");
return res;
}
I get response, what I expect.
Header:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 5
Content-Type: text/plain; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
...
X-Powered-By: ASP.NET
Date: ...
Body
hello
Depending on the situation, you can either rely on client to specify what it can accept or you can specify server to always throw json.
I usually have custom request/response objects which I throw back to client. For instance
public CustomResponse Get()
{
CustomResponse response = new CustomResponse();
// some work
response.TestProperty1 = "Test Value 1";
response.TestProperty2 = "Test value 2";
return response;
}
Now if your API would respect what your client accepts. So if client set request header "Accept: application/xml" then it would return xml or json if its json. Refer to screenshot of fiddler request below.
From memory, I think you can also specify on server as well say to always send json.
Hope this helps!
ASP.net Web Api uses something called Content Negotiation where the client specifies that content to be returned.
In your case, you could specify that you want text/plain returned and the built-in content negotiation should provide what you want.
You've not specified how you are making the request - manually in a browser (which browser) / via jquery $.ajax / .net client code / etc - and how you make the request determines how you add the accept header to that request, eg:
GET http://[url] HTTP/1.1
Accept: text/plain
It's possible to override the content negotiation and always return a specific type (eg JSON) and there are many questions on SO on how to do this - mostly because they want JSON but get xml and don't know how to request JSON. If you're creating a reusable Web API then you should leave this up to the client to request what they want, which is why it's built that way into the framework.
(Self-answering) Hmm... The reason might be that I had chosen
ASP.NET 5 Preview Templates
instead of
ASP.NET 4.5.2 Templates
when I tried to create a Web API.
I guess it might be a bug of ASP.NET 5 Preview or ASP.NET 5 has introduced another new mechanism that supersedes HttpResponseMessage.

Telling RestSharp *not* to add a specific HTTP header

I am trying to call a REST service from a C# ASP.NET 4.0 application using RestSharp.
It's a fairly straightforward POST call to a https:// address; my code is something like this (CheckStatusRequest is a plain simple DTO with about four or five string and int properties - nothing fancy):
public CheckStatusResponse CheckStatus(CheckStatusRequest request) {
// set up RestClient
RestClient client = new RestClient();
string uri = "https://.......";
// create the request (see below)
IRestRequest restRequest = CreateRequestWithHeaders(url, Method.POST);
// add the body to the request
restRequest.AddBody(request);
// execute call
var restResponse = _restClient.Execute<CheckStatusResponse>(restRequest);
}
// set up request
private IRestRequest CreateRequestWithHeaders(string uri, Method method) {
// define request
RestRequest request = new RestRequest(uri, method);
// add two required HTTP headers
request.AddHeader("Accept", "application/json");
request.AddHeader("Content-Type", "application/json");
// define JSON as my format
request.RequestFormat = DataFormat.Json;
// attach the JSON.NET serializer for RestSharp
request.JsonSerializer = new RestSharpJsonNetSerializer();
return request;
}
The problem I'm having when I send these requests through Fiddler to see what's going on is that my request suddenly gets a third and unwanted HTTP header:
POST https://-some-url- HTTP/1.1
Accept: application/json
User-Agent: RestSharp/104.4.0.0
Content-Type: application/json
Host: **********.com
Content-Length: 226
Accept-Encoding: gzip, deflate <<<=== This one here is UNWANTED!
Connection: Keep-Alive
I suddenly have that Accept-Encoding HTTP header, which I never specified (and which I don't want to have in there). And now my response is no longer proper JSON (which I'm able to parse), but suddenly I get back gzipped binary data instead (which doesn't do real well when trying to JSON-deserialize)....
How can I get rid of that third unwanted HTTP header?
I tried to set it to something else - whatever I enter just gets appended to those settings
I tried to somehow "clear" that HTTP header - without any success
I tried finding a property on the RestClient or the RestRequest classes to specify "do not use GZip"
Looking at the sources (Http.Sync.cs and Http.Async.cs) of RestSharp you can see that these values are hardcoded:
webRequest.AutomaticDecompression =
DecompressionMethods.Deflate | DecompressionMethods.GZip | DecompressionMethods.None;
There is also an open issue that describes this problem. It was opened August 2014 but still not solved. I think you can leave a comment there and maybe they will pay attention.

HttpClient.SendAsync not sending request body

I am using the ASP.NET Web API Client Libraries for .NET 4.0 (Microsoft.AspNet.WebApi.Client version 4.0.30506.0).
I need to send an HTTP DELETE with a request body. I have coded it as follows:
using (var client = new HttpClient())
{
client.BaseAddress = Uri;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// I would normally use httpClient.DeleteAsync but I can't because I need to set content on the request.
// For this reason I use httpClient.SendAsync where I can both specify the HTTP DELETE with a request body.
var request = new HttpRequestMessage(HttpMethod.Delete, string.Format("myresource/{0}", sessionId))
{
var data = new Dictionary<string, object> {{"some-key", "some-value"}};
Content = new ObjectContent<IDictionary<string, object>>(data, new JsonMediaTypeFormatter())
};
var response = await client.SendAsync(request);
// code elided
}
Per Fiddler, the request body is never serialized:
DELETE http://localhost:8888/myApp/sessions/blabla123 HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
Host: localhost:8888
Content-Length: 38
Expect: 100-continue
The response from the server:
HTTP/1.1 408 Request body incomplete
Date: Sun, 10 Aug 2014 17:55:17 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Cache-Control: no-cache, must-revalidate
Timestamp: 13:55:17.256
The request body did not contain the specified number of bytes. Got 0, expected 38
I have tried a number of workarounds, including changing the type being serialized to something else, doing the serialization myself with JsonSerialize, changing the HTTP DELETE to PUT, etc...
Nothing worked. Any help would be much appreciated.
I resolved the issue, though it does not make sense. I noticed that if I changed my call to HTTP PUT or POST, it still failed to serialize the Content as a request body. That was rather strange as previous PUTs and POSTs were successful. After doing a ton of debugging into framework libraries (using Reflector), I finally got to the only thing left that was "different."
I am using NUnit 2.6.2. The structure of my test is:
[Test]
async public void Test()
{
// successful HTTP POST and PUT calls here
// successful HTTP DELETE with request body here (after
// moving it from the TearDown below)
}
[TearDown]
async public void TerminateSession()
{
// failed HTTP DELETE with request body here
}
Why does this fail in the TearDown but not in the Test itself? I have no idea. Is something going on with the TearDown attribute or with the use of the async keyword (since I await async calls)?
I am not sure what it is causing this behavior, but I do know now that I can submit an HTTP DELETE with a request body (as outlined in my code sample in the question).
Another solution that worked is as follows:
[Test]
async public void Test()
{
// create and use an HttpClient here, doing POSTs, PUTs, and GETs
}
// Notice the removal of the async keyword since now using Wait() in method body
[TearDown]
public void TerminateSession()
{
// create and use an HttpClient here and use Wait().
httpClient.SendAsync(httpRequestMessage).Wait();
}
I know it's never quite that helpful to say, "don't do it that way", but in this case I think it makes sense to split the calls into a DELETE followed or preceeded by a POST or PUT.
The HTTP RFC doesn't explicitly opine on the matter, so technically it means that we can. The other question, however, is should we do it.
In cases such as this I would look for other implementations to see what is the de facto standard. As you've found in the .net implementation, it appears that the designers did not expect to send a body with the DELETE call. So, let's look at another popular (and very different impl) Python Requests:
>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'
No body here other. So, if the spec authors didn't mention it, and popular implementations assume that there's no body, then the principle of least surprise means we shouldn't do it either.
So, if you can change the API, it will be easier on clients of the API to split into two calls. Otherwise, you'll likely have to resort to custom hackery to cram the body into a DELETE call.
The good news is that you've likely found a bug in the .net framework, which is an achievement in and of itself. Clients advertising a non-zero Content-Length without actually sending it are broken.
In case anybody else runs into this, one thing I've noticed that can cause this is if you set a header with a newline in it.
We had an encrypted OAuth token, which gets decrypted at runtime and set as the OAuth header on the app. The newline was encrypted into the token, so it was not obvious from looking at the configs or anything that it was there, but if you do:
var message = new HttpRequestMessage(HttpMethod.Post, "https://example.com");
message.Headers.ContentType = new MediaTypeHeaderValue("application/json");
message.Content = new StringContent("{ \"someKey\": \"someValue\" }", Encoding.UTF8);
// note the trailing newline
message.Headers.Authorization = new AuthenticationHeaderValue("OAuth", "my auth token\n");
var response = await httpClient.SendAsync(request);
The HTTP request will be sent, but the content will not be sent with it. There are no exceptions thrown when this happens and if you inspect the HttpRequestMessage, the content will appear to be there, but it does not actually get sent over the wire.
This happens in .NET 5 on Windows and Linux, I haven't tested it on other framework versions/platforms.

Categories