Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strings sent through Web API's gets wrapped in quotes

I've run into a small problem with my Web API's in ASP.NET 4, using C#. I'm trying to create a front-end GUI which sends and receives data through multiple Web API's. The reason for having more than one API has to do with our network, which consists of several secure zones. The servers are placed in different zones, and a simple request may have to pass through as much as 3 different API's.

Specifically, I'm sending a JSON object from the GUI to the first API. The JSON object is supposed to be forwarded to the next API and the next - and from there a new JSON object is created and returned down the same path.

The path itself works fine. The problem is that the JSON object cannot be parsed once it returns to the GUI. I am receiving a JSON string that has been wrapped in quotes, once for each API.

The string may start out like this:

Hey! I am a string, or a JSON object of sorts!

The first hop between API's gives me this:

"Hey! I am a string, or a JSON object of sorts!"

After the next hop it looks like this:

"\"Hey! I am a string, or a JSON object of sorts!\""

And by the time my GUI gets hold of it we have something like this:

"\"\\\"Hey! I am a string, or a JSON object of sorts!\\\"\""

This is where parsing fails, for obvious reasons. The JSON object itself is properly formatted, but all the quotes are causing problems for the JSON.net parser (all quotes inside the object are also wrapped multiple times).

What I've tried so far is sending the request as a application/json type, and a text/plain type. Both did the same thing. The API's return a HttpResponseMessage, which are read using ReadAsStringAsync(). I've also tried to avoid the string reading and just read directly from the HttpRequestMessage into a HttpResponseMessage and do ReadAsStringAsync() only in the GUI, but the problem still remain. The JSON string is created using JSON.nets Serialize()-method, and put into the HttpContent using StringContent(). This seems to do the job correctly. I believe that the quotes are generated when the API's and the GUI receives the HttpResponseMessage.

Any idea how I can send and receive the JSON string as a raw string, that is not treated in any way?

I can bypass this behaviour by parsing the object to a JToken or JObject at each API, and serializing it again. This isn't a good solution, however - I would rather the API's just forward the message exactly the way they got it, and not do anything with it. I looked into forwards using routing, but there is a lot of authorization-stuff going on, which do require that I use an API action, and not a redirect route.

To clarify (TLDR): The ideal solution would be for the API's to simply pass along the message without parsing or reading anything into it. As long as the source of the message is authorized, the request is encrypted and the requested URL is valid, the message itself doesn't matter much to each of the "proxy" API's.

like image 888
kiwhen Avatar asked Nov 12 '15 21:11

kiwhen


8 Answers

The quotes and backslashes are being added at each "proxy" API as the JSON string is re-serialized for each response, not when the response is received.

In your proxy API, presumably you are doing something like this (error handling omitted for brevity):

[HttpGet]
public async Task<HttpResponseMessage> GetWidget(int id)
{
    HttpClient client = new HttpClient();
    string url = "http://nextapiserver.example.org/widgets/" + id;
    string json = await client.GetStringAsync(url);
    return Request.CreateResponse(HttpStatusCode.OK, json);
}

The problem here is that Web API assumes by default that it is responsible for serializing anything you give it. For most use cases, this is exactly what you would want. But if your content is already serialized to JSON, Web API has no way of knowing that; it will happily re-serialize the string, adding extra quotes and backslashes in the process.

To pass through a JSON string untouched, you need to explicitly create the response content object (rather than letting Web API create it), making sure to set the media type to so that downstream clients will still interpret it as JSON (rather than plain text). Here is the revised code:

[HttpGet]
public async Task<HttpResponseMessage> GetWidget(int id)
{
    HttpClient client = new HttpClient();
    string url = "http://nextapiserver.example.org/widgets/" + id;
    string json = await client.GetStringAsync(url);
    HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
    response.Content = new StringContent(json, Encoding.UTF8, "application/json");
    return response;
}

I'm sure the above can improved, but that is the gist of it. Give it a shot and see if it solves the issue for you. Note that you will need to apply this fix on all the proxy APIs.

like image 75
Brian Rogers Avatar answered Sep 25 '22 03:09

Brian Rogers


For ASP.NET Core, decorate the action with [Produces("text/plain")].

Eg.

[HttpGet("/"), Produces("text/plain")]
public IActionResult HelloWorld() => Ok("Hello World");
like image 23
galdin Avatar answered Sep 25 '22 03:09

galdin


After MUCH research, I finally figured this one out.

First off; I was returning the HttpResponseMessage directly; I was not intentionally deserializing it inside each hop along the API-path.

The problem, as it turns out, was actually that we were using a mix of the "native" MVC serialization methods and JSON.net's methods. Either one by itself is fine, and provides a clean passthrough of all API's. If, however, we would combine serialized data from both native methods and JSON.net-methods, the API's further down the chain would not be able to recognize the formatting and incorrectly assume that the content should be serialized again (using native methods).

So the solution was simply to remove all JSON.net-methods from the serialization process, and we ended up with the expected results.

like image 34
kiwhen Avatar answered Sep 26 '22 03:09

kiwhen


This may not be for everyone, but I wanted to use the Ok() IActionResult from Microsoft.AspNetCore.Mvc instead of doing the full HttpResponseMessage. This also had string results in quotes:

"\"my string result\""

Instead of returning a string I returned an object with the attribute. Example of original issue:

return Ok(myStringValue);

What worked for me:

return Ok(new { id = myStringValue });

This had the added benefit of being a little more descriptive on the receiving side of the request.

like image 41
ctc Avatar answered Sep 25 '22 03:09

ctc


I had this issue so I installed Newtonsoft.Json via Nuget Packages Manager. Then to deserialize your JSON string:

string deserializedString = JsonConvert.DeserializeObject<string>(yourJsonString);

Don't forget to import the namespace:

using Newtonsoft.Json;

like image 43
Máster Avatar answered Sep 23 '22 03:09

Máster


I had the same symptom as in the title of this thread but ended up having a different problem that you may encounter. I have a column containing JSON data in SQL Server 2016 (stored in the recommended nvarchar SQL datatype). When hitting the WEB API in the browser all double-quotes in my JSON column were being escaped (backslash-quote). In my javascript GUI I was doing a JSON.parse on the resulting JSON, and not able to dereference the JSON data. I figured at first that the problem was the backslashes etc. However, it turned out that my javascript code was able to work with the escaped code just fine. The real issue was that I was try to dereference the JSON data at a sublevel before running JSON.parse on the column data.

To be more concrete...imagine one row of data in the db is returned as json ('application/json' data returned to the browser)..lets call is myJSONdata. All data for all columns are returned as JSON, but one particular column (call it myJSONcolumn) has a JSON object that is several levels deep.

Dereferencing the sublevel like this will fail:

JSON.parse(myJSONdata["myJSONcolumn"]["sublevel1"])

but this will work:

JSON.parse(myJSONdata["myJSONcolumn"])["sublevel1"]
like image 27
sarora Avatar answered Sep 26 '22 03:09

sarora


I was using Microsoft.AspNetCore.Mvc in my web API code that is the same as @ctc. E.g.

var myStringValue = "string value";
return Ok(myStringValue);

And my client code stopped wrapping the strings in quotes after removed the line sets DefaultRequestHeaders.Accept to application/json.

Original code

var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var request = new HttpRequestMessage(HttpMethod.Post, "http://<Host>/<Path>");
request.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
var httpResponseMessage = client.SendAsync(request).Result;
var responseString = httpResponseMessage.Content.ReadAsStringAsync().Result;

vs modified version (removed the second line in above snippet)

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "http://<Host>/<Path>");
request.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
var httpResponseMessage = client.SendAsync(request).Result;
var responseString = httpResponseMessage.Content.ReadAsStringAsync().Result;

My guess is that by setting the accept header, the HttpResponseMessage will try to escape the content as the accept header setting, in this case, application/json.

like image 20
Shinbo Avatar answered Sep 27 '22 03:09

Shinbo


You should accept text/plain before application/json for some reason that's matter !

For HttpClient level initialization

_client = new HttpClient()
{
    BaseAddress = new Uri("https://localhost:7267/")
};
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain")); // Should be first
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

Per request initialization: https://stackoverflow.com/a/52458995/3351489

like image 34
CorrM Avatar answered Sep 23 '22 03:09

CorrM