2013-06-26 6 views
9

Mając prosty test C# Jednostka:C# Http.Response Stream Zwraca pusty ciąg z application/json Content Type

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

i prosty zestaw działań MVC konsumować i echo zamieszczone dane z powrotem do testów jednostkowych (Zauważ, że kod w obu działań jest identyczny):

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

gdybym dodawać do pierwszej akcji otrzymuję ciąg zawierający Wysłany json, czy mogę napisać do drugiej skargi dostaję pusty ciąg.

Aby sprawy były bardziej interesujące, jeśli zmienię typ zawartości w teście jednostki na "tekst/zwykły", obie czynności zwrócą oczekiwane wartości.

Czy ktoś może rzucić jakiekolwiek światło na to, dlaczego tak się dzieje?

Warto również zauważyć, że długość żądania w przypadku obu działań w obu okolicznościach wydaje się mieć odpowiednią długość.

Dalsze informacje o środowisku: Test jednostkowy znajduje się w osobnym projekcie testu MS. Akcje są w pustym projekcie MVC 4.0 (Net 4.0).

Odpowiedz

11

Jest możliwe, że gdzieś w potoku żądania Request.InputStream został już odczytany. W tym przypadku jego pozycja jest już na końcu i oczywiście ReadToEnd nic nie czyta i zwraca pusty ciąg. To jest przyczyna problemu w naszym przypadku. Resetowanie pozycji rozwiązuje problem:

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

Aktualizacja. Po drobnym wykopaniu źródeł odkryłem również, dlaczego pozycja została przesunięta. Okazuje się, że Request.InputStream jest stosowany w JsonValueProviderFactory w następujący sposób:

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

Ta metoda jest wywoływana przez ControllerActionInvoker do pobrania wartości z żądania i powiązać je z parametrami działania. Zauważ, że jest to jedyne miejsce, w którym Request.InputStream jest używane przez wszystkie MVC.

Z tego powodu, jeżeli typ treści żądania jest json, wywoływana jest powyższa metoda, strumień wejściowy zostaje przesunięty i próbuje ponownie go odczytać bez resetowania pozycji. Jednak gdy typem zawartości jest zwykły tekst, MVC nie próbuje odczytać żądania za pomocą deserializacji json, strumień wejściowy nie jest czytany przed wywołaniem w kontrolerze i wszystko działa zgodnie z oczekiwaniami.

+0

Dobrze zauważył pan. Sprawdzałem długość treści, ale nie sądziłem, że rozważę przeczytaną pozycję. Naprawdę pomocne informacje tam również. Bardzo zobowiązany. +1 (przepraszam, że mój przedstawiciel nie jest wystarczająco wysoki, aby oddać głos). –

+0

Tak słodko, a ty dałeś nam juniorom możliwość wypróbowania tego –