Endpointer 0.1.0

This package has a SemVer 2.0.0 package version: 0.1.0+aa7a7e0.
dotnet add package Endpointer --version 0.1.0
                    
NuGet\Install-Package Endpointer -Version 0.1.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="Endpointer" Version="0.1.0">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Endpointer" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Endpointer">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
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 Endpointer --version 0.1.0
                    
#r "nuget: Endpointer, 0.1.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 Endpointer@0.1.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=Endpointer&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Endpointer&version=0.1.0
                    
Install as a Cake Tool

Endpointer

Build License: MIT netstandard2.0 Roslyn

A C# source generator for ASP.NET Core Minimal APIs implementing the REPR (Request-Endpoint-Response) pattern.

Endpointer is a thin layer above ASP.NET Core - not a framework. It generates the boilerplate at compile time with no reflection, while you keep full control over your endpoints.

Features

  • Zero runtime overhead - All code is generated at compile time
  • REPR pattern - Clean separation with Request, Endpoint, and Response in one file
  • Automatic DI registration - Primary constructor parameters are auto-registered
  • Automatic route mapping - All endpoints discovered and mapped via source generation
  • Native ASP.NET Core - Uses TypedResults, IEndpointRouteBuilder, and standard middleware
  • Incremental generator - Fast builds with Roslyn's latest IIncrementalGenerator API
  • No reflection - Everything resolved at compile time

Quick Start

1. Install the package

dotnet add package Endpointer

2. Create an endpoint

using Microsoft.AspNetCore.Http.HttpResults;

public class GetTimeEndpoint(TimeProvider timeProvider)
{
    public record GetTimeResponse(DateTimeOffset CurrentTime);

    public class Endpoint : IEndpoint
    {
        public void MapEndpoint(IEndpointRouteBuilder endpoints)
        {
            endpoints.MapGet("/time", (GetTimeEndpoint ep) => ep.Handle());
        }
    }

    public Ok<GetTimeResponse> Handle()
    {
        return TypedResults.Ok(new GetTimeResponse(timeProvider.GetUtcNow()));
    }
}

3. Register in Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointer();  // Generated - registers all endpoint classes

var app = builder.Build();

app.MapEndpointer();  // Generated - maps all endpoints

await app.RunAsync();

That's it! The source generator discovers all IEndpoint implementations and generates the registration code.

How It Works

Endpointer uses Roslyn's incremental source generator to:

  1. Discover endpoints - Finds all nested classes implementing IEndpoint
  2. Extract metadata - Captures the outer class name and its primary constructor parameters
  3. Generate registration - Creates extension methods for DI and route mapping

Generated Code

The generator produces two files:

IEndpoint.g.cs - The marker interface:

public interface IEndpoint
{
    void MapEndpoint(IEndpointRouteBuilder endpoints);
}

EndpointerRegistration.g.cs - Extension methods:

public static class EndpointerExtensions
{
    public static IServiceCollection AddEndpointer(this IServiceCollection services)
    {
        services.AddScoped<GetTimeEndpoint>();
        services.AddScoped<GetUserEndpoint>();
        services.AddScoped<CreateUserEndpoint>();
        // ... all discovered endpoints
        return services;
    }

    public static IEndpointRouteBuilder MapEndpointer(this IEndpointRouteBuilder endpoints)
    {
        new GetTimeEndpoint.Endpoint().MapEndpoint(endpoints);
        new GetUserEndpoint.Endpoint().MapEndpoint(endpoints);
        new CreateUserEndpoint.Endpoint().MapEndpoint(endpoints);
        // ... all discovered endpoints
        return endpoints;
    }
}

The REPR Pattern

REPR (Request-Endpoint-Response) organizes API code by feature rather than by layer:

Endpoints/
├── Users/
│   ├── CreateUserEndpoint.cs    # POST /users
│   ├── GetUserEndpoint.cs       # GET /users/{id}
│   ├── UpdateUserEndpoint.cs    # PUT /users/{id}
│   └── DeleteUserEndpoint.cs    # DELETE /users/{id}
└── Health/
    └── HealthEndpoint.cs        # GET /health

Each file contains:

  • Request - Input DTOs (records)
  • Endpoint - Route mapping (nested IEndpoint class)
  • Response - Output DTOs (records)
  • Handler - Business logic (methods on outer class)

Requirements

  • .NET 10.0 or later (for the application)
  • The generator itself targets netstandard2.0 for broad compatibility

Building

# Build
dotnet build src/Endpointer.slnx

# Test
dotnet test --solution src/Endpointer.slnx

Full Example

A complete endpoint with request/response DTOs, dependency injection, and OpenAPI metadata:

using Microsoft.AspNetCore.Http.HttpResults;

namespace MyApi.Endpoints.Products;

public class CreateProductEndpoint(IProductRepository repository, ILogger<CreateProductEndpoint> logger)
{
    // Request
    public record CreateProductRequest(
        string Name,
        string Description,
        decimal Price,
        string Category);

    // Response
    public record ProductResponse(
        int Id,
        string Name,
        string Description,
        decimal Price,
        string Category,
        DateTimeOffset CreatedAt);

    // Endpoint
    public class Endpoint : IEndpoint
    {
        public void MapEndpoint(IEndpointRouteBuilder endpoints)
        {
            endpoints.MapPost("/products", (CreateProductEndpoint ep, CreateProductRequest request) => ep.HandleAsync(request))
                .WithName("CreateProduct")
                .WithTags("Products")
                .WithSummary("Create a new product")
                .WithDescription("Creates a new product in the catalog and returns the created product with its assigned ID.")
                .Produces<ProductResponse>(StatusCodes.Status201Created)
                .ProducesValidationProblem()
                .WithOpenApi();
        }
    }

    // Handler
    public async Task<Results<Created<ProductResponse>, ValidationProblem>> HandleAsync(CreateProductRequest request)
    {
        if (request.Price < 0)
        {
            return TypedResults.ValidationProblem(new Dictionary<string, string[]>
            {
                ["Price"] = ["Price must be greater than or equal to zero."]
            });
        }

        logger.LogInformation("Creating product {Name} in category {Category}", request.Name, request.Category);

        var product = await repository.CreateAsync(request);

        var response = new ProductResponse(
            product.Id,
            product.Name,
            product.Description,
            product.Price,
            product.Category,
            product.CreatedAt);

        return TypedResults.Created($"/products/{response.Id}", response);
    }
}

License

This project is licensed under the MIT License - see the LICENSE file for details.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

This package has 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
0.1.0 36 1/24/2026