2

I'm in the process of porting my windows 8.1 app to windows 10 UWP, but calling PostAsync now throws an exception.

This exact code works perfectly when targeting 8.1, but when I target Windows 10 UWP, it throws the following exception:

This IRandomAccessStream does not support the GetInputStreamAt method because it requires cloning and this stream does not support cloning.

Code

    public async void TestPost()
    {
        var parameters = GetParameters();
        var formattedData = new FormUrlEncodedContent(parameters);
        using (var clientHandler = new HttpClientHandler { Credentials = GetCredentials() })
        {
            using (var httpClient = new HttpClient(clientHandler))
            {
                var response = await httpClient.PostAsync(postUrl, formattedData);
            }
        }
    }

private Dictionary<string, string> GetParameters()
{
    var parameters = new Dictionary<string, string>();
    parameters["grant_type"] = "url";
    parameters["device_id"] = "unique key";
    parameters["redirect_uri"] = "redirect url";
    return parameters;
}

public static NetworkCredential GetCredentials()
{
    return new NetworkCredential("<secret key>", "");
}

Stacktrace

 at System.IO.NetFxToWinRtStreamAdapter.ThrowCloningNotSuported(String methodName)
   at System.IO.NetFxToWinRtStreamAdapter.GetInputStreamAt(UInt64 position)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Net.Http.HttpHandlerToFilter.<SendAsync>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Net.Http.HttpClientHandler.<SendAsync>d__1.MoveNext()
Smeegs
  • 9,151
  • 5
  • 42
  • 78
  • Can you provide your `GetParameters()` and `GetCredentials()` implementation? Does the error reproduce when the server does not require authentication? – kiewic Aug 02 '15 at 17:29
  • @kiewic, sure. I've added the methods. They're nothing special. As far as the request to a server that doesn't require authentication. I don't have one handy to use. I'm hitting a secure api. – Smeegs Aug 02 '15 at 19:45
  • 1
    Does anyone know if there is an update for this so we can use `System.Net.Http`. The issue i have with `Windows.Web.Http` is that I can't find a way to send the current windows credentials without prompting the user for it for a seamless integration – slayernoah Mar 21 '18 at 21:53

3 Answers3

2

Have you tried using Windows.Web.Http.HttpClient instead?

// using Windows.Web.Http;
// using Windows.Web.Http.Filters;

var parameters = GetParameters();
var formattedData = new HttpFormUrlEncodedContent(parameters);
using (var clientHandler = new HttpBaseProtocolFilter())
{
    clientHandler.ServerCredential = GetCredentials();

    using (var httpClient = new HttpClient(clientHandler))
    {
        var response = await httpClient.PostAsync(postUrl, formattedData);
    }
}
kiewic
  • 15,852
  • 13
  • 78
  • 101
  • This isn't compiling. HttpBaseProtocolFilter doesn't have a property "clientHandler" – Smeegs Aug 02 '15 at 19:43
  • My bad, I wanted to use `ServerCredential` – kiewic Aug 02 '15 at 19:45
  • Gotcha, I updated that part, but the HttpClient class doesn't have a constructor that takes an HttpBaseProtocolFilter object. – Smeegs Aug 02 '15 at 19:49
  • Yes, it takes an `IHttpFilter` variable. – kiewic Aug 02 '15 at 20:01
  • 1
    Okay, so it sorta works. I was able to make the request. But I was prompted for my server credentials in the app itself. The credentials are just a username, with no password. however, the constructor for the windows security credentials is asking for a resource string as the first parameter. I don't have one. So I tried using the empty constructor and assigning the value to the username property. And I was still prompted. – Smeegs Aug 02 '15 at 20:15
  • Got it working, it requires a password. So I just entered a dummy password and it worked. Thanks! – Smeegs Aug 02 '15 at 20:21
  • When using `System.Net.Http` I can set `UseDefaultCredentials = true` so that it sends the current credentials to the server and doesn't prompt the user. How can we do this with `Windows.Web.Http`? Pls help! – slayernoah Mar 21 '18 at 21:51
0

Its a bug. The workaround is to use Windows.Web

using Windows.Web.Http;
using Windows.Web.Http.Filters;
using Windows.Web.Http.Headers;

    /// <summary>
    /// Performs the post asynchronous.
    /// </summary>
    /// <typeparam name="T">The generic type parameter.</typeparam>
    /// <param name="uri">The URI.</param>
    /// <param name="objectToPost">The object to post.</param>
    /// <returns>The response message.</returns>
    private static async Task<HttpResponseMessage> PerformPostAsync<T>string uri, object objectToPost)
    {
        HttpResponseMessage response = null;

        // Just add default filter (to enable enterprise authentication)
        HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();

        using (HttpClient client = HttpService.CreateHttpClient(filter))
        {
            // Now create the new request for the post
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, new Uri(uri));

            if (objectToPost != null)
            {
                // Frist get the bytes
                byte[] bytes = UTF8Encoding.UTF8.GetBytes(JsonHelper.Serialize(objectToPost));

                // Now create the HttpBufferContent from the bytes and set the request content
                IHttpContent content = new HttpBufferContent(bytes.AsBuffer());
                content.Headers.ContentType = HttpMediaTypeHeaderValue.Parse(HttpService.JsonMediaType);
                request.Content = content;
            }

            // Now complete the request
            response = await client.SendRequestAsync(request);
        }

        return response;
    }

    /// <summary>
    /// Creates the HTTP client.
    /// </summary>
    /// <param name="filter">The filter.</param>
    /// <returns>HTTP client.</returns>
    private static HttpClient CreateHttpClient(HttpBaseProtocolFilter filter = null)
    {
        HttpClient client = new HttpClient(filter);
        client.DefaultRequestHeaders.Accept.Add(new HttpMediaTypeWithQualityHeaderValue(HttpService.JsonMediaType));
        return client;
    }
}
0

We needed to use the PCL System.Net.Http library for cross-platform so we couldn't just swap everything over to use the platform specific library. We ended up using a different HttpMessageHandler for the specific problematic cases. That handler delegates the actual call to the Windows.Web.Http library.

/// <summary>
/// A System.Net.Http message handler that delegates out to Windows.Web.Http.HttpClient.
/// </summary>
public class WindowsHttpMessageHandler : HttpMessageHandler
{
    private const string UserAgentHeaderName = "User-Agent";

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Windows.Web.Http.HttpClient client = new Windows.Web.Http.HttpClient();

        Windows.Web.Http.HttpRequestMessage webRequest = new Windows.Web.Http.HttpRequestMessage
        {
            Method = ConvertMethod(request.Method),
            RequestUri = request.RequestUri,
            Content = await ConvertRequestContentAsync(request.Content).ConfigureAwait(false),
        };

        CopyHeaders(request.Headers, webRequest.Headers);

        Windows.Web.Http.HttpResponseMessage webResponse = await client.SendRequestAsync(webRequest)
            .AsTask(cancellationToken)
            .ConfigureAwait(false);

        HttpResponseMessage response = new HttpResponseMessage
        {
            StatusCode = ConvertStatusCode(webResponse.StatusCode),
            ReasonPhrase = webResponse.ReasonPhrase,
            Content = await ConvertResponseContentAsync(webResponse.Content).ConfigureAwait(false),
            RequestMessage = request,
        };

        CopyHeaders(webResponse.Headers, response.Headers);

        return response;
    }

    private static void CopyHeaders(HttpRequestHeaders input, Windows.Web.Http.Headers.HttpRequestHeaderCollection output)
    {
        foreach (var header in input)
        {
            output.Add(header.Key, GetHeaderValue(header.Key, header.Value));
        }
    }

    private static void CopyHeaders(HttpContentHeaders input, Windows.Web.Http.Headers.HttpContentHeaderCollection output)
    {
        foreach (var header in input)
        {
            output.Add(header.Key, GetHeaderValue(header.Key, header.Value));
        }
    }

    private static void CopyHeaders(Windows.Web.Http.Headers.HttpContentHeaderCollection input, HttpContentHeaders output)
    {
        foreach (var header in input)
        {
            if (!string.Equals(header.Key, "Expires", StringComparison.OrdinalIgnoreCase) || header.Value != "-1")
            {
                output.Add(header.Key, header.Value);
            }
        }
    }

    private static void CopyHeaders(Windows.Web.Http.Headers.HttpResponseHeaderCollection input, HttpResponseHeaders output)
    {
        foreach (var header in input)
        {
            output.Add(header.Key, header.Value);
        }
    }

    private static string GetHeaderValue(string name, IEnumerable<string> value)
    {
        return string.Join(string.Equals(name, UserAgentHeaderName, StringComparison.OrdinalIgnoreCase) ? " " : ",", value);
    }

    private static Windows.Web.Http.HttpMethod ConvertMethod(HttpMethod method)
    {
        return new Windows.Web.Http.HttpMethod(method.ToString());
    }

    private static async Task<Windows.Web.Http.IHttpContent> ConvertRequestContentAsync(HttpContent content)
    {
        if (content == null)
        {
            return null;
        }

        Stream contentStream = await content.ReadAsStreamAsync().ConfigureAwait(false);
        var result = new Windows.Web.Http.HttpStreamContent(contentStream.AsInputStream());

        CopyHeaders(content.Headers, result.Headers);

        return result;
    }

    private static async Task<HttpContent> ConvertResponseContentAsync(Windows.Web.Http.IHttpContent content)
    {
        var responseStream = await content.ReadAsInputStreamAsync();
        var result = new StreamContent(responseStream.AsStreamForRead());

        CopyHeaders(content.Headers, result.Headers);

        return result;
    }

    private static HttpStatusCode ConvertStatusCode(Windows.Web.Http.HttpStatusCode statusCode)
    {
        return (HttpStatusCode)(int)statusCode;
    }
}

Though since we only needed it for a couple of calls it's not 100% tested for all use cases.

RandomEngy
  • 14,931
  • 5
  • 70
  • 113