Source Generator

Athena.Cache.SourceGenerator provides compile-time optimizations that eliminate reflection overhead, enable AOT compilation, and provide build-time validation of your cache configuration.

Why Source Generation?

Traditional caching libraries rely on runtime reflection to discover cache attributes, which causes:

The Source Generator solves these issues by analyzing your code at compile time.

Installation

Add to Application Project

# Install in your main application project (not library projects)
dotnet add package Athena.Cache.SourceGenerator

Project Configuration

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <!-- Enable AOT if desired -->
    <PublishAot>true</PublishAot>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Athena.Cache.Core" Version="1.0.0" />
    <PackageReference Include="Athena.Cache.SourceGenerator" Version="1.0.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>
  </ItemGroup>
</Project>

How It Works

Before Source Generator

// Runtime reflection scanning (slow)
public void ConfigureServices(IServiceCollection services)
{
    services.AddAthenaCacheComplete();
    
    // At startup, Athena scans all assemblies using reflection
    foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
    {
        if (type.IsSubclassOf(typeof(ControllerBase)))
        {
            foreach (var method in type.GetMethods())
            {
                var cacheAttr = method.GetCustomAttribute<AthenaCacheAttribute>();
                // Expensive reflection calls...
            }
        }
    }
}

With Source Generator

// Generated at compile time (fast)
public void ConfigureServices(IServiceCollection services)
{
    services.AddAthenaCacheComplete();
    
    // Generated method - no reflection needed!
    services.ConfigureAthenaCache(registry =>
    {
        MyApp.Generated.AthenaCacheConfiguration.RegisterCacheConfigurations(registry);
    });
}

Automatic Code Generation

The Source Generator analyzes your controllers and automatically generates optimized configuration code.

Your Controller Code

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet]
    [AthenaCache(ExpirationMinutes = 30)]
    [CacheInvalidateOn("Users")]
    public async Task<UserDto[]> GetUsers(string? search = null, int page = 1)
    {
        return await _userService.GetUsersAsync(search, page);
    }

    [HttpGet("{id}")]
    [AthenaCache(ExpirationMinutes = 60, KeyPattern = "user_{id}")]
    [CacheInvalidateOn("Users")]
    public async Task<UserDto> GetUser(int id)
    {
        return await _userService.GetUserAsync(id);
    }

    [HttpPost]
    [CacheInvalidateOn("Users")]
    public async Task<UserDto> CreateUser([FromBody] CreateUserRequest request)
    {
        return await _userService.CreateUserAsync(request);
    }
}

Generated Configuration Code

// Generated: AthenaCacheConfiguration.g.cs
using Athena.Cache.Core.Configuration;
using Athena.Cache.Core.Models;

namespace MyApp.Generated
{
    /// <summary>
    /// Generated cache configuration for compile-time optimization
    /// </summary>
    public static class AthenaCacheConfiguration
    {
        /// <summary>
        /// Registers all cache configurations found in the assembly
        /// </summary>
        public static void RegisterCacheConfigurations(ICacheConfigurationRegistry registry)
        {
            // UsersController configurations
            registry.RegisterConfiguration("UsersController.GetUsers", new CacheConfiguration
            {
                ExpirationMinutes = 30,
                InvalidationTables = new[] { "Users" },
                KeyPattern = null,
                KeyPrefix = null,
                IncludeParameters = new[] { "search", "page" },
                ExcludeParameters = null
            });

            registry.RegisterConfiguration("UsersController.GetUser", new CacheConfiguration
            {
                ExpirationMinutes = 60,
                InvalidationTables = new[] { "Users" },
                KeyPattern = "user_{id}",
                KeyPrefix = null,
                IncludeParameters = new[] { "id" },
                ExcludeParameters = null
            });

            registry.RegisterInvalidationConfiguration("UsersController.CreateUser", new InvalidationConfiguration
            {
                InvalidationTables = new[] { "Users" },
                InvalidationType = InvalidationType.Table
            });
        }
    }
}

Build-time Validation

The Source Generator validates your cache configuration at compile time.

Convention Validation

// ✅ Good: Follows convention
[ApiController]
public class ProductsController : ControllerBase
{
    [CacheInvalidateOn("Products")]  // ✅ Matches controller name
    public async Task<ProductDto> CreateProduct() { /* ... */ }
}

// ⚠️ Warning: Convention mismatch
[ApiController]
public class ProductsController : ControllerBase
{
    [CacheInvalidateOn("Items")]  // ⚠️ May not match intended table
    public async Task<ProductDto> CreateProduct() { /* ... */ }
}

Build Output

Build started...
1>AthenaCacheSourceGenerator: Found 3 controllers with cache attributes
1>AthenaCacheSourceGenerator: Generated cache configuration for UsersController
1>AthenaCacheSourceGenerator: Generated cache configuration for ProductsController  
1>AthenaCacheSourceGenerator: Warning ATHENA001: Table name 'Items' in ProductsController.CreateProduct doesn't follow convention. Did you mean 'Products'?
1>Build succeeded with warnings.

Compilation Diagnostics

The Source Generator provides helpful diagnostics during compilation.

Warning Examples

// ATHENA001: Convention mismatch
[CacheInvalidateOn("WrongTableName")]  
public async Task UpdateProduct() { /* ... */ }

// ATHENA002: Long expiration warning  
[AthenaCache(ExpirationMinutes = 1440)]  // 24 hours - unusually long
public async Task GetStaticData() { /* ... */ }

// ATHENA003: Empty invalidation table
[CacheInvalidateOn("")]  // Empty string
public async Task UpdateData() { /* ... */ }

Error Examples

// ATHENA004: Invalid key pattern
[AthenaCache(KeyPattern = "invalid_{nonExistentParameter}")]
public async Task GetData(int id) { /* ... */ }  // 'nonExistentParameter' not found

// ATHENA005: Conflicting attributes
[AthenaCache]
[NoCache]  // Conflicting attributes
public async Task GetData() { /* ... */ }

AOT Compilation Support

The Source Generator enables full AOT compilation support.

AOT Project Configuration

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <PublishAot>true</PublishAot>
    <InvariantGlobalization>true</InvariantGlobalization>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Athena.Cache.Core" Version="1.0.0" />
    <PackageReference Include="Athena.Cache.SourceGenerator" Version="1.0.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>
  </ItemGroup>
</Project>

AOT-Compatible Code

// This code is fully AOT compatible with Source Generator
[ApiController]
public class AOTCompatibleController : ControllerBase
{
    [HttpGet]
    [AthenaCache(ExpirationMinutes = 30)]
    public async Task<DataDto[]> GetData()
    {
        // No reflection used - fully AOT compatible
        return await _dataService.GetDataAsync();
    }
}

Publishing for AOT

# Publish as AOT binary
dotnet publish -c Release -r linux-x64 --self-contained

# The resulting binary:
# - Contains no reflection dependencies
# - Starts up faster (no reflection scanning)
# - Uses less memory (no reflection caches)
# - Has better performance (no reflection overhead)

Debugging Generated Code

Enable Debug Output

<PropertyGroup>
    <AthenaCacheGenerateDebugOutput>true</AthenaCacheGenerateDebugOutput>
    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
    <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

This outputs generated files to the Generated folder for inspection.

Generated File Structure

Generated/
├── Athena.Cache.SourceGenerator/
│   ├── AthenaCacheConfiguration.g.cs
│   ├── CacheMetadata.g.cs
│   └── DiagnosticInfo.g.cs

Sample Generated Metadata

// Generated: CacheMetadata.g.cs
namespace MyApp.Generated
{
    internal static class CacheMetadata
    {
        public static readonly CacheControllerInfo[] Controllers = new[]
        {
            new CacheControllerInfo
            {
                Name = "UsersController",
                Actions = new[]
                {
                    new CacheActionInfo
                    {
                        Name = "GetUsers",
                        HasCacheAttribute = true,
                        ExpirationMinutes = 30,
                        InvalidationTables = new[] { "Users" },
                        Parameters = new[] { "search", "page" }
                    }
                }
            }
        };
    }
}

Performance Benefits

Startup Performance

// Benchmark results
[MemoryDiagnoser]
public class StartupBenchmarks
{
    [Benchmark]
    public void StartupWithReflection()
    {
        // Traditional approach: 50ms startup overhead
        var services = new ServiceCollection();
        services.AddAthenaCacheComplete();
        // Reflection scanning happens here
    }

    [Benchmark]
    public void StartupWithSourceGenerator()
    {
        // Source Generator approach: 5ms startup overhead
        var services = new ServiceCollection();
        services.AddAthenaCacheComplete();
        services.ConfigureAthenaCache(registry =>
        {
            // Pre-generated configuration - no reflection
            AthenaCacheConfiguration.RegisterCacheConfigurations(registry);
        });
    }
}

Runtime Performance

// No reflection overhead during cache operations
[Benchmark]
public async Task CacheOperationWithSourceGenerator()
{
    // Cache configuration is pre-built
    // No runtime reflection calls
    // Faster cache key generation
    // Better memory usage
    var result = await _controller.GetUsers();
}

MSBuild Integration

Custom MSBuild Properties

<PropertyGroup>
    <!-- Generator behavior -->
    <AthenaCacheGenerateDebugOutput>false</AthenaCacheGenerateDebugOutput>
    <AthenaCacheNamespace>$(RootNamespace).Generated</AthenaCacheNamespace>
    <AthenaCacheConfigurationClassName>CacheRegistry</AthenaCacheConfigurationClassName>
    
    <!-- Validation settings -->
    <AthenaCacheValidateConventions>true</AthenaCacheValidateConventions>
    <AthenaCacheTreatWarningsAsErrors>false</AthenaCacheTreatWarningsAsErrors>
    
    <!-- Performance settings -->
    <AthenaCacheEnableOptimizations>true</AthenaCacheEnableOptimizations>
</PropertyGroup>

Build Events

<Target Name="AthenaCachePostBuild" AfterTargets="Build">
    <Message Text="Athena Cache Source Generator completed" Importance="high" />
    <Message Text="Generated cache configurations: $(AthenaCacheGeneratedConfigurations)" />
</Target>

Integration with CI/CD

GitHub Actions

name: Build and Test with Source Generator

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup .NET
      uses: actions/setup-dotnet@v3
      with:
        dotnet-version: '8.0.x'
        
    - name: Restore dependencies
      run: dotnet restore
      
    - name: Build with Source Generator
      run: dotnet build --no-restore --configuration Release
      # Source Generator runs automatically during build
      
    - name: Test
      run: dotnet test --no-build --configuration Release
      
    - name: Publish AOT
      run: dotnet publish --configuration Release --runtime linux-x64 --self-contained -p:PublishAot=true

Docker with AOT

# Multi-stage build with AOT
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src

# Copy project files
COPY *.csproj ./
RUN dotnet restore

# Copy source code
COPY . .

# Build with Source Generator and publish as AOT
RUN dotnet publish -c Release -r linux-x64 --self-contained -p:PublishAot=true -o /app/publish

# Runtime stage (smaller base image for AOT)
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .

# AOT binary runs directly (no .NET runtime needed)
ENTRYPOINT ["./MyApp"]

Advanced Configuration

Custom Code Templates

Create custom templates for generated code:

// CustomCacheGenerator.cs
[Generator]
public class CustomCacheGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // Custom generation logic
        var customTemplate = """
            // Custom cache configuration template
            public static void RegisterCustomCacheConfigurations(ICacheConfigurationRegistry registry)
            {
                
                //  configurations
                
                registry.RegisterConfiguration(".", new CacheConfiguration
                {
                    ExpirationMinutes = ,
                    CustomProperty = ""
                });
                
                
            }
            """;
    }
}

Integration with Existing Code

// Combine generated configuration with manual configuration
public void ConfigureServices(IServiceCollection services)
{
    services.AddAthenaCacheComplete();
    
    services.ConfigureAthenaCache(registry =>
    {
        // Register generated configurations
        AthenaCacheConfiguration.RegisterCacheConfigurations(registry);
        
        // Add manual configurations for special cases
        registry.RegisterConfiguration("CustomController.SpecialAction", new CacheConfiguration
        {
            ExpirationMinutes = 120,
            KeyPattern = "special_{id}_{timestamp}",
            InvalidationTables = new[] { "SpecialData", "AuditLog" }
        });
    });
}

Troubleshooting

Common Issues

Source Generator not running

<!-- Ensure correct package reference -->
<PackageReference Include="Athena.Cache.SourceGenerator" Version="1.0.0">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>

Generated code not found

# Clean and rebuild
dotnet clean
dotnet build

AOT compilation fails

<!-- Check for unsupported reflection usage -->
<PropertyGroup>
    <PublishAot>true</PublishAot>
    <PublishTrimmed>true</PublishTrimmed>
    <TrimMode>link</TrimMode>
</PropertyGroup>

Build warnings about conventions

// Fix naming conventions or suppress warnings
[CacheInvalidateOn("Users")]  // Match controller name
// OR
[CacheInvalidateOn("CustomTable")]
[SuppressMessage("ATHENA001", "Intentional table name difference")]

Next Steps