2
votes

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()
3
Can you provide your GetParameters() and GetCredentials() implementation? Does the error reproduce when the server does not require authentication? - kiewic
@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
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

3 Answers

2
votes

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);
    }
}
0
votes

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
votes

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.