This post has been included in the 2023 edition of C# Advent. Be sure to follow @CsAdvent on Twitter (X) and watch the #csadvent hashtag for more C# goodness.
ASP.NET was first released in January 2002 and .NET developers everywhere started learning WebForms. It felt like an easy step to the web for WinForm developers, but it abstracted much of how the internet works. As a result, .NET developers didn’t think about things like HTTP verbs or payload size.
Many of those developers, myself included, got a little “closer to the metal” when Microsoft officially released ASP.NET MVC in early 2009. My mind really enjoyed the MVC pattern for building web applications & APIs. Though there have been several improvements to the framework since, developers who have left the .NET world will still feel familiar with the conventions of the latest iteration.
That said, the past several years have seen an explosion of development shifting to the web and APIs are popping up everywhere. While .NET developers were previously limited to MVC, the introduction of Minimal API and other non-MS .NET API frameworks is providing them with a plethora of options.
Let’s review a few of those options to help you choose the best option for your API needs.
A quick note about the code snippets below: These snippets do not contain all the code needed to run the APIs. I’ve purposely not shown code related to Entity Framework DbContexts or POCO classes. They do include the code that ASP.NET uses to build the
/todoitems
routes for retrieving all and oneToDoItem
.
permalinkControllers
Controllers have been the “bread and butter” of .NET API building for a long time. Their structure is familiar to all .NET developers; even those that are just now working on web-based projects.
Adding attributes to properties, methods, and classes makes it relatively painless to add support for routing, authentication/authorization, or building Swagger and OpenAPI documentation.
If you’re building an API that requires Swagger or OpenAI documentation, one huge benefit is support for reading code comments into that documentation. This makes it super easy for documentation to become part of the product itself.
Of course, your scenario is rarely as basic as the example above and unfortunately, most Controller-based API examples use the pattern of injecting an Entity Framework context and manipulating it within the endpoint method. For more complex applications, you’ll probably be injecting your own services with business logic customized to your needs.
One “con” of the approach of Controller-based APIs, is that dependency injection occurs at the controller level. While this does mean your service, DbContext, etc. are available to all methods within the controller, it also means that the application may be spinning up resources that your particular web request may not need. However, that overhead is usually one of the last places you need to start optimizing.
An additional benefit of Controller-based APIs is the built-in support for generating Swagger and OpenAPI documentation based on your code comments and class attributes.
permalinkMinimal API
Minimal API is a newer approach to building APIs with ASP.NET Core and its fluent syntax is very appealing to developers coming from the JavaScript and Python worlds. In fact, after spending the past few years building applications with TypeScript, I found Minimal API a much simpler path to onboard to .NET APIs.
Here is the same two API endpoints shown above, but written using Minimal API:
I’m sure you immediately notice the conciseness of Minimal API. One benefit to
using Minimal API is the granularity of control over the construction of your
endpoints. While both of the endpoints have a ToDoDb
DBContext injected, you
can probably imagine a world where different services are provided to different
endpoints.
As for documentation, Minimal API does support Swagger and OpenAPI documentation
generation, but the process for documenting endpoints is more invasive than the
Controller-based method. For instance, to modify the /
route above to include
a summary and description of the endpoint, you’d need to use the WithOpenApi
fluent method as shown below.
Also, if you’re not returning TypedResults, you’ll need to document the response types of your endpoint. Here’s an example:
permalinkFastEndpoints
Bonus Time! In addition to the Microsoft-supported methods above, many community frameworks exist for building APIs with .NET. FastEndpoints is an option I found recently that seems very promising. With performance benchmarks that put them on par with Minimal API, they are firmly ahead of Controller-based APIs.
Also like Minimal API, FastEndpoints uses a fluent-based approach to
configuration. However, one major difference is found in how endpoints are
created. While the Minimal API framework expects many endpoints to exist within
a class and be organized within a MapGroup
, the FastEndpoints convention
expects each endpoint to live within its own class.
Those Endpoint
classes also define the request and response signatures via DTO
classes. Using FastEndpoints, our two endpoints would look like the below
example:
For Swagger & OpenAPI documentation, you’ll use fluent methods within the
endpoints Configure
method.
One big bonus that FastEndpoints provides is a large collection of supported features, including model binding, rate limiting, caching, pre/post processors, and more.
permalinkWhich is Right for You?
If you have a background in JavaScript, Python, or Functional programming, Minimal API will feel more natural to you. But if you’ve spent a lot of time using .NET for the web or WinForms, you’ll likely find Controllers more accessible.
Another point of consideration is documentation. While it’s possible to document your API via Swagger or OpenAPI with all three, unless you enjoy documenting your endpoints using fluent methods, you’ll likely find writing Controller-based APIs less cumbersome to manage.
In the end, all are valid and welcome additions to the .NET ecosystem. You truly can’t go wrong with any of them and I’d recommend building with all three to find what works best with your existing application patterns and processes.