PostAsync in UWP App not working (no content sent) - c#

I'm a bit stuck, I am trying to create a UWP App that will post XML content to a web service. I can get this to work in a regular .net console app without an issue. Trying to re-create this using UWP is proving to be tricky. Using fiddler I've narrowed down that the web service end point isn't receiving my content. It looks like the headers are setup properly the content length is sent correctly but the actual content isn't sent. Here is the heart of the code, it crashes/throws an exception after:
HttpResponseMessage ResponseMessage = await request.PostAsync(requestUri, httpContent2).ContinueWith(
(postTask) => postTask.Result.EnsureSuccessStatusCode());
When I try to execute the PostASync, looking at fiddler, I'm getting:
HTTP/1.1 408 Request body incomplete
Date: Mon, 14 Nov 2016 15:38:53 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Cache-Control: no-cache, must-revalidate
Timestamp: 10:38:53.430
The request body did not contain the specified number of bytes. Got 0, expected 617
I'm positive that I am getting content to post correct (I read it from a file, I send it to debug window to verify and it's correct). I think it might have to do with HttpContent httpContent2 - In regular .NET I've never needed to use this but with PostAsync I need to use it.
Any thoughts would be appreciated, thank you!
public async void PostWebService()
{
string filePath = "Data\\postbody.txt";
string url = "https://outlook.office365.com/EWS/Exchange.asmx";
Uri requestUri = new Uri(url); //replace your Url
var myClientHandler = new HttpClientHandler();
myClientHandler.Credentials = new NetworkCredential("user#acme.com", "password");
HttpClient request = new HttpClient(myClientHandler);
string contents = await ReadFileContentsAsync(filePath);
Debug.WriteLine(contents);
HttpContent httpContent2 = new StringContent(contents, Encoding.UTF8, "text/xml");
string s = await httpContent2.ReadAsStringAsync();
Debug.WriteLine(s); //just checking to see if httpContent has the correct data
//HttpResponseMessage ResponseMessage = await request.PostAsync(requestUri, httpContent);
request.MaxResponseContentBufferSize = 65000;
HttpResponseMessage ResponseMessage = await request.PostAsync(requestUri, httpContent2).ContinueWith(
(postTask) => postTask.Result.EnsureSuccessStatusCode());
Debug.WriteLine(ResponseMessage.ToString());
}

Well it seems like I found the root cause to my problem. This is appears to be a known bug with System.Net.Http.HttpClient when using network authentication. See this article here
My initial mistake was that I wasn't catching an exceptions thrown by PostAsync. once I wrapped that inside a try/catch block I got the following exception thrown:
“This IRandomAccessStream does not support the GetInputStreamAt method because it requires cloning and this stream does not support cloning.”
The first paragraph of the article I linked to above states:
When you use the System.Net.Http.HttpClient class from a .NET
framework based Universal Windows Platform (UWP) app and send a
HTTP(s) PUT or POST request to a URI which requires Integrated Windows
Authentication – such as Negotiate/NTLM, an exception will be thrown.
The thrown exception will have an InnerException property set to the
message:
“This IRandomAccessStream does not support the GetInputStreamAt method
because it requires cloning and this stream does not support cloning.”
The problem happens because the request as well as the entity body of
the POST/PUT request needs to be resubmitted during the authentication
challenge. The above problem does not happen for HTTP verbs such as
GET which do not require an entity body.
This is a known issue in the RTM release of the Windows 10 SDK and we
are tracking a fix for this issue for a subsequent release.
The recommendation and work around that worked for me was to use the Windows.Web.Http.HttpClient instead of System.Net.Http.HttpClient
Using that recommendation, the following code worked for me:
string filePath = "Data\\postbody.txt";
string url = "https://outlook.office365.com/EWS/Exchange.asmx";
Uri requestUri = new Uri(url); //replace your Url
string contents = await ReadFileContentsAsync(filePath);
string search_str = txtSearch.Text;
Debug.WriteLine("Search query:" + search_str);
contents = contents.Replace("%SEARCH%", search_str);
Windows.Web.Http.Filters.HttpBaseProtocolFilter hbpf = new Windows.Web.Http.Filters.HttpBaseProtocolFilter();
Windows.Security.Credentials.PasswordCredential pcred = new Windows.Security.Credentials.PasswordCredential(url, "username#acme.com", "password");
hbpf.ServerCredential = pcred;
HttpClient request = new HttpClient(hbpf);
Windows.Web.Http.HttpRequestMessage hreqm = new Windows.Web.Http.HttpRequestMessage(Windows.Web.Http.HttpMethod.Post, new Uri(url));
Windows.Web.Http.HttpStringContent hstr = new Windows.Web.Http.HttpStringContent(contents, Windows.Storage.Streams.UnicodeEncoding.Utf8, "text/xml");
hreqm.Content = hstr;
// consume the HttpResponseMessage and the remainder of your code logic from here.
try
{
Windows.Web.Http.HttpResponseMessage hrespm = await request.SendRequestAsync(hreqm);
Debug.WriteLine(hrespm.Content);
String respcontent = await hrespm.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
string e = ex.Message;
Debug.WriteLine(e);
}
Hopefully this is helpful to someone else hitting this issue.

Related

glitch.com project can't be called with c#

I am writing this question in context of glitch.com projects.
so, i made a test project(asp.net .net6 webapi) in glitch.com which returns your ip address. The link to the webpage is https://ror-test.glitch.me/getip . It runs perfectly fine and returns response accurately when called from a browser. Now, I want to access this project from c# client (to be precise unity3d) however i am unable to access it.
My first try was to use httpclient.getstringasync-
Code-
HttpClient client = new HttpClient();
string response = await client.GetStringAsync(url);
System.Console.WriteLine(response);
Output-
Unhandled exception. System.Net.Http.HttpRequestException: Response status code does not indicate success: 403 (Forbidden).
In my second try i used httpclient.getasync
Code-
HttpClient client = new HttpClient();
var response = await client.GetAsync(url);
Output-
Response - https://jpst.it/348Ik
Response.Content - https://jpst.it/348LS
Also just to say that my app is working perfectly fine when i call the project from nodejs it works perfectly-
Code -
var url = "https://ror-test.glitch.me/getip";
var XMLHttpRequest = require("xhr2");
var xhr = new XMLHttpRequest();
console.log("starting");
xhr.open("GET", url);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
console.log(xhr.status);
console.log(xhr.responseText);
}};
xhr.send();
Output -
starting
200
your ip: xx.xx.xxx.xx
In place of xx.xx.xxx.xx my real ip which i have cross checked with https://whatismyipaddress.com/ is coming. so i am sure problem is with c# client.
Plz help me or any other way i can call it from c# client(precisely unity3d).
You need to pass User-Agent request header.
HttpClient client = new HttpClient();
//add user agent
client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "fake");
var response = await client.GetAsync(url);

Translating RestSharp request to HttpClient request

I'm programmatically uploading files to a remote server. The files are moderately large and I'd like to present a progress report to my users so they can see something happening. I was able to implement the upload using Postman which helpfully translated the whole thing to RestSharp.
But RestSharp does not provide any kind of progress tracking. I tried to implement the same functionality using HttpClient but it goes wrong somewhere the and server just throws a "400 - Bad Request" without telling exactly what is bad about it (its API documentation is also not for the faint of heart).
So, here's what Postman / RestSharp provide and which is working:
var client = new RestClient("https://opencast/ingest/addMediaPackage");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Basic FooBarBaz=");
request.AddParameter("creator", file.Creator);
request.AddParameter("title", file.Title);
request.AddParameter("flavor", "presentation/source");
request.AddParameter("description", file.Description);
try
{
request.AddFile("BODY", path);
IRestResponse response = await client.ExecuteAsync(request);
_logger.LogInformation($"Response after file upload: {response.StatusCode}");
File.Delete(path);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception when uploading files: {Message}", ex.Message);
}
and here's what I tried to do with HttpClient (without try-catch):
var request = new HttpRequestMessage(HttpMethod.Post, $"https://opencast/ingest/addMediaPackage");
request.Headers.Add("Authorization", "Basic FooBarBaz=");
using var form = new MultipartFormDataContent();
using var fileContent = new ByteArrayContent(await File.ReadAllBytesAsync(path));
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data");
form.Add(fileContent, "BODY", Path.GetFileName(path));
form.Add(new StringContent(file.Creator), "creator");
form.Add(new StringContent(file.Title), "title");
form.Add(new StringContent("presentation/source"), "flavor");
form.Add(new StringContent(file.Description), "description");
request.Content = form;
var client = clientFactory.CreateClient(); //which is a IHttpClientFactory
var response = await client.SendAsync(request);
This code sends the file to the server which, after completing the upload, throws a 400.
Currently not seeing the difference. I could intercept the requests to see where they differ but maybe someone here can see the problem right away?
Update: It gets weirder. If I just use clientFactory.PostAsync(form) and add the Auth headers through form.Add then I get a 200 (i.e. Success) but the server simply swallows the file.
Okay, I found the solution. I'm not sure whether the WTF is me or the guys behind the server but...
... you need to add the fileContent last.
Yes, the order of the parameters matters for this.

Http cannot send message to server

I have a website solution written in .net framework 4.8 and I am trying to send JSON message over HTTP Post to a payment server and get a response, yet there is no error and no response message, it doesn't even time out. I tested the same code in another project, it is working fine but it is written in .net 5.0 (the latest and current).
I have no idea why the same code will yield such different results (although pointing to different versions of dll). Anyone can help me on this? Is there any documentation on this? I think that there is something can be done at the server side as well but I have no idea where to start. Basically I need my website to be able to "talk" to the payment server, but I'm stuck here.
Here's a snippet of my code
private static readonly HttpClient client = new HttpClient();
public static async Task<string> SubmitPaymentTransaction (string reqJsonstr)
{
try
{
HttpContent httpContent = new StringContent(reqJsonstr, Encoding.UTF8);
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
var message = new HttpRequestMessage();
message.Content = httpContent;
message.Method = HttpMethod.Post;
message.RequestUri = new Uri($"https://payment.com/PayURL");
HttpResponseMessage response = await client.SendAsync(message);
response.EnsureSuccessStatusCode();
string result = await response.Content.ReadAsStringAsync();
return result;
}
catch(Exception error)
{
return error.ToString();
}
}
UPDATE:
I am able to capture the error message ... by removing the await: client.SendAsync(message)
InnerException = {"Authentication failed because the remote party has closed the transport stream."}
Message = "The underlying connection was closed: An unexpected error occurred on a send."
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
at Mall.PayPlugin.Pay.PaymentCore.<SubmitPaymentTransaction>d__19.MoveNext()
I did a quick search on the error and it turns out the security protocol is the underlying issue. My web application is actually running on .net 4.5 so by default it is using TLS 1.0 which is unacceptable for the API server. Specifying the security protocol, I am able to get a response from the server, however, still at the expense of disabling the await.
Special thanks to #paulsm4 for helping, guiding and most importantly, inspiring me through solving this problem when I have almost given up.
The post and the comments should describe clearly the problem and my journey to the solution. I'm not going to repeat myself so I'm just going to share my code, which is working now for me.
private static readonly HttpClient client = new HttpClient();
private enum MySecurityProtocolType
{
//
// Summary:
// Specifies the Secure Socket Layer (SSL) 3.0 security protocol.
Ssl3 = 48,
//
// Summary:
// Specifies the Transport Layer Security (TLS) 1.0 security protocol.
Tls = 192,
//
// Summary:
// Specifies the Transport Layer Security (TLS) 1.1 security protocol.
Tls11 = 768,
//
// Summary:
// Specifies the Transport Layer Security (TLS) 1.2 security protocol.
Tls12 = 3072
}
public static async Task<string> SubmitPaymentTransaction (string reqJsonstr)
{
try
{
HttpContent httpContent = new StringContent(reqJsonstr, Encoding.UTF8);
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
ServicePointManager.SecurityProtocol = (SecurityProtocolType)(MySecurityProtocolType.Tls12 | MySecurityProtocolType.Tls11 |
MySecurityProtocolType.Tls | MySecurityProtocolType.Ssl3);
var message = new HttpRequestMessage();
message.Content = httpContent;
message.Method = HttpMethod.Post;
message.RequestUri = new Uri($"https://payment.com/PayURL");
HttpResponseMessage response = await client.SendAsync(message).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
string result = await response.Content.ReadAsStringAsync();
return result;
}
catch(Exception error)
{
return error.ToString();
}
}

InvalidSignatureException with AWS Kinesis Video Stream C#

I am attempting to execute my own HTTP signed request since there is no SDK in C# for the PutMedia API for the AWS Kinesis Video Stream, but I am getting the following error message:
StatusCode: 403, ReasonPhrase: 'Forbidden'
x-amzn-ErrorType: InvalidSignatureException:http://internal.amazon.com/coral/com.amazon.coral.service/
Here is a gist of what my code looks like:
var streamName = "audio-stream-test";
var service = "kinesisvideo";
var endpoint = GetPutMediaEndpoint(streamName);
var host = GetHostFromEndpoint(endpoint);
var region = GetRegionFromEndpoint(endpoint);
var t = DateTime.UtcNow;
var canonical_uri = $"{endpoint}/putMedia";
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(canonical_uri));
httpRequestMessage.Headers.Add("connection", "keep-alive");
httpRequestMessage.Headers.Add("host", host);
httpRequestMessage.Headers.Add("Transfer-Encoding", "chunked");
httpRequestMessage.Headers.Add("user-agent", "AWS-SDK-KVS/2.0.2");
httpRequestMessage.Headers.Add("x-amzn-fragment-acknowledgment-required", "1");
httpRequestMessage.Headers.Add("x-amzn-fragment-timecode-type", "ABSOLUTE");
httpRequestMessage.Headers.Add("x-amzn-producer-start-timestamp", (t - DateTime.MinValue).TotalMilliseconds.ToString());
httpRequestMessage.Headers.Add("x-amzn-stream-name", streamName);
httpRequestMessage.Headers.Add("x-amz-security-token", sessionToken);
var byteArray = File.ReadAllBytes(filePath);
var content = new ByteArrayContent(byteArray);
httpRequestMessage.Content = content;
var httpClient = new HttpClient();
var aws4RequestSigner = new AWS4RequestSigner(accessKey, secretAccessKey);
var signedHttpRequestMessage = aws4RequestSigner.Sign(httpRequestMessage, service, region).Result;
var httpResponseMessage = httpClient.SendAsync(signedHttpRequestMessage);
Screenshot of Error
I am using the Aws4RequestSigner NuGet package to sign the request. Any ideas what I am doing wrong here? Has anyone tried to use the AWS Kinesis Video Stream with C#/.NET successfully?
Two potential issues with the pseudo-code.
If using session token then the request signing should include the session token as well not only access key/secret access key combination.
The body of the PutMedia is "endless" as it streams out as a realtime stream. As such, the data shouldn't be included in the signature calculation.
This is answer to your question "the actual "content" is not being added to the stream. I see the Put Connection from KVS but no data added".
After you get 200 by setting http headers properly for the signing with below code, you need to have your content set in signedHttpRequestMessage.
var httpResponseMessage = httpClient.SendAsync(signedHttpRequestMessage);

Using a Keep-Alive connection in WinRT's HttpClient class?

Our WinRT app is incredibly slow when opening connections to our servers. Requests take ~500ms to run. This is blocking some of our scenarios.
When debugging, we noticed that when Fiddler is active, the requests are much faster - ~100ms per request. Some searches later we understood that was because Fiddler was using Keep-Alive connections when proxying calls, which makes our proxied calls much faster.
We double-checked this in two ways.
We set UseProxy to false and observed that the request went back to being slow.
We turned off Fiddler's "reuse connections" option and observed that the requests went back to being slow.
We tried enabling keep-alive through the Connection header (.Connection.Add("Keep-Alive")) but this does not seem to have any effect - in fact, the header seems to be blatantly ignored by the .NET component and is not being sent on the request (again, by inspecting thru Fiddler).
Does anyone know how to set keep-alive on requests in Windows 8, WinRT, HttpClient class?
The following sets the correct headers to turn on keep-alive for me (client is an HttpClient)
client.DefaultRequestHeaders.Connection.Clear();
client.DefaultRequestHeaders.ConnectionClose = false;
// The next line isn't needed in HTTP/1.1
client.DefaultRequestHeaders.Connection.Add("Keep-Alive");
If you want to turn keep-alive off, use
client.DefaultRequestHeaders.Connection.Clear();
client.DefaultRequestHeaders.ConnectionClose = true;
Try using the HttpContent class to add the headers - something like this based on (but untested) http://social.msdn.microsoft.com/Forums/en-CA/winappswithcsharp/thread/ce2563d1-cd96-4380-ad41-6b0257164130
Behind the scenes HttpClient uses HttpWebRequest which would give you direct access to KeepAlive but since you are going through HttpClient you can't directly access that property on the HttpWebRequest class.
public static async Task KeepAliveRequest()
{
var handler = new HttpClientHandler();
var client = new HttpClient(handler as HttpMessageHandler);
HttpContent content = new StringContent(post data here if doing a post);
content.Headers.Add("Keep-Alive", "true");
//choose your type depending what you are sending to the server
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
HttpResponseMessage response = await client.PostAsync(url, content);
Stream stream = await response.Content.ReadAsStreamAsync();
return new StreamReader(stream).ReadToEnd();
}
EDIT
Since you only want GET, you can do that with:
public static async Task KeepAliveRequest(string url)
{
var client = new HttpClient();
var request = new HttpRequestMessage()
{
RequestUri = new Uri("http://www.bing.com"),
Method = HttpMethod.Get,
};
request.Headers.Add("Connection", new string[] { "Keep-Alive" });
var responseMessage = await client.SendAsync(request);
return await responseMessage.Content.ReadAsStringAsync();
}

Categories