Basics

Athena.Cache is designed around attributes that you add to your controller actions. The library handles cache key generation, storage, retrieval, and invalidation automatically.

Core Concept

The central concept in Athena.Cache is declarative caching - you declare what should be cached and when it should be invalidated using attributes, and the library handles the rest.

[HttpGet]
[AthenaCache(ExpirationMinutes = 30)]           // Cache this response
[CacheInvalidateOn("Users")]                     // Invalidate when Users table changes
public async Task<ActionResult<UserDto[]>> GetUsers()
{
    // Your business logic - no cache code needed!
    return Ok(await _userService.GetUsersAsync());
}

The AthenaCache Attribute

The [AthenaCache] attribute marks an action for caching.

Basic Usage

[HttpGet("{id}")]
[AthenaCache(ExpirationMinutes = 60)]
public async Task<UserDto> GetUser(int id)
{
    return await _userService.GetUserAsync(id);
}
// Generated cache key: "UsersController:GetUser_id:123"
// Cache expires after 60 minutes

Expiration Options

// Time-based expiration
[AthenaCache(ExpirationMinutes = 30)]

// Sliding expiration (resets timer on access)
[AthenaCache(ExpirationMinutes = 15, SlidingExpiration = true)]

// Use global default (configured in Program.cs)
[AthenaCache]

Cache Key Customization

// Custom key pattern
[AthenaCache(KeyPattern = "user_{id}_{role}")]
public async Task<UserDto> GetUserByRole(int id, string role) { ... }
// Result: "user_123_admin"

// Custom cache key prefix
[AthenaCache(KeyPrefix = "api_v2")]
public async Task<DataDto> GetData() { ... }
// Result: "api_v2:DataController:GetData"

Cache Invalidation

The [CacheInvalidateOn] attribute specifies when caches should be cleared.

Table-based Invalidation

[HttpPost]
[CacheInvalidateOn("Users")]  // Clear all caches tagged with "Users"
public async Task<UserDto> CreateUser([FromBody] CreateUserRequest request)
{
    var user = await _userService.CreateUserAsync(request);
    // All caches with [CacheInvalidateOn("Users")] are automatically cleared
    return user;
}

Multiple Tables

[HttpPut("{id}")]
[CacheInvalidateOn("Users")]        // Clear user-related caches
[CacheInvalidateOn("UserProfiles")] // Also clear profile-related caches
public async Task UpdateUser(int id, [FromBody] UpdateUserRequest request)
{
    await _userService.UpdateUserAsync(id, request);
}

Pattern-based Invalidation

[HttpDelete("{id}")]
[CacheInvalidateOn("Users", InvalidationType.Pattern, "user_*")]
public async Task DeleteUser(int id)
{
    await _userService.DeleteUserAsync(id);
    // Clears all cache keys matching "user_*" pattern
}

Cache Status Monitoring

Athena.Cache provides cache status information through HTTP headers.

# First request (cache miss)
GET /api/users/123
# Response includes: X-Athena-Cache: MISS

# Second request (cache hit)  
GET /api/users/123
# Response includes: X-Athena-Cache: HIT

Programmatic Cache Statistics

[ApiController]
public class CacheStatsController : ControllerBase
{
    private readonly IAthenaCache _cache;

    public CacheStatsController(IAthenaCache cache)
    {
        _cache = cache;
    }

    [HttpGet("cache/stats")]
    public async Task<IActionResult> GetStats()
    {
        var stats = await _cache.GetStatisticsAsync();
        return Ok(new
        {
            HitRate = stats.HitRate,
            TotalRequests = stats.TotalRequests,
            CacheSize = stats.CacheSize
        });
    }
}

Disabling Cache

Skip Caching for Specific Actions

[HttpGet("realtime")]
[NoCache]  // This action will never be cached
public async Task<RealtimeData> GetRealtimeData()
{
    return await _dataService.GetLiveDataAsync();
}

Conditional Caching

[HttpGet]
[AthenaCache(ExpirationMinutes = 30)]
public async Task<UserDto[]> GetUsers([FromQuery] bool skipCache = false)
{
    if (skipCache)
    {
        // Business logic to bypass cache
        Response.Headers.Add("X-Cache-Bypassed", "true");
    }
    
    return await _userService.GetUsersAsync();
}

Convention-based Configuration

Athena.Cache can automatically infer table names from controller names.

// With convention enabled (default):
public class UsersController : ControllerBase
{
    [HttpGet]
    [AthenaCache]  
    // Automatically adds [CacheInvalidateOn("Users")] based on controller name
    public async Task<UserDto[]> GetUsers() { ... }
}

// Disable convention for specific actions:
[HttpGet]
[AthenaCache]
[NoConventionInvalidation]  // Don't auto-add invalidation
public async Task<UserDto[]> GetUsersNoConvention() { ... }

Error Handling

Configure how cache errors are handled:

// Program.cs
builder.Services.AddAthenaCacheComplete(options =>
{
    options.ErrorHandling.OnCacheError = CacheErrorAction.LogAndContinue;
    options.ErrorHandling.ThrowOnSerializationError = false;
});

Error Handling Options

Cache Configuration

Global Configuration

// Program.cs
builder.Services.AddAthenaCacheComplete(options =>
{
    // Basic settings
    options.Namespace = "MyApp";
    options.DefaultExpirationMinutes = 30;
    
    // Convention settings
    options.Convention.EnableConventionBasedInvalidation = true;
    options.Convention.ControllerSuffix = "Controller";
    
    // Logging
    options.Logging.LogCacheHitMiss = true;
    options.Logging.LogCacheInvalidation = true;
});

Using appsettings.json

{
  "AthenaCache": {
    "Namespace": "MyApp",
    "DefaultExpirationMinutes": 30,
    "Convention": {
      "EnableConventionBasedInvalidation": true
    },
    "Logging": {
      "LogCacheHitMiss": true,
      "LogCacheInvalidation": true
    }
  }
}
// Program.cs
builder.Services.AddAthenaCacheComplete(
    builder.Configuration.GetSection("AthenaCache"));

Complete Example

Here’s a complete controller showing common patterns:

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IUserService _userService;

    public UsersController(IUserService userService)
    {
        _userService = userService;
    }

    // Cached list with automatic invalidation
    [HttpGet]
    [AthenaCache(ExpirationMinutes = 30)]
    [CacheInvalidateOn("Users")]
    public async Task<ActionResult<UserDto[]>> GetUsers(
        [FromQuery] string? search = null,
        [FromQuery] int page = 1)
    {
        return Ok(await _userService.GetUsersAsync(search, page));
    }
    
    // Cached individual user
    [HttpGet("{id}")]
    [AthenaCache(ExpirationMinutes = 60)]
    [CacheInvalidateOn("Users")]
    public async Task<ActionResult<UserDto>> GetUser(int id)
    {
        var user = await _userService.GetUserAsync(id);
        return user == null ? NotFound() : Ok(user);
    }
    
    // Cache invalidation on create
    [HttpPost]
    [CacheInvalidateOn("Users")]
    public async Task<ActionResult<UserDto>> CreateUser([FromBody] CreateUserRequest request)
    {
        var user = await _userService.CreateUserAsync(request);
        return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
    }
    
    // Cache invalidation on update
    [HttpPut("{id}")]
    [CacheInvalidateOn("Users")]
    [CacheInvalidateOn("UserProfiles")]  // Also invalidate related data
    public async Task<IActionResult> UpdateUser(int id, [FromBody] UpdateUserRequest request)
    {
        await _userService.UpdateUserAsync(id, request);
        return NoContent();
    }
    
    // Cache invalidation on delete
    [HttpDelete("{id}")]
    [CacheInvalidateOn("Users")]
    public async Task<IActionResult> DeleteUser(int id)
    {
        await _userService.DeleteUserAsync(id);
        return NoContent();
    }
    
    // Real-time data without caching
    [HttpGet("online")]
    [NoCache]
    public async Task<ActionResult<int>> GetOnlineUserCount()
    {
        return Ok(await _userService.GetOnlineUserCountAsync());
    }
}

Next Steps

Now that you understand the basics, explore specific features: