FoxEndpoints 1.3.1

dotnet add package FoxEndpoints --version 1.3.1
                    
NuGet\Install-Package FoxEndpoints -Version 1.3.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="FoxEndpoints" Version="1.3.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FoxEndpoints" Version="1.3.1" />
                    
Directory.Packages.props
<PackageReference Include="FoxEndpoints" />
                    
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 FoxEndpoints --version 1.3.1
                    
#r "nuget: FoxEndpoints, 1.3.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 FoxEndpoints@1.3.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=FoxEndpoints&version=1.3.1
                    
Install as a Cake Addin
#tool nuget:?package=FoxEndpoints&version=1.3.1
                    
Install as a Cake Tool

FoxEndpoints

FoxEndpoints is a lightweight layer on top of ASP.NET Core minimal APIs inspired by FastEndpoints. It keeps the mental model of "one class = one endpoint" while staying close to the built-in primitives so it remains fast, dependency-light (Microsoft packages only), and predictable. Validation, formatting, and domain rules are intentionally left to the consumer for maximum control.

Supports .NET 9.0 and .NET 10.0

Design Goals

  • Minimal abstraction: re-use ASP.NET Core hosting, routing, DI, and results without introducing controllers.
  • No third-party runtime dependencies: only Microsoft.AspNetCore.App framework reference plus Asp.Versioning.Http from the dotnet org for optional versioning support.
  • Consumer-managed validation and behavioral policies; the library only handles binding plus basic 400 responses for malformed payloads.
  • Faster startup and execution than MVC controllers by emitting delegates directly and caching activators per endpoint type.
  • First-class support for typed requests/responses, without forcing FluentValidation/FastEndpoints pipelines.

Feature Highlights

  • Four base classes cover the common use cases: Endpoint<TRequest, TResponse>, EndpointWithoutRequest<TResponse>, EndpointWithoutResponse<TRequest>, and Endpoint.
  • Automatic discovery of endpoint classes in the entry assembly via app.UseFoxEndpoints().
  • Constructor injection works out of the box; endpoints are resolved in scoped DI wrappers so scoped services behave like regular ASP.NET Core handlers.
  • Request binding merges route values, query parameters, JSON bodies, and (optionally) multipart form data into strongly typed records/classes.
  • File uploads can be buffered or streamed using IFormFile, IFormFileCollection, or the provided StreamFile abstraction.
  • Optional global authorization, shared form options, and default file binding modes can be configured once per WebApplication.
  • API versioning integrates with Asp.Versioning attributes and readers when you opt into AddApiVersioning() in your host.

Quick Start

Program setup

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();
// Optional: builder.Services.AddApiVersioning(...).AddApiExplorer(...);

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.UseFoxEndpoints(); // discovers endpoints from the entry assembly

app.Run();

Basic endpoint

public sealed class GetUserEndpoint : Endpoint<GetUserRequest, GetUserResponse>
{
    private readonly IUserRepository _repository;

    public GetUserEndpoint(IUserRepository repository)
    {
        _repository = repository;
    }

    public override void Configure()
    {
        Get("/users/{id}")
            .WithName("GetUser")
            .WithTags("Users")
            .RequireAuthorization();
    }

    public override async Task<IResult> HandleAsync(GetUserRequest request, CancellationToken ct)
    {
        var user = await _repository.GetAsync(request.Id, ct);
        // await is optional on Send methods - both patterns work:
        return Send.Ok(new GetUserResponse(user.Id, user.Name));
    }
}

public sealed record GetUserRequest(int Id);
public sealed record GetUserResponse(int Id, string Name);

Each endpoint class must select exactly one HTTP verb helper (Get, Post, Put, Patch, or Delete). Create separate endpoint classes when you need multiple verbs for the same resource.

Note on async/await: The Send methods return Task<IResult> for API consistency. You can either return await Send.Ok(...) or simply return Send.Ok(...) - both work identically. Use await on actual I/O operations (database, HTTP calls, etc.).

Endpoint Variants

  • Endpoint<TRequest, TResponse>: route + request body/params + response payload.
  • EndpointWithoutRequest<TResponse>: ex: listings without parameters.
  • EndpointWithoutResponse<TRequest>: commands that return 204 No Content or similar.
  • Endpoint: health checks, or triggers that don't require a request body or response payload.

All base classes expose a typed Send helper for creating IResult instances without repeatedly calling Results.*. Common responses like NoContent(), Ok(), NotFound(), and Unauthorized() are cached for optimal performance.

Request Binding & Validation

  • Route values and query parameters are always inspected for matching property names (case-insensitive).
  • For POST, PUT, and PATCH requests the JSON body is bound first via [FromBody], then route values are merged into default-valued properties.
  • For multipart form data, the binder inspects form fields plus files and honours custom FormOptions if provided.
  • Use [BindAttribute("PropA", "PropB")] on the request type to create an allowlist of bindable properties.
  • Use [BindNever] on individual properties to exclude them from binding.
  • Validation frameworks (FluentValidation, DataAnnotations, custom logic, etc.) are not integrated. Perform validation inside HandleAsync and return the appropriate Send.BadRequest(...)/`` response yourself.

File Uploads

When a request type exposes IFormFile, List<IFormFile>, IFormFileCollection, or StreamFile, FoxEndpoints automatically switches the binder to form mode.

using Microsoft.AspNetCore.Mvc;

public sealed record UploadDocumentRequest
{
    [FromForm]
    public Guid Id { get; init; }
    
    [FromForm]
    public IFormFile? File { get; init; }
}

public sealed class UploadDocument : EndpointWithoutResponse<UploadDocumentRequest>
{
    private readonly IDocumentStorage _storage;

    public UploadDocument(IDocumentStorage storage)
    {
        _storage = storage;
    }

    public override void Configure()
    {
        Post("/estimates/{EstimateId}/documents")
            .AllowFileUploads()         // Adds Accepts("multipart/form-data") metadata
            .WithFormOptions(new FormOptions { MultipartBodyLengthLimit = 50 * 1024 * 1024 })
            .WithTags("Documents");
    }

    public override async Task<IResult> HandleAsync(UploadDocumentRequest request, CancellationToken ct)
    {
        if (request.File is null)
            return await Send.BadRequest("File is required");

        await _storage.SaveAsync(request.Id, request.File, ct);
        return await Send.NoContent();
    }
}

Additional options:

  • .AllowFileUploads() → Adds Accepts("multipart/form-data") metadata only.
  • .DisableAntiforgery() → Call explicitly when you serve cookie-authenticated clients that post form data.
  • .WithFormOptions(FormOptions) → Override multipart thresholds per endpoint.
  • app.UseFoxEndpoints(c => c.UseFileBindingMode(FileBindingMode.Stream)) → Switch the default to streaming StreamFile payloads.

API Versioning (Optional)

The package references Asp.Versioning.Http, so you only need to opt into the services and add attributes.

builder.Services
    .AddApiVersioning(o =>
    {
        o.DefaultApiVersion = new ApiVersion(1, 0);
        o.AssumeDefaultVersionWhenUnspecified = true;
        o.ReportApiVersions = true;
        o.ApiVersionReader = ApiVersionReader.Combine(
            new QueryStringApiVersionReader("api-version"),
            new HeaderApiVersionReader("X-Api-Version"));
    })
    .AddApiExplorer(o =>
    {
        o.GroupNameFormat = "'v'VVV";
        o.SubstituteApiVersionInUrl = true;
    });

Annotate endpoints with [ApiVersion("1.0")] (semantic) or [ApiVersion("2024-10-01")] (date-based) and optionally [ApiExplorerSettings(GroupName = "v1.0")]. UseFoxEndpoints will:

  • Build a version set from all discovered ApiVersion attributes.
  • Map each endpoint to its declared versions.
  • Attach the version set to the underlying route builder.

Consumers are responsible for deciding how clients specify the version (query string, header, etc.) by configuring the ApiVersionReader.

Authorization

FoxEndpoints supports both global and endpoint-level authorization.

Endpoint-Level Authorization

Use .RequireAuthorization() to require authentication, or pass policy/role names for fine-grained control:

public override void Configure()
{
    Post("/admin/users")
        .RequireAuthorization("AdminPolicy")     // Single policy
        .WithTags("Admin");
        
    // Or multiple policies
    Put("/sensitive-data/{id}")
        .RequireAuthorization("DataAccess", "SeniorRole")
        .WithTags("Data");
}

Policies must be registered in Program.cs:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminPolicy", policy => policy.RequireRole("Admin"));
    options.AddPolicy("DataAccess", policy => policy.RequireClaim("DataAccess", "Read"));
});

Global Authorization

Apply authorization to all endpoints by default, allowing individual endpoints to opt out:

app.UseFoxEndpoints(config =>
{
    config.RequireAuthorization();  // All endpoints require auth by default
});

Endpoints can opt out using .AllowAnonymous():

public override void Configure()
{
    Get("/health")
        .AllowAnonymous();  // Public endpoint
}

Global Configuration Hooks

app.UseFoxEndpoints(config =>
{
    config.RequireAuthorization();                      // Force auth unless endpoints call .AllowAnonymous()
    config.ConfigureFormOptions(o => { ... });           // Global multipart settings
    config.UseFileBindingMode(FileBindingMode.Stream);   // Default to streaming uploads
});

UseFoxEndpoints returns the same WebApplication instance, so you can chain additional middleware registrations if desired.

Known Behaviors & Limitations

  • One HTTP verb per endpoint class. The last call to Get/Post/Put/Patch/Delete wins, so create one class per verb/route.
  • Only entry-assembly endpoints are discovered. If you place endpoints in a referenced class library, make sure that library is the application entry assembly or load those endpoints into the entry assembly.
  • No automatic validation pipeline. Consumers should validate inside HandleAsync or plug in their own middleware/decorators.
  • No automatic filters or behaviors. Cross-cutting concerns should be handled via standard ASP.NET Core middleware or shared services.

Why FoxEndpoints?

  • Familiar FastEndpoints ergonomics without adopting a completely new runtime.
  • Works inside existing minimal API projects; keeps middleware, filters, and hosting exactly as before.
  • Encourages small, focused endpoint classes that are easy to test and reason about.
  • Keeps controllers out of the hot path for scenarios where latency matters.

You're more than welcome to contribute or create an issue!

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 is compatible.  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.

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.3.1 473 12/8/2025
1.3.0 160 12/6/2025
1.2.2 361 11/21/2025
1.2.1 449 11/20/2025
1.2.0 427 11/19/2025
1.0.0 432 11/19/2025