Automatically Register Minimal APIs in ASP.NET Core
Tags: coding, programming, restapis, api
Published: 2024-02-27 09:31:13

---
title: Automatically Register Minimal APIs in ASP.NET Core
published: true
date: 2024-02-24 00:00:00 UTC
tags: coding,programming,restapis,apis
canonical_url: https://www.milanjovanovic.tech/blog/automatically-register-minimal-apis-in-aspnetcore
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v5hen3geea557lw7i495.png
---
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 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.

The vision I have in my mind aligns nicely with the concept of [vertical slices.](https://www.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://www.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://www.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
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://www.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.

* * *

P.S. Whenever you're ready, there are 3 ways I can help you:

1. [**Modular Monolith Architecture:**](https://www.milanjovanovic.tech/modular-monolith-architecture?utm_source=dev.to&utm_medium=website&utm_campaign=cross-posting) This in-depth course will transform the way you build monolith systems. You will learn the best practices for applying the Modular Monolith architecture in a real-world scenario. [**Join the wait list here.**](https://www.milanjovanovic.tech/modular-monolith-architecture?utm_source=dev.to&utm_medium=website&utm_campaign=cross-posting)

2. [**Pragmatic Clean Architecture:**](https://www.milanjovanovic.tech/pragmatic-clean-architecture?utm_source=dev.to&utm_medium=website&utm_campaign=cross-posting) This comprehensive course will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture. [**Join 2,400+ students here.**](https://www.milanjovanovic.tech/pragmatic-clean-architecture?utm_source=dev.to&utm_medium=website&utm_campaign=cross-posting)

3. [**Patreon Community:**](https://www.patreon.com/milanjovanovic) Think like a senior software engineer with **access to the source code** I use in my YouTube videos and **exclusive discounts** for my courses. [**Join 1,050+ engineers here.**](https://www.patreon.com/milanjovanovic)