Build ASP.NET Core Minimal APIs with .NET 10 best practices
You are a Minimal API expert specializing in ASP.NET Core 10 backend-first web applications.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSqlite<AppDbContext>("Data Source=app.db");
builder.Services.AddScoped<IOrderService, OrderService>();
var app = builder.Build();
app.UseExceptionHandler("/error");
app.UseHttpsRedirection();
app.MapOrderEndpoints();
app.Run();
public static class OrderEndpoints
{
public static RouteGroupBuilder MapOrderEndpoints(this IEndpointRouteBuilder app)
{
var group = app.MapGroup("/api/orders")
.WithTags("Orders");
group.MapGet("/", GetOrders);
group.MapGet("/{id:guid}", GetOrderById);
group.MapPost("/", CreateOrder);
group.MapPut("/{id:guid}", UpdateOrder);
group.MapDelete("/{id:guid}", DeleteOrder);
return group;
}
}
static async Task<IResult> GetOrders(
IOrderService orderService,
[FromQuery] string? search,
[FromQuery] int page = 1)
{
var orders = await orderService.GetOrdersAsync(search, page);
return Results.Ok(orders);
}
static async Task<IResult> GetOrderById(
Guid id,
IOrderService orderService)
{
var order = await orderService.GetByIdAsync(id);
return order is not null
? Results.Ok(order)
: Results.NotFound();
}
public sealed class CreateOrderRequest
{
[Required]
public Guid CustomerId { get; init; }
[Required, StringLength(500, MinimumLength = 10)]
public string Description { get; init; } = string.Empty;
[Range(0.01, 1_000_000)]
public decimal TotalAmount { get; init; }
public DateOnly? DueDate { get; init; }
}
static async Task<IResult> CreateOrder(
CreateOrderRequest request,
IOrderService orderService)
{
if (!MiniValidator.TryValidate(request, out var errors))
return Results.ValidationProblem(errors);
var id = await orderService.CreateAsync(request);
return Results.Created($"/api/orders/{id}", new { id });
}
app.Map("/error", (HttpContext context) =>
{
var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;
return Results.Problem(
title: "An unexpected error occurred",
detail: exception?.Message,
statusCode: StatusCodes.Status500InternalServerError
);
});
public class RequireApiKeyFilter : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context,
EndpointFilterDelegate next)
{
var httpContext = context.HttpContext;
if (!httpContext.Request.Headers.TryGetValue("X-API-KEY", out _))
return Results.Unauthorized();
return await next(context);
}
}
Usage:
group.MapPost("/", CreateOrder)
.AddEndpointFilter<RequireApiKeyFilter>();
public sealed class AppDbContext : DbContext
{
public DbSet<Order> Orders => Set<Order>();
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options) { }
}
src/
├── Program.cs
├── Endpoints/
│ ├── OrderEndpoints.cs
│ └── ProjectEndpoints.cs
├── Contracts/
│ ├── CreateOrderRequest.cs
│ └── UpdateOrderRequest.cs
├── Services/
│ ├── IOrderService.cs
│ └── OrderService.cs
├── Data/
│ └── AppDbContext.cs
├── Domain/
│ └── Order.cs
└── Infrastructure/
├── Filters/
└── Middleware/
builder.Services.AddAntiforgery();
app.Use(async (context, next) =>
{
if (HttpMethods.IsPost(context.Request.Method))
await context.RequestServices
.GetRequiredService<IAntiforgery>()
.ValidateRequestAsync(context);
await next();
});