Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

62 Commits
62 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Endpointer

Build Coverage NuGet License: MIT Roslyn .NET 8 .NET 9 .NET 10

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.

About

C# source generator for ASP.NET Core Minimal APIs implementing the REPR pattern. Zero reflection, compile-time code generation.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.