FoxEndpoints 1.3.1
dotnet add package FoxEndpoints --version 1.3.1
NuGet\Install-Package FoxEndpoints -Version 1.3.1
<PackageReference Include="FoxEndpoints" Version="1.3.1" />
<PackageVersion Include="FoxEndpoints" Version="1.3.1" />
<PackageReference Include="FoxEndpoints" />
paket add FoxEndpoints --version 1.3.1
#r "nuget: FoxEndpoints, 1.3.1"
#:package FoxEndpoints@1.3.1
#addin nuget:?package=FoxEndpoints&version=1.3.1
#tool nuget:?package=FoxEndpoints&version=1.3.1
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.Httpfrom 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>, andEndpoint. - 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 providedStreamFileabstraction. - Optional global authorization, shared form options, and default file binding modes can be configured once per
WebApplication. - API versioning integrates with
Asp.Versioningattributes and readers when you opt intoAddApiVersioning()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 return204 No Contentor 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, andPATCHrequests 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
FormOptionsif 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
HandleAsyncand return the appropriateSend.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()→ AddsAccepts("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 streamingStreamFilepayloads.
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
ApiVersionattributes. - 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/Deletewins, 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
HandleAsyncor 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 | Versions 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. |
-
net10.0
- Asp.Versioning.Http (>= 8.1.0)
-
net9.0
- Asp.Versioning.Http (>= 8.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.