Shaun Hevey
First look at ASP.NET Core minimal API
November 28, 2021
By Shaun Hevey

In this post, I wanted to take a quick look at how you can get started with the newly released with .NET 6, which is ASP.NET Core minimal API. What is a minimal API, you might be wondering? Well, it is a new way to write your ASP.NET API endpoints with very little ceremony. Let's take a look at what I mean.

//Program.cs
var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();



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



app.Run();

That is a fully complete Program.cs in .NET 6 using C# 10 and ASP.NET Minimal APIs. This was generated from the new template. If you want to follow along at home, you can run the command dotnet new web -o MinimalAPI replace MinimalAPI with the name of your application.

This post isn't about that other feature that allows the code to be only four lines. As you can see, Microsoft has made many improvements across the board to remove some of the ceremony that is required to get started building applications with C# and .NET.

Note: if you want to put the using statements or the namespace back in, you can include them, and the compiler will still be happy.

The out of the box template seen above creates an API that contains one GET endpoint that returns "Hello World!". Let's take a quick look at this example.

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

As this example calls the MapGet method, this will create a GET endpoint on your API. This method takes two parameters; the first is the route to add to your API (in this case, '/' is the base address), and the second is the code to invoke when a request is made to this API endpoint. In my mind, this is a pretty dull API so let's go about adding some of our endpoints and dive a little deeper into how minimal APIs work.

I am going to demonstrate this by creating some developer tool-focused endpoints. I will add a short explanation and comments as required, but all of these examples will show you the code needed to add each of these types of endpoints. If you want to add each of these manually, replace the Program.cs from the template above with the following code.

var builder = WebApplication.CreateBuilder(args);



var app = builder.Build();



app.UseHttpsRedirection();



//Add endpoint code here



app.Run();

GET request

This simple endpoint returns a new guid as a string.

app.MapGet("/guid", () => Guid.NewGuid().ToString());

GET request with a parameter

For completeness, you are getting two endpoints this time. These endpoints are will either encode a string to base64 or decode the provided base64 string. These demonstrate the ability for you to add a query parameter to your GET requests.

//The {data} format in the string gets mapped to the data parameter
app.MapGet("/encode-base64/{data}", (string data) =>

{

var bytesArray = System.Text.Encoding.UTF8.GetBytes(data);

return Convert.ToBase64String(bytesArray);

});
//The {data} format in the string gets mapped to the data parameter

app.MapGet("/decode-base64/{data}", (string data) =>

{

var base64EncodedBytes = Convert.FromBase64String(data);

return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);

});

POST request with extras

This endpoint shows you how to add another HTTP VERB, in this case, POST. It is also showing you how to inject built-in objects, as well as how to read the body of the request directly. This post endpoint will take any valid JSON and return it nicely formatted (I have excluded checking if the JSON is valid to keep the example small).

Other HTTP VERBs are available; check out the official docs linked at the end of the post to find out how to use them.

//The built-in HttpRequest object that ASP.NET creates when a request comes in can be given to your endpoint by just adding it as a parameter. From that request object, you can read the body. I have included the full namespaces on these objects, so the code isn't complicated with adding using, but you could easily add the correct Using statements and make this code easier to read.
app.MapPost("/format-json", async (HttpRequest request) =>

{

using var reader = new StreamReader(request.Body, System.Text.Encoding.UTF8, true, 1024, true);

var jDoc = System.Text.Json.JsonDocument.Parse(await reader.ReadToEndAsync());

return System.Text.Json.JsonSerializer.Serialize(jDoc, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });

});

Injecting DI data into your endpoint

This example demonstrates how you can use the built-in DI container to inject objects into code at runtime. The endpoint should return the IP address that the request came from.

var builder = WebApplication.CreateBuilder(args);

//this line adds the required object with the correct lifetime scope to the DI container.
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

...

//To have ASP.NET inject the required object from the DI container, just add it as a parameter (in this example, it is IHttpContextAccessor).
app.MapGet("/current-ip", (IHttpContextAccessor request) => request.HttpContext?.Connection?.RemoteIpAddress?.ToString());

Final thoughts

These are just some things you can achieve with minimal APIs if you want to dive deeper into what else can be achieved, including authentication and authorizing endpoints, returning different status codes, etc. Check out the offical docs.

You can find the complete code from the examples above right below.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
var app = builder.Build();

app.UseHttpsRedirection();

app.MapGet("/guid", () => Guid.NewGuid().ToString());
//The {data} format in the string gets mapped to the data parameter
app.MapGet("/encode-base64/{data}", (string data) =>
{
var bytesArray = System.Text.Encoding.UTF8.GetBytes(data);
return Convert.ToBase64String(bytesArray);
});
//The {data} format in the string gets mapped to the data parameter
app.MapGet("/decode-base64/{data}", (string data) =>
{
var base64EncodedBytes = Convert.FromBase64String(data);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
});
//The built-in HttpRequest object that ASP.NET creates when a request comes in can be given to your endpoint by just adding it as a parameter. From that request object, you can read the body. I have included the full namespaces on these objects, so the code isn't complicated with adding using, but you could easily add the correct Using statements and make this code easier to read.
app.MapPost("/format-json", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body, System.Text.Encoding.UTF8, true, 1024, true);
var jDoc = System.Text.Json.JsonDocument.Parse(await reader.ReadToEndAsync());
return System.Text.Json.JsonSerializer.Serialize(jDoc, new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
});
//To have ASP.NET inject the required object from the DI container, just add it as a parameter (in this example, it is IHttpContextAccessor).
app.MapGet("/current-ip", (IHttpContextAccessor request) => request.HttpContext?.Connection?.RemoteIpAddress?.ToString());

app.Run();