diff --git a/src/Autofac/Core/ComponentRegistrationExtensions.cs b/src/Autofac/Core/ComponentRegistrationExtensions.cs index 523c05f3a..1cacfdea4 100644 --- a/src/Autofac/Core/ComponentRegistrationExtensions.cs +++ b/src/Autofac/Core/ComponentRegistrationExtensions.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Autofac.Core.Lifetime; +using Autofac.Core.Resolving.Pipeline; namespace Autofac.Core { @@ -40,5 +41,32 @@ public static IEnumerable MatchingLifetimeScopeTags(this IComponentRegis return Enumerable.Empty(); } + + /// + /// Provides access to the registration's pipeline builder, allowing custom middleware to be added. + /// + /// The component registration. + /// An action that can configure the registration's pipeline. + /// + /// Attaching to this event after a component registration + /// has already been built will throw an exception. + /// + public static void ConfigurePipeline(this IComponentRegistration componentRegistration, Action configurationAction) + { + if (componentRegistration is null) + { + throw new ArgumentNullException(nameof(componentRegistration)); + } + + if (configurationAction is null) + { + throw new ArgumentNullException(nameof(configurationAction)); + } + + componentRegistration.PipelineBuilding += (sender, pipeline) => + { + configurationAction(pipeline); + }; + } } } diff --git a/src/Autofac/Core/IComponentRegistration.cs b/src/Autofac/Core/IComponentRegistration.cs index ecd1cecb9..fbff3dd94 100644 --- a/src/Autofac/Core/IComponentRegistration.cs +++ b/src/Autofac/Core/IComponentRegistration.cs @@ -69,7 +69,11 @@ public interface IComponentRegistration : IDisposable /// Provides an event that will be invoked just before a pipeline is built, and can be used to add additional middleware /// at that point. /// - public event EventHandler PipelineBuilding; + /// + /// Attaching to this event after a component registration + /// has already been built will throw an exception. + /// + event EventHandler PipelineBuilding; /// /// Builds the resolve pipeline. diff --git a/src/Autofac/Core/Registration/ComponentRegistration.cs b/src/Autofac/Core/Registration/ComponentRegistration.cs index c789cb9d1..b68066760 100644 --- a/src/Autofac/Core/Registration/ComponentRegistration.cs +++ b/src/Autofac/Core/Registration/ComponentRegistration.cs @@ -20,6 +20,8 @@ public class ComponentRegistration : Disposable, IComponentRegistration { private readonly IComponentRegistration? _target; private readonly IResolvePipelineBuilder _lateBuildPipeline; + + private EventHandler? _pipelineBuildEvent; private IResolvePipeline? _builtComponentPipeline; /// @@ -200,7 +202,23 @@ public ComponentRegistration( public RegistrationOptions Options { get; } /// - public event EventHandler? PipelineBuilding; + public event EventHandler? PipelineBuilding + { + add + { + if (_builtComponentPipeline is object) + { + throw new InvalidOperationException(ComponentRegistrationResources.PipelineAlreadyBuilt); + } + + _pipelineBuildEvent += value; + } + + remove + { + _pipelineBuildEvent -= value; + } + } /// public IResolvePipeline ResolvePipeline @@ -218,9 +236,13 @@ public void BuildResolvePipeline(IComponentRegistryServices registryServices) return; } - if (PipelineBuilding is object) + if (_pipelineBuildEvent is object) { - PipelineBuilding.Invoke(this, _lateBuildPipeline); + _pipelineBuildEvent.Invoke(this, _lateBuildPipeline); + + // Reset the PipelineBuilding event so we don't accidentally retain + // references we don't need to. + _pipelineBuildEvent = null; } ResolvePipeline = BuildResolvePipeline(registryServices, _lateBuildPipeline); diff --git a/src/Autofac/Core/Registration/ComponentRegistrationExtensions.cs b/src/Autofac/Core/Registration/ComponentRegistrationExtensions.cs index f1bc62950..879c5e888 100644 --- a/src/Autofac/Core/Registration/ComponentRegistrationExtensions.cs +++ b/src/Autofac/Core/Registration/ComponentRegistrationExtensions.cs @@ -4,7 +4,7 @@ namespace Autofac.Core.Registration { /// - /// Extension methods for component registrations. + /// Internal Extension methods for component registrations. /// internal static class ComponentRegistrationExtensions { diff --git a/src/Autofac/Core/Registration/ComponentRegistrationResources.Designer.cs b/src/Autofac/Core/Registration/ComponentRegistrationResources.Designer.cs index dfd473bd6..6ff4a1ead 100644 --- a/src/Autofac/Core/Registration/ComponentRegistrationResources.Designer.cs +++ b/src/Autofac/Core/Registration/ComponentRegistrationResources.Designer.cs @@ -69,6 +69,15 @@ internal static string ComponentPipelineHasNotBeenBuilt { } } + /// + /// Looks up a localized string similar to Component pipeline has already been built, and cannot be modified.. + /// + internal static string PipelineAlreadyBuilt { + get { + return ResourceManager.GetString("PipelineAlreadyBuilt", resourceCulture); + } + } + /// /// Looks up a localized string similar to Not Built. /// diff --git a/src/Autofac/Core/Registration/ComponentRegistrationResources.resx b/src/Autofac/Core/Registration/ComponentRegistrationResources.resx index 449faad6e..773d328d6 100644 --- a/src/Autofac/Core/Registration/ComponentRegistrationResources.resx +++ b/src/Autofac/Core/Registration/ComponentRegistrationResources.resx @@ -120,6 +120,9 @@ Component pipeline has not yet been built. + + Component pipeline has already been built, and cannot be modified. + Not Built diff --git a/test/Autofac.Test/ContainerBuilderTests.cs b/test/Autofac.Test/ContainerBuilderTests.cs index d6e2fb77d..dfaf74771 100644 --- a/test/Autofac.Test/ContainerBuilderTests.cs +++ b/test/Autofac.Test/ContainerBuilderTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Autofac.Core; using Autofac.Core.Resolving.Pipeline; using Xunit; @@ -54,13 +55,13 @@ public void WhenComponentIsRegisteredDuringResolveItShouldRaiseTheRegisteredEven var builder = new ContainerBuilder(); builder.RegisterCallback(x => x.Registered += (o, registration) => { - registration.ComponentRegistration.PipelineBuilding += (o, builder) => + registration.ComponentRegistration.ConfigurePipeline(builder => builder.Use(PipelinePhase.Activation, (ctxt, next) => { next(ctxt); activatedInstances.Add(ctxt.Instance); - }); + })); }); builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)); diff --git a/test/Autofac.Test/Core/ComponentRegistrationExtensionsTests.cs b/test/Autofac.Test/Core/ComponentRegistrationExtensionsTests.cs index 66596343b..12d292993 100644 --- a/test/Autofac.Test/Core/ComponentRegistrationExtensionsTests.cs +++ b/test/Autofac.Test/Core/ComponentRegistrationExtensionsTests.cs @@ -59,5 +59,30 @@ public void MatchingLifetimeScopeTags_Singleton() var c = b.Build(); Assert.Empty(c.RegistrationFor().MatchingLifetimeScopeTags()); } + + [Fact] + public void ConfigurePipelineValidatesNullHandler() + { + var services = new Service[] { new TypedService(typeof(object)) }; + + var registration = Factory.CreateSingletonRegistration(services, Factory.CreateProvidedInstanceActivator(new object())); + + Assert.Throws(() => registration.ConfigurePipeline(null)); + } + + [Fact] + public void ConfigurePipelineShouldFailIfAlreadyBuilt() + { + var services = new Service[] { new TypedService(typeof(object)) }; + + var registration = Factory.CreateSingletonRegistration(services, Factory.CreateProvidedInstanceActivator(new object())); + + var builder = Factory.CreateEmptyComponentRegistryBuilder(); + builder.Register(registration); + + builder.Build(); + + Assert.Throws(() => registration.ConfigurePipeline(pipeline => { })); + } } } diff --git a/test/Autofac.Test/Core/ComponentRegistrationTests.cs b/test/Autofac.Test/Core/ComponentRegistrationTests.cs index 0a2f3e579..997ed2ed3 100644 --- a/test/Autofac.Test/Core/ComponentRegistrationTests.cs +++ b/test/Autofac.Test/Core/ComponentRegistrationTests.cs @@ -47,5 +47,20 @@ public void ShouldHaveAscendingRegistrationOrderMetadataValue() Assert.Same(registration2, orderedRegistrations[1]); Assert.Same(registration3, orderedRegistrations[2]); } + + [Fact] + public void AttachingToPipelineBuildingShouldFailIfAlreadyBuilt() + { + var services = new Service[] { new TypedService(typeof(object)) }; + + var registration = Factory.CreateSingletonRegistration(services, Factory.CreateProvidedInstanceActivator(new object())); + + var builder = Factory.CreateEmptyComponentRegistryBuilder(); + builder.Register(registration); + + builder.Build(); + + Assert.Throws(() => registration.PipelineBuilding += (s, p) => { }); + } } } diff --git a/test/Autofac.Test/Core/ContainerTests.cs b/test/Autofac.Test/Core/ContainerTests.cs index a87efd310..d648925c6 100644 --- a/test/Autofac.Test/Core/ContainerTests.cs +++ b/test/Autofac.Test/Core/ContainerTests.cs @@ -208,10 +208,10 @@ private class ReplaceInstanceModule : Module { protected override void AttachToComponentRegistration(IComponentRegistryBuilder componentRegistry, IComponentRegistration registration) { - registration.PipelineBuilding += (o, builder) => builder.Use(PipelinePhase.Activation, (ctxt, next) => + registration.ConfigurePipeline(builder => builder.Use(PipelinePhase.Activation, (ctxt, next) => { ctxt.Instance = new ReplaceableComponent { IsReplaced = true }; - }); + })); } }