Overview
Codebelt.Extensions.Carter adds the two primitives that the rest of the repository builds on: endpoint metadata helpers for minimal API route builders, and an abstract response negotiator base for Carter. It is the package to start with when you want Carter endpoints to advertise concrete response metadata or when you need to implement your own formatter-backed negotiator.
The package does not ship a ready-made JSON, YAML, or XML negotiator by itself. Those concrete negotiators live in the sibling packages, while this package defines the reusable negotiation flow they extend.
Key APIs
EndpointConventionBuilderExtensions adds Produces<TResponse> and Produces(int) to IEndpointConventionBuilder, so Carter route mappings can attach ProducesResponseTypeMetadata without dropping down to lower-level metadata APIs.
ConfigurableResponseNegotiator<TOptions> is the base class for negotiators that serialize models through a StreamFormatter<TOptions>, while also enforcing option contracts for content negotiation, exception details, and parameter validation.
ConfigurableResponseNegotiator<TOptions>.CanHandle inspects the incoming Accept header against the configured supported media types and stores the matched content type for the later write step.
ConfigurableResponseNegotiator<TOptions>.GetEncoding resolves the response encoding from Accept-Charset quality values and falls back to the negotiator's default encoding when the request does not name a usable charset.
ConfigurableResponseNegotiator<TOptions>.Handle<T> streams the formatter output into the ASP.NET Core response body and sets the negotiated ContentType with the selected charset.
Basic usage
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Codebelt.Extensions.Carter;
using Codebelt.Extensions.Xunit;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Metadata;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
using Xunit;
namespace MyProject.Tests;
public class EndpointMetadataTest : Test
{
public EndpointMetadataTest(ITestOutputHelper output) : base(output) { }
[Fact]
public void ShouldAdvertiseJsonResponseMetadata()
{
var builder = new FakeEndpointConventionBuilder();
builder.Produces<OrderStatusResponse>(StatusCodes.Status202Accepted, "application/json");
var endpoint = new RouteEndpointBuilder(_ => Task.CompletedTask, RoutePatternFactory.Parse("/orders/{id}"), 0);
foreach (var convention in builder.Conventions) { convention(endpoint); }
var metadata = Assert.Single(endpoint.Metadata.OfType<IProducesResponseTypeMetadata>());
TestOutput.WriteLine($"Produces {metadata.StatusCode} as {string.Join(", ", metadata.ContentTypes)}.");
Assert.Equal(typeof(OrderStatusResponse), metadata.Type);
}
private sealed record OrderStatusResponse(string Id, string State);
private sealed class FakeEndpointConventionBuilder : IEndpointConventionBuilder
{
public List<Action<EndpointBuilder>> Conventions { get; } = [];
public void Add(Action<EndpointBuilder> convention) => Conventions.Add(convention);
}
}
Use this pattern when you want Carter route definitions to declare the response contract that tooling and consumers should expect. It matters because the package turns a minimal route builder into a place where response metadata stays explicit and close to the endpoint mapping.
Installation
dotnet add package Codebelt.Extensions.Carter
Usage guidance
Choose this package when you need the Produces route-builder extensions or when you are building a custom Carter negotiator on top of ConfigurableResponseNegotiator<TOptions>. If you only need a ready-made serializer for JSON, YAML, or XML responses, the sibling negotiator packages are a better fit than implementing the abstraction yourself.