PostAsync 在定位 windows 10 UWP 时抛出 IRandomAccessStream 错误

PostAsync throwing IRandomAccessStream error when targeting windows 10 UWP

我正在将我的 windows 8.1 应用程序移植到 windows 10 UWP,但现在调用 PostAsync 会引发异常。

这段代码在针对 8.1 时完美运行,但是当我针对 Windows 10 UWP 时,它会抛出以下异常:

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

代码

    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>", "");
}

堆栈跟踪

 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()

您尝试过使用 Windows.Web.Http.HttpClient 吗?

// 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);
    }
}

这是一个错误。解决方法是使用 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;
    }
}

我们需要使用 PCL System.Net.Http 跨平台库,因此我们不能将所有内容都换成使用特定于平台的库。对于特定的问题案例,我们最终使用了不同的 HttpMessageHandler。该处理程序将实际调用委托给 Windows.Web.Http 库。

/// <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;
    }
}

虽然我们只需要它进行几次调用,但它并未针对所有用例进行 100% 测试。