Downloading a file with a Post request in WinRT - c#

I have WCF service that I want to make a post request to with some parameters and it will return me a file. The service is ok, I tested it using curl. The file is about 20 MB. I know that BackgroundDownloader is made for such large files but it does not support post requests.
My code is as follows:
var requestBody = "my parameters ...";
var handler = new HttpClientHandler { UseDefaultCredentials = true, AllowAutoRedirect = false };
var client = new HttpClient(handler);
HttpContent httpContent = new StringContent(requestBody, Encoding.UTF8, "application/json");
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
HttpResponseMessage response = await client.PostAsync("the url...", httpContent);
response.EnsureSuccessStatusCode();
var stream = await response.Content.ReadAsStreamAsync();
///some code to store the stream to a file
The problem is that the code bever gets to the ReadAsStreamAsync part, it always fails with A task was canceled exception.
I use similiar code to download strings from that service (just using ReadAsStringAsync instead of ReadAsStreamAsync) and it works fine.
What is the problem? Or what is the proper way to do this?

You should reconsider the use of BackgroundDownloader. A background download will continue even if your app suspends. In a WinRT application, you can expect suspension to happen all the time. Forcing the user to keep your app running while downloading is not a good idea.
You can configure a WCF service to accept GET requests by setting the serviceMetadata element in web.config or the WebGet or WebInvoke attributes in code. Check Download File using WCF Rest Service for an example that uses the WebGet attribute and returns a Stream object.
Concerning your original question, you should create a proper WCF proxy as described in Accessing WCF Services with a Windows Store Client App. Web service calls require a lot of header and body setup beyond setting the media type. The proxy does all that for you.
Just don't do it for downloading files.

It looks like the problem was the file size. I solved it by setting a high Timeout and MaxResponseContentBufferSize on the HttpClient

Related

HttpClient requests not working for a particular domain in Azure

I have an Azure function that sends a request to a URL and sends back the response. This function kept failing with timeout error for URLs from a particular domain (confidential).
To debug this, I created a very minimal Azure function:
var content = string.Empty;
using (var response = await _httpClient.GetAsync(url))
{
response.EnsureSuccessStatusCode();
content = await response.Content.ReadAsByteArrayAsync();
}
return new OkObjectResult(content);
This code works fine in local. When I try using the deployed Azure function, it works for all the other domains I tried (ex: https://google.com) but it hits request timeout error for a particular domain after trying for about 90 seconds. The error happens at this particular line: _httpClient.GetAsync(url). Again, it works fine for this (confidential) domain in local.
I have tried deploying the Azure function to two completely different Azure service plans and regions. Same result. It doesn't work for URLs from the required domain. Works for URLs of other domains.
Error:
System.IO.IOException: Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request..
Update (solution):
I tried sending a request from Postman, copied the code from there for C# and deployed it to the Azure function and it is now working for the problematic domain. Something like below:
var client = new RestClient(url);
client.Timeout = -1;
var request = new RestRequest(Method.GET);
IRestResponse response = client.Execute(request);
The key here is client.Timeout = -1, which seems to have fixed the problem.
Now, in my original code, I tried setting HttpClient's timeout to Timeout.InfiniteTimeSpan both in Startup configuration as well as at individual request level but it did not work.
services.AddHttpClient("AzureTestClient", options =>
{
options.Timeout = Timeout.InfiniteTimeSpan;
});
Am I setting the timeout wrong in the HttpClient solution?
If you are using a Consumption plan then maybe the confidential URL need to whitelist the whole Azure Data center. You can follow the guide here or consider upgrading the Consumption plan to a premium one and have a dedicated linked VNET.
Maybe your local machine is already linked to the domain/whitelisted so azure function operates from different range.
Another reason maybe the URL returns a different HttpStatusCode that is't Successful range (200-299) so it fails with "EnsureSuccessStatusCode" in the old code?
Normally for the http code initialization, I did something like that:
public void Configure(IWebJobsBuilder builder)
{
builder.Services.AddHttpClient("AzureTestClient",
options => { options.Timeout = Timeout.InfiniteTimeSpan; });
}
Then when I want to use it, I do like that in any other function and it worked:
var client = clientFactory.CreateClient("AzureTestClient");

httpClient.PostAsync() performs GET request anyway

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.

ADFS v4.0 and /userinfo endpoint giving 405

Integrating older ASP.NET server-side application into ADFS for authentication, which means I pretty much had to write everything from scratch. have everything working (/authorize, /token) up until the /userinfo call.
My code, in a nutshell -
HttpClient client = new HttpClient();
var req = new HttpRequestMessage {
RequestUri = new Url("https://<server_ip>/adfs/oauth2/userinfo"),
Method = HttpMethod.Get,
};
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
req.Headers.UserAgent.Clear();
req.Headers.UserAgent.Add(new ProductInfoHeaderValue("OldApp", "11.3.0"));
var result = await client.SendAsync(req);
The result is a HTTP error 405 - Method Not Allowed. Doing searches online, I see this as a common issue when the trailing "/" is left off the url, but I get the same result with a trailing slash.
After poking around, there are a lot of examples that use newer libraries and such that I can't use, sadly. None mention usage of the /userinfo, and I'm thinking that the issue isn't necessarily in how I'm calling the URL, but configuration of the 'Application Group' in ADFS.
Okay - I found the issue, and will document it here in case others come across the same thing..
While I am not sure why /userinfo is giving a 405 - the URL I was using is wrong, despite it being listed in the Endpoints folder. There shouldn't be any "oauth2" in the URL. The correct code (and URL) is:
var req = new HttpRequestMessage {
RequestUri = new Url("https://<server_ip>/adfs/userinfo"),
Method = HttpMethod.Get,
};
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
req.Headers.UserAgent.Clear();
req.Headers.UserAgent.Add(new ProductInfoHeaderValue("OldApp", "11.3.0"));
var result = await client.SendAsync(req);
Also something to keep in mind - this has been stated elsewhere, but not as clearly as here, I hope:
The /userinfo will ONLY give you the NameIdentifier ("sub") claim. (As far as I can see.) No matter what scope you pass it. You will get all your information (that should normally be in the /userinfo call) in the "id_token" parameter from you /token call, encoded as JWT.
Personally I was led to do the same thing as you, the only solution was to download the ADAL library (You will find the link below) and debug the code in order to re-produce the same HTTP stream from ADAL.
You can create a new project so that you can integrate ADAL, for debugging or else intercepting the HTTP stream
Link ADAL

HttpClient PostAsync does not return

I've seen a lot of question about this, and all points to me using ConfigureAwait(false), but even after doing so, it still doesn't returned any response. When I run the debugger, the code stops at the PostAsync and does not continue with my code. Am I doing something wrong here? Does it have to do with me calling an API via HTTPS?
Here's the code:
public async static Task<PaymentModel> AddAsync(Card card)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:", "hidden"))));
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
var cardJson = JsonConvert.SerializeObject(card);
var postRequest = new StringContent(cardJson, Encoding.UTF8, "application/json");
var request = await client.PostAsync(new Uri("https://sample-3rd-party-api/api/endpoint/here"), postRequest).ConfigureAwait(false);
var content = await request.Content.ReadAsStringAsync().ConfigureAwait(false);
}
EDIT:
In response to the comments below, the code is contained from a method AddAsync(Card card) called from a button click with a handler:
public async void OnExecute(object sender, EventArgs args)
{
//some code here
payment = await PaymentModel.AddAsync(card).ConfigureAwait(false);
}
EDIT 2:
I tried pinging the API, but it returns a request timed out, but when I tried it using Postman, it's doing fine (the API is just a Sandbox which is open for all, so it's okay to share this):
EDIT 3:
I think the problem lies with where I don't have an SSL certificate to access the API. I have a PHP server that connects to the same API and I have to set SSL_VERIFYPEER to false just so I can access it (don't worry, I added a cacert now so its on true again). Can the same issue be happening here? If so, what can I do to create a valid certificate for my xamarin forms app
You can use this
var json = JsonConvert.SerializeObject(card);
using (var client = new HttpClient())
{
var t = await client.PostAsJsonAsync("https://sample-3rd-party-api/api/endpoint/here", json);
Response R =JsonConvert.DeserializeObject<Response>((JsonConvert.DeserializeObject(t.Content.ReadAsStringAsync().Result.ToString())).ToString());
}
What's most likely happening here is your OnExecute method has a return type of void instead of Task which prevents the UI thread from being able to await it. Try either changing that return type to Task or creating a new UI thread to perform this work. I wouldn't worry about the ping timing out as long as Postman works. Many public web servers disable their ping response.
Does it have to do with me calling an API via HTTPS?
As you are remaining in the same network and calling the same API from POSTMAN and .NET HTTP Client and only getting success with POSTMAN.So this issue gets cancelled.
Next
tried pinging the API, but it returns a request timed out
This is answered on top of mine.
Can you Please try setting the timeout option for HTTPClient while initializing.
client.Timeout = TimeSpan.FromSeconds(10);
and if still Problem persists please setup Fiddler and compare both the req sent from POstman and .NET client
So I think the problem is resolved now since I'm able to receive content from the request, what I did was simply follow the docs here: https://learn.microsoft.com/en-us/xamarin/cross-platform/app-fundamentals/transport-layer-security?tabs=windows
It looks like my settings are outdated in platform level.
Update the HttpClient implementation and SSL/TLS implementation
options to enable TLS 1.2 security.
Update the HttpClient Implementation option to enable TSL 1.2
security. (NSUrlSession (iOS 7.0+)
I was having the same issue and below trick fixed the issue.
Change your var request = await client.PostAsync(...); as below
var task = client.PostAsync(new Uri("https://sample-3rd-party-api/api/endpoint/here"), postRequest);
var request = task.GetAwaiter().GetResult();

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