Skip to content
On this page

Decorators, Interceptors, and Activators

TIP

If you need some kind of interception policy that doesn't fit into the admittedly simpler examples shown here, just ask for help in the Lamar Discord channel.

Lamar v5 finally added the full complement of interceptors that users have grown to expect from .Net IoC containers. To differentiate between the three terms, Lamar now supports:

  1. Decorators - the classic Gang of Four Decorator pattern where at runtime Lamar will "wrap" a concrete decorator object around the registered service
  2. Interceptors - in a user supplied Lambda, get a chance to work with the newly built object or even return a different object with a possibly dynamic proxy
  3. Activators - a user supplied Lambda that is called right after an object is created by Lamar, but before it is returned to the caller

Decorators

TIP

When you use a decorator in Lamar, it effectively replaces the original registration with a new registration for the concrete decorator type, then moves the original registration to be an inline dependency of the decorated type. Be aware that the WhatDoIHave() display will show the concrete decorator type instead of the original registration because of this.

If you look in the Lamar codebase, you'll find dozens of tests that use a fake type named IWidget that does something:

cs
public interface IWidget
{
    void DoSomething();
}

snippet source | anchor

Let's say that we have service registrations in our system for that IWidget interface, but we want each of them to be decorated by another form of IWidget like this to perform some kind of cross cutting concern before and/or after the call to the specific widget:

cs
public class WidgetDecorator : IWidget
{
    public WidgetDecorator(IThing thing, IWidget inner)
    {
        Inner = inner;
    }

    public IWidget Inner { get; }

    public void DoSomething()
    {
        // do something before 
        Inner.DoSomething();
        // do something after
    }
}

snippet source | anchor

We can configure Lamar to add a decorator around all other IWidget registrations with this syntax:

cs
var container = new Container(_ =>
{
    // This usage adds the WidgetHolder as a decorator
    // on all IWidget registrations
    _.For<IWidget>().DecorateAllWith<WidgetDecorator>();

    // The AWidget type will be decorated w/ 
    // WidgetHolder when you resolve it from the container
    _.For<IWidget>().Use<AWidget>();

    _.For<IThing>().Use<Thing>();
});

// Just proving that it actually works;)
container.GetInstance<IWidget>()
    .ShouldBeOfType<WidgetDecorator>()
    .Inner.ShouldBeOfType<AWidget>();

snippet source | anchor

Activators

TIP

Activators have to be synchronous at this point, so there might have to be some GetAwaiter().GetResult() action happening to force asynchronous methods to run inline. #sadtrombone.

An activator gives you the chance to make some kind of call against an object that has just been built by Lamar, but before that object is passed to the original service requester.

As an example, let's say that you have some sort of service in your system that is responsible for polling an external web service for more information. If you're like me, you really don't like to perform any kind of work besides putting together necessary state in a constructor function, so you likely have some kind of Start() method on the polling service to actually start things up like this class:

cs
public class Poller : IPoller
{
    public void Dispose()
    {
        // stop polling
    }

    public void Start()
    {
        // start the actual polling
    }
}

snippet source | anchor

When we register the class above with Lamar, we can supply a Lambda function to start up Poller like this:

cs
var container = new Container(x =>
{
    x.For<IPoller>().Use<Poller>()

        // This registers an activator on just this
        // one registration
        .OnCreation(poller => poller.Start());
});

snippet source | anchor

In the sample above, we registered an activator on one and only one service registration. Lamar will also let you apply an activator across service registrations that implement or inherit from a common type. Back to the polling example, what if we introduce a new marker interface to denote objects that need to be started or activated like this:

cs
public interface IStartable : IDisposable
{
    void Start();
}

snippet source | anchor

cs
public interface IStartable
{
    bool WasStarted { get; }

    void Start();
}

snippet source | anchor

cs
public interface IStartable
{
    bool WasStarted { get; }

    void Start();
}

snippet source | anchor

Now, let's create a new StartablePoller:

cs
public class StartablePoller : IPoller, IStartable
{
    public void Dispose()
    {
        // shut things down
    }

    public void Start()
    {
        // start up the polling
    }
}

snippet source | anchor

And going back to the IPoller registration, we could now do this:

cs
var container = new Container(services =>
{
    // Remember that Lamar natively understands .Net
    // DI registrations w/o any adapter
    services.AddSingleton<IPoller, StartablePoller>();

    // Other registrations that might include other 
    // IStartable types

    services.For<IStartable>()
        .OnCreationForAll(x => x.Start());
});

snippet source | anchor

The call to For<T>().OnCreationForAll(Action<T>) will apply to any registrations of type T or any registrations where the implementation type can be cast to T. So in th case above, the activator will be applied to the StartablePoller type.

As a last example, you also have an overload to use the constructing container in an activator if you need access to other services. Let's say that for some kind of diagnostic in our system, we want to track what IStartable objects have been created and floating around in our system at any time with this simple class:

cs
public class StartableTracker
{
    public List<IStartable> Startables { get; } = new();
}

snippet source | anchor

Now, we'd like our IStartable objects to both Start() and be tracked by the class above, so we'll use an activator like this:

cs
var container = new Container(services =>
{
    services.AddSingleton<IPoller, StartablePoller>();

    // Other registrations that might include other 
    // IStartable types

    services.AddSingleton<StartableTracker>();

    services.For<IStartable>()
        .OnCreationForAll((context, startable) =>
        {
            var tracker = context.GetInstance<StartableTracker>();
            tracker.Startables.Add(startable);

            startable.Start();
        });
});

snippet source | anchor

Interceptors

TIP

The main usage for this feature is most likely for dynamic proxies and Aspect Oriented Programming

Interceptors are similar to activators, but with two differences:

  1. Interceptors allow you to return a different object than the one initially created by Lamar
  2. Because of the potential to return a new object, interceptors are applied at the service type level

Here's a sample from the unit tests:

cs
[Fact]
public void intercept_a_single_instance()
{
    var container = new Container(x =>
    {
        x.For<IWidget>().Add<ActivatedWidget>()
            .Named("yes")
            .OnCreation(w => new DecoratedWidget(w));

        x.For<IWidget>().Add<ActivatedWidget>().Named("no");
    });

    container.GetInstance<IWidget>("yes")
        .ShouldBeOfType<DecoratedWidget>()
        .Inner.ShouldBeOfType<ActivatedWidget>();

    container.GetInstance<IWidget>("no")
        .ShouldBeOfType<ActivatedWidget>();
}

snippet source | anchor

Just like activators, there is the option to only use the original object or the ability to use the original object and the constructing container.