CSharpEssentials.Endpoints 7.0.0

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

CSharpEssentials.Endpoints

Source-generated endpoint organization for ASP.NET Core Minimal APIs. A thin layer over Minimal APIs: route calls stay in your code, so binding, filters, IResult, OpenAPI and the Request Delegate Generator work unchanged.

Installation

dotnet add package CSharpEssentials.Endpoints

Target frameworks: net11.0, net10.0, net9.0, net8.0. The source generator, analyzer and code fix are bundled in the package.

Endpoints and groups

using CSharpEssentials.Endpoints;

public sealed class AppsGroup : IEndpointGroup
{
    public static string Prefix => "apps";

    public static void Configure(RouteGroupBuilder group) => group.WithTags("Apps").RequireAuthorization();
}

[EndpointGroup(typeof(AppsGroup))]
public sealed class CreateApp : IEndpoint
{
    public static void Map(IEndpointRouteBuilder app) =>
        app.MapPost("/", (CreateAppRequest request, IAppService service) => service.CreateAsync(request));
}
  • IEndpoint.Map is static. Endpoint types are never instantiated; dependencies come from handler parameters.
  • IEndpointGroup.Prefix is passed to MapGroup. Configure applies group-wide conventions.
  • [EndpointGroup(typeof(TGroup))] or [EndpointGroup<TGroup>] places an endpoint or a group under a group. Groups can nest.
  • [ExcludeFromMapping] excludes an endpoint, a group (with everything under it) or an assembly.

Mapping

The bundled source generator creates one registry per assembly in the Microsoft.AspNetCore.Builder namespace:

var app = builder.Build();

app.MapAppsEndpoints();   // one assembly's registry
// or
app.MapAllEndpoints();    // own registry + every referenced assembly's registry
  • Map{Asm}Endpoints maps the endpoints of one assembly. {Asm} is the sanitized assembly name (MyCompany.Apps.Api → MyCompanyAppsApi) or the [assembly: EndpointRegistryName("Apps")] value.
  • {Asm}EndpointRegistry.EndpointTypes lists the endpoint types in mapping order. [assembly: EndpointModule(typeof(...))] is emitted by the generator to mark an assembly that has a registry; do not write it yourself.
  • MapAllEndpoints is internal and generated automatically in Exe/WinExe projects that are not test projects. Use [assembly: GenerateEndpointAggregate] to opt in from a library or test project, [assembly: DisableEndpointAggregate] to opt out. Each registry is mapped once, own registry first, then referenced registries by assembly name.
  • Mapping is not idempotent: calling Map{Asm}Endpoints or MapAllEndpoints twice on the same route builder maps every route twice, and ASP.NET Core reports an ambiguous match at request time. Mapping the same registry onto different route builders (for example two version groups) is legitimate, so call each method once per builder.
  • Each endpoint type is mapped inside its own MapGroup(""), so route patterns, OpenAPI metadata, filters and authorization are the same as direct mapping. Route calls such as MapGet stay in your Map method.

Reflection fallback

When a registry cannot be generated (for example, plugin assemblies loaded at runtime), map endpoints by scanning assemblies:

app.MapEndpointsFromAssemblies(typeof(CreateApp).Assembly);
app.MapEndpointsFromAssemblies(options => options.LogDiscovered = true, pluginAssembly);
  • Uses the same discovery rules, ordering, conventions and options as the generated registries.
  • Types the analyzer would report as errors (CSE1001, CSE1002, CSE1003, CSE1007, CSE1008) are skipped and logged at Warning, category CSharpEssentials.Endpoints. If some types of an assembly cannot be loaded, the loadable types are mapped and each loader exception is logged at Warning.
  • Marked [RequiresUnreferencedCode] and [RequiresDynamicCode]. Prefer the generated methods in trimmed and Native AOT apps.

API versioning

CSharpEssentials.AspNetCore provides MapVersionedGroup, which composes with any registry:

app.MapVersionedGroup(2).MapAppsEndpoints();   // routes under /v2/...

Mapping options

app.MapAppsEndpoints(options =>
{
    options.Filter(type => type.Namespace != "Sample.Internal");
    options.OperationNaming = OperationNaming.TypeName;
    options.AutoTagFromGroup = true;
    options.LogDiscovered = true;
    options.ConfigureEach((builder, type) => builder.WithMetadata(new MyMetadata(type)));
});
Option Behavior
Filter Skips endpoint types for which a predicate returns false. Multiple predicates are AND-combined. Groups whose endpoints are all filtered out are not created.
ConfigureEach Runs once per endpoint type after group Configure and per-type metadata, in registration order.
OperationNaming None (default), TypeName or Custom(...). TypeName builds unique names from the type and its containing types (Orders_Endpoint), adds the HTTP method when a type maps several routes (Items_Get, Items_Post), qualifies colliding names with the namespace and throws when two types share a full name. An explicit WithName(...) always wins.
AutoTagFromGroup Tags endpoints without explicit tags with the innermost group name (UsersGroup → Users).
LogDiscovered Logs mapped and filtered endpoint types at Debug, category CSharpEssentials.Endpoints.

Every endpoint gets EndpointTypeMetadata, which identifies the endpoint type at runtime.

TypeName names are not stable across changes to the application. Adding an endpoint or type with the same name can qualify an existing name with its namespace or give it a numeric suffix, which renames the generated operationId and the method names of NSwag or Kiota clients. Set the name with WithName(...) in Map for endpoints whose client method names must not change.

Explicit names are reserved at build time. The generator collects constant names set with WithName(...) or with WithMetadata(new EndpointNameAttribute(...)), new EndpointNameMetadata(...) or new RouteNameMetadata(...) anywhere in the project, including plain app.MapGet(...).WithName("Items") calls outside endpoint types. The generated registry reserves them before it maps, and MapAllEndpoints reserves the host project's names even when the host has no endpoint types. With TypeName, a generated name that would collide with a reserved one gets a numeric suffix (Items_2); the explicit name never changes. Not reserved: names that are not constants (CSE1011), [EndpointName] attributes on handlers outside endpoint types, endpoints mapped with the MapEndpointsFromAssemblies reflection fallback, and names of a project whose registry is not called (calling only a library's Map{Asm}Endpoints reserves only that library's names). A reserved name stays reserved even when its endpoint is not mapped.

Reservation and CSE1010 work per project (compilation), not per application. A project that builds two WebApplications and gives an endpoint in each the same WithName("Items") gets CSE1010, and each application also reserves the other's names, so a generated name that matches one of them gets an unneeded _2 suffix. In that case the duplicate is intended; turn CSE1010 off for the project in .editorconfig (dotnet_diagnostic.CSE1010.severity = none). A host without endpoint types that calls a library's Map{Asm}Endpoints directly does not reserve its own explicit names, because the host has no registry to emit them; call MapAllEndpoints in the host instead, which reserves them before it maps the libraries.

Typed routes

RouteOf<TEndpoint> builds a request path from the endpoint type, which keeps tests independent of route strings:

string path = app.RouteOf<GetApp>(new { id = 42 });                  // "/apps/42"
string remove = app.RouteOf<AppCommands>("DELETE", new { id = 42 }); // select by HTTP method or WithName(...)
  • Uses EndpointTypeMetadata on the mapped endpoints, so group prefixes and versioned groups are included. Extra values become query string entries.
  • Throws InvalidOperationException when the type has no route, has more than one route and no selector is given, or a route value is missing or fails its constraint.
  • Reads values from object properties, so it is marked [RequiresUnreferencedCode].

Authorization shortcuts

Thin wrappers over RequireAuthorization for any endpoint, group or route handler builder:

group.RequireRoles("admin", "editor");       // any listed role
group.RequirePolicies("CanRead", "CanWrite"); // every listed policy
group.RequireAuthSchemes("Bearer");           // any listed scheme
  • Each call adds AuthorizeAttribute metadata, so it combines with RequireAuthorization, [Authorize] and AllowAnonymous as usual.
  • Empty lists and null, blank or comma-separated entries throw ArgumentException.

Validation

CSharpEssentials.AspNetCore adds a validation endpoint filter for CSharpEssentials.Validation validators. Call it on the route handler inside Map:

[EndpointGroup<AppsGroup>]
public sealed class CreateApp : IEndpoint
{
    public static void Map(IEndpointRouteBuilder app) =>
        app.MapPost("/", (CreateAppRequest request, IAppService service) => service.CreateAsync(request))
           .WithValidation<CreateAppRequest>();
}

Failed validation returns a 400 problem response and the handler does not run. See the AspNetCore Readme for the details.

Diagnostics

The bundled analyzer reports types the generator cannot map and explicit endpoint names that repeat or cannot be reserved. Invalid types are skipped, so the generated registry always compiles. Types and assemblies marked [ExcludeFromMapping] are not analyzed, except by the endpoint name rules CSE1010 and CSE1011.

ID Severity Rule
CSE1001 Error Endpoint or group type is not accessible from generated code (private/protected nested, file-local, or nested in an inaccessible type).
CSE1002 Error Group nesting cycle: the [EndpointGroup] chain returns to the same group. Reported on each group in the cycle.
CSE1003 Error More than one group attribute on one type ([EndpointGroup(typeof(X))] and [EndpointGroup<Y>]).
CSE1004 Warning Endpoint declares instance fields, auto-properties or a constructor with parameters. Endpoint types are never instantiated; take dependencies as handler parameters. Code fix: removes the state when nothing else uses it.
CSE1005 Warning Two endpoints in the same group map the same HTTP method and constant route (MapGet/MapPost/MapPut/MapDelete/MapPatch/MapMethods on the Map parameter). Ungrouped endpoints count as one group.
CSE1006 Info Abstract or open-generic endpoint or group type is skipped.
CSE1007 Error [EndpointGroup(typeof(X))] target does not implement IEndpointGroup, or is abstract, open-generic or a ref struct.
CSE1008 Error Endpoint or group type is a ref struct. Generated code passes it as a generic type argument, which ref structs cannot be, so it is not mapped.
CSE1009 Warning Two referenced assemblies produce the same registry name, for example Foo.Api and FooApi both produce FooApiEndpointRegistry. MapAllEndpoints skips both registries so the project still compiles. The project's own registry counts too: when it has the same name as a referenced one, only the own registry is mapped. Give one of them a distinct name with [assembly: EndpointRegistryName("...")]. Reported only in projects that generate the aggregate.
CSE1010 Warning The same constant endpoint name is set at two call sites with WithName(...) or with EndpointNameAttribute, EndpointNameMetadata or RouteNameMetadata in WithMetadata(...). Duplicate names produce duplicate operationIds and make link generation by name ambiguous. Endpoint names and route names are compared separately, so WithMetadata(new EndpointNameMetadata("X")) and WithMetadata(new RouteNameMetadata("X")) on one endpoint are not reported. Reported on each call site.
CSE1011 Info An explicit endpoint name outside an IEndpoint or IEndpointGroup type is not a compile-time constant, so the generated registry cannot reserve it and OperationNaming.TypeName can generate the same name. Use a constant. Names set inside endpoint and group types are seen at runtime and are not reported.

The bundled code fix for CSE1004 ("Remove instance state from '{Type}'") deletes the instance fields, auto-properties and parameterized constructors of the endpoint. It is offered only when nothing else in the solution uses them, and not for records.

Migrating from Carter

Carter modules are instances created at startup; this package maps static methods from a generated registry, so no module is constructed and no reflection runs.

Carter CSharpEssentials.Endpoints
builder.Services.AddCarter() Nothing to register
app.MapCarter() app.Map{Assembly}Endpoints() or app.MapAllEndpoints()
ICarterModule.AddRoutes(IEndpointRouteBuilder app) static void Map(IEndpointRouteBuilder app) on an IEndpoint
CarterModule base class with a base path and fluent conventions in the constructor An IEndpointGroup (Prefix, Configure) and [EndpointGroup<TGroup>] on each endpoint
Constructor injection into the module Handler parameters ((IAppService service) => ...)
Explicit module lists in the Carter configurator options.Filter(...) or [ExcludeFromMapping]
FluentValidation helpers .WithValidation<T>() from CSharpEssentials.AspNetCore with CSharpEssentials.Validation
Response negotiators IResult and TypedResults

Before:

using Carter;

public sealed class AppsModule : CarterModule
{
    public AppsModule() : base("/apps") => WithTags("Apps");

    public override void AddRoutes(IEndpointRouteBuilder app)
    {
        app.MapGet("/{id:int}", (int id, IAppService service) => service.GetAsync(id));
        app.MapPost("/", (CreateAppRequest request, IAppService service) => service.CreateAsync(request));
    }
}

After: the module becomes a group, and each route becomes an endpoint type. One endpoint type can also keep several related routes.

public sealed class AppsGroup : IEndpointGroup
{
    public static string Prefix => "apps";

    public static void Configure(RouteGroupBuilder group) => group.WithTags("Apps");
}

[EndpointGroup<AppsGroup>]
public sealed class GetApp : IEndpoint
{
    public static void Map(IEndpointRouteBuilder app) =>
        app.MapGet("/{id:int}", (int id, IAppService service) => service.GetAsync(id));
}

[EndpointGroup<AppsGroup>]
public sealed class CreateApp : IEndpoint
{
    public static void Map(IEndpointRouteBuilder app) =>
        app.MapPost("/", (CreateAppRequest request, IAppService service) => service.CreateAsync(request));
}

Then replace AddCarter() and MapCarter() with app.MapAllEndpoints() and remove the Carter package. Build: the analyzer reports module state left on endpoint types (CSE1004) and duplicate routes in a group (CSE1005).

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.  net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net11.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
7.0.0 0 10/20/2026
6.5.2 91 10/10/2026
6.5.1 87 10/10/2026
6.5.0 93 10/9/2026
6.4.0 81 10/9/2026
6.3.0 81 10/8/2026
6.2.0 87 10/8/2026
6.1.0 80 10/7/2026
6.0.0 84 10/7/2026
5.2.1 88 10/7/2026
5.2.0 93 10/7/2026
5.1.0 84 10/7/2026
5.0.0 82 10/6/2026
4.1.0 89 10/6/2026