Repository: autofac/Autofac

Issue:
Allow access to constructed container when deciding whether or not a decorator should be applied
## Problem Statement

There is support today for conditionally applying decorators by providing a lambda on the `RegisterDecorator` call. However, only information about the instance being decorated is provided there, so it is impossible to request something from the container that could be relevant to making the decision.

I have a scenario where I need to inspect an `IOptionsSnapshot<T>` object with a specific setting to decide whether or not a given decorator should be applied. This must be done everytime the decorator is attempted to be resolved, so that the setting can work "in real time" (i.e. without having to restart the application).

## Desired Solution

The overload to `Decorate` that takes a decision predicate should be expanded to also provide the `IContainer` itself so that the caller can resolve services from the container at the time of the decision is made.

https://github.com/autofac/Autofac/blob/f56d1fe90f322042ecdf35ecc38cabeb71b27295/src/Autofac/RegistrationExtensions.Decorators.cs#L162

This should have a variation such as:
```csharp
    public static void RegisterDecorator<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TDecorator, TService>(
        this ContainerBuilder builder, 
        Func<IDecoratorContext, IContainer, bool>? condition = null)
        where TDecorator : notnull, TService
```

## Alternatives You've Considered

As a poor-man's alternative, I used the overloads that don't take a concrete decorator (which expose `IContainer`) and then manually constructed my decorator there based on a service registered in the container, or used the `instance` value when no decoration was needed.

This works, but is more convoluted/less flexible.

```csharp
builder.RegisterDecorator<IMyService>(
  (context, parameters, instance) => 
  {
    if (context.Resolve<IOptionsSnapshot<MyOptions>>().Value.EnableStuffing)
    {
        return new StuffedService(instance);
    }
    else
    {
        return instance;
    }
  });
```

This also becomes unwieldy when the concrete decorator takes more dependencies, as you have to also manually resolve each one.



Hints:
OK, I can see the benefit of being able to resolve services inside the condition callback. 

Might I propose though that instead of adding `IContainer` or a similar scope to the callback signature, we make `IDecoratorContext` derive from `IComponentContext`?  

That means the decorator context would provide all the resolve functionality of `IContainer`/`ILifetimeScope` without modifying the signature, so you could do:

```csharp
builder.RegisterDecorator<MyDecorator, IMyService>(
    context => context.Resolve<IOptionsSnapshot<MyOptions>>().Value.EnableStuffing)
```

Does that seem ok, and would you be willing to raise a PR to that effect? I actually don't think the changes are particularly complicated; when we create the `DecoratorContext` in `DecoratorMiddleware` we can pass in the `ResolveRequestContext`, and route all requests done via `DecoratorContext` through that provided request context.
> Might I propose though that instead of adding `IContainer` or a similar scope to the callback signature, we make `IDecoratorContext` derive from `IComponentContext`?
> 
> That means the decorator context would provide all the resolve functionality of `IContainer`/`ILifetimeScope` without modifying the signature, so you could do:
> 
> ```cs
> builder.RegisterDecorator<MyDecorator, IMyService>(
>     context => context.Resolve<IOptionsSnapshot<MyOptions>>().Value.EnableStuffing)
> ```
> 
> Does that seem ok...

That's perfectly fine in my book.


>..., and would you be willing to raise a PR to that effect?

It is unlikely I'll be able to, but not impossible. I'm actually only dealing with Autofac here in a legacy integration, which is where I stumbled upon the situation I described. If I do, I'll let everyone know by replying here before starting though.

Patch:
diff --git a/src/Autofac/Features/Decorators/DecoratorContext.cs b/src/Autofac/Features/Decorators/DecoratorContext.cs
index 3929fbdbd..d674814a8 100644
--- a/src/Autofac/Features/Decorators/DecoratorContext.cs
+++ b/src/Autofac/Features/Decorators/DecoratorContext.cs
@@ -1,6 +1,8 @@
 ﻿// Copyright (c) Autofac Project. All rights reserved.
 // Licensed under the MIT License. See LICENSE in the project root for license information.
 
+using Autofac.Core;
+
 namespace Autofac.Features.Decorators;
 
 /// <summary>
@@ -8,6 +10,8 @@ namespace Autofac.Features.Decorators;
 /// </summary>
 public sealed class DecoratorContext : IDecoratorContext
 {
+    private readonly IComponentContext _componentContext;
+
     /// <inheritdoc />
     public Type ImplementationType { get; private set; }
 
@@ -23,13 +27,18 @@ public sealed class DecoratorContext : IDecoratorContext
     /// <inheritdoc />
     public object CurrentInstance { get; private set; }
 
+    /// <inheritdoc />
+    public IComponentRegistry ComponentRegistry => _componentContext.ComponentRegistry;
+
     private DecoratorContext(
+        IComponentContext componentContext,
         Type implementationType,
         Type serviceType,
         object currentInstance,
         IReadOnlyList<Type>? appliedDecoratorTypes = null,
         IReadOnlyList<object>? appliedDecorators = null)
     {
+        _componentContext = componentContext;
         ImplementationType = implementationType;
         ServiceType = serviceType;
         CurrentInstance = currentInstance;
@@ -40,13 +49,14 @@ private DecoratorContext(
     /// <summary>
     /// Create a new <see cref="DecoratorContext"/>.
     /// </summary>
+    /// <param name="componentContext">The component context this decorator context sits in.</param>
     /// <param name="implementationType">The type of the concrete implementation.</param>
     /// <param name="serviceType">The service type being decorated.</param>
     /// <param name="implementationInstance">The instance of the implementation to be decorated.</param>
     /// <returns>A new decorator context.</returns>
-    internal static DecoratorContext Create(Type implementationType, Type serviceType, object implementationInstance)
+    internal static DecoratorContext Create(IComponentContext componentContext, Type implementationType, Type serviceType, object implementationInstance)
     {
-        return new DecoratorContext(implementationType, serviceType, implementationInstance);
+        return new DecoratorContext(componentContext, implementationType, serviceType, implementationInstance);
     }
 
     /// <summary>
@@ -64,6 +74,9 @@ internal DecoratorContext UpdateContext(object decoratorInstance)
         appliedDecoratorTypes.AddRange(AppliedDecoratorTypes);
         appliedDecoratorTypes.Add(decoratorInstance.GetType());
 
-        return new DecoratorContext(ImplementationType, ServiceType, decoratorInstance, appliedDecoratorTypes, appliedDecorators);
+        return new DecoratorContext(_componentContext, ImplementationType, ServiceType, decoratorInstance, appliedDecoratorTypes, appliedDecorators);
     }
+
+    /// <inheritdoc />
+    public object ResolveComponent(ResolveRequest request) => _componentContext.ResolveComponent(request);
 }
diff --git a/src/Autofac/Features/Decorators/DecoratorMiddleware.cs b/src/Autofac/Features/Decorators/DecoratorMiddleware.cs
index d4abceebc..4521c8d05 100644
--- a/src/Autofac/Features/Decorators/DecoratorMiddleware.cs
+++ b/src/Autofac/Features/Decorators/DecoratorMiddleware.cs
@@ -62,7 +62,7 @@ public void Execute(ResolveRequestContext context, Action<ResolveRequestContext>
 
         if (context.DecoratorContext is null)
         {
-            context.DecoratorContext = DecoratorContext.Create(context.Instance.GetType(), serviceType, context.Instance);
+            context.DecoratorContext = DecoratorContext.Create(context, context.Instance.GetType(), serviceType, context.Instance);
         }
         else
         {
diff --git a/src/Autofac/Features/Decorators/IDecoratorContext.cs b/src/Autofac/Features/Decorators/IDecoratorContext.cs
index 164065884..bffd31db5 100644
--- a/src/Autofac/Features/Decorators/IDecoratorContext.cs
+++ b/src/Autofac/Features/Decorators/IDecoratorContext.cs
@@ -6,7 +6,7 @@ namespace Autofac.Features.Decorators;
 /// <summary>
 /// Defines the context interface used during the decoration process.
 /// </summary>
-public interface IDecoratorContext
+public interface IDecoratorContext : IComponentContext
 {
     /// <summary>
     /// Gets the implementation type of the service that is being decorated.


Test patch:
diff --git a/test/Autofac.Specification.Test/Features/DecoratorTests.cs b/test/Autofac.Specification.Test/Features/DecoratorTests.cs
index 87382fa0b..77e1cc4c0 100644
--- a/test/Autofac.Specification.Test/Features/DecoratorTests.cs
+++ b/test/Autofac.Specification.Test/Features/DecoratorTests.cs
@@ -1,6 +1,7 @@
 ﻿// Copyright (c) Autofac Project. All rights reserved.
 // Licensed under the MIT License. See LICENSE in the project root for license information.
 
+using Autofac.Core;
 using Autofac.Features.Decorators;
 using Autofac.Features.Metadata;
 using Autofac.Features.OwnedInstances;
@@ -1252,6 +1253,40 @@ public void OpenGenericInModuleCanBeDecoratoredByDecoratorOutsideModuleWhereModu
         Assert.IsType<GenericComponent<int>>(((GenericDecorator<int>)instance).Decorated);
     }
 
+    [Fact]
+    public void DecoratorConditionalFunctionCanResolveFromContext()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterInstance(new ConditionalShouldDecorate { ShouldDecorate = true });
+        builder.RegisterType<ImplementorA>().As<IDecoratedService>();
+        builder.RegisterDecorator<DecoratorA, IDecoratedService>(context => context.Resolve<ConditionalShouldDecorate>().ShouldDecorate);
+
+        var container = builder.Build();
+
+        var instance = container.Resolve<IDecoratedService>();
+
+        Assert.IsType<DecoratorA>(instance);
+        Assert.IsType<ImplementorA>(instance.Decorated);
+    }
+
+    [Fact]
+    public void DecoratorConditionalFunctionThrowsCircularDependencyErrorOnResolveSelf()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterInstance(new ConditionalShouldDecorate { ShouldDecorate = true });
+        builder.RegisterType<ImplementorA>().As<IDecoratedService>();
+        builder.RegisterDecorator<DecoratorA, IDecoratedService>(context =>
+        {
+            // Can't do this; circular reference.
+            context.Resolve<IDecoratedService>();
+            return true;
+        });
+
+        var container = builder.Build();
+
+        Assert.Throws<DependencyResolutionException>(() => container.Resolve<IDecoratedService>());
+    }
+
     private class MyMetadata
     {
         public int A { get; set; }
@@ -1457,4 +1492,9 @@ public GenericDecorator(IGenericService<T> decorated)
 
         public IGenericService<T> Decorated { get; }
     }
+
+    private class ConditionalShouldDecorate
+    {
+        public bool ShouldDecorate { get; set; }
+    }
 }
diff --git a/test/Autofac.Test/Features/Decorators/DecoratorContextTests.cs b/test/Autofac.Test/Features/Decorators/DecoratorContextTests.cs
index bfc5636c3..24f3bdd78 100644
--- a/test/Autofac.Test/Features/Decorators/DecoratorContextTests.cs
+++ b/test/Autofac.Test/Features/Decorators/DecoratorContextTests.cs
@@ -12,7 +12,7 @@ public void CreateSetsContextToPreDecoratedState()
     {
         const string implementationInstance = "Initial";
 
-        var context = DecoratorContext.Create(typeof(string), typeof(string), implementationInstance);
+        var context = DecoratorContext.Create(Factory.CreateEmptyContext(), typeof(string), typeof(string), implementationInstance);
 
         Assert.Equal(typeof(string), context.ServiceType);
         Assert.Equal(typeof(string), context.ImplementationType);
@@ -25,7 +25,7 @@ public void CreateSetsContextToPreDecoratedState()
     public void UpdateAddsDecoratorStateToContext()
     {
         const string implementationInstance = "Initial";
-        var context = DecoratorContext.Create(typeof(string), typeof(string), implementationInstance);
+        var context = DecoratorContext.Create(Factory.CreateEmptyContext(), typeof(string), typeof(string), implementationInstance);
 
         const string decoratorA = "DecoratorA";
         context = context.UpdateContext(decoratorA);
diff --git a/test/Autofac.Test/Features/Decorators/DecoratorServiceTests.cs b/test/Autofac.Test/Features/Decorators/DecoratorServiceTests.cs
index 10cb15a62..03c64c850 100644
--- a/test/Autofac.Test/Features/Decorators/DecoratorServiceTests.cs
+++ b/test/Autofac.Test/Features/Decorators/DecoratorServiceTests.cs
@@ -19,7 +19,7 @@ public void ConditionDefaultsToTrueWhenNotProvided()
     {
         var service = new DecoratorService(typeof(string));
 
-        var context = DecoratorContext.Create(typeof(string), typeof(string), "A");
+        var context = DecoratorContext.Create(Factory.CreateEmptyContext(), typeof(string), typeof(string), "A");
 
         Assert.True(service.Condition(context));
     }