.NET  

Caching in ASP.NET Core 10: In-Memory, Distributed, and Output Caching Explained

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:

  • Load-balanced APIs

  • Kubernetes deployments

  • Cloud-native applications

  • Microservices

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:

  1. A client requests product details.

  2. Output Cache checks for a cached HTTP response.

  3. If unavailable, the Product Service checks Redis.

  4. If Redis contains the data, it is returned immediately.

  5. Otherwise, the database is queried.

  6. The result is stored in Redis.

  7. The HTTP response is cached.

  8. Future requests are served without accessing the database.

This layered approach minimizes expensive operations while maintaining fast response times.

Choosing the Right Cache

FeatureIn-Memory CacheDistributed CacheOutput Cache
Shared Across ServersNoYesDepends
Stores ObjectsYesYesHTTP Responses
Best ForSingle-instance appsCloud deploymentsRead-heavy APIs
Database Load ReductionHighHighVery High
Setup ComplexityLowMediumLow

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:

  • Absolute expiration

  • Sliding expiration

  • Time-based refresh

  • Manual invalidation

  • Event-driven invalidation

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:

  1. Product information is cached.

  2. The product price changes.

  3. 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.