Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpClient PostAsJsonAsync behaving different in .NET Core and Classic .NET

I am hosting a Classic .NET WebAPI endpoint in IIS that accepts POST requests to upload a document.

I have created two console applications that will connect to the WebAPI and upload a document: One is a classic .NET (v4.6.2) console application, the other is a .NET Core console application. The code is identical, but the service responds differently to each. The WebAPI seems to be unable to read the request body of the .NET Core POST request, but is able to do so with the Classic .NET POST request.

Is there something I'm missing?

WebAPI endpoint

http://localhost/WebApi/Library/api/Documents

[Route("api/Documents")]
public class DocumentsController : ApiController
{
    [HttpPost()]
    public void Create([FromBody] Document document)
    {
        if (document == null)
        {
            Debug.WriteLine("Posted document is null.");
        }
        else
        {
            Debug.WriteLine($"Document text: {document.Text}");
        }
    }
}

public class Document
{
    public string Text { get; set; }
}

.NET Core/Classic .NET console application

class Program
{
    static void Main(string[] args)
    {
        RunAsAsync().Wait();
    }

    static async Task RunAsAsync()
    {
        var httpClient = new HttpClient()
        {
            BaseAddress = new Uri("http://api-localhost"),
        };

        httpClient.DefaultRequestHeaders.Accept.Clear();
        httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        await httpClient.PostAsJsonAsync("/library/api/documents", new Document() { Text = "The document text" });
    }
}

public class Document
{
    public string Text { get; set; }
}

The thing is this: When I attach the debugger to the WebAPI the following is outputted:

POST Request from the Classic .NET console application:

Document text: The document text

POST Request from the .NET Core console application:

Posted document is null.

I am at a loss at explaining the difference in behavior.

Some sprinkling of extra information:

.NET Core console application's project.json file:

{
  "version": "1.0.0-*",
  "buildOptions": {
    "emitEntryPoint": true
  },

  "dependencies": {
    "Microsoft.AspNet.WebApi.Client": "5.2.3",
    "Microsoft.NETCore.App": {
      "type": "platform",
      "version": "1.0.1"
    },
    "System.Net.Http": "4.3.0",
    "System.Runtime.Serialization.Xml": "4.3.0"

  },

  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dnxcore50",
        "portable-net451+win8"
      ]
    }
  }
}

Classic .NET packages.config:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net462" />
  <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net462" />
</packages>

From Fiddler, the raw .NET Core post:

POST http://localhost/library/api/documents HTTP/1.1
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Accept: application/json
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: api-localhost

1c
{"Text":"The document text"}
0

And here's the raw Classic .NET POST:

POST http://localhost/library/api/documents HTTP/1.1 Accept:
application/json Content-Type: application/json; charset=utf-8 Host:
api-localhost Content-Length: 28 Expect: 100-continue Connection:
Keep-Alive

{"Text":"The document text"}

It seems to me that the weird "1c" and "0" might have something to do with my problem. But I don't see how/why they should even be there.

Update: Some observations

I also created a very basic .NET Core WebAPI end point that essentially does the same thing as the Classic .NET described up top. This proved to be able to accept the POST request from both console applications.

I also created a third console application for .NET Core where I skip using the PostAsJsonAsync and instead use PostAsync with StringContent:

public class Program
{
    static void Main(string[] args)
    {
        RunAsAsync().Wait();
    }

    static async Task RunAsAsync()
    {
        var httpClient = new HttpClient()
        {
            BaseAddress = new Uri("http://localhost"),
        };

        httpClient.DefaultRequestHeaders.Accept.Clear();
        httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        var document = new Document() { Text = "The document text." };

        var content = JsonConvert.SerializeObject(document);

        HttpContent httpContent = new StringContent(content, Encoding.UTF8, "application/json");

        await httpClient.PostAsync("/library2/api/documents", httpContent);
    }
}

public class Document
{
    public string Text { get; set; }
}

Alternating between /library/... and /library2/..., the POST request from this application is accepted by both the .NET Core and the Classic .NET WebAPIs.

Summary

On the server side I have two WebAPIs

  • /library - Classic .NET
  • /library2 - .NET Core

On the client side I have three variants of the same console applicaton:

  • ConsoleApplication1 - Classic .NET using PostAsJsonAsync
    • Works fine with both WebAPIs
  • ConsoleApp1 - .NET Core using PostAsJsonAsync
    • Fails with /library (Classic .NET), works fine with /library2 (.NET Core)
  • ConsoleApp2 - .NET Core using PostAsync
    • Works fine with both WebAPIs

The obvious work-around here is to never use PostAsJsonAsync in a .NET Core client applicaton, which is a shame since it is really convenient. :(

-S

like image 583
Sigurd Garshol Avatar asked Feb 06 '17 17:02

Sigurd Garshol


1 Answers

The underlying problem appears to be that the Classic .NET WebAPI fails to process chunked requests in it current newest version (v5.2.3). I'm going to assume that's a bug and as a work-around downgrade to v5.0 for the time being.

Special thanks to @Developer for helpful input.

-S

like image 159
Sigurd Garshol Avatar answered Oct 18 '22 23:10

Sigurd Garshol