Middleware & Filters in Minimal APIs
Add authentication, authorization, exception handling, and endpoint filters to Minimal API pipelines.
Middleware & Filters in Minimal APIs is a free C# Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Middleware vs Endpoint Filters
ASP.NET Core has two extensibility layers: middleware (wraps the entire pipeline, runs for every request) and endpoint filters (run only for specific endpoints). Both work in Minimal APIs.
Adding Middleware to Minimal APIs
Standard ASP.NET Core middleware is added with app.Use* methods. The order matters — middleware runs in the order it is registered.
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseRateLimiter();
app.MapGet("/secure", () => "Protected endpoint")
.RequireAuthorization();
app.Run();Inline Endpoint Filters
Endpoint filters are like middleware scoped to a single endpoint. Use AddEndpointFilter to add a filter inline. Filters can short-circuit by returning a result without calling next.
app.MapGet("/guarded", () => "Secret data")
.AddEndpointFilter(async (ctx, next) =>
{
var header = ctx.HttpContext.Request.Headers["X-Api-Key"];
if (header != "my-secret-key")
return Results.Unauthorized();
return await next(ctx); // proceed to handler
});Class-Based Endpoint Filters
Implement IEndpointFilter to create reusable filters. They can be applied to individual endpoints or entire route groups.
public class LoggingFilter : IEndpointFilter
{
private readonly ILogger<LoggingFilter> _logger;
public LoggingFilter(ILogger<LoggingFilter> logger) => _logger = logger;
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext ctx,
EndpointFilterDelegate next)
{
_logger.LogInformation("Before: {Path}", ctx.HttpContext.Request.Path);
var result = await next(ctx);
_logger.LogInformation("After: {Path}", ctx.HttpContext.Request.Path);
return result;
}
}
// Apply to a group
app.MapGroup("/api").AddEndpointFilter<LoggingFilter>();Exception Handling Middleware
Use app.UseExceptionHandler or the built-in ProblemDetails middleware to catch unhandled exceptions and return RFC 7807 problem responses.
builder.Services.AddProblemDetails();
var app = builder.Build();
app.UseExceptionHandler();
// OR for custom logic:
app.UseExceptionHandler(errApp =>
errApp.Run(async ctx =>
{
var ex = ctx.Features.Get<IExceptionHandlerFeature>()?.Error;
ctx.Response.StatusCode = 500;
await ctx.Response.WriteAsJsonAsync(new { error = ex?.Message });
}));Short-Circuit Middleware
Use app.MapShortCircuit() (.NET 8+) or Results.StatusCode() in a filter to immediately return a response without running the full pipeline.
// Block known bad bots at the edge
app.MapGet("/robots.txt", () => "User-agent: *\nDisallow: /")
.ShortCircuit(); // .NET 8: returns response immediately
// Or in a filter:
app.MapGet("/maintenance", () => "OK")
.AddEndpointFilter((ctx, next) =>
ValueTask.FromResult<object?>(Results.StatusCode(503)));Rate Limiting in .NET 7+
The built-in rate limiter middleware protects endpoints from abuse. Apply policies globally or per endpoint with RequireRateLimiting().
using System.Threading.RateLimiting;
builder.Services.AddRateLimiter(opt =>
{
opt.AddFixedWindowLimiter("fixed", o =>
{
o.PermitLimit = 10;
o.Window = TimeSpan.FromSeconds(10);
o.QueueLimit = 0;
o.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
});
opt.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});
app.UseRateLimiter();
app.MapGet("/search", Search).RequireRateLimiting("fixed");CORS Configuration
Configure CORS policies in services and apply them globally or per endpoint to control which origins can access your API.
builder.Services.AddCors(opt =>
opt.AddPolicy("AllowFrontend", policy =>
policy.WithOrigins("https://myapp.com")
.AllowAnyHeader()
.AllowAnyMethod()));
app.UseCors("AllowFrontend");
// Or per endpoint:
app.MapGet("/public-data", () => "data")
.RequireCors("AllowFrontend");Output Caching Middleware
AddOutputCache caches responses server-side. Apply it to specific endpoints to serve cached responses without hitting the database on every request.
builder.Services.AddOutputCache();
app.UseOutputCache();
app.MapGet("/products", async (AppDbContext db) =>
Results.Ok(await db.Products.AsNoTracking().ToListAsync()))
.CacheOutput(p => p.Expire(TimeSpan.FromMinutes(5)));Filter Order and Chaining
Filters execute in the order they are added (outermost first, innermost last — like middleware). You can chain multiple filters on the same endpoint or group.
app.MapPost("/orders", CreateOrder)
.AddEndpointFilter<LoggingFilter>() // runs first
.AddEndpointFilter<ValidationFilter>() // runs second
.AddEndpointFilter<AuthFilter>() // runs third
.RequireAuthorization(); // JWT checked by middlewareReal-World: API Key Middleware
A reusable API key middleware that reads the key from a header and short-circuits with 401 if invalid.
public class ApiKeyMiddleware
{
private readonly RequestDelegate _next;
private readonly string _expectedKey;
public ApiKeyMiddleware(RequestDelegate next, IConfiguration cfg)
{
_next = next;
_expectedKey = cfg["ApiKey"] ?? throw new Exception("ApiKey not configured");
}
public async Task InvokeAsync(HttpContext ctx)
{
if (!ctx.Request.Headers.TryGetValue("X-Api-Key", out var key)
|| key != _expectedKey)
{
ctx.Response.StatusCode = 401;
await ctx.Response.WriteAsync("Invalid API key");
return;
}
await _next(ctx);
}
}
app.UseMiddleware<ApiKeyMiddleware>();Quick Check
What is the key difference between middleware and endpoint filters in Minimal APIs?
Recap: Middleware & Filters
Key takeaways:
- Middleware (UseX) runs globally in pipeline order for every request
- Endpoint filters (AddEndpointFilter) are scoped to specific endpoints or groups
- Implement IEndpointFilter for reusable, DI-friendly filter classes
- Built-in middleware: rate limiting, CORS, output caching, exception handling
- Filter order matters — they nest like middleware (first added = outermost)
Frequently asked questions
Is the “Middleware & Filters in Minimal APIs” lesson free?
Yes — the full text of “Middleware & Filters in Minimal APIs” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.
What will I learn in “Middleware & Filters in Minimal APIs”?
Add authentication, authorization, exception handling, and endpoint filters to Minimal API pipelines. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start C# Academy?
No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Middleware & Filters in Minimal APIs” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this C# Academy lesson?
Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Creating Your First Minimal API
- Route Groups, Parameters & Validation
- Middleware & Filters in Minimal APIs
- OpenAPI, Versioning & Deployment