10

簡単な C# 単体テストを行う:

[TestMethod]
public void JsonPostTest()
{
  string testUri1 = "http://localhost:1293/Test/StreamDebug";
  string testUri2 = "http://localhost:1293/Test/StreamDebug2?someParameter=abc";

  string sampleJson = @"
  {
    ""ID"": 663941764,
    ""MessageID"": ""067eb623-7580-4d82-bb5c-f5d7dfa69b1e""
  }";

  HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(testUri1);
  EmailConfig config = GetTestConfigLive();

  // Add postmark headers
  request.Accept = "application/json";
  request.ContentType = "application/json";

  request.Method = "POST";
  using (var outStream = new StreamWriter(request.GetRequestStream()))
  {
    outStream.Write(sampleJson);
  }

  // Get response
  HttpWebResponse response = (HttpWebResponse)request.GetResponse();
  string resultText = "";
  using (var reader = new StreamReader(response.GetResponseStream()))
  {
    resultText = reader.ReadToEnd();
  }

  Assert.Inconclusive();
}

そして、投稿されたデータを消費して単体テストにエコーバックする MVC アクションの単純なセット (両方のアクションのコードが同一であることに注意してください)。

[HttpPost]
[ValidateInput(false)]
public ActionResult StreamDebug()
{
  string postbody = "";
  using (StreamReader reader = new StreamReader(Request.InputStream, Encoding.UTF8))
  {
    postbody = reader.ReadToEnd();
  }
  return this.Content(postbody);
}

[HttpPost]
[ValidateInput(false)]
public ActionResult StreamDebug2(string someParameter)
{
  string postbody = "";
  using (StreamReader reader = new StreamReader(Request.InputStream, Encoding.UTF8))
  {
    postbody = reader.ReadToEnd();
  }
  return this.Content(postbody);
}

最初のアクションに投稿すると、投稿された json を含む文字列が取得され、2 番目のアクションに投稿すると空の文字列が取得されます。

さらに興味深いことに、単体テストのコンテンツ タイプを "text/plain" に変更すると、両方のアクションが期待値を返します。

なぜこれが起こっているのか、誰かが光を当てることができますか?

また、両方の状況下での両方のアクションのリクエストの長さが適切な長さであるように見えることも注目に値します。

その他の環境情報: 単体テストは別の MS テスト プロジェクトにあります。アクションは、空の MVC 4.0 プロジェクト (Net 4.0) にあります。

4

1 に答える 1

13

要求パイプラインのどこかRequest.InputStreamが既に読み取られている可能性があります。この場合、その位置はすでに最後にあり、もちろんReadToEnd何も読み取らず、空の文字列を返します。これが私たちの場合の問題の根本です。位置をリセットすると問題が解決します。

[HttpPost]
[ValidateInput(false)]
public ActionResult StreamDebug2(string someParameter)
{
  string postbody = "";
  Request.InputStream.Position = 0;
  using (StreamReader reader = new StreamReader(Request.InputStream, Encoding.UTF8))
  {
    postbody = reader.ReadToEnd();
  }
  return this.Content(postbody);
}

アップデート。ソースを少し掘り下げた後、位置がシフトした理由もわかりました。が次のようにRequest.InputStream使用されていることがわかります。JsonValueProviderFactory

// System.Web.Mvc.JsonValueProviderFactory
private static object GetDeserializedObject(ControllerContext controllerContext)
{
    if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
    {
        return null;
    }
    StreamReader streamReader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
    string text = streamReader.ReadToEnd();
    if (string.IsNullOrEmpty(text))
    {
        return null;
    }
    JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
    return javaScriptSerializer.DeserializeObject(text);
}

このメソッドはControllerActionInvoker、リクエストから値を取得し、それらをアクション パラメータにバインドするために呼び出されます。これは、Request.InputStreamすべての MVC で使用される唯一の場所であることに注意してください。

したがって、リクエストのコンテンツ タイプが json の場合、上記のメソッドが呼び出され、入力ストリームがシフトされ、位置をリセットせずにもう一度読み込もうとすると失敗します。ただし、コンテンツ タイプがプレーン テキストの場合、MVC は json の逆シリアル化を使用して要求を読み取ろうとせず、入力ストリームはコントローラーでの呼び出しの前に読み取られず、すべてが期待どおりに機能します。

于 2013-06-26T12:29:44.687 に答える