Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpClient.SendAsync not sending request body

I am using the ASP.NET Web API Client Libraries for .NET 4.0 (Microsoft.AspNet.WebApi.Client version 4.0.30506.0).

I need to send an HTTP DELETE with a request body. I have coded it as follows:

using (var client = new HttpClient())
{
    client.BaseAddress = Uri;
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    // I would normally use httpClient.DeleteAsync but I can't because I need to set content on the request.
    // For this reason I use httpClient.SendAsync where I can both specify the HTTP DELETE with a request body.
    var request = new HttpRequestMessage(HttpMethod.Delete, string.Format("myresource/{0}", sessionId))
      {
        var data = new Dictionary<string, object> {{"some-key", "some-value"}};
        Content = new ObjectContent<IDictionary<string, object>>(data, new JsonMediaTypeFormatter())
      };
    var response = await client.SendAsync(request);
    // code elided
}

Per Fiddler, the request body is never serialized:

DELETE http://localhost:8888/myApp/sessions/blabla123 HTTP/1.1 Accept: application/json Content-Type: application/json; charset=utf-8 Host: localhost:8888 Content-Length: 38 Expect: 100-continue

The response from the server:

HTTP/1.1 408 Request body incomplete Date: Sun, 10 Aug 2014 17:55:17 GMT Content-Type: text/html; charset=UTF-8 Connection: close Cache-Control: no-cache, must-revalidate Timestamp: 13:55:17.256 The request body did not contain the specified number of bytes. Got 0, expected 38

I have tried a number of workarounds, including changing the type being serialized to something else, doing the serialization myself with JsonSerialize, changing the HTTP DELETE to PUT, etc...

Nothing worked. Any help would be much appreciated.

like image 338
Philippe Avatar asked Aug 10 '14 18:08

Philippe


2 Answers

I resolved the issue, though it does not make sense. I noticed that if I changed my call to HTTP PUT or POST, it still failed to serialize the Content as a request body. That was rather strange as previous PUTs and POSTs were successful. After doing a ton of debugging into framework libraries (using Reflector), I finally got to the only thing left that was "different."

I am using NUnit 2.6.2. The structure of my test is:

[Test]
async public void Test()
{
  // successful HTTP POST and PUT calls here
  // successful HTTP DELETE with request body here (after 
  //       moving it from the TearDown below)
}

[TearDown]
async public void TerminateSession()
{
  // failed HTTP DELETE with request body here
}

Why does this fail in the TearDown but not in the Test itself? I have no idea. Is something going on with the TearDown attribute or with the use of the async keyword (since I await async calls)?

I am not sure what it is causing this behavior, but I do know now that I can submit an HTTP DELETE with a request body (as outlined in my code sample in the question).

Another solution that worked is as follows:

[Test]
async public void Test()
{
  // create and use an HttpClient here, doing POSTs, PUTs, and GETs
}

// Notice the removal of the async keyword since now using Wait() in method body
[TearDown]
public void TerminateSession()
{
  // create and use an HttpClient here and use Wait().
  httpClient.SendAsync(httpRequestMessage).Wait();
}
like image 137
Philippe Avatar answered Nov 10 '22 05:11

Philippe


I know it's never quite that helpful to say, "don't do it that way", but in this case I think it makes sense to split the calls into a DELETE followed or preceeded by a POST or PUT.

The HTTP RFC doesn't explicitly opine on the matter, so technically it means that we can. The other question, however, is should we do it.

In cases such as this I would look for other implementations to see what is the de facto standard. As you've found in the .net implementation, it appears that the designers did not expect to send a body with the DELETE call. So, let's look at another popular (and very different impl) Python Requests:

>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'

No body here other. So, if the spec authors didn't mention it, and popular implementations assume that there's no body, then the principle of least surprise means we shouldn't do it either.

So, if you can change the API, it will be easier on clients of the API to split into two calls. Otherwise, you'll likely have to resort to custom hackery to cram the body into a DELETE call.

The good news is that you've likely found a bug in the .net framework, which is an achievement in and of itself. Clients advertising a non-zero Content-Length without actually sending it are broken.

like image 1
Chris Scott Avatar answered Nov 10 '22 05:11

Chris Scott