ASP.NET Core cookbook · Beginner
Organize minimal API endpoints into groups
Program.cs grows into hundreds of lines of endpoints with repeated prefixes, auth and tags.
Approach
Put each feature's endpoints in a static extension method that maps a route group with shared prefix, tags,
authorization and filters. Handlers are static methods returning TypedResults unions.
Solution
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthorization();
builder.Services.AddSingleton<ProductStore>();
var app = builder.Build();
app.UseAuthorization();
app.MapProductEndpoints();
app.Run();
// Products/ProductEndpoints.cs
using Microsoft.AspNetCore.Http.HttpResults;
public static class ProductEndpoints
{
public static IEndpointRouteBuilder MapProductEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/products").WithTags("Products");
group.MapGet("/", List);
group.MapGet("/{id:int}", Get).WithName("GetProduct");
group.MapPost("/", Create).RequireAuthorization();
return app;
}
static Ok<IReadOnlyList<Product>> List(ProductStore store) => TypedResults.Ok(store.All());
static Results<Ok<Product>, NotFound> Get(int id, ProductStore store) =>
store.Find(id) is { } product ? TypedResults.Ok(product) : TypedResults.NotFound();
static CreatedAtRoute<Product> Create(NewProduct input, ProductStore store)
{
var product = store.Add(input.Name, input.Price);
return TypedResults.CreatedAtRoute(product, "GetProduct", new { id = product.Id });
}
}
public record Product(int Id, string Name, decimal Price);
public record NewProduct(string Name, decimal Price);
public class ProductStore
{
private readonly List<Product> _items = [new(1, "Mug", 8.50m)];
public IReadOnlyList<Product> All() => _items;
public Product? Find(int id) => _items.FirstOrDefault(p => p.Id == id);
public Product Add(string name, decimal price)
{
var product = new Product(_items.Max(p => p.Id) + 1, name, price);
_items.Add(product);
return product;
}
}
Pitfalls
- Lambdas with many injected services inline in Program.cs; static methods are easier to test and read.
- Returning
IResulteverywhere, losing response metadata for OpenAPI. - Applying
RequireAuthorization()endpoint by endpoint and forgetting one; put it on the group.
Variations
- Nest groups (
group.MapGroup("/{id}/reviews")) for sub-resources. - Use a fallback authorization policy so every endpoint requires auth unless marked
AllowAnonymous().
Further reading: Route groups
Reference: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/route-handlers#route-groups
More ASP.NET Core recipes
- Accept file uploads with size and type limits
- Add OpenTelemetry traces and metrics
- Apply EF Core migrations safely during deployment
- Bind and validate options at startup
- Cache responses with output caching and evict by tag
- Call external APIs with typed HttpClients and resilience
- Configure CORS for a browser front end
- Expose liveness and readiness health checks