web-dev-qa-db-ja.com

Web APIを介して送信された文字列は引用符で囲まれます

ASP.NET 4のWeb APIでC#を使用すると、小さな問題が発生しました。複数のWeb APIを介してデータを送受信するフロントエンドGUIを作成しようとしています。複数のAPIを持つ理由は、いくつかの安全なゾーンで構成されるネットワークに関係しています。サーバーは異なるゾーンに配置され、単純なリクエストは3つの異なるAPIを通過する必要がある場合があります。

具体的には、GUIから最初のAPIにJSONオブジェクトを送信しています。 JSONオブジェクトは次のAPIと次のAPIに転送されることになっています。そこから新しいJSONオブジェクトが作成され、同じパスで返されます。

パス自体は正常に機能します。問題は、JSONオブジェクトがGUIに戻ると解析できないことです。 APIごとに1回、引用符で囲まれたJSON文字列を受け取ります。

文字列は次のように始まります。

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

API間の最初のホップはこれを私に与えます:

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

次のホップの後、次のようになります。

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

そして、私のGUIがそれを手に入れるまでには、次のようなものがあります。

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

これは、明らかな理由で解析が失敗する場所です。 JSONオブジェクト自体は適切にフォーマットされていますが、すべての引用符がJSON.netパーサーに問題を引き起こしています(オブジェクト内のすべての引用符も複数回ラップされます)。

私がこれまでに試したのは、アプリケーション/ jsonタイプとtext/plainタイプとしてリクエストを送信することです。両方とも同じことをしました。 APIはHttpResponseMessageを返します。これは、ReadAsStringAsync()を使用して読み取られます。また、文字列の読み取りを回避し、HttpRequestMessageからHttpResponseMessageに直接読み取り、GUIでのみReadAsStringAsync()を実行しようとしましたが、問題は残ります。 JSON文字列はJSON.nets Serialize()メソッドを使用して作成され、StringContent()を使用してHttpContentに入れられます。これは仕事を正しく行うようです。 APIとGUIがHttpResponseMessageを受信すると、引用符が生成されると考えています。

JSON文字列を生の文字列として送受信する方法はありますか?

各APIでオブジェクトをJTokenまたはJObjectに解析し、再度シリアル化することにより、この動作をバイパスできます。ただし、これは良い解決策ではありません。APIがメッセージを受け取ったとおりに転送し、何もしないでください。ルーティングを使用して転送を検討しましたが、多くの承認処理が行われているため、リダイレクトルートではなくAPIアクションを使用する必要があります。

明確にするため(TLDR):理想的な解決策は、APIがメッセージを解析したり読み取ったりすることなく、単にメッセージを渡すことです。メッセージのソースが許可されている限り、リクエストは暗号化され、リクエストされたURLは有効です。メッセージ自体は、各「プロキシ」APIにとって重要ではありません。

31
kiwhen

多くの研究の後、私はついにこれを見つけました。

最初に; HttpResponseMessageを直接返していました。 APIパスに沿った各ホップ内で意図的にデシリアライズしていませんでした。

問題は、実際には、「ネイティブ」なMVCシリアル化メソッドとJSON.netのメソッドを組み合わせて使用​​していたことです。どちらか一方だけでも問題なく、すべてのAPIのクリーンパススルーを提供します。ただし、ネイティブメソッドとJSON.netメソッドの両方からのシリアル化されたデータを組み合わせる場合、チェーンのさらに下のAPIはフォーマットを認識できず、コンテンツを再度シリアル化する必要があると誤って想定します(ネイティブメソッドを使用)。

そのため、解決策は単純にすべてのJSON.netメソッドをシリアル化プロセスから削除することであり、期待どおりの結果が得られました。

9
kiwhen

応答を受信したときではなく、応答ごとにJSON文字列が再シリアル化されるため、各「プロキシ」APIに引用符とバックスラッシュが追加されます。

プロキシAPIでは、おそらく次のようなことをしています(簡潔にするためにエラー処理は省略されています)。

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

ここでの問題は、Web APIはデフォルトで、ユーザーが提供したものをシリアル化する責任があると想定していることです。ほとんどのユースケースでは、これはまさにあなたが望むものです。ただし、コンテンツが既に JSONにシリアル化されている場合、Web APIにはそれを知る方法がありません。幸いなことに文字列を再シリアル化し、プロセスに余分な引用符とバックスラッシュを追加します。

JSON文字列をそのまま渡すには、(Web APIで作成するのではなく)明示的に応答コンテンツオブジェクトを作成し、ダウンストリームクライアントがプレーンテキストではなくJSONとして解釈するようにメディアタイプを設定する必要があります)。修正されたコードは次のとおりです。

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

上記は改善できると確信していますが、それが要点です。試してみて、問題が解決するかどうかを確認してください。この修正をallプロキシAPIに適用する必要があることに注意してください。

48
Brian Rogers

ASP.NET Coreの場合、[Produces("text/plain")]でアクションを修飾します。

例えば。

[HttpGet("/"), Produces("text/plain")]
public IActionResult HelloWorld() => Ok("Hello World");
6
gldraphael

これは万人向けではないかもしれませんが、完全なHttpResponseMessageを実行する代わりに、Microsoft.AspNetCore.MvcのOk()IActionResultを使用したかったのです。これには引用符で囲まれた文字列結果もあります。

"\"my string result\""

文字列を返す代わりに、属性を持つオブジェクトを返しました。元の問題の例:

return Ok(myStringValue);

私のために働いたもの:

return Ok(new { id = myStringValue });

これには、リクエストの受信側でもう少しわかりやすいという追加の利点がありました。

1
ctc

このスレッドのタイトルと同じ症状がありましたが、最終的には別の問題が発生する可能性があります。 SQL Server 2016にJSONデータを含む列があります(推奨されるnvarchar SQLデータ型に格納されています)。ブラウザーでWEB APIにアクセスすると、JSON列の二重引用符がすべてエスケープされていました(バックスラッシュ引用符)。私のjavascript GUIでは、結果のJSONでJSON.parseを実行していましたが、JSONデータを逆参照できませんでした。最初は問題はバックスラッシュなどであると考えました。しかし、私のJavaScriptコードはエスケープされたコードでうまく機能することがわかりました。本当の問題は、サブレベルでJSONデータを逆参照しようとしたことですbefore列データでJSON.parseを実行しました。

より具体的には、dbのデータの1行がjsonとして返されることを想像してください(「application/json」データがブラウザに返されます)。lets呼び出しはmyJSONdataです。すべての列のすべてのデータはJSONとして返されますが、1つの特定の列(myJSONcolumnと呼ばれます)には、数レベルの深さのJSONオブジェクトがあります。

このようなサブレベルの参照解除は失敗します。

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

しかし、これは動作します:

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

結果を返す前に手でJSONにシリアル化していたため、System.Web.Http.ApiControllerで同じ問題が発生しました(JsonHelperNewtonsoft.Json.JsonConvertをラップするヘルパークラスです):

var jason = JsonHelper.SerializeToJson(result);
return Ok(jason);

これは、同じ"\"{ ... }\""効果につながりました。シリアル化プロセスにはいくつかの特定のルールがあるため、事前シリアル化が必要でした。

幸いにも、同じことを行うことができるSystem.Web.Http.Results.JsonResult<T>があります。

return new JsonResult<MyDataType>(result, JsonHelper.Settings, Encoding.UTF8, this);
0
Dee J. Doena

この問題が発生したので、Nuget Packages Managerを介して_Newtonsoft.Json_をインストールしました。次に、JSON文字列をデシリアライズするには:

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

名前空間をインポートすることを忘れないでください:

_using Newtonsoft.Json;_

0
Santiago Trejo

@ctcと同じWeb APIコードでMicrosoft.AspNetCore.Mvcを使用していました。例えば。

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

また、行セットDefaultRequestHeaders.Acceptからapplication/jsonを削除した後、クライアントコードは引用符で文字列をラップすることを停止しました。

元のコード

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;

変更されたバージョン(上記のスニペットの2行目を削除)

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;

私の推測では、acceptヘッダーを設定すると、HttpResponseMessageはコンテンツをacceptヘッダー設定としてエスケープしようとします。この場合はapplication/jsonです。

0
Shinbo