Cache Key Generation

Athena.Cache automatically generates cache keys from your controller context and method parameters. Understanding how this works helps you optimize cache usage and avoid key collisions.

Automatic Key Generation

By default, cache keys are generated using this pattern:

{Namespace}:{ControllerName}:{ActionName}_{parameters}

Basic Example

[AthenaCache(ExpirationMinutes = 30)]
public async Task<UserDto> GetUser(int id)
{
    return await _userService.GetUserAsync(id);
}
// Generated key: "MyApp:UsersController:GetUser_id:123"

Multiple Parameters

[AthenaCache(ExpirationMinutes = 30)]
public async Task<UserDto[]> GetUsers(string? search, int page, int size)
{
    return await _userService.GetUsersAsync(search, page, size);
}
// Generated key: "MyApp:UsersController:GetUsers_search:john_page:1_size:10"

Complex Objects

public class UserFilter
{
    public string? Name { get; set; }
    public int? MinAge { get; set; }
    public string[]? Roles { get; set; }
}

[AthenaCache(ExpirationMinutes = 15)]
public async Task<UserDto[]> SearchUsers([FromQuery] UserFilter filter)
{
    return await _userService.SearchUsersAsync(filter);
}
// Generated key: "MyApp:UsersController:SearchUsers_filter:Name=john,MinAge=25,Roles=admin,user"

Key Customization

Custom Key Patterns

Use the KeyPattern property to define custom key templates:

[AthenaCache(KeyPattern = "user_{id}")]
public async Task<UserDto> GetUser(int id) { ... }
// Result: "user_123"

[AthenaCache(KeyPattern = "users_page_{page}_size_{size}")]
public async Task<UserDto[]> GetUsers(int page, int size) { ... }
// Result: "users_page_1_size_10"

[AthenaCache(KeyPattern = "user_{id}_profile_{includeDetails}")]
public async Task<UserDto> GetUserProfile(int id, bool includeDetails) { ... }
// Result: "user_123_profile_true"

Key Prefixes

Add custom prefixes to distinguish between different versions or environments:

[AthenaCache(KeyPrefix = "v2")]
public async Task<DataDto> GetData() { ... }
// Result: "v2:MyApp:DataController:GetData"

[AthenaCache(KeyPrefix = "admin_api")]
public async Task<UserDto[]> GetUsersForAdmin() { ... }
// Result: "admin_api:MyApp:UsersController:GetUsersForAdmin"

Namespace Configuration

Set the global namespace in your configuration:

// Program.cs
builder.Services.AddAthenaCacheComplete(options =>
{
    options.Namespace = "MyApp_Prod";  // All keys will start with this
});
// appsettings.json
{
  "AthenaCache": {
    "Namespace": "MyApp_Prod"
  }
}

Parameter Handling

Ignored Parameters

Some parameters are automatically ignored for key generation:

[AthenaCache]
public async Task<UserDto[]> GetUsers(
    string search,
    int page,
    [FromServices] ILogger<UsersController> logger)  // Ignored (service injection)
{
    // logger parameter doesn't affect cache key
}
// Key: "MyApp:UsersController:GetUsers_search:john_page:1"

Custom Parameter Inclusion/Exclusion

[AthenaCache(IncludeParameters = new[] { "search", "category" })]
public async Task<ProductDto[]> SearchProducts(
    string search, 
    string category, 
    int page,           // Excluded from key
    int size)           // Excluded from key
{
    // Only search and category affect the cache key
}
// Key: "MyApp:ProductsController:SearchProducts_search:laptop_category:electronics"

[AthenaCache(ExcludeParameters = new[] { "trackingId" })]
public async Task<OrderDto> GetOrder(int id, string trackingId)
{
    // trackingId doesn't affect cache key
}
// Key: "MyApp:OrdersController:GetOrder_id:123"

Custom Key Generators

For advanced scenarios, implement your own key generation logic:

public class CustomCacheKeyGenerator : ICacheKeyGenerator
{
    public string GenerateKey(string baseKey, object parameters)
    {
        // Custom logic for key generation
        var hash = ComputeHash(parameters);
        return $"custom_{baseKey}_{hash}";
    }
    
    private string ComputeHash(object parameters)
    {
        // Your custom hashing logic
        return parameters.ToString().GetHashCode().ToString("X");
    }
}

// Register in Program.cs
builder.Services.AddSingleton<ICacheKeyGenerator, CustomCacheKeyGenerator>();

Key Collision Avoidance

Controller Name Disambiguation

When you have controllers with similar names:

// AdminUsersController
[AthenaCache(KeyPrefix = "admin")]
public async Task<UserDto[]> GetUsers() { ... }
// Key: "admin:MyApp:AdminUsersController:GetUsers"

// PublicUsersController  
[AthenaCache(KeyPrefix = "public")]
public async Task<UserDto[]> GetUsers() { ... }
// Key: "public:MyApp:PublicUsersController:GetUsers"

Action Name Disambiguation

[AthenaCache(KeyPattern = "users_active")]
public async Task<UserDto[]> GetActiveUsers() { ... }

[AthenaCache(KeyPattern = "users_inactive")]
public async Task<UserDto[]> GetInactiveUsers() { ... }

Key Length Optimization

For very long parameter lists, Athena.Cache automatically uses hash-based keys:

[AthenaCache]
public async Task<ReportDto> GenerateReport(
    DateTime startDate,
    DateTime endDate,
    string[] categories,
    string[] regions,
    bool includeDetails,
    ReportFormat format)
{
    // Long parameter list is automatically hashed
}
// Key: "MyApp:ReportsController:GenerateReport_hash:A1B2C3D4E5F6"

Manual Hash Mode

Force hash-based keys for sensitive data:

[AthenaCache(UseHashedKey = true)]
public async Task<UserDto> GetUserByEmail(string email)
{
    // Email is hashed for privacy
}
// Key: "MyApp:UsersController:GetUserByEmail_hash:F7E8D9C0B1A2"

Key Inspection

Debug Key Generation

Enable key logging to see generated keys:

// Program.cs
builder.Services.AddAthenaCacheComplete(options =>
{
    options.Logging.LogCacheOperations = true;  // Logs key generation
});

Runtime Key Inspection

[ApiController]
public class CacheDebugController : ControllerBase
{
    private readonly ICacheKeyGenerator _keyGenerator;

    [HttpPost("preview-key")]
    public IActionResult PreviewKey([FromBody] KeyPreviewRequest request)
    {
        var key = _keyGenerator.GenerateKey(request.BaseKey, request.Parameters);
        return Ok(new { GeneratedKey = key });
    }
}

Best Practices

1. Keep Keys Predictable

// Good: Clear, predictable pattern
[AthenaCache(KeyPattern = "product_{id}_details")]
public async Task<ProductDto> GetProduct(int id) { ... }

// Avoid: Complex patterns that are hard to debug
[AthenaCache(KeyPattern = "prod_{id}_{DateTime.Now.Ticks}")]
public async Task<ProductDto> GetProduct(int id) { ... }

2. Use Meaningful Prefixes

// Good: Clear separation between API versions
[AthenaCache(KeyPrefix = "api_v1")]
public async Task<UserDto> GetUserV1(int id) { ... }

[AthenaCache(KeyPrefix = "api_v2")]
public async Task<UserDtoV2> GetUserV2(int id) { ... }

3. Consider Parameter Sensitivity

// Good: Hash sensitive parameters
[AthenaCache(UseHashedKey = true)]
public async Task<UserDto> GetUserBySSN(string ssn) { ... }

// Good: Exclude non-essential parameters
[AthenaCache(ExcludeParameters = new[] { "requestId", "correlationId" })]
public async Task<DataDto> GetData(string query, string requestId, string correlationId) { ... }

4. Namespace for Multi-tenant Applications

// Configure per tenant
builder.Services.AddAthenaCacheComplete(options =>
{
    options.Namespace = $"Tenant_{tenantId}";
});

// Or use custom key patterns
[AthenaCache(KeyPattern = "tenant_{tenantId}_user_{userId}")]
public async Task<UserDto> GetUser(string tenantId, int userId) { ... }

Troubleshooting

Common Issues

Keys are too long

Key collisions

Parameters not included in key

Cache not working as expected

For more advanced scenarios, see: