Efficiently Automate Minimal API Registration in ASP.NET Core
Written on
Chapter 1: Understanding Minimal API Registration
In ASP.NET Core applications that utilize Minimal APIs, the process of registering each endpoint through methods like app.MapGet, app.MapPost, etc., can lead to repetitive coding practices. As your project expands, this manual registration can become increasingly tedious and difficult to manage.
To address this, you can group your Minimal API endpoints using extension methods, which helps declutter the Program file. This method is effective for scaling projects, yet it can feel reminiscent of traditional controller patterns.
I prefer to conceptualize each Minimal API endpoint as an independent component, aligning with the idea of vertical slices. In this guide, I will demonstrate how to streamline your Minimal API registration through a straightforward abstraction.
Section 1.1: The Importance of Automatic Registration
Automatically registering Minimal APIs reduces boilerplate code, simplifies development, and enhances maintainability by creating a central registration system.
To start, we need to define a basic IEndpoint interface that will represent individual endpoints:
public interface IEndpoint
{
void MapEndpoint(IEndpointRouteBuilder app);
}
Each implementation of IEndpoint should encompass only one Minimal API definition. While you could technically register multiple endpoints within the MapEndpoint method, it is advisable to avoid doing so. Implementing a code analyzer or architectural test can help enforce this guideline.
Subsection 1.1.1: Example Endpoint Implementation
Consider the following implementation of the GetFollowerStats endpoint:
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 result = await sender.Send(query);
return result.Match(Results.Ok, CustomResults.Problem);
})
.WithTags(Tags.Users);
}
}
Section 1.2: Utilizing Reflection for Dynamic Registration
Reflection enables dynamic inspection of code during runtime. For the purpose of registering Minimal APIs, we can leverage reflection to scan .NET assemblies for classes that implement IEndpoint. These will then be configured as services using dependency injection.
The Assembly parameter should be the assembly that contains the IEndpoint implementations. If you wish to include endpoints from multiple assemblies, the method can be easily adapted to accept a collection.
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;
}
You only need to invoke this method once within the Program file:
builder.Services.AddEndpoints(typeof(Program).Assembly);
Chapter 2: Automatically Registering Minimal APIs
The final step in our implementation is to create an extension method on the WebApplication to automatically register the endpoints. This method will retrieve all IEndpoint service registrations and register them with the application using MapEndpoint.
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 how your Program file could look once everything is assembled:
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
Automating Minimal API registration through techniques like reflection can greatly enhance developer productivity and project maintainability. However, it’s crucial to consider the potential performance implications of reflection during application startup.
As a point of improvement, you might explore using source generators for pre-compiled registration logic. Other alternatives worth considering include extension methods, FastEndpoints, and Carter.
I hope you found this information valuable. See you next week!
P.S. If you're interested in further learning opportunities, consider my Pragmatic Clean Architecture course, which teaches best practices in software architecture. Alternatively, join my Patreon community for exclusive content and discounts.