158

I'm using System.Net.Http, I found several examples on the web. I managed to create this code for make a POST request:

public static string POST(string resource, string token)
{
    using (var client = new HttpClient())
    {
        client.BaseAddress = new Uri(baseUri);
        client.DefaultRequestHeaders.Add("token", token);

        var content = new FormUrlEncodedContent(new[]
        {
             new KeyValuePair<string, string>("", "")
        });

        var result = client.PostAsync("", content).Result;
        string resultContent = result.Content.ReadAsStringAsync().Result;
        return resultContent;
    }
 }

all working fine. But suppose that I want pass a third param to the POST method, a param called data. The data param is an object like this:

object data = new
{
    name = "Foo",
    category = "article"
};

how can I do that without create the KeyValuePair? My php RestAPI wait a json input, so the FormUrlEncodedContent should send the raw json correctly. But how can I do this with Microsoft.Net.Http? Thanks.

Matthew
  • 1,630
  • 1
  • 14
  • 19
IlDrugo
  • 1,899
  • 3
  • 12
  • 19
  • If I understand your question, you want to send JSON content instead of form encoded content right (and by extension you want your anonymous type to be serialized as JSON into that content)? – CodingGorilla Apr 14 '16 at 14:19
  • @CodingGorilla yes is an anonymous type. – IlDrugo Apr 14 '16 at 14:21
  • 4
    As a side note for future readers, do not use a `using` for the `HttpClient`. https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ – maxshuty Aug 07 '18 at 12:31
  • 2
    Note from Microsoft why `using` should not be used: `HttpClient is intended to be instantiated once and reused throughout the life of an application. The following conditions can result in SocketException errors: Creating a new HttpClient instance per request. Server under heavy load. Creating a new HttpClient instance per request can exhaust the available sockets.` https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/calling-a-web-api-from-a-net-client – Ogglas Mar 25 '19 at 15:26
  • Side note: `.Result` will deadlock in any environment that has a synchronization context. – EJoshuaS - Stand with Ukraine Oct 05 '21 at 21:43
  • @EJoshuaS-StandwithUkraine right, coder should perhaps look into `ConfigureAwait(false)` to negate that... – PandaWood Mar 13 '22 at 01:15

8 Answers8

209

Update: If you're using .NET 5 or newer, use this solution.

The straight up answer to your question is: No. The signature for the PostAsync method is as follows:

public Task PostAsync(Uri requestUri, HttpContent content)

So, while you can pass an object to PostAsync it must be of type HttpContent and your anonymous type does not meet that criteria.

However, there are ways to accomplish what you want to accomplish. First, you will need to serialize your anonymous type to JSON, the most common tool for this is Json.NET. And the code for this is pretty trivial:

var myContent = JsonConvert.SerializeObject(data);

Next, you will need to construct a content object to send this data, I will use a ByteArrayContent object, but you could use or create a different type if you wanted.

var buffer = System.Text.Encoding.UTF8.GetBytes(myContent);
var byteContent = new ByteArrayContent(buffer);

Next, you want to set the content type to let the API know this is JSON.

byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

Then you can send your request very similar to your previous example with the form content:

var result = client.PostAsync("", byteContent).Result

On a side note, calling the .Result property like you're doing here can have some bad side effects such as dead locking, so you want to be careful with this.

Arad Alvand
  • 8,607
  • 10
  • 51
  • 71
CodingGorilla
  • 19,612
  • 4
  • 45
  • 65
  • Okay it's very clear. Thanks for this answer. Just a question: when a `POST, PUT, DELETE` is performed, usually the API return `TRUE`, I declared the method as `string`, but when I do: `return result;` I get: `Can't Convert HttpResponseMessage in string`, should I change the method declaration? I need the string response 'cause I'll need to deserialize it after in other class method. – IlDrugo Apr 14 '16 at 14:43
  • 3
    If you need to deserialize the body of the response, then returning the string the way you have in your question (using `result.Content.ReadAsStringAsync()`) is probably just fine. Depending on your application structure, it might be better to return the `Content` object directly if you need to inspect the headers to determine what the conent type is (e.g. XML or JSON). But if you **know** it's always going to return JSON (or some other format) then just returning the response body as a string should be ok. – CodingGorilla Apr 14 '16 at 14:48
  • Sorry to ask, but if do you need to do this if the data is of type `StringContent` ? – MyDaftQuestions Sep 03 '17 at 12:59
  • 1
    @MyDaftQuestions I'm not exactly sure what you're asking, but you can pass a `StringContent` directly to `PostAsync` since it derives from `HttpContent`. – CodingGorilla Sep 03 '17 at 22:42
  • @CodingGorilla, that *was* what I was asking. Thank you :) – MyDaftQuestions Sep 04 '17 at 14:49
  • In more recent versions of .Net, there is now a much simpler way to achieve what OP wants. Since this is the accepted (and highest voted answer), please consider an edit that points new readers towards the [answer about PostAsJsonAsync](https://stackoverflow.com/a/66036888/326110) by @charles-thayer – mo. Jun 21 '22 at 12:12
100

New .NET 5+ Solution:

In .NET 5, a new class has been introduced called JsonContent, which derives from HttpContent. See in Microsoft docs

This class contains a static method called Create(), which takes any arbitrary object as a parameter, and as the name implies returns an instance of JsonContent, which you can then pass as an argument to the PostAsync method.

Usage:

var obj = new
{
    foo = "Hello",
    bar = "World",
};

JsonContent content = JsonContent.Create(obj);

await client.PostAsync("https://...", content);

Even better, you can actually use HttpClient's new PostAsJsonAsync extension method to make this as concise as possible — see the docs for this. Usage:

var obj = new
{
    foo = "Hello",
    bar = "World",
};
await client.PostAsJsonAsync("https://...", obj);
Arad Alvand
  • 8,607
  • 10
  • 51
  • 71
96

You need to pass your data in the request body as a raw string rather than FormUrlEncodedContent. One way to do so is to serialize it into a JSON string:

var json = JsonConvert.SerializeObject(data); // or JsonSerializer.Serialize if using System.Text.Json

Now, all you need to do is pass the StringContent instance to the PostAsync method:

var stringContent = new StringContent(json, UnicodeEncoding.UTF8, "application/json"); // use MediaTypeNames.Application.Json in Core 3.0+ and Standard 2.1+

var client = new HttpClient();
var response = await client.PostAsync(uri, stringContent);
Arad Alvand
  • 8,607
  • 10
  • 51
  • 71
elolos
  • 4,310
  • 2
  • 28
  • 40
  • What is `stringContent`? In my case `stringContent` value is `"\"\""`. Is this correct value? – R15 Sep 11 '19 at 09:13
  • is it possible to obtain the string result in vb from your c# code? i found out it's quite similar.... – gumuruh Jun 24 '20 at 10:33
58

A simple solution is to use Microsoft ASP.NET Web API 2.2 Client from NuGet.

Then you can simply do this and it'll serialize the object to JSON and set the Content-Type header to application/json; charset=utf-8:

var data = new
{
    name = "Foo",
    category = "article"
};

var client = new HttpClient();
client.BaseAddress = new Uri(baseUri);
client.DefaultRequestHeaders.Add("token", token);
var response = await client.PostAsJsonAsync("", data);
trydis
  • 3,905
  • 1
  • 26
  • 31
33

There's now a simpler way with .NET Standard or .NET Core:

var client = new HttpClient();
var response = await client.PostAsync(uri, myRequestObject, new JsonMediaTypeFormatter());

NOTE: In order to use the JsonMediaTypeFormatter class, you will need to install the Microsoft.AspNet.WebApi.Client NuGet package, which can be installed directly, or via another such as Microsoft.AspNetCore.App.

Using this signature of HttpClient.PostAsync, you can pass in any object and the JsonMediaTypeFormatter will automatically take care of serialization etc.

With the response, you can use HttpContent.ReadAsAsync<T> to deserialize the response content to the type that you are expecting:

var responseObject = await response.Content.ReadAsAsync<MyResponseType>();
Ken Lyon
  • 1,230
  • 10
  • 10
  • 1
    what version of .net is this using? My version can't find "Formatting" in the System.Net.Http namespace – TemporaryFix Nov 28 '18 at 16:45
  • 1
    @Programmatic You need to be using `.NET Standard` or `.NET Core`, as I mentioned. Maybe you're using `.NET Framework`? In my project, the JsonMediaTypeFormatter is being loaded from here: C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnet.webapi.client\5.2.6\lib\netstandard2.0\System.Net.Http.Formatting.dll – Ken Lyon Nov 30 '18 at 22:14
  • 1
    @Programmatic If you're already using one of those project types, it might be that you need to add an extra NuGet package. I forget exactly which were included automatically for me. In my case, it was included as part of the Microsoft.AspNetCore.App NuGet package. – Ken Lyon Nov 30 '18 at 22:20
  • 1
    I was using .NET Core, but I don't think I my solution was set to use the latest version of the c# language. I updated and it worked. Thank you – TemporaryFix Nov 30 '18 at 22:46
  • 1
    @Programmatic You're welcome. I'm glad to hear you got it working! I've added a note to my answer about the NuGet package. – Ken Lyon Dec 03 '18 at 18:55
19

@arad good point. In fact I just found this extension method (.NET 5.0):

PostAsJsonAsync<TValue>(HttpClient, String, TValue, CancellationToken)

from https://learn.microsoft.com/en-us/dotnet/api/system.net.http.json.httpclientjsonextensions.postasjsonasync?view=net-5.0

So one can now:

var data = new { foo = "Hello"; bar = 42; };
var response = await _Client.PostAsJsonAsync(_Uri, data, cancellationToken);
Charles Thayer
  • 310
  • 2
  • 4
  • In 2022, this is now the most correct solution and must be upvoted like the dickens – mo. Jun 21 '22 at 12:08
4

You have two options depending on which framework are you coding, you could just do JsonContent.Create(yourObject); if you are on .Net 5

or create and extension method and call it on your object:

public static StringContent GetStringContent(this object obj)
{
    var jsonContent = JsonConvert.SerializeObject(obj);
    var contentString = new StringContent(jsonContent, Encoding.UTF8, "application/json");
    contentString.Headers.ContentType = new MediaTypeHeaderValue("application/json");

    return contentString;
}
0
public static async Task<string> Post(string param, string code, string subject, string description)
    {
        object mydata = new
        {
            code = code,
            subject = subject,
            description = description
        };
        var myContent = JsonConvert.SerializeObject(mydata);
        var buffer = System.Text.Encoding.UTF8.GetBytes(myContent);
        var byteContent = new ByteArrayContent(buffer);
        byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

        using (HttpClient client = new HttpClient())
        {
            using (HttpResponseMessage res = await client.PostAsync(baseURL + param, byteContent))
            {
                Console.WriteLine("Nico", res);
                using (HttpContent content = res.Content)
                {
                    string data = await content.ReadAsStringAsync();
                    if (data != null) { return data; }
                }
            }
        }
        return string.Empty;
    }

in my other form

private async void button2_Click(object sender, EventArgs e) //POST
        {
            string param = "subject";
            string code = txtCode.Text; //NC101
            string subject = txtSubject.Text;
            string description = txtDescription.Text;
            var res = await RESTHelper.Post(param, code, subject, description);
            txtRes.Text = res;
        }
John Nico Novero
  • 539
  • 5
  • 10