Custom Response Content

Custom Response Content

Note: this feature is part of the Wired.IO default IHttpHandler middleware pipeline (Response Middleware) See 1. Create a Builder to learn more about IHttpHandler.

When using the response middleware, for example:

// Example with fast endpoint

builder
    .MapGet("/route", scope => context =>
    {
        context
            .Respond()
            .Status(ResponseStatus.Ok)
            .Type("application/json")
            .Content(new JsonContent(new { Name = "Example" },
            JsonSerializerOptions.Default));
    })

This will create an IResponse in the IContext (context) that will be processed by the ResponseMiddleware, if IResponse is null, the middleware is skipped (for the cases when the user handles the response himself).

Create a custom IResponseContent

With custom IResponseContent it is possible to gain some more control over the response content while using response middleware.

Chunked responses can be handy when content length is unknown or make data availabe to the client without having to wait for the full content.

public class CustomResponseContent(ReadOnlyMemory<byte> data) : IResponseContent
{
    // Set Length to null to let the response middleware know to use
    // transfer chunked encoding
    // If Length is set, the Content-Length will be set to its value
    public ulong? Length => null!;

    public ValueTask<ulong?> CalculateChecksumAsync() => new((ulong)data.GetHashCode());

    // Define the chunked Write
   public async ValueTask WriteAsync(ChunkedPipeWriter writer, uint bufferSize)
    {
        // Access the underlying PipeWriter if need
        // ChunkedPipeWriter is just a wrapper on PipeWriter
        // to write in chunks (add chunk size and chunk terminators)
        var underlyingPipeWriter = writer.GetPipeWriter();

        // Send first chunk and flush it so that receiver can already process it
        // For NET9.0+ you can use await writer.WriteAsync(data)
        // already flushes
        writer.Write(data.Span);
        await writer.FlushAsync();

        // Send second chunk
        writer.Write("Additional information"u8);

        // Send chunked response terminator and flush it
        writer.Finish();
        await writer.FlushAsync();
    }

    // Length is null, this method will never be used
    public void Write(PlainPipeWriter writer, uint bufferSize)
    {
        throw new NotImplementedException();
    }
}

This is what the response looks like from client side:

HTTP/1.1 200 OK
Server: Wired.IO
Date: Mon, 07 Jul 2025 17:10:18 GMT
Content-Type: text/plain
Transfer-Encoding: chunked

D
response data
16
Additional information
0

When using content length based response, Length property cannot be null.

public class CustomResponseContent(ReadOnlyMemory<byte> data) : IResponseContent
{
    // Set the Length property
    public ulong? Length => (ulong)data.Length;

    public ValueTask<ulong?> CalculateChecksumAsync() => new((ulong)data.GetHashCode());

    // Length is not null, this method will never be used
    public async ValueTask WriteAsync(ChunkedPipeWriter writer, uint bufferSize)
    {
        throw new NotImplementedException();
    }

    public async ValueTask WriteAsync(PipeWriter writer, uint bufferSize)
    {
        // For NET9.0+ you can use await writer.WriteAsync(data)
        // already flushes
        writer.Write(data.Span);
        await writer.FlushAsync();
    }
}

This is what the response looks like on the client side:

HTTP/1.1 200 OK
Server: Wired.IO
Date: Mon, 07 Jul 2025 21:23:49 GMT
Content-Type: text/plain
Content-Length: 13

response data