The Minimum API, available with .NET 6, allow us to create APIs with much less “ceremony” than with traditional controllers. In this article, we take a look back at video and explore the difference from controllers. Good reading!
First of all, as a reminder, the video that was recently published on the Minimum API.
Organize your APIs well
Before even talking about how to write these APIs, we must talk about how to organize our code. With the new top level statements, we can now add endpoints directly in our file Program.cs. We could even put them all in this file, but that's precisely what we want to avoid! So to keep our program clean, we add an extension method that will be used in the Program class. This will take care of managing the addition of our endpoints, like this:
public static class EndpointRouteBuilderExtensions { public static void AddApplicationEndpoints(this IEndpointRouteBuilder endpointRouteBuilder) { endpointRouteBuilder.AddWeatherForecastEndpoints(); endpointRouteBuilder.AddPersonEndpoints(); endpointRouteBuilder.AddSecurityEndpoints(); } }
You will also notice that this method itself contains calls to other extension methods. These, located in other projects, encapsulate the endpoints according to the business domain. In this way, the organization of our endpoints is clear and well structured.
How to write an endpoint
Here is an example that we will look at in detail:
endpointRouteBuilder .MapGet( Routes.Person, (IPersonService personService) => personService.GetPersons().Select(f => f.ToPerson())) .AllowAnonymous() .WithTags("Person");
The entry point to create our endpoint is with the extension method MapGet. In this case to create a GET request, and in the same way the MapPost, MapPut, MapDelete are available to make POST, PUT, DELETE requests respectively. The first parameter is the route of our endpoint. I got into the habit of keeping them in constants, which can be in a shared project when making a Blazor application for example. The second parameter is the function that will be called. At the level of its parameters, one can use injected objects, parameters in the request or objects coming from the body of our request, the whole will be inferred to the use.
In a fluent way, we can then chain different methods to improve our endpoint. In this example, we used AllowAnonymous for access without authentication, but in a context where one must be authenticated, one would use the method RequireAuthorization. Then, we have WithTags, which allows you to group endpoints under the same category. We could also specify, among other things, the type of result produced by the endpoint and use the class Results to easily produce these results. For example, we see here a return of type 201 Created, on an endpoint which can also produce a 401 status if there are validation issues:
endpointRouteBuilder .MapPost( Routes.Person, (IPersonService personService, Person person) => { personService.Add(person.ToPersonEntity()); return Results.Created($"{Routes.Person}/{person.Id}", person ); }).ProducesValidationProblem().Produces(StatusCodes.Status201Created);
Differences with controllers
As mentioned in the video episode, there are some differences between the Minimum API and the controllers that must be considered to make the right choice of technology to use for our APIs. Here are the main differences to consider in my opinion.
Filters
First of all, it is not possible to make filters on Minimal APIs. Filters are classes that can be executed at different stages in the execution chain of a query. They are part of what is offered by MVC. So for example, it is not possible to add an authorization filter which would intercept the request if you are not authenticated. This is therefore the kind of situation where we will have to use the mechanisms in place such as the extension method RequireAuthorization on each of our endpoints.
The " model binding"
The model binding allows for example with an implementation of IModelBinder
to make a controller action receive an ID and will act to return the object of this ID in the controller method (instead of sending the ID and manually making the request in the method). While this works with controllers, this feature is not supported by the Minimal APIs. We must therefore do this kind of operation manually in our endpoints or in the services they use.
The validation
The Minimum API do not offer the support for validation found with MVC, in particular with the interface IModelValidator
. We must therefore turn to another alternative, for example the excellent bookstore Fluent Validation. With this library, we can create validators like this:
public class PersonValidator : AbstractValidator{ public PersonValidator() { RuleFor(p => p.FirstName).NotEmpty(); RuleFor(p => p.LastName).NotEmpty(); } }
Subsequently, with the necessary initialization, we just need to inject this validator into the endpoint. As a bonus, if your APIs are used for an application Blazor and that your validators are in the shared project, you can use them at the form level thanks to the library Blazored Fluent Validation.
Conclusion
As you could see, the Minimum API offer a nice alternative to controllers, which in my opinion is easier to use and set up. If the differences are not a blocking element for you, it can definitely be considered for your next projects. There are a few more differences than listed above, I suggest you read the following article to fully assess: Differences between Minimal APIs and Controllers
Minimal API documentation available via this link: Minimum API
To see the solution used for video and code samples: AspNetMinimalApi on GitHub
If you have any questions or comments about the Minimum API, feel free to let me know in the comments!
Bruno