Performance is one of the most important characteristics of a modern web application. Users expect APIs and web applications to respond almost instantly, regardless of traffic volume. However, repeatedly querying databases, calling external APIs, or performing expensive calculations for every request can quickly become a bottleneck.
Caching is one of the simplest and most effective ways to improve application performance. By temporarily storing frequently requested data, applications can significantly reduce database load, decrease response times, and improve scalability.
ASP.NET Core 10 provides multiple caching mechanisms, each designed for different scenarios. Understanding when to use In-Memory Cache, Distributed Cache, and Output Cache is essential for building high-performance production applications.
In this article, you'll learn how each caching strategy works, where it should be used, and how to implement caching without introducing stale data or consistency issues.
Why Caching Matters
The Cost of Repeated Operations
Consider an API that retrieves product information.
Without caching, every request follows this path:
Client
│
▼
ASP.NET Core API
│
▼
Business Service
│
▼
SQL Database
If thousands of users request the same product every minute, the database executes the same query repeatedly.
With caching:
Client
│
▼
ASP.NET Core API
│
├────────► Cache
│ │
│ ▼
│ Cached Result
│
└────────► Database (Cache Miss)
Most requests are served directly from the cache, reducing database traffic and improving response times.
In-Memory Caching
What Is In-Memory Cache?
In-Memory Cache stores data inside the application's process memory.
Register the cache service:
builder.Services.AddMemoryCache();
Using the cache:
public class ProductService
{
private readonly IMemoryCache _cache;
public ProductService(IMemoryCache cache)
{
_cache = cache;
}
public async Task<Product?> GetProductAsync(int id)
{
return await _cache.GetOrCreateAsync(
$"product-{id}",
async entry =>
{
entry.AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(5);
return await LoadProductFromDatabase(id);
});
}
}
Why Use In-Memory Cache?
The first request retrieves data from the database.
Subsequent requests within five minutes are served directly from memory, reducing latency and unnecessary database queries.
This approach works well for single-server applications where cached data doesn't need to be shared across multiple instances.
Distributed Caching
What Is Distributed Cache?
Distributed Cache stores cached data in an external service such as Redis.
Unlike in-memory caching, multiple application instances share the same cached data.
Configuration:
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration =
builder.Configuration.GetConnectionString("Redis");
});
Using the distributed cache:
public class ProductCache
{
private readonly IDistributedCache _cache;
public ProductCache(IDistributedCache cache)
{
_cache = cache;
}
}
Why Use Distributed Cache?
Distributed caching ensures all application instances access the same cached information.
This is particularly valuable for:
Without a distributed cache, each application instance would maintain its own cache, leading to inconsistent data and lower cache hit rates.
Output Caching
What Is Output Caching?
Output Caching stores complete HTTP responses.
Enable Output Cache:
builder.Services.AddOutputCache();
Configure middleware:
app.UseOutputCache();
Apply caching to an endpoint:
app.MapGet("/products", GetProducts)
.CacheOutput();
Why Use Output Cache?
Instead of executing the endpoint for every request, ASP.NET Core returns the cached HTTP response.
This avoids executing:
Business logic
Database queries
Serialization
Response generation
Output caching is especially effective for frequently requested read-only endpoints.
End-to-End Implementation
Consider an e-commerce application.
Architecture:
Customer
│
▼
ASP.NET Core API
│
┌───┴─────────────┐
▼ ▼
Output Cache Product Service
│
Distributed Cache
│
▼
SQL Database
Workflow:
A client requests product details.
Output Cache checks for a cached HTTP response.
If unavailable, the Product Service checks Redis.
If Redis contains the data, it is returned immediately.
Otherwise, the database is queried.
The result is stored in Redis.
The HTTP response is cached.
Future requests are served without accessing the database.
This layered approach minimizes expensive operations while maintaining fast response times.
Choosing the Right Cache
| Feature | In-Memory Cache | Distributed Cache | Output Cache |
|---|
| Shared Across Servers | No | Yes | Depends |
| Stores Objects | Yes | Yes | HTTP Responses |
| Best For | Single-instance apps | Cloud deployments | Read-heavy APIs |
| Database Load Reduction | High | High | Very High |
| Setup Complexity | Low | Medium | Low |
Each caching strategy addresses a different performance requirement, and they can be combined within the same application.
Cache Expiration
Choosing an appropriate expiration policy is critical.
Common strategies include:
Frequently changing data should have shorter cache lifetimes, while relatively static data can remain cached longer.
Cache Invalidation
Caching introduces a new challenge: stale data.
For example:
Product information is cached.
The product price changes.
Users continue receiving outdated data.
Whenever business data changes, invalidate or refresh the corresponding cache entry to ensure users receive accurate information.
A caching strategy is only effective if cache invalidation is considered during application design.
Best Practices
Cache frequently requested data.
Cache expensive database queries.
Choose meaningful cache keys.
Set appropriate expiration policies.
Monitor cache hit rates.
Invalidate cache after updates.
Avoid caching sensitive information.
Use distributed caching in multi-server deployments.
Combine output caching with data caching when appropriate.
Common Mistakes
One common mistake is caching everything indiscriminately. Highly dynamic data may become stale quickly, reducing the usefulness of caching.
Another issue is forgetting to invalidate cached entries after updates, causing users to receive outdated information.
Developers also sometimes use in-memory caching in load-balanced environments, leading to inconsistent responses because each server maintains its own cache.
Testing and Validation
Before deploying caching to production, verify:
Cache hits and misses
Expiration behavior
Cache invalidation
Distributed cache availability
Output cache correctness
Performance under load
Multi-instance consistency
Recovery after cache failures
Testing should confirm that the application continues functioning correctly even if the cache becomes temporarily unavailable.
Performance Considerations
Caching can significantly improve performance, but improper usage may waste memory or reduce cache effectiveness.
Consider these recommendations:
Cache only expensive operations.
Monitor memory consumption.
Use asynchronous cache operations.
Measure cache hit ratios.
Avoid storing excessively large objects.
Profile database performance before and after introducing caching.
Performance improvements should always be validated using production-like workloads rather than assumptions.
Security Considerations
Cached data must be protected just like database data.
Follow these recommendations:
Never cache sensitive user information unnecessarily.
Encrypt external cache communication.
Restrict access to Redis or other cache servers.
Avoid caching authenticated responses unless properly segmented.
Apply expiration policies consistently.
Monitor cache access for unusual activity.
Security remains an important consideration even when data is stored temporarily.
Troubleshooting
Cache Is Never Used
Verify that cache keys remain consistent across requests and that entries are being stored successfully.
Users Receive Outdated Data
Review cache invalidation logic and ensure cached entries are refreshed after updates.
Poor Performance Despite Caching
Measure cache hit rates. Low hit rates often indicate ineffective cache keys or expiration policies that are too short.
Distributed Cache Connection Issues
Confirm the external cache server is available and that connection settings are configured correctly.
Conclusion
Caching is a fundamental performance optimization technique for ASP.NET Core 10 applications. In-Memory Cache offers a simple solution for single-instance applications, Distributed Cache enables consistent performance across multiple servers, and Output Cache dramatically reduces processing for read-heavy endpoints. By selecting the appropriate caching strategy, implementing proper expiration and invalidation policies, and continuously monitoring cache effectiveness, developers can build applications that are faster, more scalable, and better prepared for production workloads.