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:

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 or Action:


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 func = (string firstName, string lastName) => $"Hi! I am {firstName} {lastName}"; 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:


// 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?

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?

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.

August 28, 2022
  • .NET 6
  • C# 10
  • Minimal APIs
  • NancyFX