Blog
Overview of Minimal APIs with .NET 6 and C# 10
What are minimal APIs ?
An unopiniatonated way to build high performance APIs in .NET with the minimal amount of implicitly added features and the maximal amount of flexibility to explicitly opt in the features and the structure that you need
- Nick Chaspas, NDC London 2022
The Minimal APIs journey start with the will of the .NET development team to allow developers to embrace minimalism. We speak a lot for years now about how to avoid massive monotlithic applications and to prefer microservices but .NET never offered a way to really create minimal APIs. The reason is because APIs are based on the MVC layer so they ineherit from a lot of useless code which make them more complex and resources-cosuming than they should be. Minimal APIs are the answer of the .NET team of all this useless complexity. Now, .NET, like the majority of the others development languages, offers a way to create really minimal APIs with just what you need inside. Regarding that, Minimal APIs are a major .NET feature.
Minimal APIs is not a new concept
Even if they didn't exist in .NET, minimal APIs are existing for years now in other development environments.
Node.JS
const express = require('express')
const app = express()
const port = 3000
app.get('hello world', (req, res) => { req.send('Hello World!'); })
app.listen(port)
GO - GIN
import {
"github.com/gin-gonic/gin",
"net/http"
}
func main() {
r := gin.default()
r.GET("/", func(c *gin.Context){
c.String(http.StatusOK, "Hello World!")
})
r.Run()
}
Python - FastAPI
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
def read_root():
return { "hello": "Hello World!" }
To be fair it is not exactly right to say that Minimal APIs weren't existing in .NET before .NET 6 and C# 10, it was existing but you needed to use a package named NancyFX, a lightweight and low-ceremony framework for building HTTP based services on .Net and Mono but it was pretty confidential:
.NET - NancyFX
public class SampleModule : Nancy.NancyModule
{
public SampleModule()
{
Get["/"] = _ => "Hello World!";
}
}
If NancyFX is still available notice that this project has been archived on GitHub since .NET Core and is not longer maintained: ** Announcement ** - Nancy is no longer being maintained!.
The code samples below are just a few examples of Minimal APIs in other developments environments but they exists in pretty much of the languages, .NET and C# is one of the last to incorporate them with official support.
How can they work?
The Minimal APIs are based on several C# 9 and C# 10 features, mainly C# 10 features:
- Top-level statements - C# 9 feature
- Implicit using statements - C# 10 feature
- Global using statements - C# 10 feature
- Inferred lambda types - C# 10 feature
- Attributes on lambdas - C# 10 feature
Let's dig a little about theses features.
Top-level statements
Top-level statements enable you to avoid the extra ceremony required by placing your program's entry point in a static method in a class.
Before C# 9, a minimal console application was written with 11 lines:
using System;
namespace Application
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
Since Top-level statements and Implicit and Global using statements you can write a minimal console application with only one line, the only one who actually do something:
Console.WriteLine("Hello, World!");
Global using statements
Global using statements, ame with .NET 6 and C# 10, is the possibility to place all the common using statements in a single file which make them available for use within the entire project so you won't have to put them in the top of each file anymore.
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
Implicit using statements
.NET 6 with C# 10 introduced the support of the implicit namespace for C# projects, which was already existing for Visual Basic projects by the way.
Implicit usings is the possibility for .NET to bring used namespace into the default ones so you don't have to explicitly declare them. Behind the scenes, .NET stores them as global usings into an hidden auto generated file in your obj folder.
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
Inferred lambda types
The Inferred lambda types feature allow you to use var or inline return type in lambdas. Prior to C# 10 you had to explicitly define the delegate type such as Func
Func func = (string firstName, string lastName) => $"Hi! I am {firstName} {lastName}";
Action writeName = (string firstName, string lastName) =>
{
Console.WriteLine($"Hi! I am {firstName} {lastName}");
};
writeName("Adrien", "Torris");
Console.ReadLine();
It was a bit frustrating to write all this code but since C# 10 the type can be inferred by the compiler. If the compiler cannot determine the type to use it will throw an error so you can define it for him.
So our Func
line in C# 9 become with C# 10:
var func = (string firstName, string lastName) => $"Hi! I am {firstName} {lastName}";
Attributes on lambdas
Beginning with C# 10, you can add attributes to a lambda expression and its parameters. Here an example with an obsolete
tag:
var func = [Obsolete](string firstName, string lastName) => $"Hi! I am {firstName} {lastName}";
You can also add attributes to the input parameters or return value, as the following example shows:
var sum = ([Example(1)] int a, [Example(2), Example(3)] int b) => a + b;
var inc = [return: Example(1)] (int s) => s++;
What are Minimal APIs?
A minimal API has 2 fundamental components:
- the web application builder, where to put all the configuration of the application
- the web application itself, where to configure all the middleware
// Application builder which can access the arguments passed to the application
var builder = WebApplication.CreateBuilder(args);
// Materialization of the configuration builder
var app = builder.Build();
// Start the web application.
app.Run();
At this point, the application is totally valid and works, but it has no endpoint so it's pretty useless for now.
Before we add some behaviors to this API, notice that you can treat the lines between our tow first as the ConfigureServices of the startup.cs file of our old/current fashion to do web API, and the lines between our two last lines as the Configure method of the startup file.
var builder = WebApplication.CreateBuilder(args);
// ConfigureServices
var app = builder.Build();
// Configure
app.Run();
Let's add some endpoints now:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("hello", () => "Hello You! How are you?");
app.Run();
I used strings for simplicity here but you can use objects if you want to:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("hello2", () => new
{
Message = "Hello You! How are you?"
});
app.Run();
We also can use the Results class which handle some results behaviors:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("user", () => Results.Ok(new User("Adrien Torris"));
app.Run();
record User(string fullName);
We also can use the HTTP context or the HTTP request objects, and use the HTTP response object to customize our response:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("hello", async (HttpRequest request, HttpResponse response) =>
{
await response.WriteAsJsonAsync(request.Query);
});
app.Run();
We made some GET examples but you have a Map method for several HTTP verbs like POST, PUT or DELETE.
The minimal API is capable of dealing with route parameters the same ways than controllers:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("user/{id:int}", (int id) =>
{
id
});
app.Run();
You can use the routing API the same way as with controllers:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("user/{id:int}", ([FromQuery]int id, [FromHeader]string accept, [FromBody]User user) =>
{
id, accept, user
});
app.Run();
record User(string fullName);
To see more of the possibilities offers by the Minimal APIs you should take the time to browse the Dodyg samples on it, there are just a goldmine (like all his samples): Minimal APIs samples.
What minimal API does not support?
- No support for filters (It seems that an equivalent of them will coming pretty soon but it won't be the exactly same thing as today so we probably won't use the filters we have in our actual MVC projects).
- No built-in support for validation
- No support for JsonPatch
- No support for OData
- No support for APIversioning
All the Minimal APIs stuff could seems weird at start because it's a real breaking change compared to the way we developped APIs before .NET 6 but when you think about it, is it not the natural way to do it? After all, all the major environnements has adopted this approach for years and .NET didn't do it sooner for simplicity, because the functionnalities has already been developed in a stack so it was simpler to just use it. But are Controllers and all the MVC stuff really great for APIs? Are APIs that close to MVC websites?
Why the Controllers approach is weird for APIs?
- Controllers are coming from MVC, for which they were designed but APIs does not needs all the MVC stuff
- They have method that never call one another, and it's good because generaly there purposes are totally different, so why always put them in the same object?
- They violate the Single Responsability principle (depending of your definition of "Single")
- They can have services injected that aren't used by all Actions, which is a waste of ressources
- They never share states bewteen Actions, so what is the whole point of having them together?
- Most of the time even the private methods are used by only one Action
- They invoke Middlewares and Filters you do not need, they invoke them because the all stuff comes from MVC (like the validation for example)
- They can grow a lot leading to "fat controllers"
Can Minimal APIs have some structure?
The complaint I hear the most about Minimal APIs is that it has no structure, but this complaint just doesn't make any sense! Minimal APIs are not "Single File API's", they are Minimal APIs. If you develop an MVC-way API and put all your code in a single file, it doesn't make the MVC-way bad, it's just you who doesn't make things right. It the same for Minimal APIs, they can be developped in a single file, which will most of the time a bad practice, and they can structured like any .NET project in an elegant and coherent structure. I invite you to consult the clean minimal API sample by Nick Chaspas which is a structured Minimal API project :
In this sample, Nick Chaspas has created separates endpoint classes to avoid useless coupling between them.
This is an example for the endpoint to get an item, a consumer here:
using Customers.Api.Contracts.Requests;
using Customers.Api.Contracts.Responses;
using Customers.Api.Mapping;
using Customers.Api.Services;
using FastEndpoints;
using Microsoft.AspNetCore.Authorization;
namespace Customers.Api.Endpoints;
[HttpGet("customers/{id:guid}"), AllowAnonymous]
public class GetCustomerEndpoint : Endpoint
{
private readonly ICustomerService _customerService;
public GetCustomerEndpoint(ICustomerService customerService)
{
_customerService = customerService;
}
public override async Task HandleAsync(GetCustomerRequest req, CancellationToken ct)
{
var customer = await _customerService.GetAsync(req.Id);
if (customer is null)
{
await SendNotFoundAsync(ct);
return;
}
var customerResponse = customer.ToCustomerResponse();
await SendOkAsync(customerResponse, ct);
}
}
As you can see this project is perfectly structured but has the minimal amount of built-in code, which is all the purpose of Minimal APIs.
To conclude
When you think about it, the minimalism and low-ceremony approach seems to be the better one, the "natural one". Call it "Minimal APIs" is maybe a mistake because it infers that it's not the default way to do it, which can be a little scary, when the old fashion is probably the wrong one. Maybe Microsoft should have name "Minimal APIs" just "APIs" and the MVC-way of doing it "Unessential APIs" or "Massive APIs".
In any case, .NET 6 brings us an elegant and efficient way of developing APIs that I invite you to discover and try if you haven't already done so. The first step (or second if you are reading theses lines...) should be to watch the brilliant talk of Nick Chaspas at the NDC London.