Last active
July 5, 2025 10:50
-
-
Save dj-nitehawk/a8a60d7d7d5a1802490c36488857fe10 to your computer and use it in GitHub Desktop.
Content Negotiation Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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