# Automatically Register Minimal APIs in ASP.NET Core

> In ASP.NET Core applications using Minimal APIs, registering each API endpoint with app.MapGet, app.MapPost, etc. can introduce repetitive code. Today, I'll show you how to automatically register your Minimal APIs with a simple abstraction.

Published: 2024-02-24. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/automatically-register-minimal-apis-in-aspnetcore

Define an `IEndpoint` interface with a single `MapEndpoint` method and implement it once per endpoint.
Scan the assembly with reflection, register every implementation with dependency injection, then call `MapEndpoints` at startup to map them all.
You can pass in a `RouteGroupBuilder` to apply a route prefix or API versioning to every endpoint.

In ASP.NET Core applications using [Minimal APIs](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/overview?view=aspnetcore-8.0),
registering each API endpoint with `app.MapGet`, `app.MapPost`, etc., can introduce repetitive code.
As projects grow, this manual process becomes increasingly time-consuming and prone to maintenance headaches.

You can try grouping the [**Minimal API endpoints**](https://milanjovanovic.tech/blog/minimal-apis-dotnet) using extension methods so as not to clutter the `Program` file.
This approach scales well as the project grows.
However, it feels like reinventing controllers.

I like to view each Minimal API endpoint as a [**standalone component**](https://milanjovanovic.tech/blog/repr-pattern-aspnetcore).

The vision I have in my mind aligns nicely with the concept of [vertical slices.](https://milanjovanovic.tech/blog/vertical-slice-architecture)

Today, I'll show you how to register your Minimal APIs automatically with a simple abstraction.

## The Endpoint Comes First

Automatically registering Minimal APIs significantly reduces boilerplate, streamlining development.
It makes your codebase more concise and improves maintainability by establishing a centralized registration mechanism.

Let's create a simple `IEndpoint` abstraction to represent a single endpoint.

The `MapEndpoint` accepts an `IEndpointRouteBuilder`, which we can use to call `MapGet`, `MapPost`, etc.

```csharp
public interface IEndpoint
{
    void MapEndpoint(IEndpointRouteBuilder app);
}
```

Each `IEndpoint` implementation should contain exactly one Minimal API endpoint definition.

Nothing prevents you from registering multiple endpoints in the `MapEndpoint` method.
But you (really) shouldn't.

Additionally, you could implement a code analyzer or [architecture test](https://milanjovanovic.tech/blog/enforcing-software-architecture-with-architecture-tests) to enforce this rule.

```csharp
public class GetFollowerStats : IEndpoint
{
    public void MapEndpoint(IEndpointRouteBuilder app)
    {
        app.MapGet("users/{userId}/followers/stats", async (
            Guid userId,
            ISender sender) =>
        {
            var query = new GetFollowerStatsQuery(userId);

            Result<FollowerStatsResponse> result = await sender.Send(query);

            return result.Match(Results.Ok, CustomResults.Problem);
        })
        .WithTags(Tags.Users);
    }
}
```

## Sprinkle Some Reflection Magic

Reflection allows us to dynamically examine code at runtime.
For Minimal API registration, we'll use reflection to scan our .NET assemblies and find classes that implement `IEndpoint`.
Then, we will configure them as services with dependency injection.

The `Assembly` parameter should be the assembly that contains the `IEndpoint` implementations.
If you want to have endpoints in multiple assemblies (projects), you can easily extend this method to accept a collection.

```csharp
public static IServiceCollection AddEndpoints(
    this IServiceCollection services,
    Assembly assembly)
{
    ServiceDescriptor[] serviceDescriptors = assembly
        .DefinedTypes
        .Where(type => type is { IsAbstract: false, IsInterface: false } &&
                       type.IsAssignableTo(typeof(IEndpoint)))
        .Select(type => ServiceDescriptor.Transient(typeof(IEndpoint), type))
        .ToArray();

    services.TryAddEnumerable(serviceDescriptors);

    return services;
}
```

We only need to call this method once from the `Program` file:

```csharp
builder.Services.AddEndpoints(typeof(Program).Assembly);
```

## Registering Minimal APIs

The final step in our implementation is to register the endpoints automatically.
We can create an extension method on the `WebApplication`, which lets us resolve services using the `IServiceProvider`.

We're looking for all registrations of the `IEndpoint` service.
These will be the endpoint classes we can now register with the application by calling `MapEndpoint`.

I'm also adding an option to pass in a `RouteGroupBuilder` if you want to apply conventions to all endpoints.
A great example is adding a route prefix, authentication, or [API versioning.](https://milanjovanovic.tech/blog/api-versioning-in-aspnetcore)

```csharp
public static IApplicationBuilder MapEndpoints(
    this WebApplication app,
    RouteGroupBuilder? routeGroupBuilder = null)
{
    IEnumerable<IEndpoint> endpoints = app.Services
        .GetRequiredService<IEnumerable<IEndpoint>>();

    IEndpointRouteBuilder builder =
        routeGroupBuilder is null ? app : routeGroupBuilder;

    foreach (IEndpoint endpoint in endpoints)
    {
        endpoint.MapEndpoint(builder);
    }

    return app;
}
```

## Putting It All Together

Here's what the `Program` file could look like when we put it all together.

We're calling `AddEndpoints` to register the `IEndpoint` implementations.

Then, we're calling `MapEndpoints` to automatically register the Minimal APIs.

I'm also configuring a route prefix and API Versioning for each endpoint using a `RouteGroupBuilder`.

```csharp {6,19}
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddEndpoints(typeof(Program).Assembly);

WebApplication app = builder.Build();

ApiVersionSet apiVersionSet = app.NewApiVersionSet()
    .HasApiVersion(new ApiVersion(1))
    .ReportApiVersions()
    .Build();

RouteGroupBuilder versionedGroup = app
    .MapGroup("api/v{version:apiVersion}")
    .WithApiVersionSet(apiVersionSet);

app.MapEndpoints(versionedGroup);

app.Run();
```

## Takeaway

Automatic Minimal API registration with techniques like reflection can significantly improve developer efficiency and project maintainability.

While highly beneficial, it's important to acknowledge the potential **performance impact of reflection** on application startup.

So, an improvement point could be using source generators for pre-compiled registration logic.

A few alternatives worth exploring:

- [Extension methods](https://milanjovanovic.tech/blog/how-to-structure-minimal-apis)
- [FastEndpoints](https://fast-endpoints.com/)
- [Carter](https://github.com/CarterCommunity/Carter)

Hope this was helpful.

See you next week.

**P.S.** Here's the complete [source code](https://github.com/m-jovanovic/minimal-endpoints) for this article.

---

## Frequently asked questions

### How do you automatically register Minimal API endpoints in ASP.NET Core?

Define an IEndpoint interface with a MapEndpoint method and implement it once per endpoint. Use reflection to scan the assembly for implementations and register them with dependency injection, then call an extension method at startup that resolves every IEndpoint and calls MapEndpoint on it.

### Why register each Minimal API endpoint as its own class?

Registering every endpoint with app.MapGet or app.MapPost in the Program file creates repetitive code that becomes a maintenance headache as the project grows. One class per endpoint keeps each one a standalone component, which aligns with vertical slice architecture. Grouping endpoints with extension methods works but feels like reinventing controllers.

### Should one endpoint class register multiple Minimal API endpoints?

No. Each IEndpoint implementation should contain exactly one Minimal API endpoint definition. Nothing technically prevents registering several in one MapEndpoint method, but you should keep it to one, and you can enforce the rule with a code analyzer or an architecture test.

### How do you apply a route prefix or API versioning to all Minimal API endpoints?

Pass a RouteGroupBuilder into the endpoint mapping method. Create a group with MapGroup, for example with an api/v{version} prefix, attach an API version set, and map every endpoint against that group so conventions like prefixes, authentication, or versioning apply to all of them.

### What is the downside of using reflection to register endpoints?

Reflection can impact application startup performance because it scans assemblies at runtime. An improvement is using source generators for pre-compiled registration logic. Libraries like FastEndpoints and Carter are ready-made alternatives to building this yourself.
