Dependency Injection

Metalama offers built-in support for dependency injection. Aspects can introduce dependencies, which are automatically injected into the class where the aspect is applied. Many frameworks are supported.

There’s a common misconception that aspect-oriented programming and dependency injection are competing paradigms. In reality, they complement each other. While these concepts were formalized in the early 2000s and weren’t initially designed to work together, Metalama, redesigned in the 2020s, integrates both ideas.

Each tool has its own job. Use dependency injection for what it does best (composing the application by wiring services together at their interface boundaries), and use aspects for the cross-cutting behavior that lives inside those services. That way you don’t twist dependency injection into solving problems it was never designed for, such as adding logging or caching to methods that sit behind no interface at all.

Benefits

  • Enhanced testability. One of the primary benefits of dependency injection is improved testability. It allows you to provide different service implementations for testing and production environments. Unlike other aspect frameworks, Metalama fully supports this approach, enabling you to make your code as testable as needed.

  • Support for multiple DI frameworks. Metalama natively supports the Microsoft.Extensions.DependencyInjection namespace and the service locator pattern. Additionally, you can implement support for other frameworks. Thanks to an abstraction layer, your aspects don’t need to be tightly coupled to a specific dependency injection framework.

Example

Suppose our source code looks like this. We are adding a [Log] attribute to the ExecuteAsync method.

class Worker : BackgroundService
{
    [Log]
    protected override Task ExecuteAsync( CancellationToken stoppingToken )
    {
        Console.WriteLine( "Hello, world." );

        return Task.CompletedTask;
    }
}

The Worker class would be transformed into the following code:

class Worker : BackgroundService
{
    ILogger _logger;

    public Worker( ILogger<Worker> logger )
    {
        this._logger = logger;
    }

    [Log]
    protected override Task ExecuteAsync( CancellationToken stoppingToken )
    {
        this._logger.LogDebug( "Executing Worker.ExecuteAsync" );

        Console.WriteLine( "Hello, world." );

        return Task.CompletedTask;
    }
}

Resources