Kanject.Core.Api.Annotations 2.0.2

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

Kanject.Core.Api.Annotations

Kanject.Core.Api.Annotations is a Roslyn source generator plus analyzers for ASP.NET Core minimal APIs. You write endpoints as public static methods on a static class marked [ApiEndpoints]; at compile time it generates the MapGroup / MapGet / MapPost … registration code, carries your XML doc comments into OpenAPI summaries and descriptions, and writes a Markdown catalog of every route on each build.

Analyzers catch the mistakes that would otherwise make the generator skip an endpoint without telling you — a non-static class, a private method, duplicate routes — and four of them come with code fixes.

Installation

dotnet add package Kanject.Core.Api.Abstractions
dotnet add package Kanject.Core.Api.Annotations
<ItemGroup>
  <PackageReference Include="Kanject.Core.Api.Abstractions" />                   
  <PackageReference Include="Kanject.Core.Api.Annotations" PrivateAssets="all" /> 
</ItemGroup>

This package is not marked as a development dependency, so dotnet add package writes a plain reference without PrivateAssets or IncludeAssets. Add PrivateAssets="all" yourself so the generator and its build targets stay in your project instead of flowing to projects that reference it.

The attribute types live in Kanject.Core.Api.Abstractions (there is no separate .Attributes package). Kanject.Core.Api and Kanject.Core.ApiV2 both bring that package in, but neither references this generator — add it explicitly wherever you declare [ApiEndpoints] classes. It depends on Kanject.Core.Annotations, which NuGet restores automatically.

The analyzer assembly targets netstandard2.0. Use it in ASP.NET Core projects on .NET 8, .NET 9 or .NET 10: the attribute types come from Kanject.Core.Api.Abstractions (net8.0–net10.0), and the generated code uses minimal-API route groups.

Quick start

using Kanject.Core.Api.Abstractions.Attributes;
using Microsoft.AspNetCore.Authorization;

namespace Contoso.Orders.Endpoints;

/// <summary>Order management endpoints.</summary>
[ApiEndpoints("Orders", "api/orders")]
public static class OrderEndpoints
{
    /// <summary>Lists the caller's orders.</summary>
    [HttpGet]
    public static async Task<IResult> ListOrders(IOrderStore store, CancellationToken ct)
        => Results.Ok(await store.ListAsync(ct));

    /// <summary>Returns one order.</summary>
    /// <remarks>Responds 404 when the order does not exist.</remarks>
    [HttpGet("{id:guid}")]
    public static async Task<IResult> GetOrder(Guid id, IOrderStore store, CancellationToken ct)
        => await store.FindAsync(id, ct) is { } order ? Results.Ok(order) : Results.NotFound();

    /// <summary>Liveness probe for the orders group.</summary>
    [AllowAnonymous]
    [HttpGet("ping")]
    public static IResult PingOrders() => Results.Ok();
}
using Contoso.Orders.Endpoints;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(/* your scheme */);
builder.Services.AddAuthorization();
builder.Services.AddScoped<IOrderStore, OrderStore>();

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();

app.MapEndpoints();   // every [ApiEndpoints] class in this project

app.Run();

Enable XML documentation in the project so the compiler parses your doc comments — the generator reads <summary> and <remarks> from them, and without it KANAPI011 reports every endpoint as undocumented:

<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

What gets generated

For each [ApiEndpoints] class, in the class's namespace — the global namespace for a class declared without one, such as beside the top-level statements of a minimal-API Program.cs (abridged):

public sealed class OrderEndpointsEndpointOptions
{
    public Action<RouteHandlerBuilder>? ListOrders { get; set; }
    public Action<RouteHandlerBuilder>? GetOrder { get; set; }
    public Action<RouteHandlerBuilder>? PingOrders { get; set; }
}

public static class OrderEndpointsRouteRegistration
{
    public static IEndpointRouteBuilder MapOrderEndpoints(this IEndpointRouteBuilder app,
        Action<RouteGroupBuilder>? configureGroup = null,
        Action<OrderEndpointsEndpointOptions>? configureEndpoints = null)
    {
        var group = app.MapGroup("api/orders")
            .WithTags("Orders")
            .RequireAuthorization()
            .WithDescription("Order management endpoints.");
        configureGroup?.Invoke(group);
        // ...
        var endpointGetOrder = group.MapGet("{id:guid}", global::Contoso.Orders.Endpoints.OrderEndpoints.GetOrder)
            .WithName(nameof(global::Contoso.Orders.Endpoints.OrderEndpoints.GetOrder))
            .WithSummary("Returns one order.")
            .WithDescription("Responds 404 when the order does not exist.");
        endpointOptions.GetOrder?.Invoke(endpointGetOrder);
        // ...
        return app;
    }
}

A class nested in another type is generated beside it at the top level of the namespace, named after its containing types and itself: TodoFeature.Endpoints generates TodoFeatureEndpointsRouteRegistration.MapTodoFeatureEndpoints() and TodoFeatureEndpointsEndpointOptions, so feature classes that each nest an Endpoints class don't collide.

Plus one KanjectEndpointRegistration.MapEndpoints() that calls every group's Map{ClassName}(). It is generated into the shortest namespace among your endpoint classes, so import that namespace where you call it. If any endpoint class is in the global namespace, so is MapEndpoints(), and it needs no using at all.

That is the output when generated request delegates are off. When they are on — under PublishAot or PublishTrimmed, see Native AOT — the group.MapGet(...) line becomes OrderEndpointsRequestDelegates.MapGetOrder(group, "{id:guid}"), a generated method in the same file; the rest of the chain is identical.

Groups, routes and authorization

[ApiEndpoints] takes the OpenAPI tag and the route prefix positionally, or as named properties:

Property Default Effect
EndpointGroupName The class name (for a nested class, its generated name: TodoFeatureEndpoints) WithTags(...) on the group
Route "" Prefix passed to MapGroup(...)
RequireAuthorization true RequireAuthorization() on the group — register authentication and authorization, or set this to false
AuthPolicy none Group-wide policy, RequireAuthorization("<policy>") — applied only while RequireAuthorization is true

Verb attributes take an optional route template relative to the group ([HttpGet("{id:guid}")]); no template maps the group root. Per method, use ASP.NET Core's own attributes from Microsoft.AspNetCore.Authorization: [AllowAnonymous] opts the endpoint out of the group requirement, and [Authorize(Policy = "...")] or [Authorize(Roles = "...")] adds a requirement. The generator mirrors them as AllowAnonymous() / RequireAuthorization(...) calls on the endpoint.

Configuring groups and endpoints

Call the per-group method instead of MapEndpoints() when a group needs extra conventions:

app.MapOrderEndpoints(
    configureGroup: group => group.RequireCors("frontend"),
    configureEndpoints: endpoints =>
    {
        endpoints.PingOrders = endpoint => endpoint.ExcludeFromDescription();
    });

MapEndpoints() maps every group with no callbacks. Don't call it and a per-group Map…() for the same class, or the routes are registered twice.

Rules and gotchas

  • The class must be static (KANAPI001) and endpoint methods public static (KANAPI002); anything else is skipped by the generator.
  • The generated code calls your endpoints from the top level of the class's namespace, so the class and every type containing it must be public or internal (or protected internal), not private, protected, private protected or file (KANAPI006), and none of them generic (KANAPI016). Such a class is reported and none of its endpoints are mapped.
  • Two classes that generate the same names in one namespace — TodoFeature.Endpoints beside a top-level TodoFeatureEndpoints — are reported (KANAPI017), and neither is mapped until one is renamed. Classes of the same name in different namespaces are fine: MapEndpoints() calls each by its full name. Call them yourself through the registration class (Contoso.Admin.UsersRouteRegistration.MapUsers(app)) if both namespaces are imported where you do.
  • Each endpoint is named after its method (WithName(nameof(...))). ASP.NET Core requires endpoint names to be unique, and OpenAPI tools use them as operation IDs, so keep method names unique across all [ApiEndpoints] classes — ListOrders, not List.
  • Only the first verb attribute on a method is used (KANAPI013).
  • Return IResult or a concrete type; MVC's IActionResult belongs to controllers (KANAPI007), and void / Task give an implicit empty 200 (KANAPI008).
  • The Kanject verb attributes share their names with Microsoft.AspNetCore.Mvc.HttpGetAttribute and friends. If an endpoint file also imports Microsoft.AspNetCore.Mvc (for [FromBody], [FromServices], …), [HttpGet] becomes ambiguous — alias or fully qualify one of them.

Native AOT: generated request delegates

group.MapGet(route, Endpoints.Method) builds the route with ASP.NET Core's reflection-based RequestDelegateFactory, which is [RequiresUnreferencedCode] and [RequiresDynamicCode]. In your own code ASP.NET Core's Request Delegate Generator replaces those calls when you publish with Native AOT, but it is a source generator and cannot see calls another generator emits. So this generator writes the request delegates itself.

MSBuild property Effect
KanjectApiGeneratedRequestDelegates true emits generated request delegates; false keeps RequestDelegateFactory whatever the publish mode
EnableRequestDelegateGenerator When the property above is unset, generated delegates follow this ASP.NET Core switch, which the SDK turns on for PublishAot and PublishTrimmed

With them on, each route is registered through RouteHandlerServices.Map — the hook the Request Delegate Generator uses — with typed code for:

  • route, query and header values, including arrays, StringValues and every TryParse kind RequestDelegateFactory supports (enums, dates, numbers, Guid, Uri, your own TryParse/IParsable<T> types)
  • JSON bodies ([FromBody] or inferred), read with the JsonTypeInfo from your HttpJsonOptions resolver chain
  • [FromServices], [FromKeyedServices] and inferred services; HttpContext, HttpRequest, HttpResponse, ClaimsPrincipal, CancellationToken, Stream, PipeReader
  • [AsParameters], BindAsync / IBindableFromHttpContext<T>, IFormFile, IFormFileCollection, IFormCollection and simple [FromForm] values
  • every return shape (IResult, typed results, JSON, string, object, void, Task/ValueTask of each)
  • endpoint filters, including group filters, which see the bound arguments as typed values

The generated code reproduces what RequestDelegateFactory does for the same method on the ASP.NET Core version you compile against: the binding order, 400 and 415 responses, RouteHandlerOptions.ThrowOnBadRequest, the debug log events (under the Microsoft.AspNetCore.Http.RequestDelegateFactory category), startup errors, and the endpoint metadata it infers (accepts/produces, antiforgery, parameter binding and cookie-redirect metadata). The group conventions, WithName/WithSummary/WithTags, configureEndpoints callbacks, RequireAuthorization and DisableAntiforgery are untouched.

What a Native AOT app has to provide is what any Native AOT minimal API needs — JSON metadata for its request and response types:

[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(List<Order>))]
internal partial class AppJsonContext : JsonSerializerContext;

builder.Services.ConfigureHttpJsonOptions(options =>
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default));

Set KanjectApiGeneratedRequestDelegates to true in the project (not only at publish) to run the same binding in development and tests that you ship.

A route whose shape the generator does not reproduce exactly — a generic method, a ref parameter, a custom binding-source attribute, a complex [FromForm] type, a nested [AsParameters], a malformed TryParse/BindAsync, a custom awaitable return, and the like — keeps MapGet/MapPost and RequestDelegateFactory, and the build reports KANAPI014 on it with the reason. Change the shape, or accept the reflection-based route and suppress KANAPI014 on that method.

API schema document

Each time a build compiles the project, the package's MSBuild targets write <AssemblyName>.apischema.md next to the project file: a Markdown catalog with an overview, endpoint groups, a route map, an authorization matrix, OpenAPI documentation coverage and registration snippets. It is useful as reference documentation and as context for AI coding assistants. (Versions up to 2.0.0 described this file but wrote it only for a project that set EmitCompilerGeneratedFiles itself.)

MSBuild property Default Effect
GenerateApiSchema true Set false to skip writing the file
ApiSchemaOutputDir Project directory Relative (to the project) or absolute output directory

The document is read from the generator's source as Roslyn writes it to disk, so the package's props set EmitCompilerGeneratedFiles to true unless you have set it yourself. Generated sources then also land under obj/<configuration>/<framework>/generated/, or under your CompilerGeneratedFilesOutputPath. If you set EmitCompilerGeneratedFiles to false, no document is written and the build says why.

GenerateApiSchema=false in Directory.Build.props turns both off, because it is read before the package's defaults. In the project file it is read after them: the document is skipped, but generated sources are still written unless you also set EmitCompilerGeneratedFiles to false there.

The document is rewritten only when the compiler runs. An up-to-date incremental build leaves it as it is, so rebuild to recreate a deleted one. A compile that produces no schema (no [ApiEndpoints] classes left, or KANJECTAPISCHEMA001) writes nothing and leaves an earlier document in place. The generator also embeds the same Markdown in your compiled assembly as [assembly: AssemblyMetadata("ApiSchema", ...)]; GenerateApiSchema=false does not remove that attribute.

Diagnostics

ID Severity What it means
KANAPI001 Error [ApiEndpoints] class is not static, so the generator skips it (code fix: make static).
KANAPI002 Error A method with a verb attribute is not public static, so it is skipped (code fix: make public static).
KANAPI003 Warning A Kanject verb attribute is on a method whose class lacks [ApiEndpoints] and has no effect (code fix: add [ApiEndpoints]).
KANAPI004 Warning [ApiEndpoints] class contains no verb-annotated methods.
KANAPI005 Error Two methods in a group map the same verb and route template — a runtime route conflict.
KANAPI006 Error The [ApiEndpoints] class, or a type containing it, is private, protected, private protected or file, so generated code cannot call it and none of its endpoints are mapped. The message names the type.
KANAPI007 Info Endpoint returns MVC IActionResult / ActionResult; return IResult or a concrete type.
KANAPI008 Warning Endpoint returns void or bare Task; return an IResult for an explicit status code.
KANAPI009 Info [AllowAnonymous] is redundant because the group sets RequireAuthorization = false.
KANAPI010 Info [Authorize] without a policy or roles is redundant because the group already requires authorization.
KANAPI011 Info Endpoint method has no XML <summary>, so its OpenAPI summary will be empty.
KANAPI012 Warning Route template starts with /, which can produce // after the group prefix (code fix: remove it).
KANAPI013 Warning Method has more than one verb attribute; only the first is used.
KANAPI014 Warning Generated request delegates are on, but this endpoint's shape is not one the generator reproduces exactly, so it stays on RequestDelegateFactory (reflection; IL2026/IL3050 under Native AOT). The message says why.
KANAPI015 Warning Generated request delegates were requested, but the project does not reference ASP.NET Core 8.0 or later.
KANAPI016 Error The [ApiEndpoints] class is generic or nested in a generic type; a generated registration has no type arguments to supply, so none of its endpoints are mapped.
KANAPI017 Error Two [ApiEndpoints] classes generate the same {Name}RouteRegistration and Map{Name} in one namespace (a nested class's name includes its containing types'), so neither is mapped; rename one.
KANJECTAPISCHEMA001 Warning Generating the API schema document failed; route registrations are still generated.
Package Role Availability
Kanject.Core.Api.Abstractions The [ApiEndpoints] and verb attribute types, plus response envelope helpers for minimal APIs nuget.org
Kanject.Core.Api Controllers, exception middleware (Kanject envelope errors) and tenant middleware nuget.org
Kanject.Core.ApiV2 Controllers, exception middleware (Problem Details errors) and tenant middleware nuget.org
Kanject.Core.Annotations Shared Kanject source-generator infrastructure (dependency of this package) nuget.org

License

Licensed under the Kanject Code Libraries License Agreement (KCLLA); the full text ships in this package as LICENSE.md. Organizations whose trailing-twelve-month gross revenue and total funding raised are each below US$250,000 may use it at no cost under the Free Tier. At or above either threshold a commercial license is required — contact commercial@kanjectbusiness.com.

There are no supported framework assets in this 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
2.0.2 91 10/22/2026
2.0.1 191 10/14/2026
2.0.0 81 10/13/2026
1.9.0 92 10/12/2026
1.8.0 121 10/5/2026
1.7.1 100 10/5/2026
1.7.0 100 10/2/2026
1.6.1 122 9/27/2026
1.6.0 104 9/27/2026
1.5.7 105 9/26/2026
1.5.6 142 9/7/2026
1.5.5 121 8/27/2026
1.5.4 120 8/22/2026
1.5.3 126 8/10/2026
1.5.2 120 8/9/2026
1.5.1 137 8/5/2026
1.5.0 127 8/5/2026
1.4.0 133 8/3/2026
1.3.5 133 7/30/2026
1.3.4 133 7/18/2026
Loading failed