ASP.NET Core Output Caching: How to Make Web APIs Faster in .NET
ASP.NET Core Output Caching: How to Make Web APIs Faster in .NET When an API receives the same request repeatedly, performing the same database query and rebuilding the same response every time can waste valuable resources. For example, imagine this endpoint: GET /api/products If thousands of users request the same product catalog, your application might repeatedly: HTTP Request โ Controller โ Database Query โ Business Logic โ JSON Response For data that doesn't change frequently, this can create unnecessary database load. ASP.NET Core provides Output Caching to help solve this problem. Instead of executing the complete request pipeline every time, the application can temporarily store the generated response and reuse it for subsequent requests. In this tutorial, we'll look at how Output Caching works, how to configure it, how to invalidate cached responses, and when you should avoid using it. What Is Output Caching? Output caching stores the generated response from an endpoint. For example: First request โ GET /api/products โ Execute controller โ Query database โ Generate response โ Store response in cache Later: Second request โ GET /api/products โ Cached response โ Return immediately The database doesn't need to be queried again while the cached response is valid. Output Caching vs Response Caching These two concepts are often confused. Response Caching Response caching mainly relies on HTTP caching semantics and headers. Output Caching Output caching is controlled by ASP.NET Core and allows your application to decide which responses should be cached and for how long. Output caching provides more control over server-side response caching. 1. Add Output Caching Start by registering the output-cache services. var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddOutputCache(); var app = builder.Build(); app.UseOutputCache(); app.MapControllers(); app.Run(); The important pieces are: AddOutputCache() โ Configure caching โ UseOutputCache() โ Apply caching policies 2. Cache an API Endpoint You can apply output caching to an endpoint using the OutputCache attribute. For example: [ApiController] [Route("api/products")] public class ProductsController : ControllerBase { [HttpGet] [OutputCache(Duration = 60)] public IActionResult GetProducts() { return Ok(new[] { new { Id = 1, Name = "Laptop" }, new { Id = 2, Name = "Keyboard" }, new { Id = 3, Name = "Mouse" } }); } } The response can now be cached for 60 seconds. During that period, subsequent requests can receive the cached response instead of executing the controller again. 3. Why This Can Improve Performance Without caching: Request 1 โ Database Request 2 โ Database Request 3 โ Database Request 4 โ Database Request 5 โ Database With output caching: Request 1 โ Database โ Cache Request 2 โ Cache Request 3 โ Cache Request 4 โ Cache Request 5 โ Cache This can significantly reduce repeated work for suitable endpoints. The biggest benefit is often reduced load on downstream dependencies such as databases and external APIs. 4. Configure a Default Cache Policy Instead of adding attributes to every endpoint, you can define policies. For example: builder.Services.AddOutputCache(options => { options.AddPolicy("ProductsPolicy", policy => { policy.Expire(TimeSpan.FromMinutes(5)); }); }); Then apply the policy: [OutputCache(PolicyName = "ProductsPolicy")] [HttpGet] public IActionResult GetProducts() { return Ok(products); } This makes caching rules easier to manage as your application grows. 5. Cache by Query String Consider an endpoint: GET /api/products?category=laptop and: GET /api/products?category=mobile These requests should not necessarily receive the same cached response. You can configure caching based on query-string values. For example: builder.Services.AddOutputCache(options => { options.AddPolicy("ProductsPolicy", policy => { policy .Expire(TimeSpan.FromMinutes(5)) .SetVaryByQuery("category"); }); }); Now the cache can maintain separate responses for different category values. Conceptually: /products?category=laptop โ Cache A /products?category=mobile โ Cache B 6. Cache by Header Sometimes the response depends on a request header. For example: Accept-Language If your API returns localized content, caching should take the language into account. A policy can vary based on request headers. options.AddPolicy("LocalizedPolicy", policy => { policy .Expire(TimeSpan.FromMinutes(5)) .SetVaryByHeader("Accept-Language"); }); Now English and Hindi responses, for example, can be cached separately. 7. Be Careful With User-Specific Data This is one of the most important Output Caching considerations. Suppose you have: GET /api/profile and the response is: { "name": "User A", "email": "u****@example.com" } If you cache this incorrectly, another user could potentially receive the wrong cached response. That's a serious security problem. Be very careful when caching endpoints containing: - User profiles - Account information - Payments - Orders - Private documents - Authorization-specific information - Personalized dashboards Caching should never accidentally share private data between users. 8. Public vs Personalized Endpoints A useful rule is: Public data โ Usually good candidate for caching User-specific data โ Requires careful cache variation For example: GET /api/products might be an excellent caching candidate. But: GET /api/my-orders requires much more careful consideration. Before enabling caching, ask: Can two different users safely receive the same response? If the answer is no, don't use a simple shared output-cache policy. 9. Cache Expiration The cache duration should depend on how frequently the data changes. For example: Product categories โ 30 minutes Product catalog โ 5 minutes Frequently changing stock โ Very short duration Real-time account balance โ Usually don't cache this way There is no universal cache duration. The correct value depends on your application's consistency requirements. 10. Cache Invalidation Caching introduces an important question: What happens when the underlying data changes? Imagine: Product price = โน50,000 The response is cached. Then the database changes: Product price = โน45,000 If the cache is still valid, users might continue seeing: โน50,000 until the cache expires or is invalidated. This is why cache invalidation is one of the most important parts of a caching strategy. 11. Evict Cached Responses When application data changes, you may need to remove related cached responses. For example, after updating a product: Update product โ Save database changes โ Evict relevant cached response This ensures the next request generates fresh data. ASP.NET Core provides output-cache APIs that can be used to control cache eviction. The exact strategy depends on how your endpoints and policies are structured. 12. Cache Only What Actually Benefits From Caching Caching everything is not a good strategy. Consider: GET /api/products If this endpoint takes 500 ms because of expensive database work, caching may provide a large benefit. But if another endpoint takes: 2 ms caching it may add complexity without meaningful performance improvement. A good caching strategy focuses on endpoints that are: - Frequently requested - Relatively expensive - Safe to cache - Not changing constantly 13. Output Caching and Database Performance Consider an API receiving: 10,000 requests/minute If every request performs the same database query, the database receives: 10,000 queries/minute If the response can safely be cached for one minute, the application might perform dramatically fewer database queries. Conceptually: 10,000 API requests โ Output Cache โ Small number of DB queries This can reduce: - Database CPU - Network traffic - Query execution - Application CPU - API response latency 14. Output Caching and External APIs Caching isn't only useful for databases. Suppose your API calls an external service: Your API โ External API โ Response If the same external data is requested repeatedly, output caching can reduce unnecessary calls. This can be particularly useful when an external API has: - Rate limits - Usage costs - Slow response times - Network latency However, make sure cached data is still acceptable for your business requirements. 15. Don't Cache Sensitive Responses Avoid blindly caching responses containing sensitive information. Examples include: Payment information Authentication responses Personal information Security tokens Private account data Caching mistakes can become security vulnerabilities. Before caching an endpoint, understand exactly what data the response contains. 16. Output Caching in a Multi-Instance Application Consider an application running with multiple instances: Load Balancer โ โโโโโโโผโโโโโโ โ โ โ API1 API2 API3 Now caching becomes a distributed-system consideration. If each instance maintains its own cache, you could have: API1 โ Cache A API2 โ Cache B API3 โ Cache C The cached data may not be identical between instances. For larger systems, you should understand how your deployment architecture handles cache storage and consistency. 17. Cache-Control Is Not the Same Thing Don't confuse server-side Output Caching with browser caching. Browser caching is controlled through HTTP caching headers and client behavior. Output caching is an application-side mechanism for reusing generated responses. A complete caching architecture can involve multiple layers: Browser โ CDN โ Reverse Proxy โ ASP.NET Core Output Cache โ Application โ Database Each layer has different responsibilities. 18. Measure Before and After Don't assume caching automatically makes everything faster. Measure your application. Look at: Response time Database CPU Database query count API throughput Application CPU Memory usage For example: Before caching Average response: 450 ms After caching Average response: 35 ms The actual improvement depends on your workload. 19. Common Output Caching Mistakes Mistake 1: Caching User-Sp
Comments
No comments yet. Start the discussion.