Introduction to Minimal APIs in .NET 8
Introduction
One .NET feature that had caught my attention recently was Minimal APIs. I was curious about what the use case was for Minimal APIs as opposed to the traditional method of using the Model-View-Controller (MVC) pattern for API projects in .NET. Introduced in .NET 6, Minimal APIs promised to simplify creating Representational State Transfer (REST) APIs.
What Is an API?
Before we begin, let’s level set and make sure everyone understands what an application programming interface (API) is. An API is software that allows 2 applications to communicate with each other through a standard set of definitions and protocols. An application that needs data calls an endpoint in the API and the API provides plain text data back to the application. There are different types of APIs, including Simple Object Access Protocol (SOAP), Remote Procedure Call (RPC), WebSocket, and REST. REST APIs are the most popular choice for API development today and have been for some time.
Traditional API Development in .NET
The traditional way of creating APIs in .NET has been using the MVC pattern. You create a controller class, then decorate the methods with route attributes. While this approach has been in place for years and is production proven, it has a lot of overhead and complexity since it was initially developed for web page user interfaces, not providing application data through an endpoint. Performance is certainly an issue, as is the “magical” convention over configuration approach that makes it all work. Throw in model binding and filters and you will begin to understand the shortcomings of this approach.
Getting Started with Minimal API
When first learning about Minimal APIs, the question I kept asking myself was, why use this? It seemed like most of the demos were very simple and great for demo and starter applications, but would I really use this for a large-scale production application? Let’s take a look at a basic starter application and find out. I’ll start by selecting File, then New Project, and selecting the ASP.NET Core Web API template. Give your project a name, then click Next to go to the 3rd configuration page. Use controllers is unchecked by default and leaving that option unchecked will give you a Minimal API project.
The first thing to note is the simplicity of the project. You have Program.cs for the startup configuration and a single class file, WeatherForecast.cs, that defines a model for returning API data. Open Program.cs, and you’ll see some configuration for Swagger/OpenAPI. A bit further down in the file, you’ll see a definition for a single endpoint.
app.MapGet("/weatherforecast", (HttpContext httpContext) =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
})
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
Run the project and you’ll see a nice built-in interface for testing your API, courtesy of Swagger.
Click the Try it out button and then click the Execute button and you will see code 200 under Server response and a response body containing your weather forecast results. So, you can now see the simplicity of creating an endpoint with very minimal code.
Extracting Endpoints from Program.cs
While the default setup of Minimal APIs is fine for demo or smaller applications, adding any type of complexity to the application would quickly turn Program.cs into a mess, making it hard to read and debug. By using extension methods, we can remove the endpoints from Program.cs and make our project better organized and easier to maintain. Create a new static class called WeatherEndPoints, then add a method named RegisterWeatherEndPoints and pass in the WebApplication parameter. Finally, cut and paste the endpoint code from Program.cs into a new class as show below.
public static class WeatherEndPoints
{
public static void RegisterWeatherEndPoints(this WebApplication app)
{
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", (HttpContext httpContext) =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
})
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
}
}
In the Program.cs, use the extension method for the web application class to call the RegisterWeatherEndPoints method as show below. Then, run the app and verify the endpoint works.
Using a Base Class to Register All Endpoints
While getting the endpoint registration out of Program.cs helps, in a larger API project, you would still end up having to register each endpoint individually. With use of a base class, we can simplify endpoint registration to a single line of code. First, we will create an abstract class called EndpointGroupBase that has a single abstract method called Map with the WebApplication class as its parameter. Then, we will create a new class called WebApplicationExtensions and add a MapEndPoints method that first searches for any classes in our assembly that use the EndpointGroupBase class and then invokes the Map method for those classes.
public abstract class EndpointGroupBase
{
public abstract void Map(WebApplication app);
}
public static class WebApplicationExtensions
{
public static WebApplication MapEndpoints(this WebApplication app)
{
var endpointGroupType = typeof(EndpointGroupBase);
var assembly = Assembly.GetExecutingAssembly();
var endpointGroupTypes = assembly.GetExportedTypes()
.Where(t => t.IsSubclassOf(endpointGroupType));
foreach (var type in endpointGroupTypes)
{
if (Activator.CreateInstance(type) is EndpointGroupBase instance)
{
instance.Map(app);
}
}
return app;
}
}
Next, I’d recommend creating a folder called EndPoints. In that folder, create a class called Weather to contain our weather endpoint code. Having each endpoint in a separate class file helps to better organize our project and self contains the code for each endpoint into a single class file. In the Weather class, implement the base class (EndpointGroupBase), then override the Map method of that map to register the Weather endpoint. Notice I’m first using MapGroup to create a RouteGroupBuilder, which will help make it easy to create the routes we may need for this endpoint and avoid repetitious code by allowing us to apply other attributes to the entire group (like authorization or tags).
public class Weather: EndpointGroupBase
{
public override void Map(WebApplication app)
{
var weatherForecastGroup = app.MapGroup(GetType().Name);
weatherForecastGroup.MapGet("/weatherforecast", GetForecast)
.WithName("GetForecast")
.WithSummary("Get the weather forecast.")
.WithDescription("Returns a weather forecast.");
}
private WeatherForecast[] GetForecast(HttpContext context)
{
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
})
.ToArray();
return forecast;
}
}
Finally, we will add app.MapEndpoints(); to Program.cs to look for and register all of our endpoints.
Run the application and the endpoint should work as expected. Using a base class, extension methods, and reflection, we have kept Program.cs neat and tidy and structured our project to handle many endpoints while keeping everything organized and easy to maintain.
Conclusion
When first looking into Minimal APIs, I wondered about the use case for them, but with a little bit of work, you can easily use Minimal APIs in the largest production applications. The performance increase you get from not having your endpoints go through the MVC lifecycle is a big advantage, making this approach very lightweight and simple. Moving forward, I’ll be using Minimal APIs for all my API projects.