![](/user_photo/_userpic.png)
- •Table of Contents
- •About the Author
- •Acknowledgments
- •Introduction
- •Version Support
- •Supported Versions
- •A Unified Platform
- •Roadmap
- •Supported Operating Systems
- •Command Line Interface
- •Desktop Development
- •Blazor
- •MAUI
- •Wrapping Up
- •.NET 6 Architecture
- •Runtimes
- •CoreCLR
- •Mono
- •WinRT
- •Managed Execution Process
- •Desktop Packs
- •Wrapping Up
- •Dotnet New
- •Dotnet Restore
- •NuGet.config
- •Dotnet Build
- •Dotnet Publish
- •Dotnet Run
- •Dotnet Test
- •Using the CLI in GitHub Actions
- •Other Commands
- •Wrapping Up
- •WinAPI
- •WinForms
- •STAThread
- •WinForms Startup
- •DPI Mode
- •Responding to Scale Events
- •Visual Styles
- •Text Rendering
- •The Message Loop
- •The Form Designer
- •WPF Startup
- •XAML Layout
- •Visual Tree
- •Data Binding
- •Windows App SDK
- •Building a Windows App SDK application
- •Using Windows APIs with Windows App SDK
- •Packaging
- •Migrating to .NET 6
- •Upgrade Assistant
- •Wrapping Up
- •Blazor WebAssembly
- •Creating a Blazor Wasm Project
- •Blazor Progressive Web Apps
- •Exploring the Blazor Client Project
- •Blazor in .NET 6
- •Blazor Component System
- •Creating Blazor Pages
- •Running a Blazor App
- •Blazor Server
- •SignalR
- •Blazor Desktop
- •Wrapping Up
- •Project Structure
- •Exploring MAUI
- •The Cross-Platform World
- •Application Lifecycle
- •MVVM
- •MVVM Toolkit
- •Wrapping Up
- •Model-View-Controller
- •Routing
- •Views
- •Controllers
- •Controller-Based APIs
- •Minimal APIs
- •Wrapping Up
- •Web Apps
- •Creating an App Service
- •Static Web Apps
- •Web App for Containers
- •Docker
- •Azure Functions
- •Deploying Azure Functions
- •Wrapping Up
- •Record Types
- •Monolith Architecture
- •Microservices
- •Container Orchestration
- •Kubernetes
- •Docker Compose
- •Dapr
- •Installing Dapr
- •Dapr State Management
- •Wrapping Up
- •Roslyn
- •Compiler API
- •Diagnostic API
- •Scripting API
- •Workspace API
- •Syntax Tree
- •Roslyn SDK
- •Source Generators
- •Writing a Source Generator
- •Debugging Source Generators
- •Wrapping Up
- •Garbage Collector
- •The Heap
- •The Stack
- •Garbage Collection
- •A Look at the Threadpool
- •Async in .NET 6
- •Await/Async
- •Cancellations
- •WaitAsync
- •Conclusion
- •Index
Chapter 7 ASP.NET Core
Minimal APIs
As mentioned in the introduction of this chapter, minimal APIs are Microsoft’s answer to fast REST API development as can be found in frameworks like NodeJS, where you can put the entire API surface in one file. Looking at minimal APIs, we effectively have one file containing everything. From application setup to the actual endpoints, and even better: Swagger and Swagger UI know how to interpret this new style of .NET REST APIs.
I have implemented the same book API in minimal API; a quick count of lines of code shows the following result:
•\ |
Controller-based API: 78 lines of code |
•\ |
Minimal API: 57 lines of code |
Lines of code are the bootstrap logic in Program.cs and the book endpoints, so not counting any models or services. That is 25% less code. But less code is not always better. Listing 7-27 shows the first part of Program.cs that sets up the application.
Listing 7-27. Bootstrapping a minimal API project
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();
builder.Services.AddSingleton<IBookCatalogService, BookCatalogService>();
var app = builder.Build();
// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
215
Chapter 7 ASP.NET Core
Almost identical to controller-based APIs, except for the builder.Services. AddControllers call, we have no controllers in a minimal API, so no need to add them to the ServiceCollection.
The next part of Program.cs is the endpoints; after defining the endpoints, there is a small line of code that says app.Run(). These few characters make the API launch and be useful. Do not remove this call or nothing will work.
Listing 7-28 shows the action to fetch an array of books.
Listing 7-28. Creating a GET request in a minimal API
app.MapGet("/api/book", async ([FromServices] IBookCatalogService bookCatalogService) =>
{
Book[] books = await bookCatalogService.FetchBookCatalog(); return Results.Ok(books);
})
.WithName("GetBooks")
.Produces<Book[]>();
Creating API endpoints is done on the WebApplication object that is also used for configuring the application as seen in Listing 7-27. WebApplication has different Map methods depending on the type of HTTP verb you want to create an endpoint for. In this example, we are creating a GET request. The first parameter of the method is the path on which the endpoint is reachable. The second parameter is the delegate that is called whenever the endpoint is called. This delegate can be placed in a separate method, but I personally prefer the anonymous method style. In a minimal API, we
can’t do constructor injection since we don’t have a constructor. Instead, we can use the FromServices attribute to decorate a parameter in the delegate. The ServiceCollection will inject the instance right there. In controller-based APIs, we could use helper methods like Ok and NotFound from the controller base class. We don’t have a base class in a minimal API, but we can use the static class Results that exposes the same methods. Finally we have some configuration methods; these are mostly used for generating the OpenAPI JSON and for Swagger UI. WithName sets a name for this endpoint. Produces sets the possible HTTP status codes and the type of data we can expect. In this example, we are not explicitly setting a status code; HTTP 200 will be used as default.
216
Chapter 7 ASP.NET Core
Listing 7-29. Parameters in a minimal API endpoint
app.MapGet("/api/book/{id}", async ([FromServices] IBookCatalogService bookCatalogService, int id) =>
{
Book? book = await bookCatalogService.FetchBookById(id);
if (book == null)
{
return Results.NotFound(id);
}
return Results.Ok(book);
})
.WithName("GetBookById")
.Produces<Book>()
.Produces((int)HttpStatusCode.NotFound);
The API call for fetching book details looks very similar. The major difference is that we are expecting a parameter, the book ID, to be passed in. Passing parameters is done by setting a placeholder between curly braces in the route and creating a method parameter in the delegate with the same name as the placeholder. The framework will take care of mapping the passed in parameter to the .NET parameter, very similar to the way routing works as we have seen in ASP.NET MVC. This example also shows defining two possible results. The method can either produce a book with HTTP 200 – OK or it can produce an empty result with HTTP 404 – Not Found.
Listing 7-30. Posting data to a minimal API
app.MapPost("/api/book", async ([FromServices] IBookCatalogService bookCatalogService, [FromBody] Book book) =>
{
await bookCatalogService.AddBook(book);
return Results.Created($"/api/book/{book.Id}", book);
})
.WithName("AddBook")
.Produces<Book>((int)HttpStatusCode.Created);
217
Chapter 7 ASP.NET Core
Creating a POST endpoint is very similar; we just use the MapPost method instead of MapGet. Using the FromBody attribute, we can get the posted HTTP form data as a .NET object passed into the delegate. The return type is HTTP 201 – Created.
If you like this style of API programming but you don’t want a large Program.cs once all your endpoints are defined, you can use extension methods to split your endpoint definitions in separate files.
Listing 7-31. Extension methods for defining book endpoints
public static class BookEndpoints
{
public static void MapBookEndpoints(this WebApplication app)
{
app.MapGet("/api/book", async ([FromServices] IBookCatalogService bookCatalogService) =>
{
Book[] books = await bookCatalogService.FetchBookCatalog(); return Results.Ok(books);
})
.WithName("GetBooks")
.Produces<Book[]>();
}
}
I have only added one endpoint in Listing 7-31 for brevity. The full method with the three endpoints can be found on the book’s GitHub page. With this extension method in place, we can shorten Program.cs by calling app.MapBookEndpoints instead of defining all endpoints there.
218