Mastering ASP.NET Core with Minimal APIs & CQRS — Part 03: Building Your First Minimal APIs

Sasanga Edirisinghe
4 min readJan 4, 2025

GitHub repo: https://github.com/SasangaME/MinimalApiDemo/tree/feature/first-apis

In the previous post we have created the project structure. In this post we are going to write our first Minimal APIs.

First, let’s clean the program.cs file as follows:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApi();

var app = builder.Build();

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

app.UseHttpsRedirection();

This implementation is straightforward and simple, we’ll continue to enhance it as we progress.

After launching the application and navigating to http://localhost:5093/, an error page appears in the browser.

The error occurs because the application lacks a mapped root endpoint for the specified port. To resolve this, ensure that the root endpoint ("/") is properly configured in your application's routing setup.

Let’s create and map a root endpoint.

Creating a Root Endpoint

Let’s modify the program.cs to create a root endpoint.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApi();

var app = builder.Build();

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

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

app.UseHttpsRedirection();

app.Run();

This is a Lambda function that returns ‘Hello World!’ at the root (“/”) endpoint. When navigating to http://localhost:5093/, you will see that the error is gone, and the response is displayed in the browser window.

Now, let’s modify this endpoint to return build information.

app.MapGet("/", () => new BuildDto("Asp.NET Core Minimal APIs with CQRS", 
"1.0.0",
app.Environment.EnvironmentName));

internal record BuildDto(string Title, string Version, string Environment);

Next run and navigate to the application host address via a browser or via a REST Client (Postman, Insomnia), and it shows this.

It is bad to keep hardcoded values in the code. So let’s further modify appsettings.json file and program.cs file to read from configuration.

appsettings.json


"Build" : {
"Title" : "ASP.NET Core with Minimal APIs & CQRS",
"Version" : "1.0.0"
},

program.cs

var buildInfo = new BuildDto();
builder.Configuration.GetSection("Build").Bind(buildInfo);

app.MapGet("/", () => Results.Json(buildInfo));

internal record BuildDto
{
public string Title { get; set; } = string.Empty;
public string Version { get; set; } = string.Empty;
}

Adding More Endpoints

To effectively manage request and response data for your ‘Pets’ endpoints, it’s essential to define clear Data Transfer Objects (DTOs). DTOs help in decoupling your internal data models from the external API contracts, ensuring that only the necessary data is exposed or accepted. Here’s how you can define ‘Pets’ DTOs in C#:

record Pet
{
public int Id { get; set; }
public string Name { get; set; }
public Gender Gender { get; set; }
}

enum Gender
{
Male = 1,
Female,
Unknown
}

To enhance the ‘Pet’ Data Transfer Objects (DTOs) by incorporating a Gender enumeration, I have added Gender Enum along with the Pet DTO.

Next, we need some helper methods to be used in our endpoints.

IEnumerable<Pet> GetPets()
{
return new List<Pet>
{
new Pet { Id = 1, Name = "Pippa", Gender = Gender.Female },
new Pet { Id = 2, Name = "Ollie", Gender = Gender.Male }
};
}

Pet? GetPetById(int id)
{
return GetPets().FirstOrDefault(p => p.Id == id);
}

Finally, let’s add our Minimal APIs.

// pet endpoints
app.MapGet("/api/pets", () => Results.Ok(GetPets));

app.MapGet("/api/pets/{id}", (int id) =>
{
var pet = GetPetById(id);
return pet is not null ? Results.Ok(pet) : Results.NotFound();
});

app.MapPost("/api/pets", (Pet pet) =>
{
var newPet = CreatePet(pet);
return Results.Created($"/api/pets/{newPet.Id}", newPet);
});

Now, let’s test our endpoints to ensure they work.

To simplify matters, I have added all this code to the Program.cs file. Here is the complete code:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApi();

var app = builder.Build();

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

var buildInfo = new BuildDto();
builder.Configuration.GetSection("Build").Bind(buildInfo);

app.MapGet("/", () => Results.Json(buildInfo));

// pet endpoints
app.MapGet("/api/pets", () => Results.Ok(GetPets()));

app.MapGet("/api/pets/{id}", (int id) =>
{
var pet = GetPetById(id);
return pet is not null ? Results.Ok(pet) : Results.NotFound();
});

app.MapPost("/api/pets", (Pet pet) =>
{
var newPet = CreatePet(pet);
return Results.Created($"/api/pets/{newPet.Id}", newPet);
});

app.UseHttpsRedirection();

app.Run();

IEnumerable<Pet> GetPets()
{
return new List<Pet>
{
new Pet { Id = 1, Name = "Pippa", Gender = Gender.Female },
new Pet { Id = 2, Name = "Ollie", Gender = Gender.Male }
};
}

Pet? GetPetById(int id)
{
return GetPets().FirstOrDefault(p => p.Id == id);
}

Pet CreatePet(Pet pet)
{
pet.Id = GetPets().Max(p => p.Id) + 1;
return pet;
}

internal record BuildDto
{
public string Title { get; set; } = string.Empty;
public string Version { get; set; } = string.Empty;
}

record Pet
{
public int Id { get; set; }
public string Name { get; set; }
public Gender Gender { get; set; }
}

enum Gender
{
Male = 1,
Female,
Unknown
}

Let’s organize our Minimal APIs into a separate class in our next blog post. Happy coding!

--

--

Sasanga Edirisinghe
Sasanga Edirisinghe

No responses yet