Minimal APIs van Microsoft laten je sneller en met veel minder code web-API’s bouwen. Minimal APIs hebben nog wel wat minder mogelijkheden, maar ze zijn nu al een goed alternatief voor de gebruikelijke opzet in ASP.net MVC. In dit artikel laat ik je zien hoe je een Minimal API maakt en gebruikt.
Met .NET Minimal APIs bouw je API’s met zo min mogelijk afhankelijkheden en zo min mogelijk bestanden. Ze zijn daardoor ideaal voor kleinere API’s, bijvoorbeeld binnen een microservice-architectuur. Met een minimum aan afhankelijkheden en een goed uitgedacht framework kun je zo functionaliteit bouwen met heel weinig regels code.
.NET 6 is de eerste .NET-versie die Minimal APIs mogelijk maakt. Maar het idee is natuurlijk niet nieuw. Zo heeft NodeJS al langer de mogelijkheid om met een zeer klein aantal regels een web-API te maken.
Een deel van de features voor Minimal APIs zaten al in .NET 5 en C# 9. Zoals top-level programs, waarmee je met 2 regels een Hello World-programma kon maken. Met global usings en implicit usings in C# 10 ging dit zelfs naar 1 regel.
Hello World
Console.WriteLine(“Hello, World!”);
Op dinsdag 4 oktober organiseert Wiconic het Minimal API webinar!
.NET 6 maakt automatisch een Minimal API aan als je een webproject maakt met .NET 6 als target framework. Dat doe je vanaf de command line met:
dotnet new web -n MinimalApi -o MinimalApi
Dit commando maakt in de directory ‘MinimalApi’ een webproject aan met de naam ‘MinimalApi’. Het hele project bestaat, zoals je in de afbeelding ziet, maar uit 4 regels, onder andere door het gebruik van implicit usings. Daardoor ziet .NET veel usings als standaard. Dit maakt het geheel zeer compact en overzichtelijk.
Minimal API Template
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet(“/”, () => “Hello World!”);
app.Run();
De opzet lijkt erg op een Web API-project: we maken een WebBuilder aan en definiëren endpoints. Het verschil zit hem erin dat me nu direct een functionaliteit kunnen koppelen aan een endpoint zonder eerst bijvoorbeeld een controller aan te maken.
Met dotnet run starten we de applicatie. In de browser zien we nu ‘Hello World’ verschijnen.
Het bovenstaande resultaat word mogelijk gemaakt door de regel
app.MapGet(“/”, () => “Hello World!”);
Want daarmee maak je een nieuwe endpoint aan en koppel je functionaliteit.
‘MapGet’ in deze regel betekent dat het gaat om een get-request. De eerste parameter is het path voor het endpoint. De tweede parameter is de daadwerkelijke opdracht. Dit kan zoals hier een lambda-expressie zijn. Maar je kunt er ook een lokale functie van maken of een static method uit een andere class. Ook is het mogelijk met dependency injection een method aan te roepen van een object.
‘Hello world’ is een goed begin, maar we willen natuurlijk ook data mee kunnen geven aan een request. Hier zijn meerdere mogelijkheden voor: via routeparameters, queryparameters of in de body.
Bij het meegeven van data via een route gebruik je een parameter die uit de opgegeven route komt. De routeparameter geef je op door hem tussen accolades in de route op te nemen.
In het voorbeeld hieronder gebruiken we een parameter ‘name’ vanuit de route. We roepen deze route aan met ‘/HelloPerson/Jeroen’. Het resultaat is dat er ‘Hello Jeroen’ op het scherm komt.
Parameters gebruiken
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet(“/”, () => “Hello World!”);
app.MapGet(“/HelloPerson/{Name}”, (string name) => $”Hello {name}”);
app.Run();
Je kunt de routeparameters ook beperken door route constraints (oftewel restricties) toe te voegen. Bijvoorbeeld als je een integer verwacht. In onderstaand voorbeeld is een parameter ‘age’ aangemaakt. De ‘:int’ zorgt dat er een integer verwacht wordt. Ga je naar ‘/AllowedToVote/18’ zal gaan, dan verschijnt de tekst ‘Allowed to vote’.
Ga je naar ‘/AllowedToVote/Jeroen’, dan gebeurt er niets. Dit komt omdat deze route niet bekend is. Laat je de restrictie ‘:int’ weg, dan ontstaat er een exception omdat de .Net Runtime de tekst niet kan omzetten naar een integer.
Allowed to vote:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet(“/AllowedToVote/{age:int}”,
(int age) => age >= 18 ? “Allowed to vote” : “Not Allowed to vote”);
app.Run();
Een andere optie is om data door te geven via de queryparameters. Dit doe je door een parameter aan je lambda toe te voegen. Het voorbeeld hieronder wordt uitgevoerd zodra je naar het endpoint ‘/HelloPerson?name=Jeroen’ gaat. Laat je ‘?name=Jeroen’ weg, dan ontstaat er een foutmelding.
Query parameters:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet(“/HelloPerson”, (string name) => $”Hello {name}”);
app.Run();
Je kunt parameters optioneel maken met zogenaamde nullable reference type. Dat doe je door een vraagteken achter het type te zetten. Dit zorgt dat de parameter niet meer verplicht is. Hiermee voorkom je de foutmelding die in het voorbeeld optrad.
Je kunt parameters ook meegeven in de body van het request. In een post-request doe je dat met een json-string. Deze string zet je om naar een object met ‘System.Text.Json’.
Body-Parameters
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost(“/AddPerson”,
(Person p) => $”Naam {p.Name} : age {p.Age}”);
app.Run();
Tot nu toe gaven de voorbeelden alleen een string terug. Als we IResult implementeren, krijgen we veel meer opties, zoals ‘Results.Ok(“Hello”)’ of bijvoorbeeld ‘Results.StatusCode(400)’. De andere optie is om direct een object terug te geven. Dat wordt dan automatisch omgezet naar een json–string met ‘application/json’ als contenttype.
Het is niet handig om alle code voor het afhandelen van je requests in program.cs te zetten. Daarom kun je, net als in ASP.net MVC, dependency injection gebruiken. Daarmee kun je bijvoorbeeld je businesslogica in een aparte class zetten.
Om een class te registreren voor dependency injection gebruik je de IServiceCollection van onze WebApplicationBuilder. Hier kun je classes registeren zoals we ook bij ASP.Net MVC gewend zijn. Vervolgens kun je deze Service als parameter gebruiken in het endpoint en bijvoorbeeld een functie aanroepen.
In het onderstaande voorbeeld registreren we de class ‘HelloService’ en roepen we vervolgens de method ‘Hello’ aan.
Dependency injection:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<HelloService>();
var app = builder.Build();
app.MapGet(“/HelloPerson”, (string name, HelloService service) => service.Hello(name));
app.Run();
HelloService
public class HelloService
{
public string Hello(string name ) => $”Hello {name}”;
}
Een veel gebruikte tool bij het ontwikkelen van API’s is Swagger. Swagger maakt automatisch een beschrijving de API. Die kun je dan gebruiken in diverse andere tools, bijvoorbeeld om client libraries te genereren voor je API. Met de tool SwaggerUI kun je bijvoorbeeld in een simpele user interface snelle nieuwe endpoints aanmaken en testen.
Als eerste voegen we de swagger services toe:
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc(“v1”, new()
{
Title = “MinimalApi”,
Version = “v1”
});
});
Daarna moeten we de Swagger-middleware activeren:
app.UseSwagger();
app.UseSwaggerUI(c =>
c.SwaggerEndpoint(“/swagger/v1/swagger.json”,“MinimalApi v1” ));
Deze calls zorgen ervoor dat de compiler op het endpoint ‘/swagger/v1/swagger.json’ de beschrijving neerzet van je API. Deze beschrijving is (op het moment dat dit artikel online gaat) volgens de OpenApi Specification versie 3.
Op het endpoint ‘/swagger’ vind je vervolgens de swaggerUI. Dit gebruik je om handmatig de API te testen.
In het voorbeeld hierboven kun je zien hoe met Minimal APIs snel en met weinig code een API kunt ontwikkelen. Maar Minimal APIs zijn geen vervanging voor het bouwen van Web-API’s. Het gaat hier om een aanvullende techniek, die vooral geschikt is voor kleine toepassingen en services. Of binnen een microservice-architectuur. Maar het is ook een perfecte manier om een proof of concept te maken en een demo te geven zonder veel randzaken te hoeven inregelen. Het grote voordeel van Minimal APIs is de compacte opzet. Dat maakt ze overzichtelijk (het betekent trouwens niet dat de code ook makkelijker te schrijven is).
Houd er bij je keuze voor Minimal APIs rekening mee dat deze techniek nog niet zo lang bestaat. Er mist dus functionaliteit die wel in ASP.net MVC zit. Denk bijvoorbeeld aan het filteren van endpoints en typed results. Microsoft heeft veel van deze functionaliteiten al wel aangekondigd en beschikbaar gesteld in previewversies van .NET 7. Ik verwacht dus dat de mogelijkheden van Minimal APIs snel zullen gaan groeien.
Het is voorlopig geen goed idee om bestaande projecten om te schrijven naar Minimal APIs. De voordelen daarvan zouden niet opwegen tegen de inspanning die het kost. De performance zou iets kunnen verbeteren, maar performance is afhankelijk van heel veel factoren, zoals de grootte en de complexiteit van de API, dus ook dat is niet zeker.
Maar begin je aan een nieuwe API? Dan zou ik die zeker met de de Minimal API-functie schrijven. Wil je later toch overstappen naar de vertrouwde controller classes, dan is die wijziging relatief makkelijk te maken.