'C# string encoding issue via mocked HttpResponseMessage

I am trying to test fetching data from a remote API. I setup the HttpClient as follows:

HttpClient httpClient = SetupHttpClient((HttpRequestMessage request) =>
{
    FileStream file = new FileStream("API_Data.json"), FileMode.Open, FileAccess.Read);
    StreamReader sr = new StreamReader(file, true);
    var response = request.CreateResponse(HttpStatusCode.OK, sr.ReadToEnd());
    response.Content.Headers.ContentEncoding.Add("UTF-8");
    return Task.FromResult(response);
});

The SetupHttpClient is not relevant here - what matters is the Response that is delivered, which as you can see is created by creating a StreamReader from a FileStream and reading that Stream into the Response.

Using the Text Visualizer I can see that the file has been successfully read into the Response Stream and all special chars such as new lines, tabs and double quotes appear correctly, as per this screenshot:

Hover_Debug_Input

On the other end, I fetch the content from the HttpResponseMessage as follows:

Stream responseStream = await response.Content.ReadAsStreamAsync();
StreamReader responseReader = null;

if (response.Content.Headers.ContentEncoding.Count > 0)
    responseReader = new StreamReader(responseStream, System.Text.Encoding.GetEncoding(response.Content.Headers.ContentEncoding.First()));
else
    responseReader = new StreamReader(responseStream, true);

string content = await responseReader.ReadToEndAsync();
return content;

Hover debugging the response again at this point shows that the data is still OK:

enter image description here

The Text Visualizer shows exactly the same as in the 1st screenshot above. Here comes the problem - even though the response content is a string, I can't access the Value property and all the retrieval mechanisms that response.Content offers are via Streams. OK fine, so I get the content via a Stream, however after having been through the Stream, all special chars are now double escaped as you can see here:

enter image description here enter image description here

This means that I now have to un-escape all these special chars in order to be able to use the returned string as json - if I don't un-escape it then the JsonDeserializer chokes when I try to deserialize it. The StreamReader also adds a (single-escaped) double-quote as the first and last char for good measure.

Googling this all I can find are references to using the correct encoding. As such, I ensured that I saved the source file as UTF-8, that I sent 'UTF-8' as the encoding with the HttpResponseMessage (response.Content.Headers.ContentEncoding.Add("UTF-8");), and that when decoding the Response 'UTF-8' was again used as the encoding (responseReader = new StreamReader(responseStream, System.Text.Encoding.GetEncoding(response.Content.Headers.ContentEncoding.First()));) - as you can see, this has not achieved the desired effect of obtaining a string which is not double-escaped.

I don't want to have to do a 'manual' un-escaping of all special chars when I get the response string from the Stream - this is a terrible hack, however it feels like the only option at the minute - either that or use reflection to get the content of the response.Content.Value property if I detect that response.Content is a String - again another hack that I don't want to do.

How can I ensure that when fetching the response.Content value via a StreamReader that I don't get double-escaped special chars?

EDIT: For clarity, here is the SetupHttpClient method:

public HttpClient SetupHttpClient(Func<HttpRequestMessage, Task<HttpResponseMessage>> response)
{
    var configuration = new HttpConfiguration();
    var clientHandlerStub = new HttpDelegatingHandlerStub((request, cancellationToken) =>
    {
        request.SetConfiguration(configuration);
        return response(request);
    });

    HttpClient httpClient = new HttpClient(clientHandlerStub);
    mockHttpClientFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(httpClient);
    return httpClient;
}

and the HttpDelegatingHandlerStub

public class HttpDelegatingHandlerStub : DelegatingHandler
{
    private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
    public HttpDelegatingHandlerStub()
    {
        _handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
    }

    public HttpDelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc)
    {
        _handlerFunc = handlerFunc;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return _handlerFunc(request, cancellationToken);
    }
}

EDIT2: A minimal, reproducible example - this requires the following packages - Microsoft.AspNet.WebApi.Core, Microsoft.Extensions.Http, Moq:

using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;

using Moq;

namespace StreamReaderEncoding
{
    internal class Program
    {
        static Mock<IHttpClientFactory> mockHttpClientFactory;

        static void Main(string[] args)
        {
            MainAsync().Wait();
        }

        static async Task MainAsync()
        {
            mockHttpClientFactory = new Mock<IHttpClientFactory>();
            string content = @"{
    ""test"": ""true""
}";
            Console.WriteLine($"content before: {content}");

            HttpClient httpClient = SetupHttpClient((HttpRequestMessage request) =>
            {
                var stream = new MemoryStream();
                var writer = new StreamWriter(stream);
                writer.Write(content);
                writer.Flush();
                stream.Position = 0;

                StreamReader sr = new StreamReader(stream, true);
                var response = request.CreateResponse(HttpStatusCode.OK, sr.ReadToEnd());
                response.Content.Headers.ContentEncoding.Add("UTF-8");
                return Task.FromResult(response);
            });

            HttpResponseMessage response = await httpClient.GetAsync("https://www.test.com");

            Stream responseStream = await response.Content.ReadAsStreamAsync();
            StreamReader responseReader = null;

            if (response.Content.Headers.ContentEncoding.Count > 0)
                responseReader = new StreamReader(responseStream, System.Text.Encoding.GetEncoding(response.Content.Headers.ContentEncoding.First()));
            else
                responseReader = new StreamReader(responseStream, true);

            content = await responseReader.ReadToEndAsync();
            Console.WriteLine($"content after: {content}");
        }

        static HttpClient SetupHttpClient(Func<HttpRequestMessage, Task<HttpResponseMessage>> response)
        {
            var configuration = new HttpConfiguration();
            var clientHandlerStub = new HttpDelegatingHandlerStub((request, cancellationToken) =>
            {
                request.SetConfiguration(configuration);
                return response(request);
            });

            HttpClient httpClient = new HttpClient(clientHandlerStub);
            mockHttpClientFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(httpClient);
            return httpClient;
        }
    }

    internal class HttpDelegatingHandlerStub : DelegatingHandler
    {
        private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
        public HttpDelegatingHandlerStub()
        {
            _handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
        }

        public HttpDelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc)
        {
            _handlerFunc = handlerFunc;
        }

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            return _handlerFunc(request, cancellationToken);
        }
    }
}

Output from the example:

content before: {
    "test": "true"
}
content after: "{\r\n    \"test\": \"true\"\r\n}"


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source