|
1 | | -# Endpointer |
| 1 | +# Endpointer |
| 2 | + |
| 3 | +[](https://github.com/stephanprobst/Endpointer/actions/workflows/build.yml) |
| 4 | +[](https://opensource.org/licenses/MIT) |
| 5 | +[](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) |
| 6 | +[](https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/) |
| 7 | + |
| 8 | +A **C# source generator** for ASP.NET Core Minimal APIs implementing the **REPR (Request-Endpoint-Response) pattern**. |
| 9 | + |
| 10 | +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. |
| 11 | + |
| 12 | +## Features |
| 13 | + |
| 14 | +- **Zero runtime overhead** - All code is generated at compile time |
| 15 | +- **REPR pattern** - Clean separation with Request, Endpoint, and Response in one file |
| 16 | +- **Automatic DI registration** - Primary constructor parameters are auto-registered |
| 17 | +- **Automatic route mapping** - All endpoints discovered and mapped via source generation |
| 18 | +- **Native ASP.NET Core** - Uses `TypedResults`, `IEndpointRouteBuilder`, and standard middleware |
| 19 | +- **Incremental generator** - Fast builds with Roslyn's latest `IIncrementalGenerator` API |
| 20 | +- **No reflection** - Everything resolved at compile time |
| 21 | + |
| 22 | +## Quick Start |
| 23 | + |
| 24 | +### 1. Install the package |
| 25 | + |
| 26 | +```bash |
| 27 | +dotnet add package Endpointer |
| 28 | +``` |
| 29 | + |
| 30 | +### 2. Create an endpoint |
| 31 | + |
| 32 | +```csharp |
| 33 | +using Microsoft.AspNetCore.Http.HttpResults; |
| 34 | + |
| 35 | +public class GetTimeEndpoint(TimeProvider timeProvider) |
| 36 | +{ |
| 37 | + public record GetTimeResponse(DateTimeOffset CurrentTime); |
| 38 | + |
| 39 | + public class Endpoint : IEndpoint |
| 40 | + { |
| 41 | + public void MapEndpoint(IEndpointRouteBuilder endpoints) |
| 42 | + { |
| 43 | + endpoints.MapGet("/time", (GetTimeEndpoint ep) => ep.Handle()); |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + public Ok<GetTimeResponse> Handle() |
| 48 | + { |
| 49 | + return TypedResults.Ok(new GetTimeResponse(timeProvider.GetUtcNow())); |
| 50 | + } |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +### 3. Register in Program.cs |
| 55 | + |
| 56 | +```csharp |
| 57 | +var builder = WebApplication.CreateBuilder(args); |
| 58 | + |
| 59 | +builder.Services.AddEndpointer(); // Generated - registers all endpoint classes |
| 60 | +
|
| 61 | +var app = builder.Build(); |
| 62 | + |
| 63 | +app.MapEndpointer(); // Generated - maps all endpoints |
| 64 | +
|
| 65 | +await app.RunAsync(); |
| 66 | +``` |
| 67 | + |
| 68 | +That's it! The source generator discovers all `IEndpoint` implementations and generates the registration code. |
| 69 | + |
| 70 | +## How It Works |
| 71 | + |
| 72 | +Endpointer uses Roslyn's incremental source generator to: |
| 73 | + |
| 74 | +1. **Discover endpoints** - Finds all nested classes implementing `IEndpoint` |
| 75 | +2. **Extract metadata** - Captures the outer class name and its primary constructor parameters |
| 76 | +3. **Generate registration** - Creates extension methods for DI and route mapping |
| 77 | + |
| 78 | +### Generated Code |
| 79 | + |
| 80 | +The generator produces two files: |
| 81 | + |
| 82 | +**IEndpoint.g.cs** - The marker interface: |
| 83 | +```csharp |
| 84 | +public interface IEndpoint |
| 85 | +{ |
| 86 | + void MapEndpoint(IEndpointRouteBuilder endpoints); |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +**EndpointerRegistration.g.cs** - Extension methods: |
| 91 | +```csharp |
| 92 | +public static class EndpointerExtensions |
| 93 | +{ |
| 94 | + public static IServiceCollection AddEndpointer(this IServiceCollection services) |
| 95 | + { |
| 96 | + services.AddScoped<GetTimeEndpoint>(); |
| 97 | + services.AddScoped<GetUserEndpoint>(); |
| 98 | + services.AddScoped<CreateUserEndpoint>(); |
| 99 | + // ... all discovered endpoints |
| 100 | + return services; |
| 101 | + } |
| 102 | + |
| 103 | + public static IEndpointRouteBuilder MapEndpointer(this IEndpointRouteBuilder endpoints) |
| 104 | + { |
| 105 | + new GetTimeEndpoint.Endpoint().MapEndpoint(endpoints); |
| 106 | + new GetUserEndpoint.Endpoint().MapEndpoint(endpoints); |
| 107 | + new CreateUserEndpoint.Endpoint().MapEndpoint(endpoints); |
| 108 | + // ... all discovered endpoints |
| 109 | + return endpoints; |
| 110 | + } |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +## The REPR Pattern |
| 115 | + |
| 116 | +REPR (Request-Endpoint-Response) organizes API code by feature rather than by layer: |
| 117 | + |
| 118 | +``` |
| 119 | +Endpoints/ |
| 120 | +├── Users/ |
| 121 | +│ ├── CreateUserEndpoint.cs # POST /users |
| 122 | +│ ├── GetUserEndpoint.cs # GET /users/{id} |
| 123 | +│ ├── UpdateUserEndpoint.cs # PUT /users/{id} |
| 124 | +│ └── DeleteUserEndpoint.cs # DELETE /users/{id} |
| 125 | +└── Health/ |
| 126 | + └── HealthEndpoint.cs # GET /health |
| 127 | +``` |
| 128 | + |
| 129 | +Each file contains: |
| 130 | +- **Request** - Input DTOs (records) |
| 131 | +- **Endpoint** - Route mapping (nested `IEndpoint` class) |
| 132 | +- **Response** - Output DTOs (records) |
| 133 | +- **Handler** - Business logic (methods on outer class) |
| 134 | + |
| 135 | +## Requirements |
| 136 | + |
| 137 | +- **.NET 10.0** or later (for the application) |
| 138 | +- The generator itself targets **netstandard2.0** for broad compatibility |
| 139 | + |
| 140 | +## Building |
| 141 | + |
| 142 | +```bash |
| 143 | +# Build |
| 144 | +dotnet build src/Endpointer.slnx |
| 145 | + |
| 146 | +# Test |
| 147 | +dotnet test --solution src/Endpointer.slnx |
| 148 | +``` |
| 149 | + |
| 150 | +## License |
| 151 | + |
| 152 | +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. |
0 commit comments