How do I prevent ReadAsStringAsync returning a doubly escaped string?
Asked Answered
I

7

33

I have a Web API method that looks a bit like this:

    [HttpPost]
    public ResponseMessageResult Post(Thing thing)
    {
        var content = "\r";
        var httpResponseMessage = Request.CreateResponse(HttpStatusCode.Accepted, content);
        return ResponseMessage(httpResponseMessage);
    }

In some other client code, when I call:

    var content = httpResponseMessage.Content.ReadAsStringAsync().Result;

content is:

    "\\r"

but I would like it to remain as the original:

    "\r"

why is the client receiving a doubly escaped string and how can I prevent it happening?

Inexpensive answered 5/11, 2013 at 13:51 Comment(8)
The string returned is "\\r" because @"\r" is equivalent to "\\r". If you want to pass the return character instead of the escaped version, remove the verbatim modifier.Redoubtable
JSON would need it to be \\r in a string if you want the ``.Dysuria
@Ant P, no I am receiving verbatim '@"\\r"'Inexpensive
Just making sure: are you looking at the value through the debugger?Univalent
@AlexFilipovici no I'm receiving the response in a WPF app and setting it as SomeTextBox.TextInexpensive
And what's the actual value as you see it in the text box? This is very ambiguous.Redoubtable
@AntP the text box displays \r (literally) it's not ambiguous, I set the value @"\r", receive the value @"\\r" but need the value @"\r" in my client code as stated in the question.Inexpensive
I refer you back to my first comment. \r in a text box is the string "\\r" which is equal to @"\r". Remove the verbatim character.Redoubtable
I
25

It is doing what it is doing because you are cracking an egg with a sledgehammer.

When you call Request.CreateResponse<string>(HttpStatusCode statusCode, T value) you are telling web API that you would like your value serialized using one of the media type formatters. So Web API stuffs your value into an instance of ObjectContent does a whole slew of conneg code, and determines that it can use Formatter X to serialize your "object".

Chances are it is the JSONSerializer that is doing its best to try an return you the string it thinks you want rather than the CR character.

Anyway you can cut to the chase and avoid executing 70 bajillion lines of code by using the HttpContent object that is designed for sending simple strings over the wire.

[HttpPost]
public ResponseMessageResult Post(Thing thing)
{
    var content = "\r";
    var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.Accepted) {
      RequestMessage = Request,
      Content = new StringContent(content)
    };
    return ResponseMessage(httpResponseMessage);
}
Intercalation answered 5/11, 2013 at 15:39 Comment(2)
Thank you, you have greatly improved my day!Inexpensive
@Darrel, Loved this.. wasted around 3 hrs to find the charm :)Burglarize
S
65

I know that I'm probably going to cause 70 bajillion lines of code to execute by doing this (sorry Darrel Miller) but I found that it was just as effective, and less disruptive to my chosen development pattern to use this:

response.Content.ReadAsAsync<string>().Result;

or

await response.Content.ReadAsAsync<string>();

instead of this (that escapes the quotes):

response.Content.ReadAsStringAsync().Result;

Note: the ReadAsAsync is an extension method in the System.Net.Http.HttpContentExtensions, in the System.Net.Http.Formatting assembly. If it's not available in your project, you can add the NuGet package Microsoft.AspNet.WebApi.Client.

Sleepless answered 30/12, 2015 at 9:4 Comment(4)
This answer worked better for me. The extension method mentioned can be found in Microsoft.AspNet.WebApi.Client.dll as it was not part of the web api template by defaultGuaco
@Guaco I've updated the answer to include your information. In some projects (e.g. Xamarin) it's not available by default.Irony
var jobj = await response.Content.ReadAsAsync<Newtonsoft.Json.Linq.JObject>(); worked for meWhitener
If you don't have access to the server code (i.e. facing this problem while consuming a 3rd party webservice) then fixing it on the client side this way is the best optionDodona
I
25

It is doing what it is doing because you are cracking an egg with a sledgehammer.

When you call Request.CreateResponse<string>(HttpStatusCode statusCode, T value) you are telling web API that you would like your value serialized using one of the media type formatters. So Web API stuffs your value into an instance of ObjectContent does a whole slew of conneg code, and determines that it can use Formatter X to serialize your "object".

Chances are it is the JSONSerializer that is doing its best to try an return you the string it thinks you want rather than the CR character.

Anyway you can cut to the chase and avoid executing 70 bajillion lines of code by using the HttpContent object that is designed for sending simple strings over the wire.

[HttpPost]
public ResponseMessageResult Post(Thing thing)
{
    var content = "\r";
    var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.Accepted) {
      RequestMessage = Request,
      Content = new StringContent(content)
    };
    return ResponseMessage(httpResponseMessage);
}
Intercalation answered 5/11, 2013 at 15:39 Comment(2)
Thank you, you have greatly improved my day!Inexpensive
@Darrel, Loved this.. wasted around 3 hrs to find the charm :)Burglarize
E
2

In my specific scenario, facing the same issue & in the hope it might help others, it was caused by unneccessarily adding the line

httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

to my code. Removing this line in my Azure Function got rid of the double escaped.

Enplane answered 1/7, 2020 at 19:32 Comment(0)
F
1
     [HttpPost]
     public async Task<string> Post(Thing thing)
     {
         var content = "\r";
         var httpResponseMessage = Request.CreateResponse(HttpStatusCode.Accepted, content);
         var escapedString = await httpResponseMessage.Content.ReadAsStringAsync();
         return Content(escapedString, "application/json");            
     }  
Faxun answered 12/9, 2018 at 14:27 Comment(0)
R
0

You aren't receiving the value @"\\r" back, you are receiving "\\r" - you won't get a verbatim character in your response because a verbatim character is just an instruction to escape a string a particular way - the verbatim modifier itself isn't stored as part of the string. The result is the appropriately escaped version of what you applied the verbatim modifier to.

i.e. @"\r" gives you the string "\\r" which, when applied to a text box, displays as \r - an escaped backslash and an 'r'.

You just need to take the verbatim modifier off your initial assignment.

This has nothing to do with ReadAsStringAsync - you're just assigning the wrong string literal in the first place.

Redoubtable answered 5/11, 2013 at 14:7 Comment(1)
Apologies, you're correct the verbatim strings in my question were the result of being confused from looking at this for too long and trying to adapt my code for the question. I've updated the question.Inexpensive
D
0

If you are getting a literal two-character \r sequence out ("\\r" in C# form), then that is almost certainly what you are putting in. You say your Web API method "looks a bit like this". I strongly suspect the problem lies in the difference between what you have posted in your question, and what is in your actual implementation.

You need to verify that your response message contains actual carriage returns and not the literal text "\r". A text reading API is not going to look for literal C# escape sequences and handle them specially because C# string escape sequences have no meaning in plain text. If your text file contained the text c:\name.txt, then you wouldn't expect a text reading API to read it as c:<NEWLINE>ame.txt.

If you want to find and convert C#-style escape sequences, you will have to do it yourself. You can use a method like this (add additional escape sequences as necessary):

private static string Unescape(string value) {
    if (value == null)
        return null;

    var length = value.Length;
    var result = new StringBuilder(length);

    for (var i = 0; i < length; i++) {
        var c = value[i];

        if (c == '\\' && i++ < length) {
            c = value[i];

            switch (c) {
                case 'n':
                    result.Append('\n');
                    break;
                case 'r':
                    result.Append('\r');
                    break;
                case 't':
                    result.Append('\t');
                    break;
                case '\\':
                    result.Append('\\');
                    break;
                default:
                    result.Append(c);
                    break;
            }
        }
        else {
            result.Append(c);
        }
    }

    return result.ToString();
}
Domenicadomenico answered 5/11, 2013 at 14:19 Comment(0)
N
0

A solution is to deserialize the result of "response.Content.ReadAsStringAsync()" before using it. Here's an example:

public static async Task<string> DeserializeHttpResponseMessageContentStringAsync(HttpResponseMessage httpResponseMessage) =>
    JsonSerializer.Deserialize<JsonElement>(await httpResponseMessage.Content.ReadAsStringAsync()).GetProperty("message").GetString();

The extra backslashes are gone after that because we're reversing the process of serialization that added the extra backslashes.

Nephro answered 7/6 at 9:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.