Search  
Always will be ready notify the world about expectations as easy as possible: job change page
Aug 27

Minimal APIs in .NET 8: A simplified approach to build services

Author:
Source:
Views:
675

Introduction

Minimal APIs in .NET 8 make a grand entrance, redefining the way we build web services. If you’re curious about what makes Minimal APIs tick and how they can streamline your development process, let’s dive in with some engaging examples.

The birth of minimalism in .NET

Think of a time when we had to write pages of code for a simple web service. Tedious, right? Minimal APIs are like a breath of fresh air. Introduced in .NET 6 and refined in .NET 8, they strip away the complexity of traditional ASP.NET Core MVC models. No more controllers, no extensive configurations — just what you need.

Why Minimal APIs?

  1. Efficiency: Write less, do more. A mantra for the modern developer.
  2. Performance: They’re lean, mean, and fast, perfect for high-performance scenarios.
  3. Ease of use: New to .NET? No problem! Minimal APIs are accessible and easy to grasp.
  4. Flexibility: Simplicity doesn’t mean limited. From microservices to large-scale applications, they’ve got you covered.

Getting started

To embark on this journey, ensure you have the .NET 8 SDK installed. Open your favorite code editor and let’s create our first Minimal API.

Minimal APIs

Important setup information: Before running the examples provided in this article, please ensure you have the necessary NuGet packages installed. For the functionality involving OpenAPI documentation, you will need the Microsoft.AspNetCore.OpenApi package. You can install it via the NuGet Package Manager Console with the following command:

Install-Package Microsoft.AspNetCore.OpenApi

This package is essential for using the WithOpenApi extension methods shown in below examples.

Example 1: Hello world!

A classic starter. This API returns a friendly greeting. Run this, and voila! Your API greets the world at http://localhost:<port>/. Please note that the port number might change based on your settings.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

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

app.Run();
Response body:
Hello, world!
Response headers:
content-type: text/plain; charset=utf-8
date: Tue,21 Nov 2023 13:38:37 GMT
server: Kestrel

Setting the stage: The book service

Before we jump into the routes, let’s look into the setup. We’ve defined an IBookService interface and its implementation BookService, which simulates a database of books. This is a classic example of encapsulating data access logic within a service layer, promoting separation of concerns and making the code more maintainable.

Context introduction

“Imagine walking into a well-organized digital library where each book is just a click away. That’s what we’re creating with our BookService in the Minimal API setup. This service acts as the backbone of our application, managing and providing book data."

namespace MinimalApis
{
    public interface IBookService
    {
        List<Book> GetBooks();
        
        Book GetBook(int id);
    }

    public class BookService : IBookService
    {
        private readonly List<Book> _books;

        public BookService()
        {
            _books = new List<Book>
            {
                new()
                {
                    Id = 1,
                    Title = "Dependency Injection in .NET",
                    Author = "Mark Seemann"
                },
                new()
                {
                    Id = 2,
                    Title = "C# in Depth",
                    Author = "Jon Skeet"
                },
                new()
                {
                    Id = 3,
                    Title = "Programming Entity Framework",
                    Author = "Julia Lerman"
                },
                new()
                {
                    Id = 4,
                    Title = "Programming WCF Services",
                    Author = "Juval Lowy and Michael Montgomery"
                }
            };
        }

        public List<Book> GetBooks()
        {
            return this._books;
        }

        public Book GetBook(int id)
        {
            var book = this._books.FirstOrDefault(x => x.Id == id);

            return book;
        }
    }

    public class Book
    {
        public int Id { get; set; }

        public string Title { get; set; }

        public string Author { get; set; }
    }
}

Here, IBookService is our blueprint, declaring what functionalities our service must provide – listing all books and fetching a specific book by ID. BookService is the concrete implementation, where we define how these functionalities work.

Inside BookService, we have a private list _books that acts as our mock database. It's pre-populated with a few books to simulate. This setup allows us to focus on the API functionality without worrying about actual database connections.

The GetBooks method returns all the books, while GetBook searches for a book by its ID using LINQ. It's a straightforward yet powerful way to handle our data.

builder.Services.AddSingleton<IBookService, BookService>();

Finally, we integrate our BookService into the Minimal API pipeline by registering it with the dependency injection system. This makes BookService available wherever we need it in our application.

Example 2: Fetching all books

This example demonstrates how to list all books in the library.

app.MapGet("/books", (IBookService bookService) =>
    TypedResults.Ok(bookService.GetBooks()))
    .WithName("GetBooks")
    .WithOpenApi(x => new OpenApiOperation(x)
    {
        Summary = "Get Library Books",
        Description = "Returns information about all the available books from the Amy's library.",
        Tags = new List<OpenApiTag> { new() { Name = "Amy's Library" } }
    });

Endpoint definition: app.MapGet("/books", ...): This line defines a GET endpoint at the URL /books.

Dependency Injection: (IBookService bookService): Here, IBookService is injected automatically, allowing access to the book service.

Service interaction: bookService.GetBooks(): Calls the GetBooks method of the BookService to fetch all books.

Response: TypedResults.Ok(...): Wraps the list of books in an HTTP 200 OK response.

Swagger documentation:

  • .WithName("GetBooks"): Assigns a name to the endpoint for clarity.
  • .WithOpenApi(...): Adds descriptive information for the Swagger UI, enhancing API documentation.

In Postman or a browser, send a GET request to https://localhost:<port>/books to receive a JSON list of all books.

[
  {
    "id": 1,
    "title": "Dependency Injection in .NET",
    "author": "Mark Seemann"
  },
  {
    "id": 2,
    "title": "C# in Depth",
    "author": "Jon Skeet"
  },
  {
    "id": 3,
    "title": "Programming Entity Framework",
    "author": "Julia Lerman"
  },
  {
    "id": 4,
    "title": "Programming WCF Services",
    "author": "Juval Lowy and Michael Montgomery"
  }
]

Example 3: Fetching a specific book by ID

This example shows how to retrieve a specific book by its ID.

app.MapGet("/books/{id}", Results<Ok<Book>, NotFound> (IBookService bookService, int id) =>
        bookService.GetBook(id) is { } book
            ? TypedResults.Ok(book)
            : TypedResults.NotFound()
    )
    .WithName("GetBookById")
    .WithOpenApi(x => new OpenApiOperation(x)
    {
        Summary = "Get Library Book By Id",
        Description = "Returns information about selected book from the Amy's library.",
        Tags = new List<OpenApiTag> { new() { Name = "Amy's Library" } }
    });

Endpoint definition: app.MapGet("/books/{id}", ...): Defines a GET endpoint at /books/{id}, where {id} is a variable for the book ID.

Route logic: bookService.GetBook(id) is { } book: Tries to find the book by ID. If found, book is not null.

Conditional response:

  • If found: TypedResults.Ok(book): Returns the book with an OK status.
  • If not found: TypedResults.NotFound(): Returns a 404 Not Found status.

Swagger documentation: Similar to Example 2, provides meaningful information for the Swagger UI.

To find a book with ID 3, send a GET request to https://localhost:<port>/books/3. You'll either get the book's details in JSON format or a 404 Not Found response if the ID doesn't exist.

{
  "id": 3,
  "title": "Programming Entity Framework",
  "author": "Julia Lerman"
}

Dive into the full code: The complete Program.cs

In this section, let’s explore the Program.cs file. This is where all the pieces of our Minimal API come together, from setting up services to defining endpoints. I'll walk you through the entire code, breaking it down into digestible parts for a clearer understanding.

Full code listing

using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.OpenApi.Models;
using MinimalApis;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<IBookService, BookService>();

var app = builder.Build();

// configure exception middleware
app.UseStatusCodePages(async statusCodeContext
    => await Results.Problem(statusCode: statusCodeContext.HttpContext.Response.StatusCode)
        .ExecuteAsync(statusCodeContext.HttpContext));

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();


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

// example 2
app.MapGet("/books", (IBookService bookService) =>
    TypedResults.Ok(bookService.GetBooks()))
.WithName("GetBooks")
.WithOpenApi(x => new OpenApiOperation(x)
{
    Summary = "Get Library Books",
    Description = "Returns information about all the available books from the Amy's library.",
    Tags = new List<OpenApiTag> { new() { Name = "Amy's Library" } }
});

// example 3
app.MapGet("/books/{id}", Results<Ok<Book>, NotFound> (IBookService bookService, int id) =>
        bookService.GetBook(id) is { } book
            ? TypedResults.Ok(book)
            : TypedResults.NotFound()
    )
    .WithName("GetBookById")
    .WithOpenApi(x => new OpenApiOperation(x)
    {
        Summary = "Get Library Book By Id",
        Description = "Returns information about selected book from the Amy's library.",
        Tags = new List<OpenApiTag> { new() { Name = "Amy's Library" } }
    });

app.Run();

Setting up the services

Here, we’re initiating our application and injecting the BookService into our service collection. This makes BookService available throughout our application. Additionally, we're setting up Swagger for API documentation.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IBookService, BookService>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

Configuring middleware

This segment is all about middleware configuration. UseStatusCodePages helps in handling HTTP status codes, while UseHttpsRedirection ensures secure connections. Swagger UI is enabled in the development environment for easy testing and exploration.

var app = builder.Build();
app.UseStatusCodePages(...);
app.UseHttpsRedirection();
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

Defining the endpoints

Here, we define three endpoints: a simple greeting, one to list all books, and another to get a specific book by ID. These endpoints use our BookService to interact with the book data.

app.MapGet("/", () => "Hello, World!");
app.MapGet("/books", ...);
app.MapGet("/books/{id}", ...);

Running the application

Finally, this line boots up our application, making it listen for incoming requests.

app.Run();

The Program.cs file is like the conductor of an orchestra, ensuring each part of our Minimal API performs harmoniously. It's a testament to the power of Minimal APIs in .NET 8 – simple yet robust, enabling us to build efficient and effective web services with ease.

OnExit

These code snippets effectively demonstrate how Minimal APIs in .NET can be used to create clean, maintainable, and well-documented web services. By utilizing dependency injection and clear routing, we’ve created an API that’s both easy to understand and use. Whether listing all books or finding a specific one, API handles these tasks with elegance and efficiency.

Similar
Jul 22
Author: Gabriele Tronchin
Introduction One common way to speed up our applications is by introducing a cache. Typically, the first option that comes to mind is using a MemoryCache (RAM) to save some data in order to speed up retrieval. This approach works...
Jul 24, 2023
Author: Mario Bittencourt
We have been living in a distributed world for quite some time, and when considering how should you implement the communication between any two parties involved, the discussion seems to gravitate around two options: API or messaging. Instead of focusing...
Jan 18, 2023
Author: Jay
Aha! There is always something intimidating about Dependency Injection. I distinctly remember a couple of years ago, telling a .Net recruiter for a contract project, that, “I had no idea what DI is” He immediately stopped talking to me. It’s...
May 2
Author: Ankit Sahu
...
Send message
Type
Email
Your name
*Message