Skip to content

Commit

Permalink
Merge pull request #1236 from alistairjevans/componentreg-configurepi…
Browse files Browse the repository at this point in the history
…peline

Add ConfigurePipeline to IComponentRegistration
  • Loading branch information
tillig authored Dec 14, 2020
2 parents 8d873e6 + 7d81987 commit 0afd90f
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 9 deletions.
28 changes: 28 additions & 0 deletions src/Autofac/Core/ComponentRegistrationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using Autofac.Core.Lifetime;
using Autofac.Core.Resolving.Pipeline;

namespace Autofac.Core
{
Expand Down Expand Up @@ -40,5 +41,32 @@ public static IEnumerable<object> MatchingLifetimeScopeTags(this IComponentRegis

return Enumerable.Empty<object>();
}

/// <summary>
/// Provides access to the registration's pipeline builder, allowing custom middleware to be added.
/// </summary>
/// <param name="componentRegistration">The component registration.</param>
/// <param name="configurationAction">An action that can configure the registration's pipeline.</param>
/// <exception cref="InvalidOperationException">
/// Attaching to this event after a component registration
/// has already been built will throw an exception.
/// </exception>
public static void ConfigurePipeline(this IComponentRegistration componentRegistration, Action<IResolvePipelineBuilder> 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);
};
}
}
}
6 changes: 5 additions & 1 deletion src/Autofac/Core/IComponentRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
public event EventHandler<IResolvePipelineBuilder> PipelineBuilding;
/// <exception cref="InvalidOperationException">
/// Attaching to this event after a component registration
/// has already been built will throw an exception.
/// </exception>
event EventHandler<IResolvePipelineBuilder> PipelineBuilding;

/// <summary>
/// Builds the resolve pipeline.
Expand Down
28 changes: 25 additions & 3 deletions src/Autofac/Core/Registration/ComponentRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class ComponentRegistration : Disposable, IComponentRegistration
{
private readonly IComponentRegistration? _target;
private readonly IResolvePipelineBuilder _lateBuildPipeline;

private EventHandler<IResolvePipelineBuilder>? _pipelineBuildEvent;
private IResolvePipeline? _builtComponentPipeline;

/// <summary>
Expand Down Expand Up @@ -200,7 +202,23 @@ public ComponentRegistration(
public RegistrationOptions Options { get; }

/// <inheritdoc />
public event EventHandler<IResolvePipelineBuilder>? PipelineBuilding;
public event EventHandler<IResolvePipelineBuilder>? PipelineBuilding
{
add
{
if (_builtComponentPipeline is object)
{
throw new InvalidOperationException(ComponentRegistrationResources.PipelineAlreadyBuilt);
}

_pipelineBuildEvent += value;
}

remove
{
_pipelineBuildEvent -= value;
}
}

/// <inheritdoc />
public IResolvePipeline ResolvePipeline
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Autofac.Core.Registration
{
/// <summary>
/// Extension methods for component registrations.
/// Internal Extension methods for component registrations.
/// </summary>
internal static class ComponentRegistrationExtensions
{
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@
<data name="ComponentPipelineHasNotBeenBuilt" xml:space="preserve">
<value>Component pipeline has not yet been built.</value>
</data>
<data name="PipelineAlreadyBuilt" xml:space="preserve">
<value>Component pipeline has already been built, and cannot be modified.</value>
</data>
<data name="PipelineNotBuilt" xml:space="preserve">
<value>Not Built</value>
</data>
Expand Down
5 changes: 3 additions & 2 deletions test/Autofac.Test/ContainerBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using Autofac.Core;
using Autofac.Core.Resolving.Pipeline;
using Xunit;

Expand Down Expand Up @@ -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<>));
Expand Down
25 changes: 25 additions & 0 deletions test/Autofac.Test/Core/ComponentRegistrationExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,30 @@ public void MatchingLifetimeScopeTags_Singleton()
var c = b.Build();
Assert.Empty(c.RegistrationFor<DivideByZeroException>().MatchingLifetimeScopeTags());
}

[Fact]
public void ConfigurePipelineValidatesNullHandler()
{
var services = new Service[] { new TypedService(typeof(object)) };

var registration = Factory.CreateSingletonRegistration(services, Factory.CreateProvidedInstanceActivator(new object()));

Assert.Throws<ArgumentNullException>(() => 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<InvalidOperationException>(() => registration.ConfigurePipeline(pipeline => { }));
}
}
}
15 changes: 15 additions & 0 deletions test/Autofac.Test/Core/ComponentRegistrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<InvalidOperationException>(() => registration.PipelineBuilding += (s, p) => { });
}
}
}
4 changes: 2 additions & 2 deletions test/Autofac.Test/Core/ContainerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
});
}));
}
}

Expand Down

0 comments on commit 0afd90f

Please sign in to comment.