httpClient.PostAsync() performs GET request anyway - c#

I really am not sure what is happening.
I'm using an HttpClient to post XML content to a remote server using the PostAsync method like this:
using var content = new StringContent(payload, Encoding.UTF8, "application/xml");
using var response = await _httpClient.PostAsync(string.Empty, content);
... where payload is a string, and relative uri is empty because I just need to call base uri of httpclient.
I can perform same request in Postman and it works fine.
The issue is, for some reason httpclient actually performs a GET request instead of POST, and ignores content whatsoever:
I've checked in Postman, and it seems like it is a normal response from the server to GET request.
I've also tried
using var response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, string.Empty){Content = content});
... and it gives the same result.
This looks like a very weird issue to me, as I've never seen http client behaving like this in the past. Could anyone please explain why is this happening? Thanks!

OK, so the issue was actually with server.
It redirected all the requests with URLs not ending with "/", like http://address.com/page to the same address but ending with "/" - http://address.com/page/, and lost the method and content in process.
As #Jimi mentioned, the RequestMessage field in HttpResponseMessage contains the info about the last request that reached the server, therefore initial request data was lost, and I mistook it for HttpClient making wrong requests.

Related

Asana API 403 Response in C#

I am trying to implement a Xamarin app that works with the Asana API.
I have successfully implemented the OAuth as documented in the Asana documentation here... at least I assume it is successful. I get an access token from the token endpoint in an HTTPResponse with HTTP Status "OK".
But then when I turn around and try to make an API call with that same access token, I get a 403 Forbidden error. I tried the same API call in my browser (after logging in to Asana), and it works fine, which leads me to believe that I do have access to the resource, I must have an issue with authorizing the request on my end.
The API call in question is (documented here): https://app.asana.com/api/1.0/workspaces.
My C# code is as follows (abbreviated to relevant parts, and assume that ACCESS_TOKEN contains the access token I got from the token exchange endpoint):
HttpClient client = new HttpClient();
client.BaseAddress = "https://app.asana.com/api/1.0";
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", ACCESS_TOKEN);
client.DefaultRequestHeaders.Add("Accept", "application/json");
And then I use this HttpClient (named client) in the following function:
// Returns a list of the Asana workspace names for the logged in user.
private async Task<List<string>> GetWorkspacesAsync()
{
List<string> namesList = new List<string>();
// Send the HTTP Request and get a response.
this.UpdateToken(); // Refreshes the token if needed using the refresh token.
using (HttpResponseMessage response = await client.GetAsync("/workspaces"))
{
// Handle a bad (not ok) response.
if (response.StatusCode != HttpStatusCode.OK)
{
// !!!THIS KEEPS TRIGGERING WITH response.StatusCode AS 403 Forbidden!!!
// Set up a stream reader to read the response.
// This is for TESTING ONLY
using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync()))
{
// Extract the json object from the response.
string content = reader.ReadToEnd();
Debug.WriteLine(content);
}
throw new HttpRequestException("Bad HTTP Response was returned.");
}
// If execution reaches this point, the Http Response returned with code OK.
// Set up a stream reader to read the response.
using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync()))
{
// Extract the json object from the response.
string content = reader.ReadToEnd();
JsonValue responseJson = JsonValue.Parse(content);
foreach (JsonValue workspaceJson in responseJson["data"])
{
string workspaceName = workspaceJson["name"];
Debug.WriteLine("Workspace Name: " + workspaceName);
namesList.Add(workspaceName);
}
}
}
// I have other awaited interactions with app storage in here, hence the need for the function to be async.
return namesList;
}
Finally found the answer. It looks like I was using HttpClient incorrectly; a subtle thing that should be equivalent, but is not due to the way it is implemented.
The answer
I needed to place the final slash at the end of the BaseAddress property of HttpClient, and NOT at the start of the relative address for the specific request. This answered question explains this.
To fix my code
I needed to change the setting up of the BaseAddress:
HttpClient client = new HttpClient();
client.BaseAddress = "https://app.asana.com/api/1.0/"; // FINAL SLASH NEEDED HERE
And remove the slash from the request's relative address:
// DO NOT put slash before relative address "workspaces" here
using (HttpResponseMessage response = await client.GetAsync("workspaces"))
Why I got the original error
When HttpClient combined the BaseAddress with the relative URI I specified in GetAsync(), it dropped off some of the base address, since the final slash was not included. The resulting address from combining the BaseAddress with the relative URI was a valid URL, but not a valid page/API call in Asana. Asana thus did an automatic redirect to a login page, which, of course, the rest of the API call would be forbidden from there.
How I discovered this
In debugging, I grabbed the access token returned during my app's authorization with Asana. I then recreated the request to the "/workspaces" API myself in Postman, and the request worked as expected. This confirmed that my authorization worked fine, and the issue must be with the specific request rather than the authorization. In debugging I then looked into the HttpResponseMessage, which has a property called RequestMessage, that includes the actual URL the GetAsync() made the request against. I observed the Login URL from Asana, rather than the BaseAddress I specified... which led me to the question/
answer linked above.
Hope this explanation helps anyone who comes across a similar error!

HTTP Request works in postman but doesn't work in code

I'm trying to access a website that requires login via a form.
I used the Postman HTTP client.
I tried to do the normally http post request but didn't seem to work, I get a successful status code (200 OK) but it doesn't log in, eventually did work with a GET request with BODY parameters (I hadn't seen GET request with body parameters).
Well, I tried to simulate this request in C# code with no luck, I even tried the generated code that Postman offers with no luck again.
Down below is the Postman request and the C# code snippet based on auto-generated Postman code. Does anyone know if is there to make this request with any library or if there is something that I miss?
Thank you in advance.
var client = new RestClient("https://thessalia-3.teilar.gr/login.asp");
var request = new RestRequest(Method.GET);
request.AddHeader("cache-control", "no-cache");
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddHeader("Referer", "https://thessalia-3.teilar.gr/login.asp");
var parameters = new Dictionary<string, string>();
parameters["userName"] = JsonConvert.SerializeObject("myusername");
parameters["pwd"] = JsonConvert.SerializeObject("mypass");
parameters["loginTrue"] = JsonConvert.SerializeObject("extravalue");
var content = new FormUrlEncodedContent(parameters);
request.AddParameter("application/x-www-form-urlencoded", content);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
Console.WriteLine(response.StatusCode);
Postman Request Photo
Edit:
Postman Request Body Parameters
I've also tried to run this but also not logged in.
Auto-generated code form Postman
If the request was successful (200) and you got the HTML page for "Invalid Credentials", then your code that's making the request should be fine and the issue is with the credentials. Like I said in my first comment, don't serialize the parameters to JSON, URL-encode them instead:
parameters["userName"] = HttpUtility.UrlEncode("myusername");
parameters["pwd"] = HttpUtility.UrlEncode("mypass");
parameters["loginTrue"] = HttpUtility.UrlEncode("extravalue");
This is the standard way and it works with writing the parameters directly to the request stream, or with a utility class like StringContent. However, since you're using the utility class FormUrlEncodedContent, it URL-encode them for you, so you don't have to. In that case, simply assign them directly as string:
parameters["userName"] = "myusername";
parameters["pwd"] = "mypass";
parameters["loginTrue"] = "extravalue";

HttpRequestMessage won't allow Authorization header value

I'm trying to reuse my HttpClient instance, as that's best practice. Therefore, in one particular request, I want to set the Authorization header on the request, instead of setting it globally on the client. From everything I've read, this ought to work:
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await _client.SendAsync(request);
This compiles, but then throws when I attempt to call it. The exception I get is:
"Misused header name. Make sure request headers are used with
HttpRequestMessage, response headers with HttpResponseMessage, and
content headers with HttpContent objects."
I'm confused why I'm allowed to set this value on the request message if it's just going to throw, and I also haven't figured out a workaround.
I've tried directly adding the header using TryAddWithoutValidation but I still get the same exception (not even a return of false like I'd expect!)
I'm suspecting this might be a bug in the framework since multiple people have posted this exact code with no apparent problems, but would be interested in any insights/workarounds.
Turns out that due to a different bug, the value of "token" was null. I would recommend checking this first if you're having this issue: note that the error message was 100% a lie.

RestSharp PUT request parameters

I'm having issues figuring out how to create a put request using RestSharp.
I need to pass an integer followed by a JSON body in the same request.
So far I have this:
for (var i = 0; i < ReorderedTasks.Count; i++) {
var reorderedTasksJson = new JavaScriptSerializer().Serialize(ReorderedTasks[i]);
var request = new RestRequest("api/task/5/{ID}/", Method.PUT);
request.AddParameter("ID", ReorderedTasks[i].ID.ToString(), ParameterType.UrlSegment);
request.AddParameter("application/json; charset=utf-8", reorderedTasksJson, ParameterType.RequestBody);
client.Execute(request);
}
I've tested out the JSON ad requestBody on POST and it works fine. I think my issue is with the first parameter I'm trying to pass ReorderedTasks[i].ID , I'm not sure if I'm handling the passing of this correctly.
I've initialised client at the beginning of my class.
Problem is the DB isn't updating and I need to isolate the problem. Is the above the correct way in dealing with my two parameters needing passed?
I suggest to put ReorderedTasks[i].ID.ToString() directly to url path.
var request = new RestRequest($"api/task/5/{ReorderedTasks[i].ID.ToString()}/", Method.PUT);
It will help to reduce possible problems with http request format.
I'll add it here, so someone will benefit from it.
If your endpoint URL have parameters like ?param=value&param2=value that you want to pass along with request RestSharp's AddParameter(string, string) won't work with PUT method (but it works just fine with GET or if endpoint doesn't have URL parameters, so it is deceiving)
Use AddParameter(string, string, ParameterType.QueryString) in order to PUT Method work correctly.
Well it depends on what does the webApi expect..
You could use Fiddler to inspect what being sent through the wire and what response You are getting (http://www.telerik.com/fiddler)
Also - here are some sample's how other users use RestSharp
How do I use PUT in RestSharp?

Creating and sending an HttpResponse message

Given either a url or a HttpResponse instance, I need to send back a HttpResponse (note Response not Request) with Status code 200. I've tried a generic HttpClient.PostAsync and GetAsync, but I can't configure the status code.
This is basically a handshake for subscribing to events for a remote service. I send a Request, get an OK back from the server and now it's expecting me to reply to the OK with my own OK.
Any ideas on how to go about this? I don't have an incoming request to respond to.
var resp = new HttpResponseMessage(HttpStatusCode.OK);
// need to sent this to the url. no content necessary.
Here's some things I cannot do:
var client = new HttpClient();
var resp = await client.GetAsync(url);
-------------------
var content = new ByteArrayContent(new byte[0]);
var client = new HttpClient();
var resp = await client.PostAsync(url, content);
When you invoke HttpClient calls you are creating HttpRequestMessages. There are a number of helper methods (like some PostAsync overloads or PostAsJsonAsync) which let you pass in a plain old C# object, which it then wraps for you. These values get set in the Content property of the HttpRequestMessage, wrapped by an instance of HttpContent. You're also perfectly free to create these request messages yourself, setting the status code and content to anything you like. HttpResponseMessage (note Response, not Request) is the type you'll get back from your client call, which you can use to read the response code or data sent back to you (stored in HttpResponseMessage.Content, not to be confused with HttpRequestMessage.Content, which you would have already set).
That said, you CAN set your request content to an instance of HttpResponseMessage, but that would be a little bit odd. Generally, the objects you use for your content should be simple objects which exist simply to define the shape of your requests' body (like JSON).
This article goes over the basics pretty well: Calling a Web API from a .Net Client
You should be able to set the status directly on the HttpResponse object
Something like this...
HttpResponse().StatusCode = 200;

Categories

Resources