Series: From Code to Cloud: Building a Production-Ready .NET Application
By: Farrukh Rehman - Senior .NET Full Stack Developer / Team Lead
LinkedIn: https://linkedin.com/in/farrukh-rehman
GitHub: https://github.com/farrukh1212cs
Source Code Backend : https://github.com/farrukh1212cs/ECommerce-Backend.git
Source Code Frontend : https://github.com/farrukh1212cs/ECommerce-Frontend.git
🎯 Introduction
In modern e-commerce applications, performance is critical — users expect fast responses, low latency, and instant data access.
One of the most effective ways to achieve this is by introducing caching.
In this lesson, we’ll integrate Redis, an in-memory data store, to cache frequently accessed data (like products, categories, and user sessions) and reduce database load.
Redis helps you:
- Decrease response times ⚡
- Reduce the number of database calls 📉
- Handle high traffic efficiently 🚀
By the end of this lesson, your e-commerce system will have a working Redis integration ready for caching services.
🧩 Step 1: Pull Redis Docker Image
Let’s begin by setting up Redis locally using Docker.
Run the following command in your terminal:
docker pull redis:latest
Once the image is downloaded, start the Redis container:
docker run --name ecommerce-redis -p 6379:6379 -d redis
You can verify that Redis is running using:
docker ps
⚙️ Step 2: Install Redis Package
In your ECommerce.Infrastructure project, install the official Redis client for .NET — StackExchange.Redis.
dotnet add ECommerce.Infrastructure package StackExchange.Redis
🧠 Step 3: Configure Redis in appsettings.json
Add your Redis connection string:
"DatabaseProvider": "PostgreSQL",
"ConnectionStrings": {
"PostgreSQL": "Host=localhost;Port=5432;Database=ECommerceDb;Username=postgres;Password=Admin123!",
"MySQL": "Server=localhost;Port=3306;Database=ECommerceDb;User=root;Password=Admin123!;",
"SqlServer": "Server=localhost,1433;Database=ECommerceDb;User Id=sa;Password=Admin123!;TrustServerCertificate=True;",
"Redis": "localhost:6379"
}
🧩 Step 4 : Create Interface ICacheService.cs
Path : ECommerce.Application/Services/Interfaces/ICacheService.cs
namespace ECommerce.Application.Services.Interfaces;
public interface ICacheService
{
Task SetAsync<T>(string key, T value, TimeSpan? expiry = null);
Task<T?> GetAsync<T>(string key);
Task RemoveAsync(string key);
}
✅ Purpose:
- Defines a simple contract for caching.
- Keeps Redis implementation hidden behind the interface.
- Allows you to switch to another caching provider (e.g., MemoryCache, DistributedCache) in the future.
⚙️ Step 5 : Implement the Interface in RedisCacheService.cs
Path : ECommerce.Infrastructure/Caching/RedisCacheService.cs
using ECommerce.Application.Services.Interfaces;
using StackExchange.Redis;
using System.Text.Json;
namespace ECommerce.Infrastructure.Caching;
public class RedisCacheService : ICacheService
{
private readonly IDatabase _database;
public RedisCacheService(IConnectionMultiplexer redis)
{
_database = redis.GetDatabase();
}
public async Task SetAsync<T>(string key, T value, TimeSpan? expiry = null)
{
var json = JsonSerializer.Serialize(value);
await _database.StringSetAsync(key, json, expiry);
}
public async Task<T?> GetAsync<T>(string key)
{
var value = await _database.StringGetAsync(key);
return value.IsNullOrEmpty ? default : JsonSerializer.Deserialize<T>(value!);
}
public async Task RemoveAsync(string key)
{
await _database.KeyDeleteAsync(key);
}
}
✅ Step 6 : Updated DependencyInjection.cs
using ECommerce.Application.Services.Interfaces;
using ECommerce.Infrastructure.Caching;
using ECommerce.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;
namespace ECommerce.Infrastructure;
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(
this IServiceCollection services,
IConfiguration configuration)
{
var provider = configuration["DatabaseProvider"] ?? "MySQL";
if (string.Equals(provider, "SqlServer", StringComparison.OrdinalIgnoreCase))
{
var conn = configuration.GetConnectionString("SqlServer");
services.AddDbContext<AppDbContext, SqlServerDbContext>(options =>
options.UseSqlServer(conn));
}
else if (string.Equals(provider, "MySQL", StringComparison.OrdinalIgnoreCase))
{
var conn = configuration.GetConnectionString("MySQL");
services.AddDbContext<AppDbContext, MySqlDbContext>(options =>
options.UseMySql(conn, ServerVersion.AutoDetect(conn)));
}
else if (string.Equals(provider, "PostgreSQL", StringComparison.OrdinalIgnoreCase))
{
var conn = configuration.GetConnectionString("PostgreSQL");
services.AddDbContext<AppDbContext, PostgresDbContext>(options =>
options.UseNpgsql(conn));
}
else
{
throw new InvalidOperationException($"Unsupported provider: {provider}");
}
// ✅ Redis cache setup
var redisConnection = configuration.GetConnectionString("Redis");
if (!string.IsNullOrEmpty(redisConnection))
{
services.AddSingleton<IConnectionMultiplexer>(sp =>
ConnectionMultiplexer.Connect(redisConnection));
services.AddSingleton<ICacheService, RedisCacheService>();
}
return services;
}
}
✅ Step 7 : Updated ProductsController with Redis Cache Support
using ECommerce.Application.DTOs;
using ECommerce.Application.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace ECommerce.API.Controllers;
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
private readonly ICacheService _cacheService;
public ProductsController(IProductService productService, ICacheService cacheService)
{
_productService = productService;
_cacheService = cacheService;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<ProductDto>>> GetAll()
{
const string cacheKey = "products:all";
// Try get from cache
var cachedProducts = await _cacheService.GetAsync<IEnumerable<ProductDto>>(cacheKey);
if (cachedProducts != null)
{
return Ok(new
{
fromCache = true,
data = cachedProducts
});
}
// Get from DB if not cached
var products = await _productService.GetAllAsync();
// Cache for 60 minutes
await _cacheService.SetAsync(cacheKey, products, TimeSpan.FromMinutes(60));
return Ok(new
{
fromCache = false,
data = products
});
}
[HttpGet("{id}")]
public async Task<ActionResult<ProductDto>> GetById(Guid id)
{
var cacheKey = $"product:{id}";
// Check cache
var cachedProduct = await _cacheService.GetAsync<ProductDto>(cacheKey);
if (cachedProduct != null)
{
return Ok(new
{
fromCache = true,
data = cachedProduct
});
}
// Fetch from service
var product = await _productService.GetByIdAsync(id);
if (product == null)
return NotFound();
// Cache for 60 minutes
await _cacheService.SetAsync(cacheKey, product, TimeSpan.FromMinutes(60));
return Ok(new
{
fromCache = false,
data = product
});
}
[HttpPost]
public async Task<ActionResult> Create(ProductDto dto)
{
await _productService.AddAsync(dto);
// Invalidate cache
await _cacheService.RemoveAsync("products:all");
return CreatedAtAction(nameof(GetById), new { id = dto.Id }, dto);
}
[HttpPut("{id}")]
public async Task<ActionResult> Update(Guid id, ProductDto dto)
{
if (id != dto.Id)
return BadRequest("Mismatched product ID.");
await _productService.UpdateAsync(dto);
// Invalidate caches
await _cacheService.RemoveAsync("products:all");
await _cacheService.RemoveAsync($"product:{id}");
return NoContent();
}
[HttpDelete("{id}")]
public async Task<ActionResult> Delete(Guid id)
{
await _productService.DeleteAsync(id);
// Invalidate caches
await _cacheService.RemoveAsync("products:all");
await _cacheService.RemoveAsync($"product:{id}");
return NoContent();
}
}
✅ Key Notes
Caching scope:
- GetAll → caches list of products (products:all)
- GetById → caches single product (product:{id})
- Invalidation logic:
- When a product is created, updated, or deleted, relevant keys are removed.
- Cache TTL (Time to Live):
- Currently set to 60 minutes
✅ Step 8 : Final Testing
Test on GetAll
Get By Id
Next Lecture Preview
Lecture 7 : Message Queues with RabbitMQ
Implementing event-driven communication, background jobs, and asynchronous processing.








Top comments (0)