diff --git a/Autofac.sln.DotSettings b/Autofac.sln.DotSettings index 8c213009d..164b29a23 100644 --- a/Autofac.sln.DotSettings +++ b/Autofac.sln.DotSettings @@ -19,4 +19,5 @@ True True True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/src/Autofac/Features/Decorators/InstanceDecorator.cs b/src/Autofac/Features/Decorators/InstanceDecorator.cs index ae170033c..913332e17 100644 --- a/src/Autofac/Features/Decorators/InstanceDecorator.cs +++ b/src/Autofac/Features/Decorators/InstanceDecorator.cs @@ -26,6 +26,7 @@ using System.Collections.Generic; using System.Linq; using Autofac.Core; +using Autofac.Core.Registration; namespace Autofac.Features.Decorators { @@ -37,6 +38,11 @@ internal static class InstanceDecorator IComponentContext context, IEnumerable parameters) { + var instanceType = instance.GetType(); + + // Issue #965: Do not apply the decorator if already applied, or if the registration is for an adapter. + if (registration.IsAdapting() || registration.Activator.LimitType != instanceType) return instance; + var decoratorRegistrations = context.ComponentRegistry.DecoratorsFor(registration); // ReSharper disable once PossibleMultipleEnumeration @@ -56,7 +62,7 @@ internal static class InstanceDecorator var serviceType = decorators[0].Service.ServiceType; var resolveParameters = parameters as Parameter[] ?? parameters.ToArray(); - var decoratorContext = DecoratorContext.Create(instance.GetType(), serviceType, instance); + var decoratorContext = DecoratorContext.Create(instanceType, serviceType, instance); foreach (var decorator in decorators) { diff --git a/test/Autofac.Specification.Test/Features/DecoratorTests.cs b/test/Autofac.Specification.Test/Features/DecoratorTests.cs index 5859dcc35..ed16e69a6 100644 --- a/test/Autofac.Specification.Test/Features/DecoratorTests.cs +++ b/test/Autofac.Specification.Test/Features/DecoratorTests.cs @@ -98,7 +98,9 @@ public void CanResolveDecoratorWithFunc() var factory = container.Resolve>(); - Assert.IsType(factory()); + var decoratedService = factory(); + Assert.IsType(decoratedService); + Assert.IsType(decoratedService.Decorated); } [Fact] @@ -111,7 +113,9 @@ public void CanResolveDecoratorWithLazy() var lazy = container.Resolve>(); - Assert.IsType(lazy.Value); + var decoratedService = lazy.Value; + Assert.IsType(decoratedService); + Assert.IsType(decoratedService.Decorated); } [Fact] @@ -259,13 +263,12 @@ public void DecoratorAndDecoratedBothDisposedWhenSingleInstance() Assert.Equal(1, decorated.DisposeCallCount); } - [Fact(Skip = "Issue #963: Need to figure out how to track which decorators have already been applied.")] - public void DecoratorAppliedOnlyOnceToComponent() + [Fact] + public void DecoratorAppliedOnlyOnceToComponentWithExternalRegistrySource() { // #965: A nested lifetime scope that has a registration lambda // causes the decorator to be applied twice - once for the container - // level, once for the scope level. This doesn't seem to happen - // if there's no registration lambda. + // level, and once for the scope level. var builder = new ContainerBuilder(); builder.RegisterType().As(); builder.RegisterDecorator(); @@ -288,6 +291,7 @@ public void DecoratorCanBeAppliedToServiceRegisteredInChildLifetimeScope() var instance = scope.Resolve(); Assert.IsType(instance); + Assert.IsType(instance.Decorated); } [Fact] @@ -301,6 +305,7 @@ public void DecoratorCanBeRegisteredInChildLifetimeScope() var scopedInstance = scope.Resolve(); Assert.IsType(scopedInstance); + Assert.IsType(scopedInstance.Decorated); var rootInstance = container.Resolve(); Assert.IsType(rootInstance); @@ -602,7 +607,7 @@ private abstract class Decorator : IDecoratedService { protected Decorator(IDecoratedService decorated) { - this.Decorated = decorated; + Decorated = decorated; } public IDecoratedService Decorated { get; } @@ -635,6 +640,7 @@ public DecoratorWithContextA(IDecoratedService decorated, IDecoratorContext cont public IDecoratorContext Context { get; } } + // ReSharper disable once ClassNeverInstantiated.Local private class DecoratorWithContextB : Decorator, IDecoratorWithContext { public DecoratorWithContextB(IDecoratedService decorated, IDecoratorContext context) @@ -657,6 +663,7 @@ public DecoratorWithParameter(IDecoratedService decorated, string parameter) public string Parameter { get; } } + // ReSharper disable once ClassNeverInstantiated.Local private class DisposableDecorator : Decorator, IDisposable { public DisposableDecorator(IDecoratedService decorated) @@ -672,6 +679,7 @@ public void Dispose() } } + // ReSharper disable once ClassNeverInstantiated.Local private class DisposableImplementor : IDecoratedService, IDisposable { public IDecoratedService Decorated => this; @@ -689,11 +697,13 @@ private class ImplementorA : IDecoratedService public IDecoratedService Decorated => this; } + // ReSharper disable once ClassNeverInstantiated.Local private class ImplementorB : IDecoratedService { public IDecoratedService Decorated => this; } + // ReSharper disable once ClassNeverInstantiated.Local private class ImplementorWithParameters : IDecoratedService { public ImplementorWithParameters(string parameter) @@ -706,11 +716,13 @@ public ImplementorWithParameters(string parameter) public string Parameter { get; } } + // ReSharper disable once ClassNeverInstantiated.Local private class ImplementorWithSomeOtherService : IDecoratedService, ISomeOtherService { public IDecoratedService Decorated => this; } + // ReSharper disable once ClassNeverInstantiated.Local private class StartableImplementation : IDecoratedService, IStartable { public IDecoratedService Decorated => this; diff --git a/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs b/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs index 670379fe8..d3c8aa874 100644 --- a/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs +++ b/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs @@ -14,6 +14,7 @@ public interface IService { } + // ReSharper disable once UnusedTypeParameter public interface ISomeOtherService { } @@ -295,7 +296,9 @@ public void CanResolveDecoratorWithFunc() var factory = container.Resolve>>(); - Assert.IsType>(factory()); + var decoratedService = factory(); + Assert.IsType>(decoratedService); + Assert.IsType>(decoratedService.Decorated); } [Fact] @@ -308,7 +311,9 @@ public void CanResolveDecoratorWithLazy() var lazy = container.Resolve>>(); - Assert.IsType>(lazy.Value); + var decoratedService = lazy.Value; + Assert.IsType>(decoratedService); + Assert.IsType>(decoratedService.Decorated); } [Fact] @@ -528,6 +533,23 @@ public void CanResolveClosedGenericDecoratorOverOpenGeneric() Assert.IsType>(instance.Decorated); } + [Fact] + public void DecoratorAppliedOnlyOnceToComponentWithExternalRegistrySource() + { + // #965: A nested lifetime scope that has a registration lambda + // causes the decorator to be applied twice - once for the container + // level, and once for the scope level. + var builder = new ContainerBuilder(); + builder.RegisterGeneric(typeof(ImplementorA<>)).As(typeof(IDecoratedService<>)); + builder.RegisterGenericDecorator(typeof(DecoratorA<>), typeof(IDecoratedService<>)); + var container = builder.Build(); + + var scope = container.BeginLifetimeScope(b => { }); + var service = scope.Resolve>(); + Assert.IsType>(service); + Assert.IsType>(service.Decorated); + } + [Fact] public void DecoratorCanBeAppliedToServiceRegisteredInChildLifetimeScope() { @@ -539,6 +561,7 @@ public void DecoratorCanBeAppliedToServiceRegisteredInChildLifetimeScope() var instance = scope.Resolve>(); Assert.IsType>(instance); + Assert.IsType>(instance.Decorated); } [Fact] @@ -552,6 +575,7 @@ public void DecoratorCanBeRegisteredInChildLifetimeScope() var scopedInstance = scope.Resolve>(); Assert.IsType>(scopedInstance); + Assert.IsType>(scopedInstance.Decorated); var rootInstance = container.Resolve>(); Assert.IsType>(rootInstance);