Skip to content

How to Build .NET Minimal APIs

The release of .net 6 came with a with of minimal api’s which enable the development of small and functional single file Web API’s.

What is a minimal api

Minimal API is an application model for building lightweight Web APIs. Enabling the creation of a microservice or quickly start prototyping without boilerplate code and structure concerns. Essentially Minimal APIs will help developers to build ASP.NET Core apps without ceremony.

Over the past few years, other programming languages and software development frameworks such JS, nodeJS, Go, python etc have been gaining popularity over the more traditional tools such as C# and .net due to the fact that frameworks obfuscate a lot of the boiler plate work required. I have previously posted about how to develop Simple lightweight node.js web server.

The .net framework project templates and such never really had this lightweight touch to developing applications. For the most part this was always primarily driven by the typical environments .net developers found themselves developing typical large monolithic enterprise applications

Over the years things have moved on containerised microservice deployed to Kubernetes are all the vogue and functionality of applications is broken up in 100s or smaller applications.

The advent of .net 6 and the optimisations within the framework itself has enabled new project templates, which include the minimal api

Build a minimal API

We will build a very simple Quotation Service, which will be the basis of several more tutorials going forward. The main responsibility of the service is to provide a get endpoint which can be called to retrieve a randomised quatation per call.

The functionality of the service will be very much a Proof Of Concept, which may evolve as its use expands, but at this stage all we want to deliver is JSON payload with two properties:

  • Quote
  • Citation
{
  "quote" : "minimal api's are cool",
  "citation" "Gary Woodfine"
}

Using the CLI

We will make use of the CLI to generate the initial stub of the service for us. Lets create an initial folder for project, which we’ll eventually follow some pretty standard OSS conventions as we’ll eventually be using Github to host the project.

https://github.com/garywoodfine/quotations
mkdir quotations
cd quotations

Once we have created our folder and changed into it we can use the Dotnet CLI to create our project using the command below. We will make use of the webapi template but we will provide the switch -minimal to use the minimal template , we will output the project to src folder using -o src and we will give the project a name of quotes using -n quotes

dotnet new webapi -minimal -o src -n quotes

This will generate the project for us in the src folder as follows

We could run this project as is, because if we open the Program.cs and view the code, the .net template has generated a very crude fictitious weather service for us.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast =  Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateTime.Now.AddDays(index),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast");

app.Run();

record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

We could run this project using the command below and then browse to https://localhost:7023/weatherforecast in our browser

dotnet run --project quotes.csproj

We’ll stop the service now because we will change it to suit our needs now. Bearing in mind that the entire purpose of minimal API’s is to enable you to iterate quickly and to keep boiler plate code out of your way to enable you to focus on delivering business functionality of your service quickly.

With that in mind, our objective at this point is to quickly develop a randomised quotation service, that will provide the consumer of the service a random quote when they make a call. We will try to implement the absolute Minimum amount of code deliver this functionality. Which is borrowing concepts from Eric Ries book, The Lean Startup, and his concept of the Minimum Viable Product (MVP).

The Lean Start-Up

How Today’s Entrepreneurs Use Continuous Innovation to Create Radically Successful Businesses

Eric Ries

The Lean Startup is a new approach being adopted across the globe, changing the way companies are built and new products are launched.

We’ll edit the Program.cs as below. We will attempt keep all the logic for service down to 1 file at the most.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

var quotations = new List<Quotation>
{
    new("Minimal Api's are cool", "Gary Woodfine"),
    new("Independent money limits wars", "Dominic Frisby"),
    new("Our system is broken, our laws don't work, our regulators are weak, our governments don't understand what's happening, and our technology is usurping our democracy", "Christopher Wylie"),
    new("1% of the population controls 42% of the wealth", "David Graeber"),
    new("DRY more accurately means that we want to avoid duplicating our system behaviour and knowledge", "Sam Newman"),
    new("Dapr Requires state stores to support eventual consistency by default", "Haishi Bai & Yaron Schneider")
};

app.MapGet("/quote", () =>
    {
        return quotations.OrderBy(_ => new Random().Next()).Take(1);
        
    })
.WithName("quote");

app.Run();

record Quotation(string Quote, string Citation);

Once we make this change, we can now run our project again using the command below and browse to https://localhost:7023/quote in our browser

dotnet run --project quotes.csproj

Our service is running as expected and further more it is delivering the functionality as defined. We have delivered the minimum amount of code to deliver this functionality.

That being said the template has also provided us with some beneficial development functionality we haven’t explored. If we open the project in our favourite IDE, my personal one is Rider: Fast & powerful cross-platform .NET IDE and Run the project

The application will run and it will open a browser session and you will automatically redirected to the Swagger page providing documentation of how to interact with the API

Advice

You don’t need to really open the project in your IDE and run it from there to access the Swagger page, you can still access swagger if you’re running from the terminal and browsing to https://localhost:7023/swagger/index.html.

I just wanted to illustrate that you can still open the project in an IDE

The configuration for Swagger documentation was carried out the top of our Program.cs and was supplied by the template and as you can see it is automatically configured to only be used during development.

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

Top-Level Programs

One of the areas that have been improved and optimised when it comes to minimal Api’s is the introduction of Top-Level programs feature of C# 9.0.

Top-Level Programs helps to eliminate boilerplate code by enabling you write any programming statement you want directly after the using statements and before any type of namespace declarations and you can only do this in one file.

The example below helps to illustrate the old boiler plate code required in .net 5

using System;
class Program
{
    static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}

Essentially Top-Level programs help to eliminate the ceremony of implementing the boiler plate code . This will help newcomers to .net by enabling developers to get going without worrying about learning about namespaces, arrays, arguments etc.

With top-level programs, you can work with normal functions, use async and await, access command-line arguments, use local functions, and more.

If we wanted to we could drastically simply the implementation of our service as follows to reduce the swagger implementation then things will be clearer as to what it requires to get a simple service up and running

var app = WebApplication.CreateBuilder(args).Build();
app.UseHttpsRedirection();

var quotations = new List<Quotation>
{
    new("Minimal Api's are cool", "Gary Woodfine"),
    new("Independent money limits wars", "Dominic Frisby"),
    new("Our system is broken, our laws don't work, our regulators are weak, our governments don't understand what's happening, and our technology is usurping our democracy", "Christopher Wylie"),
    new("1% of the population controls 42% of the wealth", "David Graeber"),
    new("DRY more accurately means that we want to avoid duplicating our system behaviour and knowledge", "Sam Newman"),
    new("Dapr Requires state stores to support eventual consistency by default", "Haishi Bai & Yaron Schneider")
};

app.MapGet("/quote", () =>
    {
        return quotations.OrderBy(_ => new Random().Next()).Take(1);
    })
.WithName("quote");

app.Run();

record Quotation(string Quote, string Citation);

Routing Api

Another big improvement of .net 6 is the routing APi which enables developers to route to any type of method – These methods can use controller-like parameter binding, JSON formatting, and action result execution.

In previous version of .net writing arbitrary response to the browser would need something similar to below which is not entirely intuitive and could be confusing.

endpoints.MapGet("/", async context =>
{
    await context.Response.WriteAsync("Hello World!");
});

Using c# 10 features this can drastically simplified to the below, almost giving C# Javascript type abilities, which may be a good or bad feature depending on your view.

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

In the sample code we have taken advantage of this simplification and implemented a get endpoint /quote and return a random JSON quote property from our list of predefined quotes.

app.MapGet("/quote", () =>
    {
        return quotations.OrderBy(_ => new Random().Next()).Take(1);
    })
.WithName("quote");

Summary

We’ve had a quick lightning tour of the Minimal Api templates and even implemented a functional service using the template. We learned that the minimal API templates implement new C# language features which enable the functionality to develop the minimal Api

Latest posts by Gary Woodfine (see all)