Modern .NET includes native performance utilities that eliminate the need for third-party middleware packages: Rate Limiting protects your APIs from abuse and denial-of-service attempts, while output caching drastically reduces database load by caching full HTTP responses server-side.

Step 1: Configuring Built-In Rate Limiting

Rate limiting controls the frequency of requests clients can make to your API. .NET offers fixed window, sliding window, token bucket, and concurrency limiters.

Configure a fixed window rate limiter policy in Program.cs:

Code snippet

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRateLimiter(options =>
{
    options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: context.User.Identity?.Name ?? context.Request.Headers.Host.ToString(),
            factory: _ => new FixedWindowRateLimiterOptions
            {
                AutoReplenishment = true,
                PermitLimit = 5,             // Max 5 requests
                QueueLimit = 2,              // Queue up to 2 requests if limit is hit
                Window = TimeSpan.FromSeconds(10) // Per 10-second window
            }));

    // Custom response when rate limit is exceeded
    options.OnRejected = async (context, cancellationToken) =>
    {
        context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
        await context.HttpContext.Response.WriteAsync("Rate limit exceeded. Please try again later.", cancellationToken);
    };
});

Step 2: Configuring Output Caching

Output caching stores the output of HTTP requests and serves them from memory for subsequent identical requests, bypassing controller logic and database queries entirely.

Add output caching services:

C#

builder.Services.AddOutputCache(options =>
{
    // Define a default policy
    options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromSeconds(30)));
    
    // Define a custom named policy
    options.AddPolicy("ExpireByMinute", builder => builder.Expire(TimeSpan.FromMinutes(1)));
});

builder.Services.AddControllers();
var app = builder.Build();

Step 3: Applying Rate Limiting and Output Caching to Endpoints

Combine output caching and rate limiting policies on your controllers or minimal endpoints using standard attributes or fluent extension methods.

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OutputCaching;
using Microsoft.AspNetCore.RateLimiting;

[ApiController]
[Route("api/reports")]
public class ReportsController : ControllerBase
{
    // Apply Output Caching with a specific policy
    [HttpGet("summary")]
    [OutputCache(PolicyName = "ExpireByMinute")]
    public async Task<IActionResult> GetReportSummary()
    {
        // Simulate an expensive database operation
        await Task.Delay(1000); 
        
        return Ok(new { GeneratedAt = DateTime.UtcNow, Data = "Expensive enterprise analytics dataset." });
    }

    // Apply specific Rate Limiting policy or disable it for public routes
    [HttpGet("public-status")]
    [EnableRateLimiting("PublicPolicy")]
    public IActionResult GetStatus()
    {
        return Ok(new { Status = "System Online" });
    }
}

Step 4: Complete Pipeline Integration in Program.cs

Ensure the middleware components are executed in the correct sequence within your application startup flow:

C#

var app = builder.Build();

// 1. Enable Rate Limiting middleware
app.UseRateLimiter();

app.UseHttpsRedirection();
app.UseAuthorization();

// 2. Enable Output Caching middleware
app.UseOutputCache();

app.MapControllers();

app.Run();