Open sandboxFocusImprove this doc

Injecting dependencies into aspects

Many aspects require services injected from a dependency injection container. For example, a caching aspect may depend on the IMemoryCache service. If you use the Microsoft.Extensions.DependencyInjection framework, your aspect should pull this service from the constructor. If the target type of the aspect doesn't already accept this service from the constructor, the aspect will need to append this parameter to the constructor.

However, the code pattern that must be implemented to pull any dependency depends on the dependency injection framework used by the project. As we've seen, the default .NET Core framework requires a constructor parameter, but other frameworks may use an [Import] or [Inject] custom attribute.

In some cases, as the author of the aspect, you may not know which dependency injection framework will be used for the classes to which your aspect will be applied.

This is where the Metalama.Extensions.DependencyInjection project comes in. Thanks to this namespace, your aspect can consume and pull a dependency with a single custom attribute. The code pattern to pull the dependency is abstracted by the IDependencyInjectionFramework interface, which is chosen by the user project.

The Metalama.Extensions.DependencyInjection namespace is open source and hosted on GitHub. It currently has implementations for the following dependency injection frameworks:

The Metalama.Extensions.DependencyInjection project is designed to make implementing other dependency injection frameworks easy.

Consuming dependencies from your aspect

To consume a dependency from an aspect:

  1. Add the Metalama.Extensions.DependencyInjection package to your project.
  2. Add a field or automatic property of the desired type in your aspect class.
  3. Annotate this field or property with the IntroduceDependencyAttribute custom attribute. The following attribute properties are available:
    • IsLazy resolves the dependency upon first use instead of upon initialization, and
    • IsRequired throws an exception if the dependency is not available.
  4. Use this field or property from any template member of your aspect.

Example: default dependency injection patterns

The following example uses Microsoft.Extensions.Hosting, typical to .NET Core applications, to build an application and inject services. The Program.Main method builds the host, and the host then instantiates our Worker class. We add a [Log] aspect to this class. The Log aspect class has a field of type IMessageWriter marked with the IntroduceDependencyAttribute custom attribute. As you can see in the transformed code, this field is introduced into the Worker class and pulled from the constructor.

1using Doc.LogDefaultFramework;
2using Metalama.Framework.Aspects;
3using Metalama.Extensions.DependencyInjection;
4
5[assembly:
6    AspectOrder( AspectOrderDirection.RunTime, typeof(LogAttribute), typeof(DependencyAttribute) )]
7
8namespace Doc.LogDefaultFramework;
9
10// Our logging aspect.
11public class LogAttribute : OverrideMethodAspect
12{
13    // Defines the dependency consumed by the aspect. It will be handled by the dependency injection framework configured for the current project.
14    // By default, this is the .NET Core system one, which pulls dependencies from the constructor.
15    [IntroduceDependency]
16    private readonly IMessageWriter _messageWriter;
17
18    public override dynamic? OverrideMethod()
19    {
20        try
21        {
22            this._messageWriter.Write( $"{meta.Target.Method} started." );
23
24            return meta.Proceed();
25        }
26        finally
27        {
28            this._messageWriter.Write( $"{meta.Target.Method} completed." );
29        }
30    }
31}
Source Code
1using Metalama.Documentation.Helpers.ConsoleApp;
2using System;
3

4namespace Doc.LogDefaultFramework;
5
6// The class using the Log aspect. This class is instantiated by the host builder and dependencies are automatically passed.
7public class Worker : IConsoleMain
8{
9    [Log]
10    public void Execute()
11    {
12        Console.WriteLine( "Hello, world." );
13    }








14}
Transformed Code
1using Metalama.Documentation.Helpers.ConsoleApp;
2using Metalama.Framework.RunTime;
3using System;
4
5namespace Doc.LogDefaultFramework;
6
7// The class using the Log aspect. This class is instantiated by the host builder and dependencies are automatically passed.
8public class Worker : IConsoleMain
9{
10    [Log]
11    public void Execute()
12    {
13        try
14        {
15            _messageWriter.Write("Worker.Execute() started.");
16            Console.WriteLine("Hello, world.");
17            return;
18        }
19        finally
20        {
21            _messageWriter.Write("Worker.Execute() completed.");
22        }
23    }
24
25    private IMessageWriter _messageWriter;
26
27    public Worker([AspectGenerated] IMessageWriter? messageWriter = null)
28    {
29        this._messageWriter = messageWriter ?? throw new System.ArgumentNullException(nameof(messageWriter));
30    }
31}
1using Metalama.Documentation.Helpers.ConsoleApp;
2using Microsoft.Extensions.DependencyInjection;
3using System;
4
5namespace Doc.LogDefaultFramework;
6
7// Program entry point. Creates the host, configure dependencies, and runs it.
8public static class Program
9{
10    private static void Main()
11    {
12        var appBuilder = ConsoleApp.CreateBuilder();
13        appBuilder.Services.AddSingleton<IMessageWriter, MessageWriter>();
14        appBuilder.Services.AddConsoleMain<Worker>();
15        using var app = appBuilder.Build();
16        app.Run();
17    }
18}
19
20// Definition of the interface consumed by the aspect.
21public interface IMessageWriter
22{
23    void Write( string message );
24}
25
26// Implementation actually consumed by the aspect.
27public class MessageWriter : IMessageWriter
28{
29    public void Write( string message )
30    {
31        Console.WriteLine( message );
32    }
33}
Worker.Execute() started.
Hello, world.
Worker.Execute() completed.

Example: Service locator

The following example is similar to the previous one but uses the service locator pattern instead of pulling dependencies from the constructor. To use the service locator:

  1. Add the Metalama.Extensions.DependencyInjection.ServiceLocator package to your project.

  2. Register the framework using a fabric.

    1using Metalama.Extensions.DependencyInjection;
    2using Metalama.Extensions.DependencyInjection.ServiceLocator;
    3using Metalama.Framework.Fabrics;
    4
    5namespace Doc.LogServiceLocator;
    6
    7// Register the ServiceLocator framework for this test.
    8// Since 2026.0, ServiceLocator no longer auto-registers and must be explicitly enabled.
    9internal class Fabric : ProjectFabric
    10{
    11    public override void AmendProject( IProjectAmender amender )
    12    {
    13        amender.ConfigureDependencyInjection(
    14            builder => builder.RegisterFramework<ServiceLocatorDependencyInjectionFramework>() );
    15    }
    16}
    17
    
  3. Configure ServiceProvider in your application startup to point to your IServiceProvider.

    ServiceProviderProvider.ServiceProvider = () => serviceProvider;
    

Here is the complete code of the example:

1using Doc.LogServiceLocator;
2using Metalama.Framework.Aspects;
3using Metalama.Extensions.DependencyInjection;
4
5[assembly:
6    AspectOrder( AspectOrderDirection.RunTime, typeof(LogAttribute), typeof(DependencyAttribute) )]
7
8namespace Doc.LogServiceLocator;
9
10// Our logging aspect.
11public class LogAttribute : OverrideMethodAspect
12{
13    // Defines the dependency consumed by the aspect. It will be handled initialized from a service locator,
14    // but note that the aspect does not need to know the implementation details of the dependency injection framework.
15    [IntroduceDependency]
16    private readonly IMessageWriter _messageWriter;
17
18    public override dynamic? OverrideMethod()
19    {
20        try
21        {
22            this._messageWriter.Write( $"{meta.Target.Method} started." );
23
24            return meta.Proceed();
25        }
26        finally
27        {
28            this._messageWriter.Write( $"{meta.Target.Method} completed." );
29        }
30    }
31}
1using Metalama.Extensions.DependencyInjection;
2using Metalama.Extensions.DependencyInjection.ServiceLocator;
3using Metalama.Framework.Fabrics;
4
5namespace Doc.LogServiceLocator;
6
7// Register the ServiceLocator framework for this test.
8// Since 2026.0, ServiceLocator no longer auto-registers and must be explicitly enabled.
9internal class Fabric : ProjectFabric
10{
11    public override void AmendProject( IProjectAmender amender )
12    {
13        amender.ConfigureDependencyInjection(
14            builder => builder.RegisterFramework<ServiceLocatorDependencyInjectionFramework>() );
15    }
16}
17
Source Code
1using System;
2using System.Threading.Tasks;
3
4namespace Doc.LogServiceLocator;

5
6// The class using the Log aspect. This class is NOT instantiated by any dependency injection container.
7public class Worker
8{
9    [Log]
10    public Task ExecuteAsync()
11    {
12        Console.WriteLine( "Hello, world." );
13


14        return Task.CompletedTask;

15    }
16}
Transformed Code
1using System;
2using System.Threading.Tasks;
3using Metalama.Extensions.DependencyInjection.ServiceLocator;
4
5namespace Doc.LogServiceLocator;
6
7// The class using the Log aspect. This class is NOT instantiated by any dependency injection container.
8public class Worker
9{
10    [Log]
11    public Task ExecuteAsync()
12    {
13        try
14        {
15            _messageWriter.Write("Worker.ExecuteAsync() started.");
16            Console.WriteLine("Hello, world.");
17
18            return Task.CompletedTask;
19        }
20        finally
21        {
22            _messageWriter.Write("Worker.ExecuteAsync() completed.");
23        }
24    }
25
26    private IMessageWriter _messageWriter;
27
28    public Worker()
29    {
30        _messageWriter = (IMessageWriter)ServiceProviderProvider.ServiceProvider().GetService(typeof(IMessageWriter)) ?? throw new InvalidOperationException("The service 'IMessageWriter' could not be obtained from the service locator.");
31    }
32}
1using Metalama.Extensions.DependencyInjection.ServiceLocator;
2using Microsoft.Extensions.DependencyInjection;
3using System;
4using System.Threading.Tasks;
5
6namespace Doc.LogServiceLocator;
7
8// The program entry point.
9public static class Program
10{
11    private static Task Main()
12    {
13        // Creates a service collection, add the service, and build a service provider.
14        var serviceCollection = new ServiceCollection();
15        serviceCollection.AddSingleton<IMessageWriter>( new MessageWriter() );
16        var serviceProvider = serviceCollection.BuildServiceProvider();
17
18        // Assigns the service provider to the global service locator.
19        ServiceProviderProvider.ServiceProvider = () => serviceProvider;
20
21        // Executes the program.
22        return new Worker().ExecuteAsync();
23    }
24}
25
26// Definition of the interface consumed by the aspect.
27public interface IMessageWriter
28{
29    void Write( string message );
30}
31
32// Implementation of the interface actually used by the aspect.
33public class MessageWriter : IMessageWriter
34{
35    public void Write( string message )
36    {
37        Console.WriteLine( message );
38    }
39}
Worker.ExecuteAsync() started.
Hello, world.
Worker.ExecuteAsync() completed.

Selecting a dependency injection framework

By default, Metalama generates code for the default .NET dependency injection framework implemented in the Microsoft.Extensions.DependencyInjection namespace (also called the .NET Core dependency injection framework).

To select a different framework for a project:

  1. Add a reference to the package implementing the desired dependency framework, e.g., Metalama.Extensions.DependencyInjection.ServiceLocator.
  2. Register the framework using a fabric by calling amender.ConfigureDependencyInjection(builder => builder.RegisterFramework<MyFramework>( priority: 10 )).

When several dependency injection frameworks can handle a specified dependency, Metalama selects the one with the lowest priority value among them. This selection strategy can be customized for the whole project or for specified namespaces or types.

To customize the selection strategy of the dependency injection framework for a specific aspect and dependency:

  1. Add a ProjectFabric or NamespaceFabric as described in Configuring aspects with fabrics.
  2. From the AmendProject or AmendNamespace method, call the amender.Outgoing.ConfigureDependencyInjection method. Supply the empty delegate builder => {} as an argument to this method.
  3. From this delegate, do one of the following:

Implementing a custom adapter

If you need to support a dependency injection framework or pattern for which no ready-made implementation exists, you can implement an adapter yourself. See Creating a custom DI framework adapter for detailed instructions and examples.