ZedEndpoints 1.1.2

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

ZedEndpoints

NuGet NuGet Downloads

A minimal library for automatic endpoint discovery and organization in ASP.NET Core Minimal APIs.

Why ZedEndpoints

Carter and similar libraries rely on reflection-based discovery that requires public classes. This prevents using C#'s file and internal modifiers, which are essential for proper encapsulation in feature-based architectures.

ZedEndpoints solves this by combining explicit registration within groups and automatic group discovery — giving you encapsulation without sacrificing convenience.

// Carter — requires public
public sealed class CreateUserEndpoint : ICarterModule { ... }

// ZedEndpoints — file modifier works
file sealed class CreateUserEndpoint : IEndpoint { ... }

Features

  • Automatic endpoint discovery via reflection
  • Group-based endpoint organization with shared configuration
  • Optional global route prefix for all endpoint groups
  • Per-group prefix override via [NoGlobalPrefix] attribute
  • Type-safe interfaces and generics
  • Fluent API with method chaining support
  • Idempotent operations (safe to call multiple times)
  • Thread-safe implementation

Installation

dotnet add package ZedEndpoints

Quick Start

Define an Endpoint

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using ZedEndpoints.Abstractions;

public class GetProductEndpoint : IEndpoint
{
    public static void Map(IEndpointRouteBuilder app)
    {
        app.MapGet("/products/{id}", (int id) => 
        {
            return Results.Ok(new { Id = id, Name = "Product" });
        })
        .WithName("GetProduct")
        .WithTags("Products");
    }
}

Create an Endpoint Group

using Microsoft.AspNetCore.Routing;
using ZedEndpoints.Abstractions;
using ZedEndpoints.Extensions;

public class ProductEndpoints : IEndpointGroup
{
    public void MapGroup(IEndpointRouteBuilder app)
    {
        var group = app.MapGroup("/products")
            .WithTags("Products")
            .RequireAuthorization();

        group.MapEndpoint<GetProductEndpoint>()
             .MapEndpoint<CreateProductEndpoint>()
             .MapEndpoint<UpdateProductEndpoint>()
             .MapEndpoint<DeleteProductEndpoint>();
    }
}

Register in Program.cs

using ZedEndpoints.Extensions;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Automatic discovery and registration
app.MapEndpointGroups();

app.Run();

Usage Patterns

Manual Endpoint Registration

app.MapEndpoint<GetProductEndpoint>();

Manual Group Registration

var productGroup = new ProductEndpoints();
productGroup.MapGroup(app);

Working with Route Groups

var apiV1 = app.MapGroup("/api/v1");
apiV1.MapEndpoint<GetProductEndpoint>();

Scanning Specific Assembly

app.MapEndpointGroups(typeof(ProductEndpoints).Assembly);

Global Route Prefix

Apply a common prefix to all discovered endpoint groups at once:

// All routes will be prefixed with /api/v1
app.MapEndpointGroups(globalPrefix: "api/v1");

// Combined with a specific assembly
app.MapEndpointGroups(typeof(ProductEndpoints).Assembly, globalPrefix: "api/v1");

This is useful for applying a consistent API versioning prefix without modifying each group individually. For example, a group that maps /products will automatically become /api/v1/products.

Opting Out of the Global Prefix

Use the [NoGlobalPrefix] attribute on a group to bypass the global prefix:

using ZedEndpoints.Attributes;

[NoGlobalPrefix]
public class HealthEndpoints : IEndpointGroup
{
    public void MapGroup(IEndpointRouteBuilder app)
    {
        app.MapGet("/health", () => Results.Ok())
            .WithName("HealthCheck");
    }
}

With a global prefix of api/v1, all other groups will be prefixed normally while HealthEndpoints will remain at /health.

Architecture

IEndpoint Interface

Defines a single endpoint with a static mapping method.

public interface IEndpoint
{
    static abstract void Map(IEndpointRouteBuilder app);
}

IEndpointGroup Interface

Defines a group of related endpoints with shared configuration.

public interface IEndpointGroup
{
    void MapGroup(IEndpointRouteBuilder app);
}

NoGlobalPrefix Attribute

Marks an endpoint group to be excluded from the global route prefix.

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class NoGlobalPrefixAttribute : Attribute;

Best Practices

Endpoint Organization

Organize endpoints by feature or domain area:

Features/
├── Products/
│   ├── GetProduct.cs
│   ├── CreateProduct.cs
│   └── ProductEndpoints.cs
├── Orders/
│   ├── GetOrder.cs
│   ├── CreateOrder.cs
│   └── OrderEndpoints.cs

Naming Conventions

  • Endpoints: {Verb}{Resource}Endpoint (e.g., GetProductEndpoint)
  • Groups: {Resource}Endpoints (e.g., ProductEndpoints)

Shared Configuration

Apply common settings at the group level:

public class ProductEndpoints : IEndpointGroup
{
    public void MapGroup(IEndpointRouteBuilder app)
    {
        var group = app.MapGroup("/products")
            .WithTags("Products")
            .RequireAuthorization()
            .RequireRateLimiting("fixed")
            .WithOpenApi();

        group.MapEndpoint<GetProductEndpoint>()
             .MapEndpoint<CreateProductEndpoint>();
    }
}

API Versioning with Global Prefix

Use the global prefix for versioning instead of repeating it in every group:

// Instead of hardcoding /api/v1 in every IEndpointGroup
app.MapEndpointGroups(globalPrefix: "api/v1");

For groups that should not follow the versioning scheme, such as health checks or webhooks, use [NoGlobalPrefix]:

[NoGlobalPrefix]
public class HealthEndpoints : IEndpointGroup { ... }

[NoGlobalPrefix]
public class WebhookEndpoints : IEndpointGroup { ... }

How It Works

Automatic Discovery

The MapEndpointGroups() extension method:

  1. Scans the specified assembly (or entry assembly by default)
  2. Finds all concrete classes implementing IEndpointGroup
  3. Creates instances using parameterless constructors
  4. Checks for the [NoGlobalPrefix] attribute on each group
  5. Invokes their MapGroup() methods with the appropriate route builder

Global Prefix

When a globalPrefix is provided, a root RouteGroupBuilder is created and passed to all endpoint groups instead of the WebApplication directly. Both implement IEndpointRouteBuilder, so existing group implementations require no changes. Groups decorated with [NoGlobalPrefix] always receive the WebApplication directly, bypassing the prefix entirely.

Idempotency

The library prevents duplicate registrations:

app.MapEndpointGroups(); // Registers all endpoint groups
app.MapEndpointGroups(); // Safe: does nothing (already registered)

This is achieved through assembly tracking:

  • Each assembly is processed only once
  • Thread-safe using lock mechanism
  • Prevents accidental duplicate endpoint registration

Integration Testing with WebApplicationFactory

When using WebApplicationFactory<T> for integration or acceptance tests, the default assembly resolution via Assembly.GetEntryAssembly() may return the test runner assembly instead of your application assembly. This means no endpoints will be discovered.

This is handled automatically since v1.1.1 — the library now uses app.Environment.ApplicationName to resolve the correct assembly, which works seamlessly with WebApplicationFactory.

If you're on an older version, pass the assembly explicitly:

app.MapEndpointGroups(typeof(Program).Assembly, globalPrefix: "api/v1");

Requirements

  • .NET 8.0 or higher
  • ASP.NET Core

License

MIT

Contributing

Contributions are welcome. Please open an issue or submit a pull request.

Support

For issues and questions, please use the GitHub issue tracker.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  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.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • 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.1.2 87 5/28/2026
1.1.1 128 2/18/2026
1.1.0 113 2/15/2026
1.0.3 111 2/13/2026
1.0.2 124 2/8/2026
1.0.1 132 2/8/2026
1.0.0 125 2/8/2026