On this page
Introduction to ASP.NET Core 10 and Minimal APIs
What is ASP.NET Core?
ASP.NET Core is .NET's web framework for building APIs, web apps, WebSockets, and microservices. It is open-source, cross-platform, and one of the fastest web frameworks in the world according to TechEmpower benchmarks.
ASP.NET Core 10 brings:
- Mature Minimal APIs optimized for performance
- Native AOT improvements for ultra-small binaries
- OpenAPI 3.1 with automatic documentation generation
- Native rate limiting
- Improved WebSockets
Creating a web project
# Create a Minimal API
dotnet new webapi -n CatalogApi -f net10.0 --use-minimal-apis
# Create an API with controllers
dotnet new webapi -n CatalogApi -f net10.0
# Run in development mode
dotnet run --launch-profile https
# Run with hot reload
dotnet watch runThe generated project has this structure:
CatalogApi/
├── Program.cs ← Entry point + configuration
├── CatalogApi.csproj ← Project file
├── appsettings.json ← App configuration
├── appsettings.Development.json ← Dev configuration
└── Properties/
└── launchSettings.json ← Local run profilesThe ASP.NET Core builder pattern
ASP.NET Core uses the builder pattern to configure the application in two phases:
Phase 1: builder.Services.* → Register services (DI container)
↓
builder.Build() → Create the application
↓
Phase 2: app.Use* → Configure middleware
app.Map* → Define endpoints
↓
app.Run() → Start the servervar builder = WebApplication.CreateBuilder(args);
// Phase 1: Services
builder.Services.AddSingleton<ICache, MemoryCache>();
builder.Services.AddScoped<IProductRepo, ProductRepo>();
builder.Services.AddTransient<IEmailService, SmtpEmailService>();
var app = builder.Build();
// Phase 2: Middleware and endpoints
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", () => "Welcome to the API!");
app.Run();Service lifetimes
| Lifetime | Created | When to use |
|---|---|---|
Singleton |
Once per application | Cache, configuration, HTTP clients |
Scoped |
Once per HTTP request | DbContext, Unit of Work |
Transient |
Every time it is injected | Lightweight, stateless services |
builder.Services.AddSingleton<IConfiguration, Configuration>(); // shared
builder.Services.AddScoped<IUserService, UserService>(); // per request
builder.Services.AddTransient<IEmailSender, EmailSender>(); // new instance each timeMinimal API endpoints
Minimal APIs define endpoints with lambda functions directly:
// Simple GET — returns plain text
app.MapGet("/", () => "API is online");
// GET with route parameter and query string
app.MapGet("/products/{id:int}", (int id, bool? includeDetail) =>
{
// id comes from route, includeDetail from query string (?includeDetail=true)
return Results.Ok(new { id, includeDetail });
});
// POST with JSON body
app.MapPost("/products", (CreateProductDto dto) =>
{
// ASP.NET deserializes JSON automatically
Console.WriteLine($"Creating: {dto.Name}");
return Results.Created("/products/1", dto);
});
// Group endpoints with RouteGroupBuilder
var group = app.MapGroup("/api/v1/products");
group.MapGet("/", GetAll);
group.MapGet("/{id}", GetById);
group.MapPost("/", Create);Dependency injection in endpoints
// DI works through parameters in endpoints
app.MapGet("/users", (IUserService userService, ILogger<Program> logger) =>
{
logger.LogInformation("Querying users");
return Results.Ok(userService.GetAll());
});
// You can also resolve from the container directly
app.MapGet("/config", (IServiceProvider sp) =>
{
var config = sp.GetRequiredService<IConfiguration>();
return Results.Ok(new { version = config["App:Version"] });
});Configuration (appsettings.json)
{
"App": {
"Name": "Catalog API",
"Version": "1.0.0",
"MaxResults": 50
},
"ConnectionStrings": {
"Default": "Host=localhost;Database=catalog;Username=app;Password=pass"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}// Strongly typed configuration
public class AppSettings
{
public string Name { get; set; } = string.Empty;
public string Version { get; set; } = string.Empty;
public int MaxResults { get; set; } = 20;
}
builder.Services.Configure<AppSettings>(builder.Configuration.GetSection("App"));
// Inject in endpoints
app.MapGet("/info", (IOptions<AppSettings> opts) =>
Results.Ok(new { opts.Value.Name, opts.Value.Version }));Environments and environment variables
// Detect the current environment
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseDeveloperExceptionPage();
}
else if (app.Environment.IsProduction())
{
app.UseExceptionHandler("/error");
app.UseHsts();
}
// Environment variables override appsettings.json
// ASPNETCORE_ENVIRONMENT=Production
// ConnectionStrings__Default=...Practice
- Complete API: Create a Minimal API with full CRUD endpoints for a
Task(int Id, string Title, bool Completed)entity using an in-memory service. - Configuration: Add an
"Api": { "MaxItems": 10 }section toappsettings.jsonand use it in theGET /tasksendpoint to limit results. - Swagger: Ensure your API has Swagger enabled in development. Use
.WithName()and.WithSummary()to document the endpoints.
In the next lesson we will learn how to organize larger APIs using controllers, routing attributes, and validation with DataAnnotations.
Minimal APIs for small projects and microservices
Minimal APIs are perfect for microservices, single-purpose APIs, and projects that prioritize simplicity. For large APIs with many endpoints, consider using Controllers (ApiController) which offer better organization through inheritance and filters.
Results helpers
ASP.NET Core 10 includes the static Results type with helpers for the most common responses: Results.Ok(), Results.Created(), Results.NotFound(), Results.BadRequest(), Results.NoContent(), Results.Unauthorized(), Results.Problem(). Always use these helpers instead of returning data directly.
Register services in the correct order
Services are registered BEFORE calling builder.Build(). Middleware is configured AFTER Build(). If you register a service after Build(), you will get a runtime exception. The separation between service configuration and middleware is fundamental in ASP.NET Core.
// Program.cs — ASP.NET Core 10 complete Minimal API
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = WebApplication.CreateBuilder(args);
// ── Register services (Dependency Injection) ─────────
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<IProductService, ProductService>();
var app = builder.Build();
// ── Configure middleware pipeline ─────────────────────
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
// ── Define endpoints ──────────────────────────────────
app.MapGet("/products", (IProductService svc) =>
Results.Ok(svc.GetAll()))
.WithName("GetProducts")
.WithOpenApi();
app.MapGet("/products/{id:int}", (int id, IProductService svc) =>
{
var product = svc.GetById(id);
return product is null
? Results.NotFound(new { message = $"Product {id} not found" })
: Results.Ok(product);
});
app.MapPost("/products", (CreateProductDto dto, IProductService svc) =>
{
var product = svc.Create(dto);
return Results.Created($"/products/{product.Id}", product);
});
app.MapPut("/products/{id:int}", (int id, UpdateProductDto dto, IProductService svc) =>
{
var updated = svc.Update(id, dto);
return updated is null ? Results.NotFound() : Results.Ok(updated);
});
app.MapDelete("/products/{id:int}", (int id, IProductService svc) =>
svc.Delete(id) ? Results.NoContent() : Results.NotFound());
app.Run();
Sign in to track your progress