🐧 Linux TippsDebian 11 Long Term Support reaches end-of-life(31.08.2026 um 02:00 Uhr)
🐧 Linux TippsUpdated Debian 13: 13.7 released(12.09.2026 um 02:00 Uhr)
🕵️ SicherheitslückenUSN-8741-1: Flatpak vulnerabilities(10.09.2026 um 10:44 Uhr)
🕵️ SicherheitslückenUSN-8742-1: Netty vulnerability(10.09.2026 um 11:01 Uhr)
🕵️ SicherheitslückenUSN-8737-2: GNU C Library vulnerabilities(10.09.2026 um 13:25 Uhr)
🕵️ SicherheitslückenUSN-8743-1: PHP vulnerabilities(10.09.2026 um 13:48 Uhr)
🕵️ SicherheitslückenUSN-8744-1: Python vulnerabilities(10.09.2026 um 15:53 Uhr)
🐧 Linux TippsUSN-8748-1: Linux kernel (NVIDIA) vulnerabilities(10.09.2026 um 17:32 Uhr)
🕵️ SicherheitslückenUSN-8745-1: KissFFT vulnerabilities(10.09.2026 um 17:36 Uhr)
🕵️ SicherheitslückenUSN-8746-1: libEBML vulnerability(10.09.2026 um 17:48 Uhr)
🐧 Linux TippsDebian 11 Long Term Support reaches end-of-life(31.08.2026 um 02:00 Uhr)
🐧 Linux TippsUpdated Debian 13: 13.7 released(12.09.2026 um 02:00 Uhr)
🕵️ SicherheitslückenUSN-8741-1: Flatpak vulnerabilities(10.09.2026 um 10:44 Uhr)
🕵️ SicherheitslückenUSN-8742-1: Netty vulnerability(10.09.2026 um 11:01 Uhr)
🕵️ SicherheitslückenUSN-8737-2: GNU C Library vulnerabilities(10.09.2026 um 13:25 Uhr)
🕵️ SicherheitslückenUSN-8743-1: PHP vulnerabilities(10.09.2026 um 13:48 Uhr)
🕵️ SicherheitslückenUSN-8744-1: Python vulnerabilities(10.09.2026 um 15:53 Uhr)
🐧 Linux TippsUSN-8748-1: Linux kernel (NVIDIA) vulnerabilities(10.09.2026 um 17:32 Uhr)
🕵️ SicherheitslückenUSN-8745-1: KissFFT vulnerabilities(10.09.2026 um 17:36 Uhr)
🕵️ SicherheitslückenUSN-8746-1: libEBML vulnerability(10.09.2026 um 17:48 Uhr)

🔧 Programmierung 🕛 vor 2 Monaten 11 Min Lesezeit
0

Minimal APIs in ASP.NET Core: Lightweight APIs Without the Boilerplate

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht




Minimal APIs in ASP.NET Core: Lightweight APIs Without the Boilerplate



A practical, in-depth guide to Minimal APIs — the lightweight way to build HTTP APIs in ASP.NET Core, ideal for microservices, small backend services, and high-throughput endpoints.









Table of Contents




  1. Introduction

  2. The Basics

  3. Routing and Route Parameters

  4. Parameter Binding

  5. Request Validation

  6. Typed Results

  7. Route Groups and Organization

  8. Dependency Injection in Minimal APIs

  9. Filters

  10. OpenAPI / Swagger Integration

  11. Minimal APIs vs. Controllers

  12. Performance Characteristics

  13. Quick Reference Table

  14. Conclusion









Introduction



Minimal APIs, introduced in .NET 6, are a way to define HTTP endpoints directly in Program.cs (or nearby files) without controllers, attributes, or the MVC conventions that come with them. They were designed for a specific gap: not every service needs full MVC — a small microservice, a health-check endpoint, or a focused internal API often just needs a handful of routes wired directly to logic.



The pitch is simple: fewer files, less ceremony, faster startup — while still supporting the things real APIs need: validation, DI, authorization, OpenAPI docs, and strongly-typed responses.




CODE
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

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

app.Run();






That's a complete, runnable web API. No controller class, no attributes, no separate Startup.cs.









1. The Basics






The four core HTTP verbs






CODE
var app = builder.Build();

app.MapGet("/products", () => products);
app.MapPost("/products", (Product product) => { products.Add(product); return Results.Created($"/products/{product.Id}", product); });
app.MapPut("/products/{id}", (int id, Product updated) => { /* update logic */ });
app.MapDelete("/products/{id}", (int id) => { /* delete logic */ });

app.Run();






Each Map* method takes a route pattern and a delegate (lambda or method group). The delegate's parameters are automatically bound from the route, query string, request body, headers, or DI container — ASP.NET Core infers the source based on the parameter's type and position.






Returning results



You can return plain strings, POCOs (serialized to JSON automatically), or explicit IResult values via the Results static class:




CODE
app.MapGet("/status", () => "OK");                          // 200, text/plain
app.MapGet("/config", () => new { Version = "1.0" }); // 200, JSON
app.MapGet("/secret", () => Results.Unauthorized()); // 401
app.MapGet("/report/{id}", (int id) =>
id > 0 ? Results.Ok(GetReport(id)) : Results.BadRequest("Invalid id"));












2. Routing and Route Parameters






Route constraints






CODE
app.MapGet("/products/{id:int}", (int id) => GetProduct(id));
app.MapGet("/orders/{orderId:guid}", (Guid orderId) => GetOrder(orderId));
app.MapGet("/archive/{year:int:min(2000)}/{month:int:range(1,12)}", (int year, int month) => GetArchive(year, month));






Constraints (:int, :guid, :min(), :range(), :alpha, :regex(), etc.) let invalid requests fail at the routing layer with a 404, before your handler even runs.






Optional parameters and defaults






CODE
app.MapGet("/search", (string? query, int page = 1, int pageSize = 20) =>
{
return Search(query, page, pageSize);
});






A request to /search?query=laptop works, filling in page and pageSize from their C# default values if omitted from the query string.






Wildcard / catch-all routes






CODE
app.MapGet("/files/{*path}", (string path) => ServeFile(path));












3. Parameter Binding



Minimal APIs infer where a parameter comes from based on type and convention, but you can always be explicit.











































Source How it's inferred Explicit attribute
Route values Parameter name matches a { } segment [FromRoute]
Query string Simple type (string, int, bool, etc.) not matching a route segment [FromQuery]
Request body Complex type (class/record), only one per endpoint [FromBody]
Headers [FromHeader]
DI container Type is a registered service [FromServices]
Form data
IFormCollection, IFormFile
[FromForm]





CODE
app.MapPost("/upload", async (IFormFile file, [FromServices] IStorageService storage) =>
{
await storage.SaveAsync(file);
return Results.Ok();
});









Binding from JSON body






CODE
public record CreateProductRequest(string Name, decimal Price, string Category);

app.MapPost("/products", (CreateProductRequest request, IProductRepository repo) =>
{
var product = repo.Add(request.Name, request.Price, request.Category);
return Results.Created($"/products/{product.Id}", product);
});









Custom binding with BindAsync (.NET 7+)



For types that need custom parsing logic (e.g., from a cookie or a composite query format), implement a static BindAsync method:




CODE
public record struct SortOptions(string Field, bool Descending)
{
public static ValueTask<SortOptions?> BindAsync(HttpContext context, ParameterInfo parameter)
{
var value = context.Request.Query["sort"].ToString();
if (string.IsNullOrEmpty(value)) return ValueTask.FromResult<SortOptions?>(null);

var descending = value.StartsWith('-');
var field = descending ? value[1..] : value;
return ValueTask.FromResult<SortOptions?>(new SortOptions(field, descending));
}
}

app.MapGet("/products", (SortOptions? sort) => GetSortedProducts(sort));












4. Request Validation



Minimal APIs don't include the automatic model-state validation that [ApiController] provides for MVC, so validation is typically explicit — which keeps behavior predictable but means you opt in.






Manual validation






CODE
app.MapPost("/products", (CreateProductRequest request) =>
{
if (string.IsNullOrWhiteSpace(request.Name))
return Results.ValidationProblem(new Dictionary<string, string[]>
{
["Name"] = ["Name is required."]
});

if (request.Price <= 0)
return Results.ValidationProblem(new Dictionary<string, string[]>
{
["Price"] = ["Price must be greater than zero."]
});

// ... create product
return Results.Created();
});









DataAnnotations + a validation filter (.NET 8+ approach)






CODE
public record CreateProductRequest(
[property: Required, MinLength(3)] string Name,
[property: Range(0.01, double.MaxValue)] decimal Price);

app.MapPost("/products", (CreateProductRequest request) => Results.Created())
.AddEndpointFilter<ValidationFilter<CreateProductRequest>>();






Where ValidationFilter<T> is a small reusable endpoint filter that runs Validator.TryValidateObject before the handler executes — this pattern lets you centralize validation logic instead of repeating if checks in every handler. (.NET 10 also introduces built-in support for validating annotated types automatically in minimal APIs, reducing the need to hand-write this filter.)






FluentValidation (popular third-party option)






CODE
public class CreateProductValidator : AbstractValidator<CreateProductRequest>
{
public CreateProductValidator()
{
RuleFor(x => x.Name).NotEmpty().MinimumLength(3);
RuleFor(x => x.Price).GreaterThan(0);
}
}






Many teams pair minimal APIs with FluentValidation via an endpoint filter, since it scales better than DataAnnotations for complex, cross-field rules.









5. Typed Results



Untyped IResult return values don't tell the OpenAPI generator (or callers) what status codes and shapes to expect. TypedResults (added in .NET 7) fixes this.




CODE
app.MapGet("/products/{id}", Results<Ok<Product>, NotFound> (int id, IProductRepository repo) =>
{
var product = repo.GetById(id);
return product is not null
? TypedResults.Ok(product)
: TypedResults.NotFound();
});






Benefits of TypedResults over Results:





  • Accurate OpenAPI/Swagger docs — the possible response types and status codes are visible to the type system, not just at runtime.


  • Compile-time safety — you can't accidentally return a type that isn't declared in the method signature.


  • Easier unit testing — you can assert on the concrete result type without spinning up an HTTP pipeline.




CODE
[Fact]
public void GetById_ReturnsNotFound_WhenMissing()
{
var result = Endpoint.GetProduct(999, new FakeRepository());
Assert.IsType<NotFound>(result.Result);
}












6. Route Groups and Organization



As an API grows past a handful of endpoints, Program.cs can get crowded. Route groups (.NET 7+) and extension methods keep things tidy without reaching for full MVC.






Grouping related endpoints






CODE
var products = app.MapGroup("/products").WithTags("Products");

products.MapGet("/", GetAllProducts);
products.MapGet("/{id:int}", GetProductById);
products.MapPost("/", CreateProduct).RequireAuthorization();
products.MapPut("/{id:int}", UpdateProduct).RequireAuthorization();
products.MapDelete("/{id:int}", DeleteProduct).RequireAuthorization("AdminOnly");






Anything applied to the group (auth, filters, tags, rate limits) cascades to every endpoint inside it.






Extracting endpoints into their own files






CODE
// Endpoints/ProductEndpoints.cs
public static class ProductEndpoints
{
public static RouteGroupBuilder MapProductEndpoints(this RouteGroupBuilder group)
{
group.MapGet("/", GetAllProducts);
group.MapGet("/{id:int}", GetProductById);
group.MapPost("/", CreateProduct);
return group;
}

private static async Task<Ok<List<Product>>> GetAllProducts(IProductRepository repo) =>
TypedResults.Ok(await repo.GetAllAsync());

private static async Task<Results<Ok<Product>, NotFound>> GetProductById(int id, IProductRepository repo)
{
var product = await repo.GetByIdAsync(id);
return product is not null ? TypedResults.Ok(product) : TypedResults.NotFound();
}

private static async Task<Created<Product>> CreateProduct(CreateProductRequest request, IProductRepository repo)
{
var product = await repo.AddAsync(request);
return TypedResults.Created($"/products/{product.Id}", product);
}
}









CODE
// Program.cs
app.MapGroup("/products").MapProductEndpoints();






This pattern — extension methods per feature area — is the community-standard way to keep minimal APIs organized without MVC's folder/attribute conventions, and it scales to dozens of endpoint groups cleanly.









7. Dependency Injection in Minimal APIs



Minimal API handlers get services the same way controllers do — via the DI container — just injected as method parameters instead of constructor parameters.




CODE
builder.Services.AddScoped<IProductRepository, SqlProductRepository>();
builder.Services.AddSingleton<IClock, SystemClock>();

app.MapGet("/products", (IProductRepository repo) => repo.GetAll());









Keyed services (.NET 8+)






CODE
app.MapPost("/notify", ([FromKeyedServices("sms")] INotifier notifier, string message) =>
{
notifier.Send(message);
return Results.Ok();
});












8. Filters



Endpoint filters (.NET 7+) are minimal APIs' answer to MVC's action filters — a way to run logic before/after a handler without duplicating it across endpoints.




CODE
public class LoggingFilter : IEndpointFilter
{
private readonly ILogger<LoggingFilter> _logger;
public LoggingFilter(ILogger<LoggingFilter> logger) => _logger = logger;

public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
_logger.LogInformation("Executing {Endpoint}", context.HttpContext.Request.Path);
var result = await next(context);
_logger.LogInformation("Executed {Endpoint} -> {Result}", context.HttpContext.Request.Path, result);
return result;
}
}

app.MapPost("/products", CreateProduct).AddEndpointFilter<LoggingFilter>();









Inline filters






CODE
app.MapPost("/products", CreateProduct)
.AddEndpointFilter(async (context, next) =>
{
var request = context.GetArgument<CreateProductRequest>(0);
if (request.Price <= 0)
return Results.BadRequest("Price must be positive.");

return await next(context);
});






Filters compose in the order they're added, and can short-circuit the pipeline (skip the handler entirely) — useful for validation, logging, and cross-cutting checks like feature flags.









9. OpenAPI / Swagger Integration






CODE
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}









Enriching endpoint metadata






CODE
app.MapGet("/products/{id}", GetProductById)
.WithName("GetProductById")
.WithSummary("Retrieves a single product by its ID")
.WithTags("Products")
.Produces<Product>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);






Using TypedResults (Section 5) instead of Results means much of this metadata — the possible status codes and response shapes — is inferred automatically, reducing how much you need to declare by hand. .NET 9 also ships a built-in OpenAPI document generator (Microsoft.AspNetCore.OpenApi) as a lighter-weight alternative to Swashbuckle for teams that only need the raw OpenAPI JSON/YAML output.









10. Minimal APIs vs. Controllers





















































Aspect Minimal APIs MVC Controllers
File structure Delegates in Program.cs or extension methods Controller classes with attributes
Boilerplate Very low Moderate — base class, attributes, routing conventions
Validation Explicit (manual, filters, or FluentValidation) Automatic via [ApiController] model-state validation
Startup performance Faster — less reflection-based discovery Slightly slower, more metadata to build
Best fit Microservices, small APIs, high-throughput endpoints, serverless functions Larger apps, teams wanting strong conventions, apps mixing API + server-rendered views
View rendering Not supported directly (API-only) Full support via Razor views
Native AOT compatibility Excellent (designed with AOT in mind) Limited — MVC relies heavily on reflection
Learning curve Lower for simple cases Steeper, but scales better with strict team conventions





A practical rule of thumb





  • Small service, a handful of endpoints, containerized, or targeting Native AOT? → Minimal APIs.


  • Large application with dozens of resources, complex validation rules, or mixed API+UI needs? → Controllers (optionally alongside minimal APIs for a few lightweight endpoints — the two can coexist in the same app).









11. Performance Characteristics



Minimal APIs were explicitly designed to reduce the overhead between "request arrives" and "your code runs":





  • Less reflection at startup — routing metadata for minimal APIs is built more directly than the attribute-scanning MVC relies on, which shows up as measurably faster app startup — a meaningful win for serverless functions and container cold starts.


  • No MVC filter pipeline overhead — minimal APIs use the lighter endpoint-filter pipeline described above rather than MVC's larger action/result filter pipeline.


  • First-class Native AOT support — because minimal APIs avoid runtime reflection-heavy model binding by default, they compile and run well under Native AOT, where startup time and memory footprint drop dramatically:




CODE
dotnet publish -r linux-x64 -c Release /p:PublishAot=true








  • Source-generated JSON serialization — pairing minimal APIs with System.Text.Json source generators ([JsonSerializable] context classes) avoids reflection-based serialization entirely, which matters most in Native AOT scenarios where reflection-based serialization isn't available at all.




CODE
[JsonSerializable(typeof(Product))]
[JsonSerializable(typeof(List<Product>))]
internal partial class AppJsonContext : JsonSerializerContext { }

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});












Quick Reference Table































































Feature Introduced Purpose

MapGet/MapPost/etc.
.NET 6 Define routes without controllers
Automatic parameter binding .NET 6 Infer route/query/body sources from method signature
Route groups (MapGroup) .NET 7 Share config across related endpoints
Typed results (TypedResults) .NET 7 Strongly-typed, OpenAPI-friendly responses
Endpoint filters .NET 7 Cross-cutting logic (validation, logging) without duplication

BindAsync custom binding
.NET 7 Custom parameter parsing logic
Keyed DI services .NET 8 Multiple implementations of one interface
Built-in Microsoft.AspNetCore.OpenApi
.NET 9 Lightweight OpenAPI document generation
JSON source generation .NET 6+, essential for AOT Reflection-free serialization
Native AOT support .NET 8 Fast cold starts, low memory footprint








Conclusion



Minimal APIs aren't a "lesser" version of MVC — they're a different default, optimized for the reality that a large share of modern backend services are small, focused, and deployed as independently scaled units (microservices, serverless functions, sidecars). By stripping away controllers, attributes, and MVC's filter pipeline, they cut the distance between a route definition and the code that handles it — while still supporting the things production APIs need: validation via filters, strongly-typed responses, DI, and OpenAPI docs.



The right choice isn't binary, either — minimal APIs and controllers coexist in the same ASP.NET Core app, so many teams use minimal APIs for lightweight, high-traffic endpoints and controllers for larger, more conventions-heavy parts of the same system.






Found this useful? Feel free to star the repo, open an issue with corrections, or share how you've organized minimal APIs in a larger codebase.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Debian 11 Long Term Support reaches end-of-life
1 Quelle
Updated Debian 13: 13.7 released
1 Quelle
USN-8741-1: Flatpak vulnerabilities
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Minimal APIs in ASP.NET Core: Lightweight APIs Without the Boilerplate

Thematisch verwandte Begriffe: Minimal, APIs, ASPNET, Core · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...