Learn / .NET API Engineering Path
Typed Minimal APIs
Keep endpoints compact while preserving explicit result shapes and discoverable contracts.
Course: .NET API Engineering Path. Level: Advanced. Topic: Typed backend.
Stage: intermediate - Intermediate API design - Typed endpoint composition. Move from reliable primitives into endpoint signatures that expose dependencies, results, validation, and failure modes clearly.
Outcomes
- Use typed results for route clarity.
- Keep DI visible in endpoint signatures.
- Return problem details consistently.
- Create HTTP endpoints with route handlers.
- Use typed results, route groups, and request DTOs.
- Separate endpoint wiring from business logic.
Concepts
- typed results
- DI
- problem details
- endpoint filters
- ASP.NET Core and Minimal APIs
- A Minimal API application
- DTOs and response shapes
- Modern C# and .NET foundations
- Guided practice
Concept flow
Show how typed endpoint composition moves from trigger to implementation outcome in .NET APIs.
- Request
- Endpoint
- Service
- Typed result
- ProblemDetails
Session flow
- Model typed results (concept, 9 min) — Name the decisions behind typed results before writing code.
- Use typed results for route clarity.
- Explain where typed results belongs in order management API.
- Build the vertical slice (walkthrough, 16 min) — Implement the smallest useful slice in Program.cs.
- Keep DI visible in endpoint signatures.
- Connect DI to a working example.
- Verify and harden (exercise, 11 min) — Document the error response.
- Return problem details consistently.
- Record one risk or follow-up before moving on.
- Minimal API Endpoints: A Minimal API application (concept, 48 min) — Minimal APIs put route definitions close to startup code while still supporting dependency injection, filters, OpenAPI, authorization, validation libraries, and test hosts. They are excellent for small to medium APIs and also work as thin endpoint layers over application services.
- Retained source example: Small catalog API
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IProductRepository, InMemoryProductRepository>();
var app = builder.Build();
var products = app.MapGroup("/products");
products.MapGet("/{id:guid}", async (Guid id, IProductRepository repository) =>
{
var product = await repository.FindAsync(id);
return product is null ? Results.NotFound() : Results.Ok(product);
});
app.Run();
Route handlers can receive route values, services, query values, body DTOs, and cancellation tokens by parameter binding.
- Minimal API Endpoints: DTOs and response shapes (walkthrough, 48 min) — Use request and response DTOs at API boundaries rather than exposing EF entities or domain objects directly. Typed results make success and failure paths visible to readers and tools.
- Retained source example: Create endpoint with validation guard
products.MapPost("/", async (CreateProductRequest request, IProductRepository repository) =>
{
if (string.IsNullOrWhiteSpace(request.Name))
{
return Results.ValidationProblem(new Dictionary<string, string[]>
{
[nameof(request.Name)] = ["Name is required"]
});
}
var product = await repository.CreateAsync(request.Name, request.Price);
return Results.Created($"/products/{product.Id}", product);
});
public sealed record CreateProductRequest(string Name, decimal Price);
Small APIs can use direct guards. Larger APIs often use endpoint filters or a validation package.
- Minimal API Endpoints: practices and mistakes (review, 5 min) — Retained guidance from the legacy PTesting lesson.
- Practice: Keep route handlers thin and move business rules into services or domain objects.
- Practice: Use route groups to apply shared prefixes, tags, filters, and authorization.
- Practice: Return appropriate status codes and problem details for errors.
- Practice: Document endpoints with OpenAPI metadata when the API is consumed by other teams or clients.
- Avoid: Putting all business logic in Program.cs.
- Avoid: Returning database entities directly from public endpoints.
- Avoid: Using 200 OK for validation failures or missing resources.
- Minimal API Endpoints: references (review, 2 min) — Original references retained from the legacy library.
- Minimal APIs overview: https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis
- ASP.NET Core fundamentals: https://learn.microsoft.com/aspnet/core/fundamentals/
Code example
C# in Program.cs.
using Microsoft.AspNetCore.Http.HttpResults;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IOrders, SqlOrders>();
builder.Services.AddProblemDetails();
var app = builder.Build();
app.MapGet("/orders/{id:guid}", async Task<Results<Ok<OrderDto>, NotFound>> (
Guid id,
IOrders orders,
CancellationToken ct) =>
{
var order = await orders.FindAsync(id, ct);
return order is null ? TypedResults.NotFound() : TypedResults.Ok(order);
});
app.Run();
Walkthrough examples
- Typed Minimal APIs in a order management API — A team is extending a production-grade ASP.NET Core order API and needs this lesson's pattern to be clear enough for review, testing, and future maintenance.
- File: Program.cs
- File: src/Orders.Application/typed-minimal-apis.cs
- File: tests/Orders.Api.Tests/typed-minimal-apis.Tests.cs
- File: docs/dotnet-advanced/typed-minimal-apis.md
- Start from the provided C# snippet and make the intent visible in names and boundaries.
- Apply the checklist item "Type route result" before adding extra behavior.
- Write down how the implementation changes when DI fails or becomes slow.
- Small catalog API — Route handlers can receive route values, services, query values, body DTOs, and cancellation tokens by parameter binding.
- Retained source code:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IProductRepository, InMemoryProductRepository>();
var app = builder.Build();
var products = app.MapGroup("/products");
products.MapGet("/{id:guid}", async (Guid id, IProductRepository repository) =>
{
var product = await repository.FindAsync(id);
return product is null ? Results.NotFound() : Results.Ok(product);
});
app.Run();
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Create endpoint with validation guard — Small APIs can use direct guards. Larger APIs often use endpoint filters or a validation package.
- Retained source code:
products.MapPost("/", async (CreateProductRequest request, IProductRepository repository) =>
{
if (string.IsNullOrWhiteSpace(request.Name))
{
return Results.ValidationProblem(new Dictionary<string, string[]>
{
[nameof(request.Name)] = ["Name is required"]
});
}
var product = await repository.CreateAsync(request.Name, request.Price);
return Results.Created($"/products/{product.Id}", product);
});
public sealed record CreateProductRequest(string Name, decimal Price);
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Endpoint extension method — Extension methods keep Program.cs readable as the number of endpoints grows.
- Retained source code:
public static class ProductEndpoints
{
public static RouteGroupBuilder MapProductEndpoints(this IEndpointRouteBuilder routes)
{
var group = routes.MapGroup("/products").WithTags("Products");
group.MapGet("/", (IProductRepository repository) => repository.ListAsync());
return group;
}
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
Practice
- Convert one endpoint to typed results.
- Add an endpoint filter for validation.
- Document the error response.
- Choose a status code: A GET /products/{id} endpoint cannot find the product. Which HTTP status should it return?
- Starter code: status = ____
- Expected output: 404
- Hint: The route is valid, but the resource does not exist.
- Reference solution: Return 404 Not Found, usually with Results.NotFound().
- Accepted answers: 404 | NotFound | Results.NotFound
Checklist
- Type route result
- Inject service
- Handle not found
- Add problem details
Quiz prompts
- What is a practical benefit of typed results in minimal APIs? — Typed results clarify the contract and help tooling understand route responses.
- A teammate wants to hide typed results inside a convenient helper. What should you check first? — Place typed results at the boundary that keeps order management API behavior explicit, testable, and reviewable.
- Which artifact best proves this .NET APIs lesson is ready for review? — Production-ready learning needs evidence: a test, trace, command, screenshot, or log that catches the risk again.
- Intermediate API design: a teammate says the happy path works, but "Typed results as contracts" is still implicit. What should you ask for before merging? — Typed results as contracts belongs in the intermediate stage only when the decision is visible, testable, and tied to a realistic failure mode.
- A reviewer has five minutes to evaluate this intermediate .NET APIs slice. Which evidence is strongest? — Design the intermediate endpoint layer for orders with typed success, not-found, validation, and problem responses, then compare the OpenAPI output before and after.
- Why use DTOs at API boundaries? — DTOs let API contracts evolve deliberately without exposing internal implementation details.
Flashcards
- Intermediate API design: what decision does "Typed results as contracts" force you to make? Make success and failure responses visible to reviewers and OpenAPI tooling. Evidence prompt: Refactor one route so its return type documents every supported response.
- Intermediate API design: what decision does "Endpoint filters and visible DI" force you to make? Centralize cross-cutting validation without hiding the service collaboration. Evidence prompt: Add one validation filter and keep the application service dependency visible in the route signature.
- Intermediate API design: what decision does "Validation and error contract matrix" force you to make? List every supported validation, not-found, conflict, and unexpected-failure response before implementation drifts. Evidence prompt: Create a route contract matrix and verify each row appears in generated OpenAPI output.
- In .NET APIs, what should you remember about typed results? typed results matters here because it supports "Use typed results for route clarity.".
- In .NET APIs, what should you remember about DI? DI matters here because it supports "Keep DI visible in endpoint signatures.".
- In .NET APIs, what should you remember about problem details? problem details matters here because it supports "Return problem details consistently.".
- In .NET APIs, what should you remember about endpoint filters? endpoint filters matters here because it supports "Use typed results for route clarity.".
Labs
- Ship a typed minimal apis slice — Extend a production-grade ASP.NET Core order API with a small but reviewable feature that proves the lesson's architecture in code.
- Design the intermediate endpoint layer for orders with typed success, not-found, validation, and problem responses, then compare the OpenAPI output before and after.
- Refactor one route so its return type documents every supported response.
- Add one validation filter and keep the application service dependency visible in the route signature.
- Create a route contract matrix and verify each row appears in generated OpenAPI output.
- Convert one endpoint to typed results.
- Add an endpoint filter for validation.
- The lab demonstrates the intermediate api design outcome without skipping earlier contract evidence.
- Each subtopic has a concrete artifact: Typed results as contracts, Endpoint filters and visible DI, Validation and error contract matrix.
- The review notes explain how this level changes ownership, verification, or operational risk.
Challenge
- Review-ready typed minimal apis (Stretch) — Design the intermediate endpoint layer for orders with typed success, not-found, validation, and problem responses, then compare the OpenAPI output before and after.
- All checklist items are either implemented or documented with a reason.
- The change can be understood from Program.cs plus one short note.
- The concept diagram names ownership, failure handling, and verification points.
- A teammate could run the verification steps without asking for hidden context.
Canonical lesson URL