Search code examples
c#decoratorautofac

Autofac 6 - register a decorator as also implementing another interface


before Autofac 6, I could do something like:

builder.RegisterType<ResourceStorageParallelOperationsLimitDecorator>()
       .As(new DecoratorService(typeof(IResourceStorage)))
       .As<INetworkQueueMetrics>();

In short, I could tell Autofac that all IResourceStorage instances should be decorated by ResourceStorageParallelOperationsLimitDecorator and that this decorator also offered the service INetworkQueueMetrics.

Now, in v6, if I do:

builder.RegisterDecorator<ResourceStorageParallelOperationsLimitDecorator,
                          IResourceStorage>()

I cannot continue the call with .As<>(...).

And if I try to amend the registration of ResourceStorageParallelOperationsLimitDecorator like:

builder.RegisterDecorator<ResourceStorageParallelOperationsLimitDecorator,
                          IResourceStorage>();
builder.RegisterType<ResourceStorageParallelOperationsLimitDecorator>()
       .As<INetworkQueueMetrics>()
       .SingleInstance();

then this leads to INetworkQueueMetrics and IResourceStorage being resolved as two different instances of ResourceStorageParallelOperationsLimitDecorator, see the following test:

[Test]
public void INetworkQueueMetrics_resolves_to_the_same_instance_as_ResourceStorage()
{
  var container = BuildContainer();
  var resourceStorage = container.Resolve<IResourceStorage>();
  var networkMetrics = container.Resolve<INetworkQueueMetrics>();

  resourceStorage.Should().BeSameAs(networkMetrics); // <<<< FAILS
}

There are plenty of overrides for ContainerBuilder.RegisterDecorator(), maybe one of them would let me do what I need, but frankly their usage is not all that clear to me.

Any idea how I can get the semantics I want with Autofac v6?


Solution

  • What you're seeing is an intentional behavior. The point of a decorator is to decorate - it's basically a "value-add proxy." A decorator, in and of itself, doesn't expose a whole separate service - its sole purpose is to wrap something else. It only worked before somewhat accidentally - via a leaky abstraction in the way the fluent registration syntax functioned. That leak got "plugged" with the latest version.

    If you need something to wrap a component and change the services exposed, that's an adapter and Autofac does have adapter support, too.

    And, of course, if you need something more general purpose (like "log every call made to this thing"), you can use type interceptors.