Content Formats

The base package registers no content formatter. Each format registers its formatter in ContentFormatters and exposes payload helpers for its content type.

JSON

Both JSON formatters read application/json, application/problem+json, and any other media type with the +json suffix, such as application/vnd.example+json, and write application/json. Install either JSON package as described in Getting started. Pass serializer options when registering the formatter:

using System.Text.Json;
using Kampute.HttpClient;
using Kampute.HttpClient.Json;

using var client = new HttpRestClient();
client.UseJson(new JsonSerializerOptions(JsonSerializerDefaults.Web));

For Newtonsoft.Json, import Kampute.HttpClient.NewtonsoftJson and call UseNewtonsoftJson(settings), passing a JsonSerializerSettings instance. The registered options or settings apply to both reading responses and writing payloads.

XML

UseXml() registers an XmlFormatter, which reads application/xml, text/xml, application/problem+xml, and any other media type with the +xml suffix, and writes application/xml. Its Serializer setting chooses the serializer. With the default, XmlSerializerKind.Auto, types marked with [DataContract] or [CollectionDataContract] use DataContractSerializer, and all other types use XmlSerializer. The rule applies to responses by the requested type and to payloads by their runtime type. Set XmlSerializerKind.XmlSerializer or XmlSerializerKind.DataContractSerializer to use one serializer for every type, and DataContractSettings to configure DataContractSerializer.

The following POST writes to your API; Resource is your application's serializable model.

using Kampute.HttpClient;
using Kampute.HttpClient.Xml;

using var client = new HttpRestClient();

client.UseXml(xml => xml.Serializer = XmlSerializerKind.DataContractSerializer);

await client.PostAsXmlAsync("https://api.example.com/resources", new Resource { Name = "Example" });

Custom Formatters

A response with a +json or +xml media type, such as application/vnd.example.resource+json, does not need its own formatter, because the JSON and XML formatters read it. Implement a custom formatter when you need to write such a media type, advertise it in the Accept header, or read it differently. The client reads a response with the first registered formatter that can read it, so add a custom formatter for a +json or +xml media type before the JSON or XML formatter.

You can also implement a content formatter for an application-specific content type. Derive from HttpContentFormatter and pass the media types it reads and the media types it writes to the base constructor. Override ReadContentAsync to read responses, CreateContent to write request payloads, or both. A formatter that only reads passes an empty list of writable media types, and one that only writes passes an empty list of readable media types. To read media types beyond the listed ones, override CanReadMediaType; HasStructuredSyntaxSuffix tells whether a media type ends with a suffix such as +json. Media types accepted this way are read but not advertised in the Accept header.

This skeleton shows the overrides to implement; replace both NotImplementedException statements with your format's read and write logic before registering it.

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Kampute.HttpClient.Content.Abstracts;

public sealed class VendorFormatter : HttpContentFormatter
{
    private const string VendorMediaType = "application/vnd.example.resource+json";

    public VendorFormatter()
        : base([VendorMediaType], [VendorMediaType])
    {
    }

    protected override Task<object?> ReadContentAsync(
        HttpContent content,
        Type modelType,
        CancellationToken cancellationToken)
    {
        // Read the vendor-specific payload here.
        throw new NotImplementedException();
    }

    protected override HttpContent CreateContent(object payload, string mediaType)
    {
        // Write the vendor-specific payload here.
        throw new NotImplementedException();
    }
}

Once you have implemented the formatter, register it with the client. The following POST uses your application's Resource model and resource payload. Responses with its media type are then read into the requested .NET type, the media type is added to the Accept header, and SendObjectAsync writes request payloads with it.

using System.Net.Http;
using Kampute.HttpClient;

using var client = new HttpRestClient();

client.ContentFormatters.Add(new VendorFormatter());

var created = await client.SendObjectAsync<Resource>(
    HttpMethod.Post,
    "https://api.example.com/resources",
    resource,
    "application/vnd.example.resource+json");

SendObjectAsync throws InvalidOperationException before sending anything if no registered formatter can write the payload in the requested media type. A payload that is already an HttpContent is sent as it is.

Combine Formats

You can combine formats when an API can return more than one content type. Here, Resource is the response model from Getting started.

using Kampute.HttpClient;
using Kampute.HttpClient.NewtonsoftJson;
using Kampute.HttpClient.Xml;

using var client = new HttpRestClient();

client.UseNewtonsoftJson();
client.UseXml();

var result = await client.GetAsync<Resource>("https://api.example.com/resource");