Oddhouse.Vestibule 1.0.1

dotnet add package Oddhouse.Vestibule --version 1.0.1
                    
NuGet\Install-Package Oddhouse.Vestibule -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Oddhouse.Vestibule" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Oddhouse.Vestibule" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Oddhouse.Vestibule" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Oddhouse.Vestibule --version 1.0.1
                    
#r "nuget: Oddhouse.Vestibule, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Oddhouse.Vestibule@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Oddhouse.Vestibule&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Oddhouse.Vestibule&version=1.0.1
                    
Install as a Cake Tool

Oddhouse.Vestibule

A lightweight .NET 9 library for building REST servers. Define endpoints by subclassing RestServer and decorating methods with HTTP method attributes — no configuration files, no middleware pipeline, no dependency injection required.

Quick start

using Oddhouse.Net;

var server = new MyServer();
Console.ReadLine(); // keep alive

public class MyServer : RestServer
{
    public MyServer() : base(8080) { }

    [HttpGet("/hello/{name}")]
    public string Hello(string name) => $"Hello, {name}!";
}

The server starts listening on both IPv4 and IPv6 as soon as the constructor returns. Dispose() shuts it down.

Defining endpoints

Decorate methods with an HTTP method attribute and a URI template:

[HttpGet("/users/{id}")]
public User GetUser(int id) { ... }

[HttpPost("/users")]
public User CreateUser([Body] User user) { ... }

[HttpPut("/users/{id}")]
public User UpdateUser(int id, [Body] User user) { ... }

[HttpDelete("/users/{id}")]
public void DeleteUser(int id) { ... }

Available attributes: [HttpGet], [HttpPost], [HttpPut], [HttpPatch], [HttpDelete], [HttpHead], [HttpOptions], [HttpTrace].

Use [Http] without a method to match any HTTP verb:

[Http("/ping")]
public string Ping() => "pong";

URI templates

Path parameters

Segments wrapped in {braces} are bound to method parameters by name:

[HttpGet("/orders/{year}/{month}")]
public IReadOnlyList<Order> GetOrders(int year, int month) { ... }

Query parameters

// Required query parameter
[HttpGet("/users?role={role}")]
public IReadOnlyList<User> GetByRole(string role) { ... }

// Optional query parameter (has a default value)
[HttpGet("/users?page={page}&size={size}")]
public IReadOnlyList<User> ListUsers(int page = 1, int size = 20) { ... }

Wildcard suffix

Append [/...] to match any number of additional path segments:

[HttpGet("/files/{name}[/...]")]
public Stream GetFile(string name) { ... }

Supported parameter types

All primitives (int, bool, double, …), string, Guid, DateTime, TimeSpan, enums, and their nullable equivalents.

Return types

Return type Effect
void / Task Empty response body
T / Task<T> Serialized into the response body
byte[] Written directly to the response stream
Stream Piped directly to the response stream

Async handlers are fully supported:

[HttpGet("/users/{id}")]
public async Task<User> GetUser(int id)
{
    return await db.Users.FindAsync(id);
}

Request body

Mark one parameter with [Body] to receive the deserialized request body. The serializer is chosen based on the request's Content-Type header:

[HttpPost("/users")]
public async Task<User> CreateUser([Body] User user) { ... }

Content negotiation

The response serializer is chosen by matching the request's Accept header against the server's Serializers collection. The built-in serializers cover:

Content type Serializer
application/json JsonSerializer
application/xml, text/xml XmlSerializer
text/plain PlainTextSerializer

DefaultContentType controls which serializer is used when the client sends no Accept header or accepts */*. It defaults to application/json.

public class MyServer : RestServer
{
    public MyServer() : base(8080)
    {
        DefaultContentType = "application/xml";
    }
}

Add or remove serializers via the Serializers collection:

Serializers.Add(new MyCustomSerializer());
Serializers.Remove("text/plain");

Force a specific content type for one endpoint using [ContentType]:

[HttpGet("/feed")]
[ContentType("application/rss+xml")]
public Stream GetFeed() { ... }

Accessing the request context

RestServer exposes static, async-local properties that give ambient access to the current request from anywhere in the call stack:

RestServer.Request      // HttpRequest  — URI, headers, body stream
RestServer.Response     // HttpResponse — status code, headers, response stream
RestServer.Connection   // HttpConnection — local/remote address, per-connection state

Set the status code directly when the default (200 OK) is not appropriate:

[HttpPost("/users")]
public User CreateUser([Body] User user)
{
    var created = repository.Insert(user);
    Response.StatusCode = HttpStatusCode.Created;
    return created;
}

Connection events

Subscribe to ClientConnected and ClientDisconnected to track active connections or initialize per-connection state:

public MyServer() : base(8080)
{
    ClientConnected += (_, connection) => Console.WriteLine($"Connected: {connection.RemoteAddress}");
    ClientDisconnected += (_, connection) => Console.WriteLine($"Disconnected: {connection.RemoteAddress}");
}

Filters

Apply [ContentType] to declare which content types an endpoint produces. If the client's Accept header cannot be satisfied, 406 Not Acceptable is returned automatically:

[HttpGet("/report")]
[ContentType("application/pdf", RequireMatchingAcceptHeader = true)]
public Stream GetReport() { ... }

Implement RequestFilterAttribute to write custom filters:

public sealed class RequireApiKeyAttribute : RequestFilterAttribute
{
    public override bool AllowRequest(
        HttpContext context, MethodInfo method,
        out HttpStatusCode? errorCode, out string? errorDescription)
    {
        if (context.Request.Headers["X-Api-Key"] == "secret")
        {
            errorCode = null;
            errorDescription = null;
            return true;
        }

        errorCode = HttpStatusCode.Unauthorized;
        errorDescription = "Unauthorized";
        return false;
    }
}

// Usage
[HttpGet("/admin/stats")]
[RequireApiKey]
public Stats GetStats() { ... }

HTTPS

Override UriScheme and Certificate to enable TLS:

public class SecureServer : RestServer
{
    public SecureServer() : base(443) { }

    protected override string UriScheme => Uri.UriSchemeHttps;
    protected override X509Certificate2? Certificate =>
        X509Certificate2.CreateFromPemFile("cert.pem", "key.pem");
}
Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 118 5/22/2026
1.0.0 106 5/17/2026