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にとって重要ではありません。
多くの研究の後、私はついにこれを見つけました。
最初に; HttpResponseMessageを直接返していました。 APIパスに沿った各ホップ内で意図的にデシリアライズしていませんでした。
問題は、実際には、「ネイティブ」なMVCシリアル化メソッドとJSON.netのメソッドを組み合わせて使用していたことです。どちらか一方だけでも問題なく、すべてのAPIのクリーンパススルーを提供します。ただし、ネイティブメソッドとJSON.netメソッドの両方からのシリアル化されたデータを組み合わせる場合、チェーンのさらに下のAPIはフォーマットを認識できず、コンテンツを再度シリアル化する必要があると誤って想定します(ネイティブメソッドを使用)。
そのため、解決策は単純にすべてのJSON.netメソッドをシリアル化プロセスから削除することであり、期待どおりの結果が得られました。
応答を受信したときではなく、応答ごとに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に適用する必要があることに注意してください。
ASP.NET Coreの場合、[Produces("text/plain")]
でアクションを修飾します。
例えば。
[HttpGet("/"), Produces("text/plain")]
public IActionResult HelloWorld() => Ok("Hello World");
これは万人向けではないかもしれませんが、完全なHttpResponseMessageを実行する代わりに、Microsoft.AspNetCore.MvcのOk()IActionResultを使用したかったのです。これには引用符で囲まれた文字列結果もあります。
"\"my string result\""
文字列を返す代わりに、属性を持つオブジェクトを返しました。元の問題の例:
return Ok(myStringValue);
私のために働いたもの:
return Ok(new { id = myStringValue });
これには、リクエストの受信側でもう少しわかりやすいという追加の利点がありました。
このスレッドのタイトルと同じ症状がありましたが、最終的には別の問題が発生する可能性があります。 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"]
結果を返す前に手でJSONにシリアル化していたため、System.Web.Http.ApiController
で同じ問題が発生しました(JsonHelper
はNewtonsoft.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);
この問題が発生したので、Nuget Packages Managerを介して_Newtonsoft.Json
_をインストールしました。次に、JSON文字列をデシリアライズするには:
string deserializedString = JsonConvert.DeserializeObject<string>(yourJsonString);
名前空間をインポートすることを忘れないでください:
_using Newtonsoft.Json;
_
@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
です。