Stats
Actions
Tags
Help us improve
Share bugs, ideas, or general feedback.
From dotnet-ai-kit
Use when configuring API response formats, custom formatters, or Accept header handling.
npx claudepluginhub faysilalshareef/dotnet-ai-kitHow this skill is triggered — by the user, by Claude, or both
Slash command
/dotnet-ai-kit:content-negotiationWhen to use
When configuring content type negotiation, custom formatters, or Accept headers
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
- Respect the `Accept` header to determine response format
Guides technical evaluation of code review feedback: read fully, restate for understanding, verify against codebase, respond with reasoning or pushback before implementing.
Share bugs, ideas, or general feedback.
Accept header to determine response formatapplication/json when no preference is specifiedapplication/problem+json for error responses (RFC 9457)// Program.cs — global JSON options
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.Converters.Add(
new JsonStringEnumConverter());
options.SerializerOptions.DefaultIgnoreCondition =
JsonIgnoreCondition.WhenWritingNull;
options.SerializerOptions.PropertyNamingPolicy =
JsonNamingPolicy.CamelCase;
});
// Controller-specific
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(
new JsonStringEnumConverter());
options.JsonSerializerOptions.DefaultIgnoreCondition =
JsonIgnoreCondition.WhenWritingNull;
});
builder.Services.AddControllers()
.AddXmlDataContractSerializerFormatters();
// Endpoints now respond to:
// Accept: application/json → JSON
// Accept: application/xml → XML
public sealed class CsvOutputFormatter : TextOutputFormatter
{
public CsvOutputFormatter()
{
SupportedMediaTypes.Add(
MediaTypeHeaderValue.Parse("text/csv"));
SupportedEncodings.Add(Encoding.UTF8);
}
protected override bool CanWriteType(Type? type)
{
return type is not null &&
(typeof(IEnumerable).IsAssignableFrom(type) ||
type.IsGenericType);
}
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context,
Encoding selectedEncoding)
{
var response = context.HttpContext.Response;
var items = (IEnumerable<object>)context.Object!;
var sb = new StringBuilder();
var properties = context.ObjectType!
.GetGenericArguments()[0]
.GetProperties();
// Header row
sb.AppendLine(string.Join(",",
properties.Select(p => p.Name)));
// Data rows
foreach (var item in items)
{
var values = properties.Select(p =>
EscapeCsvValue(p.GetValue(item)?.ToString() ?? ""));
sb.AppendLine(string.Join(",", values));
}
await response.WriteAsync(sb.ToString(), selectedEncoding);
}
private static string EscapeCsvValue(string value)
{
if (value.Contains(',') || value.Contains('"') ||
value.Contains('\n'))
return $"\"{value.Replace("\"", "\"\"")}\"";
return value;
}
}
// Registration
builder.Services.AddControllers(options =>
{
options.OutputFormatters.Add(new CsvOutputFormatter());
});
// Allow ?format=csv or /orders.csv
builder.Services.AddControllers(options =>
{
options.FormatterMappings.SetMediaTypeMappingForFormat(
"csv", "text/csv");
options.FormatterMappings.SetMediaTypeMappingForFormat(
"xml", "application/xml");
});
[HttpGet]
[FormatFilter]
[Route("/orders.{format?}")]
public async Task<ActionResult<List<OrderResponse>>> GetOrders() { }
// Minimal API — explicit content type
app.MapGet("/orders/{id}", async (Guid id, ISender sender) =>
{
var order = await sender.Send(new GetOrderQuery(id));
return order is not null
? Results.Ok(order)
: Results.NotFound();
}).Produces<OrderResponse>(StatusCodes.Status200OK, "application/json");
// Return specific content type
app.MapGet("/orders/export", async (ISender sender) =>
{
var csv = await sender.Send(new ExportOrdersCsvQuery());
return Results.Text(csv, "text/csv", Encoding.UTF8);
});
// Return file
app.MapGet("/orders/{id}/invoice", async (Guid id, ISender sender) =>
{
var pdf = await sender.Send(new GenerateInvoiceQuery(id));
return Results.File(
pdf, "application/pdf", $"invoice-{id}.pdf");
});
builder.Services.AddProblemDetails();
// Automatic problem+json responses for errors
app.UseExceptionHandler();
app.UseStatusCodePages();
// Custom problem details
app.MapGet("/orders/{id}", async (Guid id) =>
{
return Results.Problem(
title: "Order Not Found",
detail: $"No order exists with ID {id}",
statusCode: StatusCodes.Status404NotFound,
type: "https://tools.ietf.org/html/rfc9110#section-15.5.5");
});
Client sends:
Accept: application/json → JSON response
Accept: application/xml → XML response
Accept: text/csv → CSV response
Accept: */* → Default (JSON)
Accept: application/problem+json → Error responses
Server responds with:
Content-Type: application/json; charset=utf-8
application/problem+json for errorsAddJsonOptions or ConfigureHttpJsonOptions configurationOutputFormatter implementationsAddXmlDataContractSerializerFormatters calls[Produces] and [Consumes] attributes on controllersAddProblemDetails in Program.cs[Produces] attributes to document supported content types| Content Type | Use Case |
|---|---|
application/json | Default for API responses |
application/problem+json | Error responses (RFC 9457) |
text/csv | Data export |
application/xml | Legacy integrations |
application/pdf | Report generation |
application/octet-stream | File downloads |