Web API Post hit before HttpWebRequest has finishe

2019-07-15 00:12发布

In our app (Silverlight 5 out-of-browser client hitting a WebApi server) we routinely use an HttpClient for posting/getting/deleting and so on all our entities between client and server. This all works fine most of the time, but recently we have run into an issue when uploading (posting) larger entities (> 30/35mb): we start the streaming process and BEFORE it is finished our Post method on the Web API is hit, receiving a null entity.

We can't understand what is going on, and suspect there must be some timing issue related since it all depends on the size of the upload.

To further explain, our client in summary is doing this:

HttpResponseMessage response = await _client.SendAsync(request);
string jsonResult = await response.Content.ReadAsStringAsync();

... where _client is our HttpClient and request our HttpRequestMessage. In case it is also relevant (I am trying not to flood the question with code :), the content in the request is created like this:

request.Content = new StringContent(JsonConvert.SerializeObject(content), Encoding.UTF8, "application/json");

Well, when we debug this the Post method on our server is hit before the await _client.SendAsync(request) finishes, which sort of "explains" why it is receiving a null entity in such cases (larger entities), where when it works that await call is finished and THEN the Post is hit.

In case if sheds more light into it, due to certain limitations on the HttpClient (regarding access to AllowWriteStreamBuffering), we have also tested an equivalent scenario but using directly an HttpWebRequest... unfortunately, the behavior is exactly the same. This is the relevant extract:

httpRequest.BeginGetRequestStream(RequestStreamCallback, httpRequest);

(where httpRequest is our HttpWebRequest with AllowWriteStreamBuffering = false), and the callback to handle the request stream is as follows:

private void RequestStreamCallback(IAsyncResult ar)
{
   var request = ar.AsyncState as System.Net.HttpWebRequest;
   if (request != null)
   {
      var requestStream = request.EndGetRequestStream(ar);
      var streamWriter = new StreamWriter(requestStream) {AutoFlush = true};
      streamWriter.Write(_jsonContent);
      streamWriter.Close();
      requestStream.Close(); // Belt and suspenders... shouldn't be needed

      // Make async call for response
      request.BeginGetResponse(ResponseCallback, request);
   }
}

Again, for larger entities when we debug the Post method on the Web API is hit (with a null parameter) BEFORE the streamWriter.Write finalizes and the streamWriter.Close is hit.

We've been reading all over the place and fighting with this for days on now. Any help will be greatly appreciated!

1条回答
一夜七次
2楼-- · 2019-07-15 00:48

In case somebody runs into this, I finally figured out what was going on.

In essence, the model binding mechanism in the Web API Post method was throwing an exception when de-serializing the JSON, but the exception was somewhat "hidden"... at least if you did not know that much about the inner workings of the Web API, as was my case.

My Post method originally lacked this validation check:

var errors = "";
if (!ModelState.IsValid)
{
  foreach (var prop in ModelState.Values)
  {
    foreach (var modelError in prop.Errors.Where(modelError => modelError != null))
    {
     if (modelError.Exception != null)
     {
       errors += "Exception message: " + modelError.Exception.Message + Environment.NewLine;
       errors += "Exception strack trace: " + modelError.Exception.StackTrace + Environment.NewLine;
     }
     else
       errors += modelError.ErrorMessage + Environment.NewLine;

     errors += " --------------------- " + Environment.NewLine + Environment.NewLine;
   }
 }

 return Request.CreateErrorResponse(HttpStatusCode.NoContent, errors);
}

This is a "sample" check, the main idea being verifying the validity of the ModelState... in our breaking scenarios is wasn't valid because the Web API hadn't been able to bind the entity, and the reason could be found within the Errors properties of the ModelState.Values. The Post was being hit ok, but with a null entity, as mentioned.

By the way, the problem was mainly caused by the fact that we weren't really streaming the content, but using a StringContent which was attempted to be de-serialized in full... but that is another story, we were mainly concerned here with not understanding what was breaking and where.

Hope this helps.

查看更多
登录 后发表回答