Minimal API endpoints getting messy? Let's get organized
Tags: dotnet, csharp, api, programming
Published: 2022-11-11 20:08:20

I was a loud advocate and early adopter of dotnet's Minimal API framework for managing endpoints. It solves the problem of Controllers being verbose and cluttered, heavily geared for the MVC architecture and frequently being responsible for returning a fully rendered view from the backend. While also provides a much more flexible structure for your Rest methods. If you don't know what I'm talking about, make sure to check [my other article](https://dev.to/brunosilvadev/still-writing-controllers-in-dotnet-give-minimal-apis-a-try-5e4i), which should help you switch from dotnet controllers to endpoints.

We went from this:

```csharp
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using sandboxing.Models;

namespace sandboxing.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public IActionResult Index()
        {
            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}
```

To this:

```csharp
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();
```

Now, I've been hearing people complain that it gets cluttered and messy once you start having more than a few routes to specify. You don't want to be touching your Program.cs all the time and nobody wants to clutter a critical file with 100 lines that look more or less the same. I personally know the pain, as one of the microservices in the app we're building at my job has over 100 endpoints. It's easy to get lost, but there's ways to work around the mess.

Here's the solution we came up with: create an interface, separate each logical domain into a different file with its own routes and each of these files implements the interface. Then during the application startup, we iterate through every single implementation to get the routes registered.

Sounds tricky? Check this example and you'll see it's pretty straightforward.

First, the interface with the RegisterRoutes signature, that receives an IEndpointRouteBuilder

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

If you had separate controllers in the past, now you should have separate endpoints with a group of routes. The implementation will be much closer to the bare bones of Minimal APIs than with old school MVC Controllers.

This is a part of DeckEndpoint, one of the endpoints in my playing cards API Croupier ([check it on my Github](https://github.com/brunosilvadev/Croupier)) 

```csharp
public class DeckEndpoint : IEndpoint    
{
    public void RegisterRoutes(IEndpointRouteBuilder app)
    {
        app.MapGet("/new-game", NewSession);
        app.MapGet("/draw-card", DrawCard);
        app.MapGet("/see-deck", SeeDeck);
        app.MapGet("/shuffle-deck", ShuffleDeck);
    }
}
```

The second parameter in the MapGet method is a delegate. NewSession, DrawCard, SeeDeck and ShuffleDeck are the methods in that endpoint class.

Okay, now we just need to make sure every single instance of RegisterRoutes function is being called during the program startup. For that you'll need to add a line for each new endpoint in your dependency inversion definition, like this:

```csharp
services.AddTransient<IEndpoint, DeckEndpoint>();
services.AddTransient<IEndpoint, TestEndpoint>();
```

You can also achieve the same result using Reflection! But it's a little more tricky so I'll save that for another time.

Now that our endpoints are registered, we just need to iterate through the implementations of IEndpoint from the DI provider and make sure the RegisterRoutes method is called:

```csharp
var app = builder.Build();

var endpoints = app.Services.GetServices<IEndpoint>().ToList();
        
endpoints.ForEach(e => e.RegisterRoutes(app));
```

And we're done! Functionality-wise, every route from multiple endpoints is registered as if they were all listed in Program.cs. For reference, this is what my Program class looks like in the Croupier API:

```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDIServices();

var app = builder.Build();

app.UseOpenApi();
app.UseEndpoints();
app.Run();
```

Slim!

I hope this helps you decide to let go of old Controller ways and embrace the new dotnet features. Drop a question if you feel like so, check out the [Croupier API](https://github.com/brunosilvadev/Croupier) on my Github and make sure to follow me here and on my [LinkedIn](https://www.linkedin.com/in/brunosilvadev/). Thanks and have a good one.