Skip to content

Instantly share code, notes, and snippets.

@dj-nitehawk
Last active July 5, 2025 10:50
Show Gist options
  • Save dj-nitehawk/a8a60d7d7d5a1802490c36488857fe10 to your computer and use it in GitHub Desktop.
Save dj-nitehawk/a8a60d7d7d5a1802490c36488857fe10 to your computer and use it in GitHub Desktop.
Content Negotiation Example
using System.Xml.Serialization;
using Microsoft.AspNetCore.Http.Metadata;
var bld = WebApplication.CreateBuilder();
bld.Services
.AddSingleton(typeof(IRequestBinder<>), typeof(NegotiatingBinder<>))
.AddFastEndpoints()
.SwaggerDocument(o => o.SerializerSettings = s => s.PropertyNamingPolicy = null);
var app = bld.Build();
app.UseFastEndpoints(
c => c.Serializer.ResponseSerializer
= async (rsp, dto, contentType, jCtx, ct) =>
{
if (dto is null)
return;
var hCtx = rsp.HttpContext;
var requestAccepts = hCtx.Request.Headers.Accept.ToArray();
var endpointProduces = rsp.HttpContext
.GetEndpoint()?.Metadata
.OfType<IProducesResponseTypeMetadata>()?
.SelectMany(m => m.ContentTypes);
var rspContentType = endpointProduces?.Intersect(requestAccepts).FirstOrDefault();
switch (rspContentType)
{
case "application/xml":
{
hCtx.MarkResponseStart();
hCtx.Response.StatusCode = 200;
hCtx.Response.ContentType = contentType;
var xmlSerializer = new XmlSerializer(dto.GetType());
using var stream = new MemoryStream();
xmlSerializer.Serialize(stream, dto);
stream.Position = 0;
await stream.CopyToAsync(hCtx.Response.Body, ct);
break;
}
case "application/json" or null:
await rsp.WriteAsJsonAsync(dto, jCtx?.Options ?? new(), contentType, ct);
break;
}
})
.UseSwaggerGen();
app.Run();
public class NegotiatingBinder<TRequest> : RequestBinder<TRequest> where TRequest : notnull
{
public override async ValueTask<TRequest> BindAsync(BinderContext ctx, CancellationToken cancellation)
{
if (ctx.HttpContext.Request.ContentType == "application/xml")
{
var serializer = new XmlSerializer(typeof(TRequest));
using var reader = new StreamReader(ctx.HttpContext.Request.Body);
var requestBody = await reader.ReadToEndAsync();
using var stringReader = new StringReader(requestBody);
return (TRequest)serializer.Deserialize(stringReader);
}
return await base.BindAsync(ctx, cancellation);
}
}
public class Request
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Response
{
public string FullName { get; set; }
}
public class Endpoint : Endpoint<Request, Response>
{
public override void Configure()
{
Post("test");
AllowAnonymous();
Description(
x => x.Accepts<Request>("application/xml", "application/json")
.Produces<Response>(200, "application/xml", "application/json"),
clearDefaults: true);
}
public override async Task HandleAsync(Request r, CancellationToken c)
{
var response = new Response
{
FullName = r.FirstName + " " + r.LastName
};
await SendAsync(response);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment