Minimal Api, really?
I started thinking in all the curious debate about minimal apis in dotnet, and it is an idea that has been around and around in my head.

To be honest, I never understood the real intention of suggest such a model for a REASTful service, if the old models have been totally proofed and succeeded.
So, what is the need of the dotnet team to promote using minimal apis instead of traditional using controllers. I begun to read and search in all kind of media, youtube, blogs, papers, etc. And, in conclusion, everybody seems to have the same idea: Minimal appis are not a real model but, just a suggestion. Microsoft promotes using minimal apis in order to be more tiny, agile, fast, short coder and, after all, get the same result.
But, is it really truth? The result is the same anyway?
First of all, let me tell you that I studied closely a wonderful book written by Mark J. Price, “C# 11 and .NET 8”, and, in chapter 15, he gives a complete exercise to construct a traditional api with controllers. In here, he started to build a repository as a service, for every crud operation using complex C# structures like a static thread-safe Concurrent Dictionary, to cache the known educational Northwind data base, specifically the Customers Table, injected in a Dictionary fitst:
public class CustomerRepository : ICustomerRepository { private static ConcurrentDictionary<string, Customer>? customersCache;
Then, he begun to make several structured validations catching error scenarios and giving http responses.
For instance
EntityEntry<Customer> added = await db.Customers.AddAsync(c); int affected = await db.SaveChangesAsync(); if (affected == 1) {
Then, this class affect both, the cache repository and then, the disk file.
Once the repository has been structured, He begins injecting the service to construct the Controller. In here, he states logic and then call the methods for every operation.
HttpGet("{id}", Name = nameof(GetCustomer))] // named route [ProducesResponseType(200, Type = typeof(Customer))] [ProducesResponseType(404)] public async Task<IActionResult> GetCustomer(string id) { Customer? c = await repo.RetrieveAsync(id); if (c == null) {
So, this is beatiful. A real and complete api. It is structured, clean, validated, controlled, and pretty functional.
Hence, the big question is, minimal api give us the same result with just a Data base context and a set of endpoints?
Let’s see a single example:
Suppose we have again the classic Northwind data base, and we need some way to get data from it. Let’s say, CRUD operations from an http client.
Ok, then let’s begin to create a minimal api:
dotnet new webapi
Dotnet use by default the minimal api template, so there are no controllers anymore. Everything is going to be in one file, the program.cs file. The template is intended to use swagger as an api tester by default.
If you run the api in Windows it works perfectly fine but, in Linux, the endpoint app.MapGet("/weatherforecast") launch a 404 response, so you need to add some configuration to the swagger UI:
app.UseSwaggerUI( options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "Weather API V2"); options.RoutePrefix = string.Empty; // Set Swagger UI at the app's root } );
Well, all we need is a data base context. Let’s add it from services collection and start defining some endpoints like the following:
builder.Services.AddNorthwindContext(); . . app.MapGet("/customers", async (NorthwindContext context) => await context.Customers!.ToListAsync()); app.MapGet("/customers/{id}", async (string id, NorthwindContext context) => { var customer = await context.Customers!.FindAsync(id); return customer is not null ? Results.Ok(customer) : Results.NotFound(); });
Running the api, swagger allows to get a single Customer:
So, it works. Of course is a simple operation, but works! The same way as the api with controllers mentioned above does.
Even, if we build an http client app for this api, still gives the same result as traditional api.
Let’s see:
Retrieves the query result:
Here is the program.cs file, with endpoints for CRUD operations and a simple query:
using Microsoft.EntityFrameworkCore; using MinimalApi.DataModels; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddNorthwindContext(); builder.Services.AddAuthorization(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI( options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "MinimalApi v1"); options.RoutePrefix = string.Empty; // Set Swagger UI at the app's root } ); } app.UseHttpsRedirection(); app.MapGet("/customers", async (NorthwindContext context) => await context.Customers!.ToListAsync()); app.MapGet("api/customers/{country}", async (string country, NorthwindContext context) => { var customers = await context.Customers!.Where(c => c.Country == country).ToListAsync(); return customers.Count != 0 ? Results.Ok(customers) : Results.NotFound(); }); app.MapGet("/customers/{id}", async (string id, NorthwindContext context) => { var customer = await context.Customers!.FindAsync(id); return customer is not null ? Results.Ok(customer) : Results.NotFound(); }); app.MapPost("/customers", async (Customer customer, NorthwindContext context) => { await context.Customers!.AddAsync(customer); await context.SaveChangesAsync(); return Results.Created($"/customers/{customer.CustomerId}", customer); }); app.MapPut("/customers/{id}", async (string id, Customer updatedCustomer, NorthwindContext context) => { var customer = await context.Customers!.FindAsync(id); if (customer is null) { return Results.NotFound(); } customer.CompanyName = updatedCustomer.CompanyName; customer.ContactName = updatedCustomer.ContactName; customer.ContactTitle = updatedCustomer.ContactTitle; customer.Address = updatedCustomer.Address; customer.City = updatedCustomer.City; customer.Region = updatedCustomer.Region; customer.PostalCode = updatedCustomer.PostalCode; customer.Country = updatedCustomer.Country; customer.Phone = updatedCustomer.Phone; customer.Fax = updatedCustomer.Fax; await context.SaveChangesAsync(); return Results.Ok(customer); }); app.MapDelete("/customers/{id}", async (string id, NorthwindContext context) => { var customer = await context.Customers!.FindAsync(id); if (customer is null) { return Results.NotFound(); } context.Customers.Remove(customer); await context.SaveChangesAsync(); //customerRepository.DeleteAsync(customer); return Results.NoContent(); }); app.Run();
Conclusion:
From my perspective, minimal apis are real and functional, and I think this must be the way apis should be constructed in the future. Looking for simplicity and efficiency. After all, in the majority of use cases gives the same result. Many Developers complain that validations cannot be done with this model but, the good news is that .Net 10 will include a validation framework for minimal apis, it will be possible to annotate classes, parameters and restrictions before getting the endpoint control.
No more excuses, you can use minimal api.
Credits:
Mark J. Price, “C# 11, and .NET 8”, Seventh Edition, Chapter 15.
Comentarios (0)
Suscríbete al Blog
Obtén las últimas noticias en tu correo!
Leer siguientes
Minimal Api, really?
I started thinking in all the curious debate about minimal apis in dotnet, and it is an idea that has been around and around in my head.