For a long time if you wanted to post JSON using a HttpClient, you would do it like this:

// Create the HttpClient
var client = new HttpClient();

// Set the base address to simplify maintenance & requests
client.BaseAddress = new Uri("https://localhost:5001/");

// Create an object
var person = new Person() { Names = "James Bond", DateOfBirth = new DateTime(1990, 1, 1) };

// Serialize class into JSON
var payload = JsonSerializer.Serialize(person);

// Wrap our JSON inside a StringContent object
var content = new StringContent(payload, Encoding.UTF8, "application/json")

// Post to the endpoint
var response = await client.PostAsync("Create", content);

The Person object we are submitting here is this one:

public class Person
    public string Names { get; set; }
    public DateTime DateOfBirth { get; set; }

The code here relies of the fact that to do a POST to a HttpClient, it expects a StringContent object that you have to construct in advance.

This is very tedious.

Which is why you can use the extensions in the System.Net.Http.Json namespace to simplify this. The extension method we can employ here is PostAsJsonAsync.

This extension method does the heavy lifting of accepting your object and serializing it for posting to the target URL.

This is achieved like so:

// Create a second person
var otherPerson = new Person() { Names = "Jason Bourne", DateOfBirth = new DateTime(2000, 1, 1) };

// Post to the endpoint
response = await client.PostAsJsonAsync("Create", otherPerson);

The code is thus vastly simplified.

There is also an overload that allows you to pass a CancellationToken.

// Post to the endpoint with a cancellation token
response = await client.PostAsJsonAsync("Create", otherPerson, ctx);

If your upstream API is very conservative about the JSON it accepts, or has some non-default configurations, you can configure how you want the serialization of your object to be done using a JsonSerializerOptions object.

Like so:

// Configure required JSON serialization options
var options = new JsonSerializerOptions()
    AllowTrailingCommas = true,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    IgnoreReadOnlyProperties = true,
    NumberHandling = JsonNumberHandling.WriteAsString,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true

// Post to the endpoint with custom options
response = await client.PostAsJsonAsync("Create", otherPerson, options);

The beauty of this is that for most simple cases you can use the HttpClient directly and not have to depend on libraries like Refit, RestSharp and Flurl

The code is in my Github.

Happy hacking!