aspnet / AspNetWebStack

ASP.NET MVC 5.x, Web API 2.x, and Web Pages 3.x (not ASP.NET Core)
Other
858 stars 354 forks source link

PostAsJsonAsync extension method sets Content-Length to 0 in .NET Core 2.1 with any versions of System.Net.Http and System.Net.Http.Formatting #252

Closed doboczyakos closed 5 years ago

doboczyakos commented 5 years ago
var response = await _client.PostAsJsonAsync(requestUri, value, cancellationToken);

Request object is null on the server side because content length is set to 0. I've tried the newest versions of System.Net.Http and System.Net.Http.Formatting and the issue is the same. It works if I replace this with the following:

var objectContent = new ObjectContent(value, new System.Net.Http.Formatting.JsonMediaTypeFormatter(), (System.Net.Http.Headers.MediaTypeHeaderValue)null);
await objectContent.ReadAsStringAsync();
var response = await _client.PostAsync(requestUri, objectContent, cancellationToken);
dougbu commented 5 years ago

Adding @mkArtakMSFT and @danroth27

@doboczyakos have you debugged the server application to confirm the Content-Length header is present in the request and that Transfer-Encoding is not? And, is there a proxy between your client and server (proxies sometimes mess with Content-Length)?

(I edited your description to make the code a bit easier to read.)

doboczyakos commented 5 years ago

The server application is an ASP.NET web api application on local IIS. I saw that content length was 0 but I'm double checking it tomorrow. The above mentioned code is in a .NET Standard 2.0 class library and the executed client application is .NET Core 2.1

doboczyakos commented 5 years ago

Transfer-Encoding: chunked and no Content-Length header

doboczyakos commented 5 years ago

It also works if I run this from a .NET Framework 4.8 application

dougbu commented 5 years ago

This sounds like a duplicate of #197 and that was fixed in the 5.2.7 release. Are you using the latest packages in the web app?

doboczyakos commented 5 years ago

You mean on the server side?

doboczyakos commented 5 years ago

Ok. I've read the linked thread. What happens if I can make no effect on the server side?

dougbu commented 5 years ago

I meant the server side. The client sending Transfer-Encoding without Content-Length is correct according to the HTTP 1.1 spec. But, the server interpreting a missing Content-Length as 0 when the request is chunked is not correct. That's what #197 fixed.

I'm having trouble following your changes to the client and server applications. You seem to be changing package versions in the client application as well as the target framework for at least the client app. What happens when using the latest client and server Web.API package (no ASP.NET Core packages in the server app) and targeting .NET Core 3.0 in the client and .NET Framework 4.7.2 (say) in the server? Does the problem reproduce when you move the client to .NET Core 2.2 or 2.1?

doboczyakos commented 5 years ago

The client is targeting .NET Core 2.1 (and tested with 2.2 too) The server side is .NET Framework 4.8 I've made no changes on the server side.

mkArtakMSFT commented 5 years ago

Thank you for filing this issue. In order for us to investigate this issue, please provide a minimalistic repro project that illustrates the problem.

pranavkm commented 5 years ago

Ok. I've read the linked thread. What happens if I can make no effect on the server side?

@doboczyakos unfortunately a fix on the server would be required. You could consider changing the client to buffer and send a Content-Length if the server cannot be modified. However, beyond that, I'm not sure there's much we could do here.

doboczyakos commented 5 years ago

@doboczyakos unfortunately a fix on the server would be required. You could consider changing the client to buffer and send a Content-Length if the server cannot be modified. However, beyond that, I'm not sure there's much we could do here.

I have the workaround which is quite funny but works. I'm wondering why the client can't have this as a feature especially when .NET Framework has this bug on server side. Please let me understand your way of thinking about it.

pranavkm commented 5 years ago

@doboczyakos the bug was fixed on the server. We generally wouldn't accept a workaround for a bug fix that has a known patch.

Rwing commented 5 years ago

I had the same problem now, and I used Fiddler to confirm the 'Content-Length' really not in request headers.

mkArtakMSFT commented 5 years ago

Closing as there is no pending action here.

gojanpaolo commented 4 years ago

Hello, we're also getting this issue. We could send a minimal reproducible example through a private message because a subscription key should be added to the headers.

But basically, server returns Bad Request when we use PostAsJsonAsync.

var response = await httpClient.PostAsJsonAsync(
    @"https://apidata.guidestar.org/essentials/v2",
    new { search_terms = "test" });

Everything works as expected when we use PostAsync.

var response = await httpClient.PostAsync(
    @"https://apidata.guidestar.org/essentials/v2",
    new StringContent(JsonConvert.SerializeObject(new { search_terms = "test" }))
    {
        Headers = { ContentType = new MediaTypeHeaderValue("application/json") }
    });

.NET Core 3.1 Microsoft.AspNet.WebApi.Client 5.2.7

danroth27 commented 4 years ago

@gojanpaolo PostAsJsonAsync sends a chunked encoded request. It sounds like the API you're trying to call doesn't support that. If you need to send a request with a set content length, then there isn't a way to do that with PostAsJsonAsync and you'll need to buffer the request yourself as you've done in your second example.

gojanpaolo commented 4 years ago

@danroth27 I see. Is it possible for PostAsJsonAsync to support this in the future? Thanks!

danroth27 commented 4 years ago

We're actually working on some new extension methods for HttpClient based on System.Text.Json. Adding @terrajobst to capture this requirement.

terrajobst commented 4 years ago

@danroth27

Are you saying PostAsJsonAsync should also support sending non-chunked data to the server, as in in one big shot?

gperrego commented 4 years ago

@gojanpaolo PostAsJsonAsync sends a chunked encoded request. It sounds like the API you're trying to call doesn't support that. If you need to send a request with a set content length, then there isn't a way to do that with PostAsJsonAsync and you'll need to buffer the request yourself as you've done in your second example.

@danroth27 I'm seeing the similar behavior in the .net standard implementation of HttpClient.PostAsync. When calling it from .net 472 the content length is in the header, when calling it from .net Core 2.1 it is using chunked.

var callTask = Task.Run(() => HttpClient.PostAsync( PostUri, request, new JsonMediaTypeFormatter()));

  1. Is there a way to use this shared code from both clients?
  2. If not I guess because we wrote the API also I could try to add chunked support to the API? But then I'm not sure if doing so would cause the 472 framework call would fail?

I can open this as a new issue if needed?

Thanks, Greg

danroth27 commented 4 years ago

@gperrego Probably best to open a new issue. It is strange that the behavior is different depending on whether you're running on .NET 4.7.2 vs .NET Core 2.1.