How to Implement In-Memory Caching in ASP.NET Core Web API

How to Implement In-Memory Caching in ASP.NET Core Web API

In this article, I will discuss how to implement in-memory Caching in ASP.NET Core Web API applications with examples. Please read our previous articles discussing Caching in ASP.NET Core Web API with Examples. At the end of this article, you will understand the following pointers in detail:

  1. What is In-Memory Caching in ASP.NET Core Web API?
  2. Example to Understand In-Memory Caching in ASP.NET Core Web API
  3. How Does In-Memory Caching Work in ASP.NET Core?
  4. Using In-Memory Caching in ASP.NET Core Web API
  5. What is Cache Eviction, and How does it work in ASP.NET Core?
  6. In-Memory Cache Priority in ASP.NET Core Web API
  7. What is Memory Pressure, and How Does it Affect In-Memory Caching?
  8. Can We Use In-Memory Caching in a Web Farm Environment?
  9. How Do We Delete or Remove All Cache Keys in ASP.NET Core In-Memory Caching?
  10. What are the Benefits and Drawbacks of Using In-Memory Caching?
What is In-Memory Caching in ASP.NET Core Web API?

In-memory Caching in ASP.NET Core Web API is a technique used to store data temporarily in the Web Server’s Main Memory (RAM) for faster retrieval. This can significantly improve an application’s performance by reducing the need to repeatedly fetch data from a slower data source like a database or an external API. Data stored in the cache is typically accessed through keys, and the caching system ensures that frequently accessed data is quickly available.

Example to Understand In-Memory Caching in ASP.NET Core Web API

Implementing In-Memory Cache in an ASP.NET Core Web API Applications can significantly improve its performance, especially when dealing with relatively static data such as country, state, and city master data from a database. Let’s proceed to see how to implement In-Memory Caching in an ASP.NET Core Web API Application.

Set Up Your ASP.NET Core Web API Project

Open Visual Studio and then choose Create a new project, then, select “ASP.NET Core Web API” Project template. Configure your project settings (name such as InMemoryCachingDemo, location, etc.). Ensure “Configure for HTTPS” is checked. Click on the “Create” button, which should create the Project with the following structure.

Example to Understand In-Memory Caching in ASP.NET Core Web API

Add Necessary Packages:

Next, we need to add the Packages as we are going to interact with the SQL Server database using Entity Framework Core. You can install these packages using the following command in the Package Manager Console.

Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools

Define the Database Model and DbContext

In our example, we need to fetch the Country, State, and City master data, so we need three models. First, create a new folder named Models. Inside the Models folder, create three classes: Country, State, and City.

Country.cs
namespace InMemoryCachingDemo.Models
{
    public class Country
    {
        public int CountryId { get; set; }
        public string Name { get; set; }
        public List<State> States { get; set; }
    }
}
State.cs
namespace InMemoryCachingDemo.Models
{
    public class State
    {
        public int StateId { get; set; }
        public string Name { get; set; }
        public int CountryId { get; set; }
        public Country Country { get; set; }
        public List<City> Cities { get; set; }
    }
}
City.cs
namespace InMemoryCachingDemo.Models
{
    public class City
    {
        public int CityId { get; set; }
        public string Name { get; set; }
        public int StateId { get; set; }
        public State State { get; set; }
    }
}
Create DbContext:

Next, we need to create the DbContent class. First, create a new folder named Data. Inside the Data folder, create a class ApplicationDbContext.cs and then copy and paste the following code.

using InMemoryCachingDemo.Models;
using Microsoft.EntityFrameworkCore;

namespace InMemoryCachingDemo.Data
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) 
        { }

        public DbSet<Country> Countries { get; set; }
        public DbSet<State> States { get; set; }
        public DbSet<City> Cities { get; set; }
    }
}
Configure Connection String:

Next, we need to add the SQL Server connection string in appsettings.json file. So, modify the appsettings.json file as follows. Here, the EF Core will create the database named LocationDB if it has not already been created in the SQL server.

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=LocationDB;Trusted_Connection=True;TrustServerCertificate=True;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
Configure the DbContext in the Program.cs:

Please add the following line to the Program.cs class file to configure DbContext.

// Configure DbContext with SQL Server
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

Here,

  • AddDbContext<ApplicationDbContext>: This method registers your DbContext with the dependency injection (DI) container and configures it to use SQL Server with the connection string specified in your appsettings.json file.
  • builder.Configuration.GetConnectionString(“DefaultConnection”): This retrieves the connection string named “DefaultConnection” from the configuration, which typically loads from appsettings.json.

Note: Set the InvariantGlobalization value to False in the Project Properties file.

Create Database Migration:

Open the Package Manager Console and execute the Add-Migration command to create the Migration. Then, execute the Update-Database to apply the migration to the database as follows. The Add-Migration name should be unique.

Create Database Migration

Once you execute the above command, it should the LocationDB database with the required database tables as shown in the below image:

Insert Dummy Data

Insert Dummy Data:

You can insert dummy data directly into your database using SQL Server Management Studio or through seeding in EF Core. Please execute the following SQL Script in SSMS to insert some dummy data into the Countries, States, and Cities database table.

-- Inserting data into the Countries table
INSERT INTO Countries (Name) VALUES
('India'),
('United States'),
('Canada'),
('United Kingdom');

-- Inserting data into the States table
INSERT INTO States (Name, CountryId) VALUES
('California', 2),
('Texas', 3),
('British Columbia', 3),
('Ontario', 3),
('England', 4),
('Maharashtra', 1),
('Delhi', 1);

-- Inserting data into the Cities table
INSERT INTO Cities (Name, StateId) VALUES
('Los Angeles', 1),
('San Francisco', 1),
('Houston', 2),
('Dallas', 2),
('Vancouver', 3),
('Toronto', 4),
('London', 5),
('Mumbai', 6),
('Pune', 6);
Configure In-Memory Caching Services

Add the in-memory caching service to your Program.cs file. This registers the caching services with the dependency injection container. Please add the following AddMemoryCache service, which will add the in-memory caching to your ASP.NET Core Application.

builder.Services.AddMemoryCache(); // Adding memory cache

builder.Services.AddMemoryCache(): This statement is used to configure and add in-memory caching services to the application’s service container. This enables the application to store objects in memory. The AddMemoryCache() is an extension method. It is used to add the necessary services for memory caching into the Dependency Injection (DI) container. 

How Does In-Memory Caching Work in ASP.NET Core?

In-Memory Caching in ASP.NET Core is implemented using the IMemoryCache interface, which provides a way to store key-value pairs in the Main Memory of the web server. When we use in-memory caching, data is stored directly on the server’s physical memory (RAM), making it very fast to retrieve. To use IMemoryCache, we need to inject it into our service, repository, or controller via dependency injection. Once injected, we can set items in the memory cache using the Set method with an expiration policy. The cache can check if an item exists before processing or retrieving it. The IMemoryCache provides methods to store, retrieve, and manage cached items in the server’s memory.

Using In-Memory Caching in ASP.NET Core Web API:

Using In-Memory Caching in ASP.NET Core Web API involves:

  • Adding the Microsoft.Extensions.Caching.Memory namespace.
  • Injecting IMemoryCache into the constructor of your controllers or services where you want to set and get the data from the Cache.
  • Using the Get, Set, and Remove methods of IMemoryCache to manage cached data.
Create a Repository or Service:

We need to implement caching when fetching countries, states, and cities static data. First, create a folder named Repository. Inside it, create a class file named LocationRepository.cs and copy and paste the following code.

The following repository class uses an Entity Framework ApplicationDbContext and ASP.NET Core IMemoryCache to cache and retrieve data for countries, states, and cities. It shows how combining database access and caching enhances performance by reducing frequent database queries.

using InMemoryCachingDemo.Data;
using InMemoryCachingDemo.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;

namespace InMemoryCachingDemo.Repository
{
    public class LocationRepository
    {
        private readonly ApplicationDbContext _context;
        private readonly IMemoryCache _cache;
        private readonly TimeSpan _cacheExpiration = TimeSpan.FromMinutes(30);

        public LocationRepository(ApplicationDbContext context, IMemoryCache cache)
        {
            _context = context;
            _cache = cache;
        }

        public async Task<List<Country>> GetCountriesAsync()
        {
            var cacheKey = "Countries";
            if (!_cache.TryGetValue(cacheKey, out List<Country>? countries))
            {
                countries = await _context.Countries.ToListAsync();
                _cache.Set(cacheKey, countries, _cacheExpiration);
            }
            return countries ?? new List<Country>();
        }

        public async Task<List<State>> GetStatesAsync(int countryId)
        {
            string cacheKey = $"States_{countryId}";

            if (!_cache.TryGetValue(cacheKey, out List<State>? states))
            {
                states = await _context.States.Where(s => s.CountryId == countryId).ToListAsync();
                _cache.Set(cacheKey, states, _cacheExpiration);
            }

            return states ?? new List<State>();
        }

        public async Task<List<City>> GetCitiesAsync(int stateId)
        {
            string cacheKey = $"Cities_{stateId}";

            if (!_cache.TryGetValue(cacheKey, out List<City>? cities))
            {
                cities = await _context.Cities.Where(c => c.StateId == stateId).ToListAsync();
                _cache.Set(cacheKey, cities, _cacheExpiration);
            }
            return cities ?? new List<City>();
        }
    }
}
Code Explanation:

Here,

  • _context: An instance of ApplicationDbContext, which is used to interact with the database.
  • _cache: An instance of IMemoryCache, which is used to cache data to improve performance.
  • _cacheExpiration: A TimeSpan set to 30 minutes, defining how long items should remain in the cache before they expire.
  • Constructor: It initializes the class with instances of ApplicationDbContext and IMemoryCache, which are going to be injected by ASP.NET Core’s dependency injection system.
GetCountriesAsync Method:
  • Cache Key: It uses “Countries” as the cache key.
  • Caching Strategy: It first attempts to retrieve the list of countries from the cache. If the countries are not found in the cache, it queries the database for them, stores them in the cache with a set expiration, and then returns the data.
GetStatesAsync Method:
  • Cache Key: Construct a cache key using the country ID, such as “States_1” for country ID 1.
  • Caching Strategy: Similar to the GetCountriesAsync method, it checks the cache for the states associated with a given country. If the cache misses, it retrieves the states from the database, caches the result, and returns the data.
GetCitiesAsync Method:
  • Cache Key: Uses the state ID to form a cache key, like “Cities_1” for state ID 1.
  • Caching Strategy: It follows the same pattern as the other methods. If cities for a specific state aren’t found in the cache, it fetches them from the database, caches the data, and returns it.
Register the Location Repository:

Please add the following code to the Program.cs class file to register the LocationRepository into the dependency injection container so that we can inject the LocationRepository instance into the Controller and use the same.

builder.Services.AddScoped<LocationRepository>();

Integration in the Controller

Create a controller for handling requests. So, create an Empty API Controller named LocationController within the Controllers folder and copy and paste the following code. Here, you can see we have created three action methods to access the Countries, States by country ID,  and Cities by State ID. The action method uses the Location Repository to fetch the data.

using InMemoryCachingDemo.Repository;
using Microsoft.AspNetCore.Mvc;

namespace InMemoryCachingDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class LocationController : ControllerBase
    {
        private readonly LocationRepository _repository;

        public LocationController(LocationRepository repository)
        {
            _repository = repository;
        }

        [HttpGet("countries")]
        public async Task<IActionResult> GetCountries()
        {
            var countries = await _repository.GetCountriesAsync();
            return Ok(countries);
        }

        [HttpGet("states/{countryId}")]
        public async Task<IActionResult> GetStates(int countryId)
        {
            var states = await _repository.GetStatesAsync(countryId);
            return Ok(states);
        }

        [HttpGet("cities/{stateId}")]
        public async Task<IActionResult> GetCities(int stateId)
        {
            var cities = await _repository.GetCitiesAsync(stateId);
            return Ok(cities);
        }
    }
}

These controller actions provide endpoints to access the countries, states, and cities based on the country and state IDs, respectively. They make the calls to the repository methods and return the results as HTTP responses.

Test Your API:

Use tools like Postman or Swagger to test your API endpoints, and they should work as expected.

What is Cache Eviction, and How does it work in ASP.NET Core?

Cache Eviction refers to the process of managing the lifecycle of cached data, ensuring that the data remains fresh and consistent with the underlying data source, such as a database. In our example, it caches country, state, and city data. Implementing cache eviction allows us to update or invalidate cached data when the underlying data changes. ASP.NET Core supports several cache eviction or expiration settings supported by IMemoryCache:

  • Manual Eviction: Explicitly remove items from the cache when you know the data has changed.
  • Sliding Expiration: The cache entry’s expiration time is reset every time it is accessed, ensuring that it only expires if it has not been accessed for a defined span of time. This approach is useful for data that should persist in the cache as long as it is frequently used.
  • Absolute Expiration: The cache entry will expire and be removed from the cache at a specific time. Whether the entry is accessed or not, it will expire after the set time.

Let’s implement the different cache eviction strategies: Manual Eviction for Countries, Sliding Expiration for States, and Absolute Expiration for cities within our LocationRepository. So, modify the LocationRepository as follows to Implement Different Expiration Strategies.

using InMemoryCachingDemo.Data;
using InMemoryCachingDemo.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;

namespace InMemoryCachingDemo.Repository
{
    public class LocationRepository
    {
        private readonly ApplicationDbContext _context;
        private readonly IMemoryCache _cache;
      
        public LocationRepository(ApplicationDbContext context, IMemoryCache cache)
        {
            _context = context;
            _cache = cache;
        }

        //Using Manual Eviction for countries
        public async Task<List<Country>> GetCountriesAsync()
        {
            var cacheKey = "Countries";
            if (!_cache.TryGetValue(cacheKey, out List<Country>? countries))
            {
                countries = await _context.Countries.ToListAsync();
                _cache.Set(cacheKey, countries);  // No Expiration Set
            }
            return countries ?? new List<Country>();
        }

        // This Methid can be called after updating or deleting country data.
        public void RemoveCountriesFromCache()
        {
            var cacheKey = "Countries";
            _cache.Remove(cacheKey);
        }

        public async Task UpdateCountry(Country updatedCountry)
        {
            _context.Countries.Update(updatedCountry);
            await _context.SaveChangesAsync();
            RemoveCountriesFromCache();
        }

        // Using Sliding Expiration for states
        public async Task<List<State>> GetStatesAsync(int countryId)
        {
            string cacheKey = $"States_{countryId}";
            if (!_cache.TryGetValue(cacheKey, out List<State>? states))
            {
                states = await _context.States.Where(s => s.CountryId == countryId).ToListAsync();
                var cacheEntryOptions = new MemoryCacheEntryOptions()
                    .SetSlidingExpiration(TimeSpan.FromMinutes(30)); // Sliding Expiration
                _cache.Set(cacheKey, states, cacheEntryOptions);
            }
            return states ?? new List<State>();
        }

        // Using Absolute Expiration for Cities
        public async Task<List<City>> GetCitiesAsync(int stateId)
        {
            string cacheKey = $"Cities_{stateId}";
            if (!_cache.TryGetValue(cacheKey, out List<City>? cities))
            {
                cities = await _context.Cities.Where(c => c.StateId == stateId).ToListAsync();
                var cacheEntryOptions = new MemoryCacheEntryOptions()
                    .SetAbsoluteExpiration(TimeSpan.FromMinutes(30)); // Absolute Expiration
                _cache.Set(cacheKey, cities, cacheEntryOptions);
            }
            return cities ?? new List<City>();
        }
    }
}
Explanation:
  • Manual Eviction for Countries: This is implemented by allowing the cache for countries to be set without expiration. To support Manual Eviction, we need to explicitly remove the entry from the cache when needed, such as after an update or delete operation. Countries use manual eviction. This means the cache for countries is cleared when an update occurs, ensuring the cache contains up-to-date data immediately after any changes.
  • Sliding Expiration for States: Sliding expiration resets the cache duration each time the cached item is accessed. This is useful for data that should expire if not used within a certain period. For states, the expiration is reset every time the cache is accessed, which helps keep frequently accessed data in the cache. This is useful for data that does not change frequently but does not need to be kept in memory indefinitely if not used.
  • Absolute Expiration for Cities: Absolute expiration causes the cache entry to expire after a fixed duration since it was added to the cache. For Cities, the cache is cleared exactly 30 minutes after it’s set, regardless of how many times it was accessed. This is appropriate for data that might change at predictable intervals.
Modifying Controller Methods to Handle Cache Eviction

Make sure that your controllers are aware of the cache eviction strategy and are using the repository methods that handle this logic.

using InMemoryCachingDemo.Models;
using InMemoryCachingDemo.Repository;
using Microsoft.AspNetCore.Mvc;

namespace InMemoryCachingDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class LocationController : ControllerBase
    {
        private readonly LocationRepository _repository;

        public LocationController(LocationRepository repository)
        {
            _repository = repository;
        }

        [HttpGet("countries")]
        public async Task<IActionResult> GetCountries()
        {
            var countries = await _repository.GetCountriesAsync();
            return Ok(countries);
        }

        [HttpPut("countries/{id}")]
        public async Task<IActionResult> UpdateCountry(int id, Country country)
        {
            if (id != country.CountryId)
                return BadRequest();

            try
            {
                await _repository.UpdateCountry(country);
                return NoContent(); // Indicates success with no content to return
            }
            catch (Exception ex)
            {
                if (!CountryExists(id))
                    return NotFound();
                else
                {
                    var customResponse = new
                    {
                        Code = 500,
                        Message = "Internal Server Error",
                        // Do not expose the actual error to the client
                        ErrorMessage = ex.Message
                    };
                    return StatusCode(StatusCodes.Status500InternalServerError, customResponse);
                }
            }
        }

        private bool CountryExists(int id)
        {
            return _repository.GetCountriesAsync().Result.Any(e => e.CountryId == id);
        }

        [HttpGet("states/{countryId}")]
        public async Task<IActionResult> GetStates(int countryId)
        {
            var states = await _repository.GetStatesAsync(countryId);
            return Ok(states);
        }

        [HttpGet("cities/{stateId}")]
        public async Task<IActionResult> GetCities(int stateId)
        {
            var cities = await _repository.GetCitiesAsync(stateId);
            return Ok(cities);
        }
    }
}

Now, test the endpoints, and it should work as expected.

In-Memory Cache Priority in ASP.NET Core Web API:

Cache Priority in ASP.NET Core determines how a cache entry is to be removed when the system needs to free up memory. Entries can be tagged as Low, Normal, High, or NeverRemove. When we need to free up memory, entries with lower priorities are removed first. .NET provides several levels of cache item priority:

  • Low: This should be used for items that can be discarded easily.
  • Normal: The default priority if none is specified.
  • High: This is less likely to be removed than normal or low-priority items.
  • NeverRemove: This prevents items from being removed automatically due to memory pressure (though you can still manually remove them).

Here’s how you can implement cache priorities for our LocationRepository caching strategies for countries, states, and cities:

using InMemoryCachingDemo.Data;
using InMemoryCachingDemo.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;

namespace InMemoryCachingDemo.Repository
{
    public class LocationRepository
    {
        private readonly ApplicationDbContext _context;
        private readonly IMemoryCache _cache;
      
        public LocationRepository(ApplicationDbContext context, IMemoryCache cache)
        {
            _context = context;
            _cache = cache;
        }

        //Using Manual Eviction and High Priority for countries
        public async Task<List<Country>> GetCountriesAsync()
        {
            var cacheKey = "Countries";
            if (!_cache.TryGetValue(cacheKey, out List<Country>? countries))
            {
                countries = await _context.Countries.ToListAsync();
                // No Expiration Set
                var cacheEntryOptions = new MemoryCacheEntryOptions()
                    .SetPriority(CacheItemPriority.High); // High priority for countries
                _cache.Set(cacheKey, countries, cacheEntryOptions);
            }
            return countries ?? new List<Country>();
        }

        // This Methid can be called after updating or deleting country data.
        public void RemoveCountriesFromCache()
        {
            var cacheKey = "Countries";
            _cache.Remove(cacheKey);
        }

        public async Task UpdateCountry(Country updatedCountry)
        {
            _context.Countries.Update(updatedCountry);
            await _context.SaveChangesAsync();
            RemoveCountriesFromCache();
        }

        // Using Sliding Expiration and Normal Priority for States
        public async Task<List<State>> GetStatesAsync(int countryId)
        {
            string cacheKey = $"States_{countryId}";
            if (!_cache.TryGetValue(cacheKey, out List<State>? states))
            {
                states = await _context.States.Where(s => s.CountryId == countryId).ToListAsync();
                var cacheEntryOptions = new MemoryCacheEntryOptions()
                    .SetSlidingExpiration(TimeSpan.FromMinutes(30)) // Sliding Expiration
                    .SetPriority(CacheItemPriority.Normal); // Normal priority for states
                _cache.Set(cacheKey, states, cacheEntryOptions);
            }
            return states ?? new List<State>();
        }

        // Using Absolute Expiration and Low Priority for Cities
        public async Task<List<City>> GetCitiesAsync(int stateId)
        {
            string cacheKey = $"Cities_{stateId}";
            if (!_cache.TryGetValue(cacheKey, out List<City>? cities))
            {
                cities = await _context.Cities.Where(c => c.StateId == stateId).ToListAsync();
                var cacheEntryOptions = new MemoryCacheEntryOptions()
                    .SetAbsoluteExpiration(TimeSpan.FromMinutes(30)) // Absolute Expiration
                    .SetPriority(CacheItemPriority.Low); // Low priority for cities
                _cache.Set(cacheKey, cities, cacheEntryOptions);
            }
            return cities ?? new List<City>();
        }
    }
}
Explanation
  • High Priority for Countries: Countries are given a high cache priority using CacheItemPriority.High. This indicates that cached country data is retained in the cache as long as possible, even under memory pressure.
  • Normal Priority for States: States are assigned a normal priority (CacheItemPriority.Normal). This is the default level, and the cache entry is less likely to be removed under memory pressure than low-priority items.
  • Low Priority for Cities: Cities are set with a low cache priority (CacheItemPriority.Low). This means that if the system needs to free up cache memory, city data will likely be evicted before other items with higher priorities.
How Do We Delete or Remove All Cache Keys in ASP.NET Core In-Memory Caching?

Clearing the cache is necessary in scenarios where cached data becomes invalid or if we want to free up memory by removing unused data. ASP.NET Core provides a built-in method called Clear to clear all entries from its in-memory cache (IMemoryCache). To better understand this, please add the following method within the LocationRepository class, which will be used to clear all the keys.

public void ClearAllCache()
{
    // To remove all Clear Cache, do this
    if (_cache is MemoryCache concreteMemoryCache)
    {
        concreteMemoryCache.Clear();
    }
}
Cache Clearing Logic:
  • This code uses pattern matching with the is keyword to check if _cache is actually an instance of MemoryCache (a concrete implementation of IMemoryCache provided by .NET Core).
  • The is operator not only checks the type but also performs a safe cast. If _cache is of MemoryCache type, it assigns it to the local variable concreteMemoryCache.
  • Clear(): This function calls the Clear method on the MemoryCache instance. The Clear method is specific to MemoryCache and removes all entries from the cache.

MemoryCache vs. IMemoryCache: MemoryCache is the concrete class implementing the IMemoryCache interface. The actual caching behavior, including the method to clear the cache (Clear), is part of this concrete class.

Adding the API Endpoint to Clear the Cache:

Next, add the following API End Point to the Location Controller which will call the ClearAllCache method to clear all the In-Memory Caches.

[HttpPost("ClearCache")]
public IActionResult ClearCache()
{
    _repository.ClearAllCache();
    return Ok("All Cache Cleared()");
}

Now, run the application and test it, and it should work as expected.

Can We Use In-Memory Caching in a Web Farm Environment?

Web Farm means multiple servers hosting the same application. Using in-memory caching in a Web Farm Environment is not ideal because each server will have its own instance of the cache. This leads to data inconsistency across servers as each server’s cache does not synchronize automatically with others. For applications deployed in a Web Farm, Distributed Caching solutions like Redis, NCache, or SQL Server distributed cache are recommended to maintain data consistency and availability across all servers.

What are the Benefits of Using In-Memory Caching?

In-memory caching provides several benefits:

  • Faster Data Access: Since the data is stored in the Web Server’s RAM, which is much faster than disk-based storage, access times are significantly reduced. This leads to faster response times in applications, especially those requiring rapid access to data, like web applications.
  • Reduced Load on Backend Systems: Caching commonly accessed data in memory reduces the number of calls made to the backend database or API, which can be beneficial during high-traffic periods by decreasing the load on the database.
  • Improved User Experience: Faster data retrieval contributes to a smoother and quicker user experience, which is important for customer satisfaction.
  • Cost Efficiency: Reducing the number of calls to a database can decrease operational costs, especially with cloud-based services, where pricing may be based on the number of queries or loads.
  • Simplicity: Implementing in-memory caching is straightforward in ASP.NET Core, making it an easy win for improving performance.
What are the Drawbacks of In-Memory Caching?
  • Memory Usage: The primary limitation is the amount of available memory. Caching too much data can lead to memory exhaustion, which can cause the application to perform poorly or even crash.
  • Not Suitable in Web Farms: While in-memory caching can improve performance on a single server, it can complicate scaling to multiple servers. Data consistency becomes challenging as each server has its own separate cache.
  • Data Volatility: In-memory caches are volatile; the data is lost if the server restarts or crashes. 
  • Cost of Memory: High-capacity RAM is more expensive than disk storage. The cost of additional memory can be significant for applications that require a large amount of data to be cached.

In the next article, I will discuss how to Create a Custom In-Memory Cache in an ASP.NET Core Web API Application with Examples. In this article, I explain How to Implement In-Memory Caching in ASP.NET Core Web API with Examples. I hope you enjoy this article, How to Implement In-Memory Caching in ASP.NET Core Web API.

Leave a Reply

Your email address will not be published. Required fields are marked *