Skip to content

Latest commit

History

63 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Endpointer

BuildCoverageNuGetLicense: MITRoslyn.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

usingMicrosoft.AspNetCore.Http.HttpResults;publicclassGetTimeEndpoint(TimeProvidertimeProvider){publicrecordGetTimeResponse(DateTimeOffsetCurrentTime);publicclassEndpoint:IEndpoint{publicvoidMapEndpoint(IEndpointRouteBuilderendpoints){endpoints.MapGet("/time",(GetTimeEndpointep)=>ep.Handle());}}publicOk<GetTimeResponse>Handle(){returnTypedResults.Ok(newGetTimeResponse(timeProvider.GetUtcNow()));}}

3. Register in Program.cs

varbuilder=WebApplication.CreateBuilder(args);builder.Services.AddEndpointer();// Generated - registers all endpoint classesvarapp=builder.Build();app.MapEndpointer();// Generated - maps all endpointsawaitapp.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:

publicinterfaceIEndpoint{voidMapEndpoint(IEndpointRouteBuilderendpoints);}

EndpointerRegistration.g.cs - Extension methods:

publicstaticclassEndpointerExtensions{publicstaticIServiceCollectionAddEndpointer(thisIServiceCollectionservices){services.AddScoped<GetTimeEndpoint>();services.AddScoped<GetUserEndpoint>();services.AddScoped<CreateUserEndpoint>();// ... all discovered endpointsreturnservices;}publicstaticIEndpointRouteBuilderMapEndpointer(thisIEndpointRouteBuilderendpoints){newGetTimeEndpoint.Endpoint().MapEndpoint(endpoints);newGetUserEndpoint.Endpoint().MapEndpoint(endpoints);newCreateUserEndpoint.Endpoint().MapEndpoint(endpoints);// ... all discovered endpointsreturnendpoints;}}

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:

usingMicrosoft.AspNetCore.Http.HttpResults;namespaceMyApi.Endpoints.Products;publicclassCreateProductEndpoint(IProductRepositoryrepository,ILogger<CreateProductEndpoint>logger){// RequestpublicrecordCreateProductRequest(stringName,stringDescription,decimalPrice,stringCategory);// ResponsepublicrecordProductResponse(intId,stringName,stringDescription,decimalPrice,stringCategory,DateTimeOffsetCreatedAt);// EndpointpublicclassEndpoint:IEndpoint{publicvoidMapEndpoint(IEndpointRouteBuilderendpoints){endpoints.MapPost("/products",(CreateProductEndpointep,CreateProductRequestrequest)=>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();}}// HandlerpublicasyncTask<Results<Created<ProductResponse>,ValidationProblem>>HandleAsync(CreateProductRequestrequest){if(request.Price<0){returnTypedResults.ValidationProblem(newDictionary<string,string[]>{["Price"]=["Price must be greater than or equal to zero."]});}logger.LogInformation("Creating product {Name} in category {Category}",request.Name,request.Category);varproduct=awaitrepository.CreateAsync(request);varresponse=newProductResponse(product.Id,product.Name,product.Description,product.Price,product.Category,product.CreatedAt);returnTypedResults.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

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages