Caching in .NET: Getting Started with In-Memory Cache (Cache Series Part 1)
One of the fundamental goals of modern software development is to make applications faster and more efficient. Users don't like to wait, and thus, reducing application response times and optimizing server resources are of vital importance. Frequently repeated operations like database queries, API calls, or intensive computations can create bottlenecks in your application's performance. This is where caching, a powerful technique, comes to our rescue!
In this first part of our Caching in .NET series, we will cover the basic principles of caching and specifically focus on in-memory caching, which is ideal for small to medium-sized applications. We'll explain what in-memory cache is, why it's necessary, its benefits, common use cases, and provide a simple example of how you can easily use it in your ASP.NET Core applications.
What is Caching and Why is it Necessary?
A cache is a memory area where data is temporarily stored for faster access. When data is requested for the first time, it's served to the user and also stored in the cache. The next time the same data is requested, it's served directly from the cache—much faster—instead of being fetched again from the source database or an external service.
Caching offers many advantages that directly impact an application's performance and overall user experience:
Performance Improvement: Data access times are significantly reduced. Accessing data in memory is many times faster than making calls to disk or over a network.
Reduced Resource Consumption: By decreasing the number of expensive calls to databases or external APIs, you lower the load on these resources. This can reduce server costs and allow the system to handle more concurrent requests.
User Experience: The application responds more quickly to user requests, providing a smoother and more satisfying experience.
Reduced Network Traffic: For repetitive calls, especially to remote services, caching reduces unnecessary network traffic.
What is In-Memory Cache?
In-memory cache is a type of cache that stores data in the application's own RAM (memory). Each application instance (e.g., each process on a web server) manages its own in-memory cache. This makes it a fast and easy-to-use option for single-server applications or scenarios where it's acceptable for each instance of the application to have its own cached data.
Benefits of in-memory cache:
Very Fast Access: Data access speed is incredibly high because the data resides within the same process memory.
Easy Setup and Use: It doesn't require an additional server or infrastructure; it's configured and used directly within your application code.
Low Latency: There is no network latency involved.
Limitations of in-memory cache:
Instance-Bound: Each application instance has its own cache. If the application runs on multiple servers, different versions of the same data might exist on different servers (data inconsistency).
Transient Storage: Data in the cache is lost when the application restarts or crashes.
Memory Limits: It's limited by the server's physical memory. Caching very large datasets can lead to out-of-memory issues.
Common In-Memory Cache Use Cases
In-memory cache is widely used, especially in the following scenarios:
Small to Medium-Sized Applications: Single-server applications that don't require a distributed architecture or where data inconsistency isn't critical.
Frequently Accessed Static Data: Rarely changing data like application settings, fixed configuration values, or country lists.
Short-Term Caching: Data that needs to be cached for a very short duration, is performance-critical, but changes very rapidly.
Per-Request Cache: Data that is valid only for the duration of a single HTTP request and will then be discarded (though request-scoped services are often more suitable for this).
Using In-Memory Caching with ASP.NET Core
ASP.NET Core provides a built-in in-memory caching mechanism by default through the IMemoryCache
interface and the Microsoft.Extensions.Caching.Memory
package.
Here's a simple example of how to use in-memory cache in a basic ASP.NET Core application:
1. Service Configuration (Program.cs
):
You just need to add the in-memory cache service to your application.
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Caching.Memory; // Required using
var builder = WebApplication.CreateBuilder(args);
// Add in-memory cache service
builder.Services.AddMemoryCache();
// Other services
builder.Services.AddControllersWithViews();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllers(); // Map controllers
app.MapFallbackToFile("index.html"); // For SPA applications
app.Run();
2. Cache Usage (Example Web API Controller):
You can perform cache operations by injecting the IMemoryCache
interface into a controller.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MyWebApp.Controllers
{
// Sample data model
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IMemoryCache _cache;
// We receive IMemoryCache via constructor injection
public ProductsController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
[HttpGet("get-products")]
public async Task<IActionResult> GetProducts()
{
string cacheKey = "ProductList";
List<Product> products;
// Check if the product list exists in the cache
if (!_cache.TryGetValue(cacheKey, out products))
{
// If not in cache, fetch from a database or external source
// This part would normally be an expensive database call or API request.
// Simulating with a 3-second delay.
await Task.Delay(3000);
products = new List<Product>
{
new Product { Id = 1, Name = "Laptop", Price = 1200, Category = "Electronics" },
new Product { Id = 2, Name = "Mouse", Price = 25, Category = "Peripherals" },
new Product { Id = 3, Name = "Keyboard", Price = 75, Category = "Peripherals" },
new Product { Id = 4, Name = "Monitor", Price = 300, Category = "Electronics" }
};
// Add data to the cache
// SetAbsoluteExpiration: Item is removed from cache after a fixed duration from its addition.
// SetSlidingExpiration: Item is removed from cache if not accessed again within a certain period from its last access.
_cache.Set(cacheKey, products, new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(5)) // Remove definitively after 5 minutes
.SetSlidingExpiration(TimeSpan.FromMinutes(2))); // Remove if not accessed within 2 minutes
return Ok(new { Source = "Database/API", Data = products, FetchedAt = DateTime.Now });
}
else
{
// If in cache, read directly from cache
return Ok(new { Source = "In-Memory Cache", Data = products, FetchedAt = DateTime.Now });
}
}
[HttpGet("clear-products-cache")]
public IActionResult ClearProductsCache()
{
string cacheKey = "ProductList";
_cache.Remove(cacheKey); // Remove a specific key from the cache
return Ok("ProductList cache cleared.");
}
}
}
In this example:
We enable the in-memory cache service with
AddMemoryCache()
.We inject
IMemoryCache
into the controller.We check if the data exists in the cache using the
TryGetValue
method.If the data is not present, we fetch it from a simulated source (with a 3-second delay) and add it to the cache using the
Set
method. Here, we use bothAbsoluteExpiration
andSlidingExpiration
to manage the cache item's lifetime.If the data is in the cache, we return it directly from the cache.
We also demonstrate how to clear a specific cache key using the
Remove
method.
Conclusion
In-memory caching is a great starting point for providing a quick and easy performance boost to your .NET applications. It's ideal for single-server applications and scenarios with non-critical data consistency requirements. However, as your application scales or data consistency becomes more critical, you'll need to consider transitioning to distributed caching solutions (like Redis or Couchbase).
This blog post was the first step in our Caching in .NET series. In subsequent parts of the series, we will delve deeper into distributed caching solutions and advanced caching strategies. Join us on your journey to optimize your application's performance!