A high-performance backend system that reduces redundant API calls through intelligent caching and change detection mechanisms. Built using ASP.NET Core Web API, ADO.NET, Redis, and Docker, this system is ideal for mobile applications requiring minimal data synchronization and efficient resource usage.
✅ Change Detection: Timestamp-based cache validation to detect updates in services, stories, and categories.
✅ Generic ADO.NET Repository: Reusable CRUD logic using reflection with table-based configuration.
✅ Stored Procedure Integration: Safe and efficient insertions using SQL Server stored procedures with parameter binding (no SQL injection risk).
✅ Redis Caching: Cache-aside strategy with TTL, change tracking, and invalidation.
✅ GenericEntityCacheService: Entity-agnostic caching logic using factory-based DI.
✅ Custom Category Tree Logic: Recursive child category retrieval and circular reference protection.
✅ Mobile Simulation Client: Simulates smart client behavior, only requesting changed data.
✅ Dockerized Deployment: Runs Redis, SQL Server, and Web API seamlessly via Docker Compose.
Mobile Client ↔️ ChangeCheckController ↔️ Redis ↔️ ADO.NET Repositories ↔️ SQL Server
- Client sends timestamps to check for changes.
- Server compares with Redis stored timestamps.
- If changes exist, relevant data is fetched via ADO.NET and returned.
- Data is cached with a fresh LastModified value.
- .NET 8 SDK
- Docker & Docker Compose
- Redis CLI (optional for manual inspection)
git clone https://github.com/Kamilm55/SmartCacheAPI.gitdocker-compose up --build
⚠️ Note: Before running, make sure to apply EF Core migrations once.
{
"CategoryTimestamp": "2024-07-01T12:00:00Z",
"ServiceTimestamp": null,
"StoryTimestamp": "2024-07-05T08:00:00Z"
}categories:lastmodified => 2024-07-09T12:32:10Z
services:lastmodified => null
stories:lastmodified => 2024-07-08T10:20:45ZServer compares Redis timestamps with incoming ones and returns only updated data.
A flexible repository using C# reflection to auto-map entity properties to SQL columns:
public abstract class GenericRepository<T> where T : class, new()- Dynamic
SELECT,INSERT,UPDATE, andDELETEoperations - Auto-maps SqlDataReader → Entity using reflection
- Supports nullable types, strings, DateTime, and primitives
- Cleanly injectable into derived repositories
public class ServicesRepository : GenericRepository<Service>, IServicesRepository
{
public ServicesRepository(IConfiguration config) : base(config, "Services") {}
}Every query uses parameterized statements like:
command.Parameters.AddWithValue("@Name", category.Name);✅ This ensures that untrusted user input is never directly concatenated into SQL strings, which is the primary way SQL injection attacks succeed.
🔍 Why It Works:
- When using AddWithValue() or similar parameterized methods, the query is compiled with placeholders (@Name) before values are inserted.
- The input is passed as data, not executable code — so even if the user tries to inject SQL (e.g., '; DROP TABLE Categories; --), it will be treated as a literal string, not a command.
- This separates query logic from input, making it immune to most common injection attempts.
💡 Compared to:
// ❌ Dangerous: vulnerable to SQL injection
var query = $"INSERT INTO Categories (Name) VALUES ('{userInput}')";Using parameterized queries is the safest and recommended practice for interacting with SQL Server, especially when building dynamic systems like a generic ADO.NET repository.
EF Core migration adds the stored procedure:
CREATE PROCEDURE sp_CreateCategories
@Name NVARCHAR(100),
@IsActive BIT = 0,
@ParentId INT = NULL,
@Id INT OUTPUTExecuted using:
var cmd = new SqlCommand("sp_CreateCategories") {
CommandType = CommandType.StoredProcedure
};
cmd.Parameters.AddWithValue("@Name", category.Name);
// ...✅ Prevents SQL injection
✅ Clean business logic separation
✅ Supports output parameters
Reusable Redis caching service per entity type.
GetOrSetListEntityCacheAsync: Caches entire tableGetOrSetSingleEntityCacheAsync: Caches single item by IDUpdateCacheAfterCreateAsync: Updates list + single cacheInvalidateCacheAsync: Invalidates both data + LastModified keysGetLastModifiedAsync: Fetches timestamp used for change detection
Dynamically generates a generic caching service for each entity:
_commonCacheService = cacheServiceFactory.Create<Category>(
CacheKeys.CATEGORIES_DATA,
CacheKeys.CATEGORIES_LASTMODIFIED
);Keeps services decoupled and clean.
This system demonstrates:
- Clean architecture
- Efficient caching
- Safe database interaction
- Good coding practices
- Real-world production readiness
✅ Ideal for large-scale mobile backends that require real-time sync without constant polling.