In production environments, APIs face constant threats from malicious actors, web scrapers, and accidental traffic floods that can overwhelm database connections and exhaust server resources. Unprotected endpoints are vulnerable to denial-of-service (DoS) attacks, brute-force login attempts, and redundant database round-trips.

Securing your infrastructure requires a two-pronged defense strategy: Rate Limiting to control traffic velocity and Distributed Caching to absorb heavy read redundancy.

This article walks through a complete, production-grade implementation of built-in rate limiting and Redis distributed caching in an ASP.NET Core Web API.

1. Architectural Strategy: Rate Limiting vs. Caching

  • Rate Limiting (Throttling): Restricts the number of requests a client can make within a specified time window. It protects your application backend from volumetric abuse and ensures fair usage across clients.

  • Distributed Caching (Redis): Offloads repetitive read requests by storing frequently accessed data in a fast, in-memory distributed store, eliminating unnecessary database queries.

2. Step 1: Implementing Built-In Rate Limiting (.NET 7+)

ASP.NET Core includes a robust, native rate-limiting middleware that supports four primary algorithms:

  1. Fixed Window: Limits requests to a fixed time block (e.g., 100 requests per minute).

  2. Sliding Window: Divides the window into segments, smoothing out traffic spikes at window boundaries.

  3. Token Bucket: Allows short bursts of traffic while enforcing a strict long-term average refill rate.

  4. Concurrency: Limits the total number of simultaneous requests executing concurrently.

Configuring Rate Limit Policies in Program.cs

C#

// WebApi/Program.cs
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

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

// Configure Rate Limiting Policies
builder.Services.AddRateLimiter(options =>
{
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

    // Fixed window policy for standard public endpoints
    options.AddPolicy("FixedWindowPolicy", httpContext =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: httpContext.User.Identity?.Name ?? httpContext.Connection.RemoteIpAddress?.ToString() ?? "anonymous",
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 10,
                Window = TimeSpan.FromSeconds(10),
                QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
                QueueLimit = 2
            }));

    // Strict concurrency policy for sensitive endpoints (e.g., Checkout / Payment)
    options.AddConcurrencyLimiter("ConcurrencyPolicy", opt =>
    {
        opt.PermitLimit = 5;
        opt.QueueLimit = 2;
    });
});

var app = builder.Build();

// ... configure middleware pipeline
app.UseHttpsRedirection();

// Must be placed after routing and before authorization
app.UseRateLimiter();

app.UseAuthorization();
app.MapControllers();

app.Run();

3. Step 2: Applying Rate Limits to Controllers

Use the [EnableRateLimiting] attribute to secure specific controllers or action methods:

C#

// WebApi/Controllers/ProductsController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;

namespace WebApi.Controllers;

[ApiController]
[Route("api/[controller]")]
[EnableRateLimiting("FixedWindowPolicy")] // Applies fixed window limit to entire controller
public class ProductsController : ControllerBase
{
    [HttpGet]
    public IActionResult GetAll()
    {
        return Ok(new[] { "Product 1", "Product 2" });
    }

    [HttpPost("checkout")]
    [EnableRateLimiting("ConcurrencyPolicy")] // Overrides with strict concurrency limit
    public IActionResult Checkout()
    {
        return Ok(new { message = "Checkout processed successfully." });
    }
}

4. Step 3: Implementing Distributed Caching with Redis

To prevent database saturation during high-read traffic spikes, integrate Redis using ASP.NET Core’s IDistributedCache interface and the cache-aside pattern.

First, install the StackExchange Redis caching package:

Shell

dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis

Register Redis in Program.cs:

C#

// WebApi/Program.cs snippet
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("RedisConnection");
    options.InstanceName = "ApiCache_";
});

Implementing the Cache-Aside Pattern in a Service

C#

// Application/Services/CachedProductService.cs
using System.Text.Json;
using Domain.Entities;
using Microsoft.Extensions.Caching.Distributed;

namespace Application.Services;

public class CachedProductService
{
    private readonly IProductRepository _productRepository;
    private readonly IDistributedCache _cache;

    public CachedProductService(IProductRepository productRepository, IDistributedCache cache)
    {
        _productRepository = productRepository;
        _cache = cache;
    }

    public async Task<Product?> GetByIdAsync(int id, CancellationToken cancellationToken)
    {
        string cacheKey = $"product-{id}";

        // 1. Check cache first
        var cachedData = await _cache.GetStringAsync(cacheKey, cancellationToken);
        if (!string.IsNullOrEmpty(cachedData))
        {
            return JsonSerializer.Deserialize<Product>(cachedData);
        }

        // 2. Fall back to database if cache miss occurs
        var product = await _productRepository.GetByIdAsync(id, cancellationToken);
        if (product == null) return null;

        // 3. Store in Redis cache with an expiration time
        var cacheOptions = new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
        };

        await _cache.SetStringAsync(
            cacheKey, 
            JsonSerializer.Serialize(product), 
            cacheOptions, 
            cancellationToken);

        return product;
    }
}

Conclusion

By combining built-in rate-limiting middleware with distributed Redis caching, your ASP.NET Core Web API defends itself against brute-force abuse and traffic spikes while maintaining sub-millisecond response times for read-heavy operations.