Skip to content

Upgrade to 11.x

This document is intended to guide you when upgrading from GenHTTP 10.x to GenHTTP 11.

Target Frameworks

This version is shipped with the release of .NET 11. Following the support policy from Microsoft, support for .NET 8 and 9 has been dropped.

Packaging

In previous versions, you would reference both GenHTTP.Core as well as any module you wanted to use within your application. While this is still recommended for deployments, you can now use the GenHTTP.Full package (which also exist for Kestrel and Ioxide) which bundles all available modules for convenience.

Server Setup and Hosting

The IServerBuilder interface has been removed in favor of the IHost, which was mainly a facade to the server builder anyway.

Version 11 adds a new logging mechanism, which logs to console by default. Therefore, Console() has been removed from the host builder.

In addition, server companions have been removed - if you need something beyond logging, install a custom concern instead.

In previous versions, the server builder featured some low-level settings such as the backlog and buffer sizes. Those settings can no longer be set externally and are now managed by the engine in place.

API Rework

The dominant change of GenHTTP 11 is a reworked API layer that is better suited to represent incoming HTTP requests and requires less work by the engine to process the data, resulting in much higher performance (see the HTTP Arena results). Besides the response content object, the middleware is now allocation-free.

If your project interacts directly with requests or responses, you will need to adapt it accordingly. The contracts of high-level handlers such as webservices or static websites did not change, so they should still compile.

In previous versions, the API used Flexible* types to allow user-specified types. In GenHTTP 11, there is no duality of types, but the main type accepts a custom value if needed:

// old
FlexibleContentType.Get("text/my-format");

// new
new ContentType("text/my-format");

Some flexibility (such as the ability to set custom response status codes) has been removed in favor of simplicity and performance.

For general information about how the new API works, see the request API documentation.

Response API

Responses are immutable now, but you can call Rebuild() to get a fresh builder seeded with the existing status, headers and content:

// old
public async ValueTask<IResponse?> HandleAsync(IRequest request)
{
    var response = await Content.HandleAsync(request);
    if (response is not null) response.Headers["X-Powered-By"] = "GenHTTP";
    return response;
}

// new
public async ValueTask<IResponse?> HandleAsync(IRequest request)
{
    var response = await Content.HandleAsync(request);
    response?.Rebuild().Header("X-Powered-By", "GenHTTP");
    return response;
}

Content type, encoding and length moved off the response and onto the content object itself:

// old
request.Respond()
       .Content(new StringContent("<h1>Hi</h1>"))
       .Type(ContentType.TextHtml)
       .Build();

// new - the content carries its own type
request.Respond()
       .Content("<h1>Hi</h1>", ContentType.TextHtml)
       .Build();

This avoids a lot of duplications in the framework code and makes content handling way easier.

Handler Preparation

The prepare call on custom handlers and concerns is now passed the server instance, which allows access to the server property bag. With this change, some initialization logic can be moved from first request into the preparation phase.

public ValueTask PrepareAsync(IServer server) => ValueTask.CompletedTask;

File and Resource Serving

Serving files now goes through the new GenHTTP.Modules.Files package, via Asset (single file) and Assets (directory/tree):

// old
var handler = Download.From(Resource.FromFile("./243723409370947.pdf"))
                      .FileName("report.pdf");

var tree = Resources.From(ResourceTree.FromDirectory("./assets"));

// new
using GenHTTP.Modules.Files;

var handler = Asset.From("./243723409370947.pdf")
                   .AsDownload("report.pdf");

var tree = Assets.From("./assets");

Request Method Annotations

As RequestMethod is now a struct, we can no longer be used as an attribute argument. Therefore, a simple Method enum has been added to specify service method call verbs. You service method annotations need to be adapted accordingly:

// old
[ResourceMethod(RequestMethod.Post)]
public MyResult Create(MyModel model) => ...;

// new
[ResourceMethod(Method.Post)]
public MyResult Create(MyModel model) => ...;

Zstandard Changes

In previous version, we relied on an external library to allow zstd compressed responses. During release testing, we discovered a major issue in this code which can cause the server to hang. In GenHTTP 11, we use the now-native zstd capabilities of .NET Core 11. As this functionality is not available on .NET 10, zstd support has been dropped there.

Custom Compression Algorithms

Implementations of ICompressionAlgorithm now have to provide a FileExtension - the suffix the files handler appends when serving precompressed variants of static files (for example gz for gzip). Add the property to any custom algorithm; see the compression concern for a complete example.

Websockets

After being declared deprecated in 10.x, the web socket handler based on Fleck has been removed in version 11. The closest replacement is the functional flavor of the new websocket module.

Body Buffering

In previous versions, the framework buffered the body of an incoming HTTP request into a memory or file stream, depending on the size advertised by the client. This allowed users to read the request body multiple times. With version 11, the body is no longer buffered. If you need to re-read the body, you can emulate the previous behavior by copying the incoming body into a memory or temporary file stream.

var body = request.GetBody();

if (body is not null)
{
    await using var bodyStream = body.AsStream();

    using var memoryStream = new MemoryStream();

    await bodyStream.CopyToAsync(memoryStream);

    memoryStream.Seek(0, SeekOrigin.Begin);
    
    // use the stream as needed
}

Body Arguments

In previous versions, the web service framework did automatically resolve form arguments from the incoming request body and mapped them to parameters with the matching names. While this functionality is convenient, it added a performance penalty, even if no form arguments were used, so this has been extracted into extension methods. You can either fetch the arguments from the body or let them inject as a parameter.

using GenHTTP.Modules.IO;

var args = await body.AsBodyArgumentsAsync();

Caching

The caching abstraction and all dependent modules have been removed, as the implementations turned out to be inefficient and slow - the GenHTTP.Modules.Caching and GenHTTP.Modules.ServerCaching packages no longer exist, so a reference to either will fail to restore. If you are using caches in your application, consider using alternatives such as the abstraction layer provided by Microsoft instead.