diff --git a/.editorconfig b/.editorconfig
index 40257596e..3a242268e 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -15,6 +15,18 @@ insert_final_newline = true
indent_size = 4
charset = utf-8-bom
+; Force VS to recommend underscore at the start of created private fields.
+[*.{cs,vb}]
+dotnet_naming_rule.private_members_with_underscore.symbols = private_fields
+dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore
+dotnet_naming_rule.private_members_with_underscore.severity = suggestion
+
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private
+
+dotnet_naming_style.prefix_underscore.capitalization = camel_case
+dotnet_naming_style.prefix_underscore.required_prefix = _
+
; .NET project files and MSBuild - match defaults for VS
[*.{csproj,nuspec,proj,projitems,props,shproj,targets,vbproj,vcxproj,vcxproj.filters,vsixmanifest,vsct}]
indent_size = 2
diff --git a/src/Autofac/Autofac.csproj b/src/Autofac/Autofac.csproj
index 944fcafd7..85891e25d 100644
--- a/src/Autofac/Autofac.csproj
+++ b/src/Autofac/Autofac.csproj
@@ -46,10 +46,10 @@
-
+
All
-
+
All
@@ -129,6 +129,11 @@
True
ContainerResources.resx
+
+ True
+ True
+ TracerMessages.resx
+
True
True
@@ -154,16 +159,26 @@
True
ServiceRegistrationInfoResources.resx
-
+
True
True
- CircularDependencyDetectorResources.resx
+ CircularDependencyDetectorMessages.resx
True
True
ComponentActivationResources.resx
+
+ True
+ True
+ MiddlewareMessages.resx
+
+
+ True
+ True
+ ResolvePipelineBuilderMessages.resx
+
True
True
@@ -330,6 +345,10 @@
ResXFileCodeGenerator
ContainerResources.Designer.cs
+
+ ResXFileCodeGenerator
+ TracerMessages.Designer.cs
+
ResXFileCodeGenerator
LifetimeScopeResources.Designer.cs
@@ -350,14 +369,22 @@
ResXFileCodeGenerator
ServiceRegistrationInfoResources.Designer.cs
-
+
ResXFileCodeGenerator
- CircularDependencyDetectorResources.Designer.cs
+ CircularDependencyDetectorMessages.Designer.cs
ResXFileCodeGenerator
ComponentActivationResources.Designer.cs
+
+ ResXFileCodeGenerator
+ MiddlewareMessages.Designer.cs
+
+
+ ResXFileCodeGenerator
+ ResolvePipelineBuilderMessages.Designer.cs
+
ResXFileCodeGenerator
ResolveOperationResources.Designer.cs
diff --git a/src/Autofac/Builder/IRegistrationBuilder.cs b/src/Autofac/Builder/IRegistrationBuilder.cs
index c1d60684c..ee5f3b176 100644
--- a/src/Autofac/Builder/IRegistrationBuilder.cs
+++ b/src/Autofac/Builder/IRegistrationBuilder.cs
@@ -27,6 +27,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using Autofac.Core;
+using Autofac.Core.Resolving.Pipeline;
namespace Autofac.Builder
{
@@ -39,6 +40,12 @@ namespace Autofac.Builder
/// Registration style type.
public interface IRegistrationBuilder
{
+ ///
+ /// Gets the registration data.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ RegistrationData RegistrationData { get; }
+
///
/// Gets the activator data.
///
@@ -51,11 +58,8 @@ public interface IRegistrationBuilder
- /// Gets the registration data.
- ///
[EditorBrowsable(EditorBrowsableState.Never)]
- RegistrationData RegistrationData { get; }
+ IResolvePipelineBuilder ResolvePipeline { get; }
///
/// Configure the component so that instances are never disposed by the container.
diff --git a/src/Autofac/Builder/RegistrationBuilder.cs b/src/Autofac/Builder/RegistrationBuilder.cs
index fc4ebfbb7..cbb20b96a 100644
--- a/src/Autofac/Builder/RegistrationBuilder.cs
+++ b/src/Autofac/Builder/RegistrationBuilder.cs
@@ -30,7 +30,9 @@
using System.Reflection;
using Autofac.Core;
using Autofac.Core.Activators.Delegate;
+using Autofac.Core.Pipeline;
using Autofac.Core.Registration;
+using Autofac.Core.Resolving.Pipeline;
namespace Autofac.Builder
{
@@ -135,6 +137,7 @@ public static IComponentRegistration CreateRegistrationId of the registration.
/// Registration data.
/// Activator.
+ /// The component registration's resolve pipeline builder.
/// Services provided by the registration.
/// An IComponentRegistration.
public static IComponentRegistration CreateRegistration(
Guid id,
RegistrationData data,
IInstanceActivator activator,
+ IResolvePipelineBuilder pipelineBuilder,
Service[] services)
{
- return CreateRegistration(id, data, activator, services, null);
+ return CreateRegistration(id, data, activator, pipelineBuilder, services, null);
}
///
@@ -163,6 +168,7 @@ public static IComponentRegistration CreateRegistration(
/// Id of the registration.
/// Registration data.
/// Activator.
+ /// The component registration's resolve pipeline builder.
/// Services provided by the registration.
/// Optional; target registration.
/// Optional; whether the registration is a 1:1 adapters on top of another component.
@@ -174,12 +180,14 @@ public static IComponentRegistration CreateRegistration(
Guid id,
RegistrationData data,
IInstanceActivator activator,
+ IResolvePipelineBuilder pipelineBuilder,
Service[] services,
IComponentRegistration? target,
bool isAdapterForIndividualComponent = false)
{
if (activator == null) throw new ArgumentNullException(nameof(activator));
if (data == null) throw new ArgumentNullException(nameof(data));
+ if (pipelineBuilder is null) throw new ArgumentNullException(nameof(pipelineBuilder));
if (services == null) throw new ArgumentNullException(nameof(services));
var limitType = activator.LimitType;
@@ -200,7 +208,12 @@ public static IComponentRegistration CreateRegistration(
}
}
+ // The pipeline builder fed into the registration is a copy, so that the original builder cannot be edited after the registration has been created,
+ // and the original does not contain any auto-added items.
+ var clonedPipelineBuilder = pipelineBuilder.Clone();
+
IComponentRegistration registration;
+
if (target == null)
{
registration = new ComponentRegistration(
@@ -209,6 +222,7 @@ public static IComponentRegistration CreateRegistration(
data.Lifetime,
data.Sharing,
data.Ownership,
+ clonedPipelineBuilder,
services,
data.Metadata);
}
@@ -220,21 +234,13 @@ public static IComponentRegistration CreateRegistration(
data.Lifetime,
data.Sharing,
data.Ownership,
+ clonedPipelineBuilder,
services,
data.Metadata,
target,
isAdapterForIndividualComponent);
}
- foreach (var p in data.PreparingHandlers)
- registration.Preparing += p;
-
- foreach (var ac in data.ActivatingHandlers)
- registration.Activating += ac;
-
- foreach (var ad in data.ActivatedHandlers)
- registration.Activated += ad;
-
return registration;
}
diff --git a/src/Autofac/Builder/RegistrationBuilder{TLimit,TActivatorData,TRegistrationStyle}.cs b/src/Autofac/Builder/RegistrationBuilder{TLimit,TActivatorData,TRegistrationStyle}.cs
index 496acf5f8..49312044e 100644
--- a/src/Autofac/Builder/RegistrationBuilder{TLimit,TActivatorData,TRegistrationStyle}.cs
+++ b/src/Autofac/Builder/RegistrationBuilder{TLimit,TActivatorData,TRegistrationStyle}.cs
@@ -30,6 +30,8 @@
using Autofac.Core;
using Autofac.Core.Activators.Reflection;
using Autofac.Core.Lifetime;
+using Autofac.Core.Resolving;
+using Autofac.Core.Resolving.Pipeline;
using Autofac.Features.OwnedInstances;
namespace Autofac.Builder
@@ -46,6 +48,7 @@ public RegistrationBuilder(Service defaultService, TActivatorData activatorData,
ActivatorData = activatorData;
RegistrationStyle = style;
RegistrationData = new RegistrationData(defaultService);
+ ResolvePipeline = new ResolvePipelineBuilder();
}
///
@@ -66,6 +69,9 @@ public RegistrationBuilder(Service defaultService, TActivatorData activatorData,
[EditorBrowsable(EditorBrowsableState.Never)]
public RegistrationData RegistrationData { get; }
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public IResolvePipelineBuilder ResolvePipeline { get; }
+
///
/// Configure the component so that instances are never disposed by the container.
///
@@ -378,7 +384,18 @@ public IRegistrationBuilder OnPrepar
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
- RegistrationData.PreparingHandlers.Add((s, e) => handler(e));
+ ResolvePipeline.Use(nameof(OnPreparing), PipelinePhase.ParameterSelection, (ctxt, next) =>
+ {
+ var args = new PreparingEventArgs(ctxt, ctxt.Service, ctxt.Registration, ctxt.Parameters);
+
+ handler(args);
+
+ ctxt.ChangeParameters(args.Parameters);
+
+ // Go down the pipeline now.
+ next(ctxt);
+ });
+
return this;
}
@@ -391,12 +408,18 @@ public IRegistrationBuilder OnActiva
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
- RegistrationData.ActivatingHandlers.Add((s, e) =>
+ // Activation events have to run at the start of the phase, to make sure
+ // that the event handlers run in the same order as they were added to the registration.
+ ResolvePipeline.Use(nameof(OnActivating), PipelinePhase.Activation, MiddlewareInsertionMode.StartOfPhase, (ctxt, next) =>
{
- var args = new ActivatingEventArgs(e.Context, e.Component, e.Parameters, (TLimit)e.Instance, e.Service);
+ next(ctxt);
+
+ var args = new ActivatingEventArgs(ctxt, ctxt.Service, ctxt.Registration, ctxt.Parameters, (TLimit)ctxt.Instance!);
+
handler(args);
- e.Instance = args.Instance;
+ ctxt.Instance = args.Instance;
});
+
return this;
}
@@ -409,8 +432,32 @@ public IRegistrationBuilder OnActiva
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
- RegistrationData.ActivatedHandlers.Add(
- (s, e) => handler(new ActivatedEventArgs(e.Context, e.Component, e.Parameters, (TLimit)e.Instance, e.Service)));
+ // Need to insert OnActivated at the start of the phase, to ensure we attach to RequestCompleting in the same order
+ // as calls to OnActivated.
+ ResolvePipeline.Use(nameof(OnActivated), PipelinePhase.Activation, MiddlewareInsertionMode.StartOfPhase, (ctxt, next) =>
+ {
+ // Go down the pipeline first.
+ next(ctxt);
+
+ if (!ctxt.NewInstanceActivated)
+ {
+ return;
+ }
+
+ // Make sure we use the instance at this point, before it is replaced by any decorators.
+ var newInstance = (TLimit)ctxt.Instance!;
+
+ // In order to behave in the same manner as the original activation handler,
+ // we need to attach to the RequestCompleting event so these run at the end after everything else.
+ ctxt.RequestCompleting += (sender, evArgs) =>
+ {
+ var ctxt = evArgs.RequestContext;
+ var args = new ActivatedEventArgs(ctxt, ctxt.Service, ctxt.Registration, ctxt.Parameters, newInstance);
+
+ handler(args);
+ };
+ });
+
return this;
}
@@ -423,10 +470,30 @@ public IRegistrationBuilder OnActiva
/// A registration builder allowing further configuration of the component.
public IRegistrationBuilder PropertiesAutowired(IPropertySelector propertySelector, bool allowCircularDependencies)
{
- if (allowCircularDependencies)
- RegistrationData.ActivatedHandlers.Add((s, e) => AutowiringPropertyInjector.InjectProperties(e.Context, e.Instance, propertySelector, e.Parameters));
- else
- RegistrationData.ActivatingHandlers.Add((s, e) => AutowiringPropertyInjector.InjectProperties(e.Context, e.Instance, propertySelector, e.Parameters));
+ ResolvePipeline.Use(nameof(PropertiesAutowired), PipelinePhase.Activation, (ctxt, next) =>
+ {
+ // Continue down the pipeline.
+ next(ctxt);
+
+ if (!ctxt.NewInstanceActivated)
+ {
+ return;
+ }
+
+ if (allowCircularDependencies)
+ {
+ // If we are allowing circular deps, then we need to run when all requests have completed (similar to Activated).
+ ctxt.RequestCompleting += (o, args) =>
+ {
+ var evCtxt = args.RequestContext;
+ AutowiringPropertyInjector.InjectProperties(evCtxt, evCtxt.Instance!, propertySelector, evCtxt.Parameters);
+ };
+ }
+ else
+ {
+ AutowiringPropertyInjector.InjectProperties(ctxt, ctxt.Instance!, propertySelector, ctxt.Parameters);
+ }
+ });
return this;
}
diff --git a/src/Autofac/Builder/RegistrationData.cs b/src/Autofac/Builder/RegistrationData.cs
index d5ba3579d..c3731c466 100644
--- a/src/Autofac/Builder/RegistrationData.cs
+++ b/src/Autofac/Builder/RegistrationData.cs
@@ -28,6 +28,8 @@
using System.Linq;
using Autofac.Core;
using Autofac.Core.Lifetime;
+using Autofac.Core.Pipeline;
+using Autofac.Core.Registration;
using Autofac.Util;
namespace Autofac.Builder
@@ -144,21 +146,6 @@ public IComponentLifetime Lifetime
///
public DeferredCallback? DeferredCallback { get; set; }
- ///
- /// Gets the handlers for the Preparing event.
- ///
- public ICollection> PreparingHandlers { get; } = new List>();
-
- ///
- /// Gets the handlers for the Activating event.
- ///
- public ICollection>> ActivatingHandlers { get; } = new List>>();
-
- ///
- /// Gets the handlers for the Activated event.
- ///
- public ICollection>> ActivatedHandlers { get; } = new List>>();
-
///
/// Copies the contents of another RegistrationData object into this one.
///
@@ -182,9 +169,6 @@ public void CopyFrom(RegistrationData that, bool includeDefaultService)
AddAll(_services, that._services);
AddAll(Metadata, that.Metadata.Where(m => m.Key != MetadataKeys.RegistrationOrderMetadataKey));
- AddAll(PreparingHandlers, that.PreparingHandlers);
- AddAll(ActivatingHandlers, that.ActivatingHandlers);
- AddAll(ActivatedHandlers, that.ActivatedHandlers);
}
private static void AddAll(ICollection to, IEnumerable from)
diff --git a/src/Autofac/ContainerBuilder.cs b/src/Autofac/ContainerBuilder.cs
index e6a0cd9a5..8aa61e722 100644
--- a/src/Autofac/ContainerBuilder.cs
+++ b/src/Autofac/ContainerBuilder.cs
@@ -173,9 +173,9 @@ public IContainer Build(ContainerBuildOptions options = ContainerBuildOptions.No
{
Properties[MetadataKeys.ContainerBuildOptions] = options;
- #pragma warning disable CA2000 // Dispose objects before losing scope
+#pragma warning disable CA2000 // Dispose objects before losing scope
ComponentRegistryBuilder.Register(new SelfComponentRegistration());
- #pragma warning restore CA2000 // Dispose objects before losing scope
+#pragma warning restore CA2000 // Dispose objects before losing scope
Build(ComponentRegistryBuilder, (options & ContainerBuildOptions.ExcludeDefaultModules) != ContainerBuildOptions.None);
diff --git a/src/Autofac/Core/ActivatedEventArgs.cs b/src/Autofac/Core/ActivatedEventArgs.cs
index 2d1ea3215..6dd4da4c0 100644
--- a/src/Autofac/Core/ActivatedEventArgs.cs
+++ b/src/Autofac/Core/ActivatedEventArgs.cs
@@ -43,10 +43,10 @@ public class ActivatedEventArgs : EventArgs, IActivatedEventArgs
/// The service being resolved.
public ActivatedEventArgs(
IComponentContext context,
+ Service service,
IComponentRegistration component,
IEnumerable parameters,
- T instance,
- Service service)
+ T instance)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (component == null) throw new ArgumentNullException(nameof(component));
diff --git a/src/Autofac/Core/ActivatingEventArgs.cs b/src/Autofac/Core/ActivatingEventArgs.cs
index 3565fd1c8..bdd3c20a6 100644
--- a/src/Autofac/Core/ActivatingEventArgs.cs
+++ b/src/Autofac/Core/ActivatingEventArgs.cs
@@ -41,23 +41,22 @@ public class ActivatingEventArgs : EventArgs, IActivatingEventArgs
/// Initializes a new instance of the class.
///
/// The context.
+ /// The service.
/// The component.
/// The parameters.
/// The instance.
- /// The service being resolved.
- public ActivatingEventArgs(IComponentContext context, IComponentRegistration component, IEnumerable parameters, T instance, Service service)
+ public ActivatingEventArgs(IComponentContext context, Service service, IComponentRegistration component, IEnumerable parameters, T instance)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (component == null) throw new ArgumentNullException(nameof(component));
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
if (instance == null) throw new ArgumentNullException(nameof(instance));
- if (service == null) throw new ArgumentNullException(nameof(service));
+ Service = service;
Context = context;
Component = component;
Parameters = parameters;
_instance = instance;
- Service = service;
}
///
diff --git a/src/Autofac/Core/Activators/Delegate/DelegateActivator.cs b/src/Autofac/Core/Activators/Delegate/DelegateActivator.cs
index f2537e1d2..ee56cf8da 100644
--- a/src/Autofac/Core/Activators/Delegate/DelegateActivator.cs
+++ b/src/Autofac/Core/Activators/Delegate/DelegateActivator.cs
@@ -27,6 +27,8 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
+using Autofac.Core.Resolving;
+using Autofac.Core.Resolving.Pipeline;
namespace Autofac.Core.Activators.Delegate
{
@@ -46,22 +48,29 @@ public class DelegateActivator : InstanceActivator, IInstanceActivator
public DelegateActivator(Type limitType, Func, object> activationFunction)
: base(limitType)
{
- if (activationFunction == null) throw new ArgumentNullException(nameof(activationFunction));
+ _activationFunction = activationFunction ?? throw new ArgumentNullException(nameof(activationFunction));
+ }
+
+ ///
+ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServices, IResolvePipelineBuilder pipelineBuilder)
+ {
+ if (pipelineBuilder is null) throw new ArgumentNullException(nameof(pipelineBuilder));
+
+ pipelineBuilder.Use(this.DisplayName(), PipelinePhase.Activation, MiddlewareInsertionMode.EndOfPhase, (ctxt, next) =>
+ {
+ ctxt.Instance = ActivateInstance(ctxt, ctxt.Parameters);
- _activationFunction = activationFunction;
+ next(ctxt);
+ });
}
///
- /// Activate an instance in the provided context.
+ /// Invokes the delegate and returns the instance.
///
/// Context in which to activate instances.
/// Parameters to the instance.
/// The activated instance.
- ///
- /// The context parameter here should probably be ILifetimeScope in order to reveal Disposer,
- /// but will wait until implementing a concrete use case to make the decision.
- ///
- public object ActivateInstance(IComponentContext context, IEnumerable parameters)
+ private object ActivateInstance(IComponentContext context, IEnumerable parameters)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
diff --git a/src/Autofac/Core/Activators/ProvidedInstance/ProvidedInstanceActivator.cs b/src/Autofac/Core/Activators/ProvidedInstance/ProvidedInstanceActivator.cs
index 06bba1f89..4de5b1fff 100644
--- a/src/Autofac/Core/Activators/ProvidedInstance/ProvidedInstanceActivator.cs
+++ b/src/Autofac/Core/Activators/ProvidedInstance/ProvidedInstanceActivator.cs
@@ -24,9 +24,10 @@
// OTHER DEALINGS IN THE SOFTWARE.
using System;
-using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
+using Autofac.Core.Resolving;
+using Autofac.Core.Resolving.Pipeline;
namespace Autofac.Core.Activators.ProvidedInstance
{
@@ -48,21 +49,21 @@ public ProvidedInstanceActivator(object instance)
_instance = instance;
}
- ///
- /// Activate an instance in the provided context.
- ///
- /// Context in which to activate instances.
- /// Parameters to the instance.
- /// The activated instance.
- ///
- /// The context parameter here should probably be ILifetimeScope in order to reveal Disposer,
- /// but will wait until implementing a concrete use case to make the decision.
- ///
- public object ActivateInstance(IComponentContext context, IEnumerable parameters)
+ ///
+ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServices, IResolvePipelineBuilder pipelineBuilder)
{
- if (context == null) throw new ArgumentNullException(nameof(context));
- if (parameters == null) throw new ArgumentNullException(nameof(parameters));
+ if (pipelineBuilder is null) throw new ArgumentNullException(nameof(pipelineBuilder));
+
+ pipelineBuilder.Use(this.DisplayName(), PipelinePhase.Activation, MiddlewareInsertionMode.EndOfPhase, (ctxt, next) =>
+ {
+ ctxt.Instance = GetInstance();
+ next(ctxt);
+ });
+ }
+
+ private object GetInstance()
+ {
CheckNotDisposed();
if (_activated)
@@ -89,8 +90,10 @@ protected override void Dispose(bool disposing)
// Only dispose of the instance here if it wasn't activated. If it was activated,
// then either the owning lifetime scope will dispose of it automatically
// (see InstanceLookup.Activate) or an OnRelease handler will take care of it.
- if (disposing && DisposeInstance && _instance is IDisposable && !_activated)
- ((IDisposable)_instance).Dispose();
+ if (disposing && DisposeInstance && _instance is IDisposable disposable && !_activated)
+ {
+ disposable.Dispose();
+ }
base.Dispose(disposing);
}
diff --git a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
index 3111d1ffa..474b38c6a 100644
--- a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
+++ b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
@@ -30,6 +30,8 @@
using System.Linq;
using System.Reflection;
using System.Text;
+using Autofac.Core.Resolving;
+using Autofac.Core.Resolving.Pipeline;
namespace Autofac.Core.Activators.Reflection
{
@@ -43,7 +45,6 @@ public class ReflectionActivator : InstanceActivator, IInstanceActivator
private readonly Parameter[] _configuredProperties;
private readonly Parameter[] _defaultParameters;
private ConstructorInfo[]? _availableConstructors;
- private readonly object _availableConstructorsLock = new object();
///
/// Initializes a new instance of the class.
@@ -83,6 +84,28 @@ public ReflectionActivator(
///
public IConstructorSelector ConstructorSelector { get; }
+ ///
+ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServices, IResolvePipelineBuilder pipelineBuilder)
+ {
+ if (componentRegistryServices is null) throw new ArgumentNullException(nameof(componentRegistryServices));
+ if (pipelineBuilder is null) throw new ArgumentNullException(nameof(pipelineBuilder));
+
+ // Locate the possible constructors at container build time.
+ _availableConstructors = ConstructorFinder.FindConstructors(_implementationType);
+
+ if (_availableConstructors.Length == 0)
+ {
+ throw new NoConstructorsFoundException(_implementationType, string.Format(CultureInfo.CurrentCulture, ReflectionActivatorResources.NoConstructorsAvailable, _implementationType, ConstructorFinder));
+ }
+
+ pipelineBuilder.Use(this.ToString(), PipelinePhase.Activation, MiddlewareInsertionMode.EndOfPhase, (ctxt, next) =>
+ {
+ ctxt.Instance = ActivateInstance(ctxt, ctxt.Parameters);
+
+ next(ctxt);
+ });
+ }
+
///
/// Activate an instance in the provided context.
///
@@ -93,31 +116,14 @@ public ReflectionActivator(
/// The context parameter here should probably be ILifetimeScope in order to reveal Disposer,
/// but will wait until implementing a concrete use case to make the decision.
///
- public object ActivateInstance(IComponentContext context, IEnumerable parameters)
+ private object ActivateInstance(IComponentContext context, IEnumerable parameters)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
CheckNotDisposed();
- // Lazy instantiate available constructor list so the constructor
- // finder can be changed during AsSelf() registration. AsSelf() creates
- // a temporary activator just long enough to get the LimitType.
- if (_availableConstructors == null)
- {
- lock (_availableConstructorsLock)
- {
- if (_availableConstructors == null)
- {
- _availableConstructors = ConstructorFinder.FindConstructors(_implementationType);
- }
- }
- }
-
- if (_availableConstructors.Length == 0)
- throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, ReflectionActivatorResources.NoConstructorsAvailable, _implementationType, ConstructorFinder));
-
- var validBindings = GetValidConstructorBindings(_availableConstructors, context, parameters);
+ var validBindings = GetValidConstructorBindings(_availableConstructors!, context, parameters);
var selectedBinding = ConstructorSelector.SelectConstructorBinding(validBindings, parameters);
diff --git a/src/Autofac/Core/Container.cs b/src/Autofac/Core/Container.cs
index 9014b2b29..9ceb1b9b4 100644
--- a/src/Autofac/Core/Container.cs
+++ b/src/Autofac/Core/Container.cs
@@ -24,11 +24,10 @@
// OTHER DEALINGS IN THE SOFTWARE.
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
+using Autofac.Core.Diagnostics;
using Autofac.Core.Lifetime;
-using Autofac.Core.Registration;
using Autofac.Core.Resolving;
using Autofac.Util;
@@ -100,6 +99,12 @@ public ILifetimeScope BeginLifetimeScope(object tag, Action co
return _rootLifetimeScope.BeginLifetimeScope(tag, configurationAction);
}
+ ///
+ public void AttachTrace(IResolvePipelineTracer tracer)
+ {
+ _rootLifetimeScope.AttachTrace(tracer);
+ }
+
///
/// Gets the disposer associated with this container. Instances can be associated
/// with it manually if required.
@@ -166,11 +171,12 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}
+ ///
protected override async ValueTask DisposeAsync(bool disposing)
{
if (disposing)
{
- await _rootLifetimeScope.DisposeAsync();
+ await _rootLifetimeScope.DisposeAsync().ConfigureAwait(false);
// Registries are not likely to have async tasks to dispose of,
// so we will leave it as a straight dispose.
diff --git a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs
new file mode 100644
index 000000000..ef9a74263
--- /dev/null
+++ b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs
@@ -0,0 +1,230 @@
+using System;
+using System.Collections.Concurrent;
+using System.Globalization;
+using System.Text;
+using Autofac.Core.Resolving;
+using Autofac.Core.Resolving.Pipeline;
+
+namespace Autofac.Core.Diagnostics
+{
+ ///
+ /// Provides a default resolve pipeline tracer that builds a multi-line string describing the end-to-end operation flow.
+ /// Attach to the event to receive notifications when new trace content is available.
+ ///
+ public class DefaultDiagnosticTracer : IResolvePipelineTracer
+ {
+ private const string RequestExceptionTraced = "__RequestException";
+
+ private readonly ConcurrentDictionary _operationBuilders = new ConcurrentDictionary();
+
+ private static readonly string[] _newLineSplit = new[] { Environment.NewLine };
+
+ ///
+ /// Event raised when a resolve operation completes, and trace data is available.
+ ///
+ public event EventHandler? OperationCompleted;
+
+ ///
+ void IResolvePipelineTracer.OperationStart(ResolveOperationBase operation, ResolveRequest initiatingRequest)
+ {
+ var builder = _operationBuilders.GetOrAdd(operation.TracingId, k => new IndentingStringBuilder());
+
+ builder.AppendFormattedLine(TracerMessages.ResolveOperationStarting);
+ builder.AppendLine(TracerMessages.EntryBrace);
+ builder.Indent();
+ }
+
+ ///
+ void IResolvePipelineTracer.RequestStart(ResolveOperationBase operation, ResolveRequestContextBase requestContext)
+ {
+ if (_operationBuilders.TryGetValue(operation.TracingId, out var builder))
+ {
+ builder.AppendFormattedLine(TracerMessages.ResolveRequestStarting);
+ builder.AppendLine(TracerMessages.EntryBrace);
+ builder.Indent();
+ builder.AppendFormattedLine(TracerMessages.ServiceDisplay, requestContext.Service);
+ builder.AppendFormattedLine(TracerMessages.ComponentDisplay, requestContext.Registration.Activator.DisplayName());
+
+ if (requestContext.DecoratorTarget is object)
+ {
+ builder.AppendFormattedLine(TracerMessages.TargetDisplay, requestContext.DecoratorTarget.Activator.DisplayName());
+ }
+
+ builder.AppendLine();
+ builder.AppendLine(TracerMessages.Pipeline);
+ }
+ }
+
+ ///
+ void IResolvePipelineTracer.MiddlewareEntry(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware)
+ {
+ if (_operationBuilders.TryGetValue(operation.TracingId, out var builder))
+ {
+ builder.AppendFormattedLine(TracerMessages.EnterMiddleware, middleware.ToString());
+ builder.Indent();
+ }
+ }
+
+ ///
+ void IResolvePipelineTracer.MiddlewareExit(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware, bool succeeded)
+ {
+ if (_operationBuilders.TryGetValue(operation.TracingId, out var builder))
+ {
+ builder.Outdent();
+
+ if (succeeded)
+ {
+ builder.AppendFormattedLine(TracerMessages.ExitMiddlewareSuccess, middleware.ToString());
+ }
+ else
+ {
+ builder.AppendFormattedLine(TracerMessages.ExitMiddlewareFailure, middleware.ToString());
+ }
+ }
+ }
+
+ ///
+ void IResolvePipelineTracer.RequestFailure(ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException)
+ {
+ if (_operationBuilders.TryGetValue(operation.TracingId, out var builder))
+ {
+ builder.Outdent();
+ builder.AppendLine(TracerMessages.ExitBrace);
+
+ if (requestException is DependencyResolutionException && requestException.InnerException is object)
+ {
+ requestException = requestException.InnerException;
+ }
+
+ if (requestException.Data.Contains(RequestExceptionTraced))
+ {
+ builder.AppendLine(TracerMessages.ResolveRequestFailedNested);
+ }
+ else
+ {
+ builder.AppendException(TracerMessages.ResolveRequestFailed, requestException);
+ }
+
+ requestException.Data[RequestExceptionTraced] = true;
+ }
+ }
+
+ ///
+ void IResolvePipelineTracer.RequestSuccess(ResolveOperationBase operation, ResolveRequestContextBase requestContext)
+ {
+ if (_operationBuilders.TryGetValue(operation.TracingId, out var builder))
+ {
+ builder.Outdent();
+ builder.AppendLine(TracerMessages.ExitBrace);
+ builder.AppendFormattedLine(TracerMessages.ResolveRequestSucceeded, requestContext.Instance);
+ }
+ }
+
+ ///
+ void IResolvePipelineTracer.OperationFailure(ResolveOperationBase operation, Exception operationException)
+ {
+ if (_operationBuilders.TryGetValue(operation.TracingId, out var builder))
+ {
+ builder.Outdent();
+ builder.AppendLine(TracerMessages.ExitBrace);
+ builder.AppendException(TracerMessages.OperationFailed, operationException);
+
+ OperationCompleted?.Invoke(this, new OperationTraceCompletedArgs(operation, builder.ToString()));
+ }
+ }
+
+ ///
+ void IResolvePipelineTracer.OperationSuccess(ResolveOperationBase operation, object resolvedInstance)
+ {
+ if (_operationBuilders.TryGetValue(operation.TracingId, out var builder))
+ {
+ builder.Outdent();
+ builder.AppendLine(TracerMessages.ExitBrace);
+ builder.AppendFormattedLine(TracerMessages.OperationSucceeded, resolvedInstance);
+
+ // If we're completing the root operation, raise the event.
+ if (operation.IsTopLevelOperation)
+ {
+ OperationCompleted?.Invoke(this, new OperationTraceCompletedArgs(operation, builder.ToString()));
+ }
+ }
+ }
+
+ ///
+ /// Provides a string builder that auto-indents lines.
+ ///
+ private class IndentingStringBuilder
+ {
+ private const int IndentSize = 2;
+
+ private readonly StringBuilder _builder;
+ private int _indentCount;
+
+ public IndentingStringBuilder()
+ {
+ _builder = new StringBuilder();
+ }
+
+ public void Indent()
+ {
+ _indentCount++;
+ }
+
+ public void Outdent()
+ {
+ if (_indentCount == 0)
+ {
+ throw new InvalidOperationException(TracerMessages.OutdentFailure);
+ }
+
+ _indentCount--;
+ }
+
+ public void AppendFormattedLine(string format, params object?[] args)
+ {
+ AppendIndent();
+ _builder.AppendFormat(CultureInfo.CurrentCulture, format, args);
+ _builder.AppendLine();
+ }
+
+ public void AppendException(string message, Exception ex)
+ {
+ AppendIndent();
+ _builder.AppendLine(message);
+
+ var exceptionBody = ex.ToString().Split(_newLineSplit, StringSplitOptions.None);
+
+ Indent();
+
+ foreach (var exceptionLine in exceptionBody)
+ {
+ AppendLine(exceptionLine);
+ }
+
+ Outdent();
+ }
+
+ public void AppendLine()
+ {
+ // No indent on a blank line.
+ _builder.AppendLine();
+ }
+
+ public void AppendLine(string value)
+ {
+ AppendIndent();
+ _builder.AppendLine(value);
+ }
+
+ private void AppendIndent()
+ {
+ _builder.Append(' ', IndentSize * _indentCount);
+ }
+
+ public override string ToString()
+ {
+ return _builder.ToString();
+ }
+ }
+ }
+}
diff --git a/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs b/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs
new file mode 100644
index 000000000..eb1ec5b67
--- /dev/null
+++ b/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs
@@ -0,0 +1,86 @@
+using System;
+using Autofac.Core.Resolving;
+using Autofac.Core.Resolving.Pipeline;
+
+namespace Autofac.Core.Diagnostics
+{
+ ///
+ /// Defines the interface for a tracer that is invoked by a resolve pipeline during execution. Implement this class if you want
+ /// to provide custom trace output or other diagnostic functionality.
+ ///
+ ///
+ /// You can get a 'tracing ID' object from that can be used as a dictionary tracking key
+ /// to associate related operations.
+ ///
+ ///
+ public interface IResolvePipelineTracer
+ {
+ ///
+ /// Invoked at operation start.
+ ///
+ /// The pipeline resolve operation that is about to run.
+ /// The request that is responsible for starting this operation.
+ ///
+ /// A single operation can in turn invoke other full operations (as opposed to requests). Check
+ /// to know if you're looking at the entry operation.
+ ///
+ void OperationStart(ResolveOperationBase operation, ResolveRequest initiatingRequest);
+
+ ///
+ /// Invoked at the start of a single resolve request initiated from within an operation.
+ ///
+ /// The pipeline resolve operation that this request is running within.
+ /// The context for the resolve request that is about to start.
+ void RequestStart(ResolveOperationBase operation, ResolveRequestContextBase requestContext);
+
+ ///
+ /// Invoked when an individual middleware item is about to execute (just before the method executes).
+ ///
+ /// The pipeline resolve operation that this request is running within.
+ /// The context for the resolve request that is running.
+ /// The middleware that is about to run.
+ void MiddlewareEntry(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware);
+
+ ///
+ /// Invoked when an individual middleware item has finished executing (when the method returns).
+ ///
+ /// The pipeline resolve operation that this request is running within.
+ /// The context for the resolve request that is running.
+ /// The middleware that just ran.
+ ///
+ /// Indicates whether the given middleware succeeded.
+ /// The exception that caused the middleware to fail is not available here, but will be available in the next call.
+ ///
+ void MiddlewareExit(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware, bool succeeded);
+
+ ///
+ /// Invoked when a resolve request fails.
+ ///
+ /// The pipeline resolve operation that this request is running within.
+ /// The context for the resolve request that failed.
+ /// The exception that caused the failure.
+ void RequestFailure(ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException);
+
+ ///
+ /// Invoked when a resolve request succeeds.
+ ///
+ /// The pipeline resolve operation that this request is running within.
+ /// The context for the resolve request that failed.
+ void RequestSuccess(ResolveOperationBase operation, ResolveRequestContextBase requestContext);
+
+ ///
+ /// Invoked when a resolve operation fails.
+ ///
+ /// The resolve operation that failed.
+ /// The exception that caused the operation failure.
+ void OperationFailure(ResolveOperationBase operation, Exception operationException);
+
+ ///
+ /// Invoked when a resolve operation succeeds. You can check whether this operation was the top-level entry operation using
+ /// .
+ ///
+ /// The resolve operation that succeeded.
+ /// The resolved instance providing the requested service.
+ void OperationSuccess(ResolveOperationBase operation, object resolvedInstance);
+ }
+}
diff --git a/src/Autofac/Core/Diagnostics/ITracingIdentifer.cs b/src/Autofac/Core/Diagnostics/ITracingIdentifer.cs
new file mode 100644
index 000000000..e189ccde3
--- /dev/null
+++ b/src/Autofac/Core/Diagnostics/ITracingIdentifer.cs
@@ -0,0 +1,40 @@
+// This software is part of the Autofac IoC container
+// Copyright © 2020 Autofac Contributors
+// https://autofac.org
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace Autofac.Core.Diagnostics
+{
+ ///
+ /// Marker interface indicating objects that can function as a resolve operation tracing ID.
+ ///
+ [SuppressMessage(
+ "Design",
+ "CA1040:Avoid empty interfaces",
+ Justification = "Holding interface assigned to objects that can be used as a key for tracing dictionaries.")]
+ public interface ITracingIdentifer
+ {
+ }
+}
diff --git a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs
new file mode 100644
index 000000000..3db330187
--- /dev/null
+++ b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs
@@ -0,0 +1,17 @@
+using Autofac.Core.Resolving;
+
+namespace Autofac.Core.Diagnostics
+{
+ public sealed class OperationTraceCompletedArgs
+ {
+ public OperationTraceCompletedArgs(ResolveOperationBase operation, string traceContent)
+ {
+ Operation = operation;
+ TraceContent = traceContent;
+ }
+
+ public ResolveOperationBase Operation { get; }
+
+ public string TraceContent { get; }
+ }
+}
diff --git a/src/Autofac/Core/Diagnostics/TracerMessages.Designer.cs b/src/Autofac/Core/Diagnostics/TracerMessages.Designer.cs
new file mode 100644
index 000000000..efb6bdb72
--- /dev/null
+++ b/src/Autofac/Core/Diagnostics/TracerMessages.Designer.cs
@@ -0,0 +1,216 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Autofac.Core.Diagnostics {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class TracerMessages {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal TracerMessages() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Autofac.Core.Diagnostics.TracerMessages", typeof(TracerMessages).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Component: {0}.
+ ///
+ internal static string ComponentDisplay {
+ get {
+ return ResourceManager.GetString("ComponentDisplay", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to -> {0}.
+ ///
+ internal static string EnterMiddleware {
+ get {
+ return ResourceManager.GetString("EnterMiddleware", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {.
+ ///
+ internal static string EntryBrace {
+ get {
+ return ResourceManager.GetString("EntryBrace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to }.
+ ///
+ internal static string ExitBrace {
+ get {
+ return ResourceManager.GetString("ExitBrace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to X- {0}.
+ ///
+ internal static string ExitMiddlewareFailure {
+ get {
+ return ResourceManager.GetString("ExitMiddlewareFailure", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to <- {0}.
+ ///
+ internal static string ExitMiddlewareSuccess {
+ get {
+ return ResourceManager.GetString("ExitMiddlewareSuccess", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Operation FAILED.
+ ///
+ internal static string OperationFailed {
+ get {
+ return ResourceManager.GetString("OperationFailed", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Operation Succeeded; result instance was {0}.
+ ///
+ internal static string OperationSucceeded {
+ get {
+ return ResourceManager.GetString("OperationSucceeded", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Cannot outdent; no indentation specified..
+ ///
+ internal static string OutdentFailure {
+ get {
+ return ResourceManager.GetString("OutdentFailure", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Pipeline:.
+ ///
+ internal static string Pipeline {
+ get {
+ return ResourceManager.GetString("Pipeline", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Resolve Operation Starting.
+ ///
+ internal static string ResolveOperationStarting {
+ get {
+ return ResourceManager.GetString("ResolveOperationStarting", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Resolve Request FAILED.
+ ///
+ internal static string ResolveRequestFailed {
+ get {
+ return ResourceManager.GetString("ResolveRequestFailed", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Resolve Request FAILED: Nested Resolve Failed.
+ ///
+ internal static string ResolveRequestFailedNested {
+ get {
+ return ResourceManager.GetString("ResolveRequestFailedNested", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Resolve Request Starting.
+ ///
+ internal static string ResolveRequestStarting {
+ get {
+ return ResourceManager.GetString("ResolveRequestStarting", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Resolve Request Succeeded; result instance was {0}.
+ ///
+ internal static string ResolveRequestSucceeded {
+ get {
+ return ResourceManager.GetString("ResolveRequestSucceeded", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Service: {0}.
+ ///
+ internal static string ServiceDisplay {
+ get {
+ return ResourceManager.GetString("ServiceDisplay", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Target: {0}.
+ ///
+ internal static string TargetDisplay {
+ get {
+ return ResourceManager.GetString("TargetDisplay", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/Autofac/Core/Diagnostics/TracerMessages.resx b/src/Autofac/Core/Diagnostics/TracerMessages.resx
new file mode 100644
index 000000000..9dba2349a
--- /dev/null
+++ b/src/Autofac/Core/Diagnostics/TracerMessages.resx
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Component: {0}
+
+
+ -> {0}
+
+
+ {
+
+
+ }
+
+
+ X- {0}
+
+
+ <- {0}
+
+
+ Operation FAILED
+
+
+ Operation Succeeded; result instance was {0}
+
+
+ Cannot outdent; no indentation specified.
+
+
+ Pipeline:
+
+
+ Resolve Operation Starting
+
+
+ Resolve Request FAILED
+
+
+ Resolve Request FAILED: Nested Resolve Failed
+
+
+ Resolve Request Starting
+
+
+ Resolve Request Succeeded; result instance was {0}
+ .
+
+
+ Service: {0}
+
+
+ Target: {0}
+
+
\ No newline at end of file
diff --git a/src/Autofac/Core/IActivatingEventArgs.cs b/src/Autofac/Core/IActivatingEventArgs.cs
index 1ee255708..b20bea722 100644
--- a/src/Autofac/Core/IActivatingEventArgs.cs
+++ b/src/Autofac/Core/IActivatingEventArgs.cs
@@ -1,4 +1,4 @@
-// This software is part of the Autofac IoC container
+// This software is part of the Autofac IoC container
// Copyright © 2011 Autofac Contributors
// https://autofac.org
//
diff --git a/src/Autofac/Core/IComponentRegistration.cs b/src/Autofac/Core/IComponentRegistration.cs
index 26d02388c..7ad4fce0a 100644
--- a/src/Autofac/Core/IComponentRegistration.cs
+++ b/src/Autofac/Core/IComponentRegistration.cs
@@ -26,6 +26,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using Autofac.Core.Registration;
+using Autofac.Core.Resolving.Pipeline;
namespace Autofac.Core
{
@@ -76,60 +78,26 @@ public interface IComponentRegistration : IDisposable
IComponentRegistration Target { get; }
///
- /// Gets a value indicating whether the registration is a 1:1 adapter on top
- /// of another component (e.g., Meta, Func, or Owned).
- ///
- bool IsAdapterForIndividualComponent { get; }
-
- ///
- /// Fired when a new instance is required, prior to activation.
- /// Can be used to provide Autofac with additional parameters, used during activation.
+ /// Gets the resolve pipeline for the component.
///
- event EventHandler Preparing;
+ IResolvePipeline ResolvePipeline { get; }
///
- /// Called by the container when an instance is required.
- ///
- /// The context in which the instance will be activated.
- /// The service being resolved.
- /// Parameters for activation. These may be modified by the event handler.
- [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "1#", Justification = "The method may change the backing store of the parameter collection.")]
- [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This is the method that would raise the event.")]
- void RaisePreparing(IComponentContext context, Service service, ref IEnumerable parameters);
-
- ///
- /// Fired when a new instance is being activated. The instance can be
- /// wrapped or switched at this time by setting the Instance property in
- /// the provided event arguments.
- ///
- event EventHandler> Activating;
-
- ///
- /// Called by the container once an instance has been constructed.
+ /// Gets a value indicating whether the registration is a 1:1 adapter on top
+ /// of another component (e.g., Meta, Func, or Owned).
///
- /// The context in which the instance was activated.
- /// The parameters supplied to the activator.
- /// The service being resolved.
- /// The instance.
- [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
- [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "2#", Justification = "The method may change the object as part of activation.")]
- [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This is the method that would raise the event.")]
- void RaiseActivating(IComponentContext context, IEnumerable parameters, Service service, ref object instance);
+ bool IsAdapterForIndividualComponent { get; }
///
- /// Fired when the activation process for a new instance is complete.
+ /// Provides an event that will be invoked just before a pipeline is built, and can be used to add additional middleware
+ /// at that point.
///
- event EventHandler> Activated;
+ public event EventHandler PipelineBuilding;
///
- /// Called by the container once an instance has been fully constructed, including
- /// any requested objects that depend on the instance.
+ /// Builds the resolve pipeline.
///
- /// The context in which the instance was activated.
- /// The parameters supplied to the activator.
- /// The service being resolved.
- /// The instance.
- [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This is the method that would raise the event.")]
- void RaiseActivated(IComponentContext context, IEnumerable parameters, Service service, object instance);
+ /// The available services.
+ void BuildResolvePipeline(IComponentRegistryServices registryServices);
}
}
diff --git a/src/Autofac/Core/Resolving/IInstanceLookup.cs b/src/Autofac/Core/IComponentRegistryServices.cs
similarity index 53%
rename from src/Autofac/Core/Resolving/IInstanceLookup.cs
rename to src/Autofac/Core/IComponentRegistryServices.cs
index d847230dc..7474b569f 100644
--- a/src/Autofac/Core/Resolving/IInstanceLookup.cs
+++ b/src/Autofac/Core/IComponentRegistryServices.cs
@@ -23,44 +23,35 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
-using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
-namespace Autofac.Core.Resolving
+namespace Autofac.Core
{
- ///
- /// Represents the process of finding a component during a resolve operation.
- ///
- public interface IInstanceLookup
+ public interface IComponentRegistryServices
{
///
- /// Gets the component for which an instance is to be looked up.
+ /// Selects from the available registrations after ensuring that any
+ /// dynamic registration sources that may provide
+ /// have been invoked.
///
- IComponentRegistration ComponentRegistration { get; }
+ /// The service for which registrations are sought.
+ /// Registrations supporting .
+ IEnumerable RegistrationsFor(Service service);
///
- /// Gets the scope in which the instance will be looked up.
+ /// Attempts to find a default registration for the specified service.
///
- ILifetimeScope ActivationScope { get; }
+ /// The service to look up.
+ /// The default registration for the service.
+ /// True if a registration exists.
+ bool TryGetRegistration(Service service, [NotNullWhen(returnValue: true)] out IComponentRegistration? registration);
///
- /// Gets the parameters provided for new instance creation.
+ /// Determines whether the specified service is registered.
///
- IEnumerable Parameters { get; }
-
- ///
- /// Raised when the lookup phase of the operation is ending.
- ///
- event EventHandler InstanceLookupEnding;
-
- ///
- /// Raised when the completion phase of an instance lookup operation begins.
- ///
- event EventHandler CompletionBeginning;
-
- ///
- /// Raised when the completion phase of an instance lookup operation ends.
- ///
- event EventHandler CompletionEnding;
+ /// The service to test.
+ /// True if the service is registered.
+ bool IsRegistered(Service service);
}
-}
\ No newline at end of file
+}
diff --git a/src/Autofac/Core/IInstanceActivator.cs b/src/Autofac/Core/IInstanceActivator.cs
index 70856cc84..24952b518 100644
--- a/src/Autofac/Core/IInstanceActivator.cs
+++ b/src/Autofac/Core/IInstanceActivator.cs
@@ -24,7 +24,7 @@
// OTHER DEALINGS IN THE SOFTWARE.
using System;
-using System.Collections.Generic;
+using Autofac.Core.Resolving.Pipeline;
namespace Autofac.Core
{
@@ -34,16 +34,11 @@ namespace Autofac.Core
public interface IInstanceActivator : IDisposable
{
///
- /// Activate an instance in the provided context.
+ /// Allows an implementation to add middleware to a registration's resolve pipeline.
///
- /// Context in which to activate instances.
- /// Parameters to the instance.
- /// The activated instance.
- ///
- /// The context parameter here should probably be ILifetimeScope in order to reveal Disposer,
- /// but will wait until implementing a concrete use case to make the decision.
- ///
- object ActivateInstance(IComponentContext context, IEnumerable parameters);
+ /// Provides access to the set of all available services.
+ /// The registration's pipeline builder.
+ void ConfigurePipeline(IComponentRegistryServices componentRegistryServices, IResolvePipelineBuilder pipelineBuilder);
///
/// Gets the most specific type that the component instances are known to be castable to.
diff --git a/src/Autofac/Core/Lifetime/LifetimeScope.cs b/src/Autofac/Core/Lifetime/LifetimeScope.cs
index c7d02abc4..ba908a538 100644
--- a/src/Autofac/Core/Lifetime/LifetimeScope.cs
+++ b/src/Autofac/Core/Lifetime/LifetimeScope.cs
@@ -31,6 +31,7 @@
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Autofac.Builder;
+using Autofac.Core.Diagnostics;
using Autofac.Core.Registration;
using Autofac.Core.Resolving;
using Autofac.Util;
@@ -51,7 +52,8 @@ public class LifetimeScope : Disposable, ISharingLifetimeScope, IServiceProvider
private readonly ConcurrentDictionary _sharedInstances = new ConcurrentDictionary();
private readonly ConcurrentDictionary<(Guid, Guid), object> _sharedQualifiedInstances = new ConcurrentDictionary<(Guid, Guid), object>();
private object? _anonymousTag;
- private LifetimeScope? parentScope;
+ private LifetimeScope? _parentScope;
+ private IResolvePipelineTracer? _tracer;
internal static Guid SelfRegistrationId { get; } = Guid.NewGuid();
@@ -70,10 +72,23 @@ public class LifetimeScope : Disposable, ISharingLifetimeScope, IServiceProvider
/// Components used in the scope.
/// Parent scope.
protected LifetimeScope(IComponentRegistry componentRegistry, LifetimeScope parent, object tag)
+ : this(componentRegistry, parent, null, tag)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The tag applied to the .
+ /// Components used in the scope.
+ /// Parent scope.
+ /// A tracer instance for this scope.
+ protected LifetimeScope(IComponentRegistry componentRegistry, LifetimeScope parent, IResolvePipelineTracer? tracer, object tag)
: this(componentRegistry, tag)
{
- parentScope = parent ?? throw new ArgumentNullException(nameof(parent));
- RootLifetimeScope = parentScope.RootLifetimeScope;
+ _tracer = tracer;
+ _parentScope = parent ?? throw new ArgumentNullException(nameof(parent));
+ RootLifetimeScope = _parentScope.RootLifetimeScope;
}
///
@@ -119,7 +134,7 @@ public ILifetimeScope BeginLifetimeScope(object tag)
CheckNotDisposed();
CheckTagIsUnique(tag);
- var scope = new LifetimeScope(ComponentRegistry, this, tag);
+ var scope = new LifetimeScope(ComponentRegistry, this, _tracer, tag);
RaiseBeginning(scope);
return scope;
}
@@ -150,6 +165,12 @@ private void RaiseBeginning(ILifetimeScope scope)
handler?.Invoke(this, new LifetimeScopeBeginningEventArgs(scope));
}
+ ///
+ public void AttachTrace(IResolvePipelineTracer tracer)
+ {
+ _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer));
+ }
+
///
/// Begin a new anonymous sub-scope, with additional components available to it.
/// Component instances created via the new scope
@@ -202,7 +223,7 @@ public ILifetimeScope BeginLifetimeScope(object tag, Action co
CheckTagIsUnique(tag);
var localsBuilder = CreateScopeRestrictedRegistry(tag, configurationAction);
- var scope = new LifetimeScope(localsBuilder.Build(), this, tag);
+ var scope = new LifetimeScope(localsBuilder.Build(), this, _tracer, tag);
scope.Disposer.AddInstanceForDisposal(localsBuilder);
if (localsBuilder.Properties.TryGetValue(MetadataKeys.ContainerBuildOptions, out var options)
@@ -274,7 +295,7 @@ public object ResolveComponent(ResolveRequest request)
CheckNotDisposed();
- var operation = new ResolveOperation(this);
+ var operation = new ResolveOperation(this, _tracer);
var handler = ResolveOperationBeginning;
handler?.Invoke(this, new ResolveOperationBeginningEventArgs(operation));
return operation.Execute(request);
@@ -283,7 +304,7 @@ public object ResolveComponent(ResolveRequest request)
///
/// Gets the parent of this node of the hierarchy, or null.
///
- public ISharingLifetimeScope? ParentLifetimeScope => parentScope;
+ public ISharingLifetimeScope? ParentLifetimeScope => _parentScope;
///
/// Gets the root of the sharing hierarchy.
@@ -383,7 +404,7 @@ protected override void Dispose(bool disposing)
// ReSharper disable once InconsistentlySynchronizedField
_sharedInstances.Clear();
- parentScope = null;
+ _parentScope = null;
}
base.Dispose(disposing);
@@ -401,12 +422,12 @@ protected override async ValueTask DisposeAsync(bool disposing)
}
finally
{
- await Disposer.DisposeAsync();
+ await Disposer.DisposeAsync().ConfigureAwait(false);
}
// ReSharper disable once InconsistentlySynchronizedField
_sharedInstances.Clear();
- parentScope = null;
+ _parentScope = null;
}
// Don't call the base (which would just call the normal Dispose).
@@ -426,7 +447,7 @@ private void CheckNotDisposed()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsTreeDisposed()
{
- return IsDisposed || (parentScope is object && parentScope.IsTreeDisposed());
+ return IsDisposed || (_parentScope is object && _parentScope.IsTreeDisposed());
}
///
diff --git a/src/Autofac/Core/PreparingEventArgs.cs b/src/Autofac/Core/PreparingEventArgs.cs
index 419929989..768ffd429 100644
--- a/src/Autofac/Core/PreparingEventArgs.cs
+++ b/src/Autofac/Core/PreparingEventArgs.cs
@@ -39,20 +39,20 @@ public class PreparingEventArgs : EventArgs
///
/// Initializes a new instance of the class.
///
+ /// The service being resolved.
/// The context.
/// The component.
/// The parameters.
- /// The service being resolved.
- public PreparingEventArgs(IComponentContext context, IComponentRegistration component, IEnumerable parameters, Service service)
+ public PreparingEventArgs(IComponentContext context, Service service, IComponentRegistration component, IEnumerable parameters)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (component == null) throw new ArgumentNullException(nameof(component));
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
Context = context;
+ Service = service;
Component = component;
_parameters = parameters;
- Service = service;
}
///
diff --git a/src/Autofac/Core/Registration/ComponentPipelineBuildingArgs.cs b/src/Autofac/Core/Registration/ComponentPipelineBuildingArgs.cs
new file mode 100644
index 000000000..4199b0d5f
--- /dev/null
+++ b/src/Autofac/Core/Registration/ComponentPipelineBuildingArgs.cs
@@ -0,0 +1,50 @@
+// This software is part of the Autofac IoC container
+// Copyright © 2011 Autofac Contributors
+// https://autofac.org
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Linq;
+using Autofac.Core.Pipeline;
+using Autofac.Core.Resolving;
+using Autofac.Core.Resolving.Pipeline;
+using Autofac.Util;
+
+namespace Autofac.Core.Registration
+{
+ public class ComponentPipelineBuildingArgs
+ {
+ public ComponentPipelineBuildingArgs(IComponentRegistration registration, IResolvePipelineBuilder pipelineBuilder)
+ {
+ Registration = registration;
+ PipelineBuilder = pipelineBuilder;
+ }
+
+ public IComponentRegistration Registration { get; }
+
+ public IResolvePipelineBuilder PipelineBuilder { get; }
+ }
+}
diff --git a/src/Autofac/Core/Registration/ComponentRegistration.cs b/src/Autofac/Core/Registration/ComponentRegistration.cs
index 95afebbcb..806ba5b7e 100644
--- a/src/Autofac/Core/Registration/ComponentRegistration.cs
+++ b/src/Autofac/Core/Registration/ComponentRegistration.cs
@@ -28,6 +28,8 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
+using Autofac.Core.Resolving.Middleware;
+using Autofac.Core.Resolving.Pipeline;
using Autofac.Util;
namespace Autofac.Core.Registration
@@ -39,6 +41,42 @@ namespace Autofac.Core.Registration
public class ComponentRegistration : Disposable, IComponentRegistration
{
private readonly IComponentRegistration? _target;
+ private readonly IResolvePipelineBuilder _lateBuildPipeline;
+ private IResolvePipeline? _builtComponentPipeline;
+
+ private static readonly IResolveMiddleware[] _defaultStages = new IResolveMiddleware[]
+ {
+ CircularDependencyDetectorMiddleware.Default,
+ ScopeSelectionMiddleware.Instance,
+ DecoratorMiddleware.Instance,
+ SharingMiddleware.Instance,
+ };
+
+ public ComponentRegistration(
+ Guid id,
+ IInstanceActivator activator,
+ IComponentLifetime lifetime,
+ InstanceSharing sharing,
+ InstanceOwnership ownership,
+ IEnumerable services,
+ IDictionary metadata,
+ IComponentRegistration target,
+ bool isAdapterForIndividualComponents)
+ : this(id, activator, lifetime, sharing, ownership, new ResolvePipelineBuilder(), services, metadata, target, isAdapterForIndividualComponents)
+ {
+ }
+
+ public ComponentRegistration(
+ Guid id,
+ IInstanceActivator activator,
+ IComponentLifetime lifetime,
+ InstanceSharing sharing,
+ InstanceOwnership ownership,
+ IEnumerable services,
+ IDictionary metadata)
+ : this(id, activator, lifetime, sharing, ownership, new ResolvePipelineBuilder(), services, metadata)
+ {
+ }
///
/// Initializes a new instance of the class.
@@ -48,6 +86,7 @@ public class ComponentRegistration : Disposable, IComponentRegistration
/// Determines how the component will be associated with its lifetime.
/// Whether the component is shared within its lifetime scope.
/// Whether the component instances are disposed at the end of their lifetimes.
+ /// The resolve pipeline builder for the registration.
/// Services the component provides.
/// Data associated with the component.
public ComponentRegistration(
@@ -56,6 +95,7 @@ public ComponentRegistration(
IComponentLifetime lifetime,
InstanceSharing sharing,
InstanceOwnership ownership,
+ IResolvePipelineBuilder pipelineBuilder,
IEnumerable services,
IDictionary metadata)
{
@@ -69,6 +109,9 @@ public ComponentRegistration(
Lifetime = lifetime;
Sharing = sharing;
Ownership = ownership;
+
+ _lateBuildPipeline = pipelineBuilder;
+
Services = Enforce.ArgumentElementNotNull(services, nameof(services));
Metadata = metadata;
IsAdapterForIndividualComponent = false;
@@ -82,6 +125,7 @@ public ComponentRegistration(
/// Determines how the component will be associated with its lifetime.
/// Whether the component is shared within its lifetime scope.
/// Whether the component instances are disposed at the end of their lifetimes.
+ /// The resolve pipeline builder for the registration.
/// Services the component provides.
/// Data associated with the component.
/// The component registration upon which this registration is based.
@@ -92,14 +136,14 @@ public ComponentRegistration(
IComponentLifetime lifetime,
InstanceSharing sharing,
InstanceOwnership ownership,
+ IResolvePipelineBuilder pipelineBuilder,
IEnumerable services,
IDictionary metadata,
IComponentRegistration target,
bool isAdapterForIndividualComponents)
- : this(id, activator, lifetime, sharing, ownership, services, metadata)
+ : this(id, activator, lifetime, sharing, ownership, pipelineBuilder, services, metadata)
{
if (target == null) throw new ArgumentNullException(nameof(target));
-
_target = target;
IsAdapterForIndividualComponent = isAdapterForIndividualComponents;
}
@@ -116,9 +160,6 @@ public ComponentRegistration(
///
public Guid Id { get; }
- ///
- /// Gets or sets the activator used to create instances.
- ///
public IInstanceActivator Activator { get; set; }
///
@@ -147,71 +188,65 @@ public ComponentRegistration(
public IDictionary Metadata { get; }
///
- public bool IsAdapterForIndividualComponent { get; }
+ public event EventHandler? PipelineBuilding;
- ///
- /// Fired when a new instance is required, prior to activation.
- /// Can be used to provide Autofac with additional parameters, used during activation.
- ///
- public event EventHandler? Preparing;
-
- ///
- /// Called by the container when an instance is required.
- ///
- /// The context in which the instance will be activated.
- /// The service being resolved.
- /// Parameters for activation.
- public void RaisePreparing(IComponentContext context, Service service, ref IEnumerable parameters)
+ ///
+ public IResolvePipeline ResolvePipeline
{
- var handler = Preparing;
- if (handler == null) return;
-
- var args = new PreparingEventArgs(context, this, parameters, service);
- handler(this, args);
- parameters = args.Parameters;
+ get => _builtComponentPipeline ?? throw new InvalidOperationException(ComponentRegistrationResources.ComponentPipelineHasNotBeenBuilt);
+ protected set => _builtComponentPipeline = value;
}
- ///
- /// Fired when a new instance is being activated. The instance can be
- /// wrapped or switched at this time by setting the Instance property in
- /// the provided event arguments.
- ///
- public event EventHandler>? Activating;
+ ///
+ public bool IsAdapterForIndividualComponent { get; }
- ///
- /// Called by the container once an instance has been constructed.
- ///
- /// The context in which the instance was activated.
- /// The parameters supplied to the activator.
- /// The service being resolved.
- /// The instance.
- public void RaiseActivating(IComponentContext context, IEnumerable parameters, Service service, ref object instance)
+ ///
+ public void BuildResolvePipeline(IComponentRegistryServices registryServices)
{
- var handler = Activating;
- if (handler == null) return;
+ if (_builtComponentPipeline is object)
+ {
+ // Nothing to do.
+ return;
+ }
- var args = new ActivatingEventArgs
-
+
diff --git a/test/Autofac.Test/ActivatorPipelineExtensions.cs b/test/Autofac.Test/ActivatorPipelineExtensions.cs
new file mode 100644
index 000000000..51a239577
--- /dev/null
+++ b/test/Autofac.Test/ActivatorPipelineExtensions.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using Autofac.Core;
+using Autofac.Core.Resolving;
+using Autofac.Core.Resolving.Pipeline;
+
+namespace Autofac.Test
+{
+ ///
+ /// Extension methods to help test activator pipelines.
+ ///
+ public static class ActivatorPipelineExtensions
+ {
+ ///
+ /// Get an invoker for an activator's pipeline.
+ ///
+ /// The activator.
+ /// The applicable component registry.
+ /// A func to call that invokes the pipeline.
+ public static Func, object> GetPipelineInvoker(this IInstanceActivator activator, IComponentRegistry registry)
+ {
+ return GetPipelineInvoker(activator, registry);
+ }
+
+ ///
+ /// Get an invoker for an activator's pipeline.
+ ///
+ /// The activator.
+ /// The applicable component registry.
+ /// A func to call that invokes the pipeline.
+ public static Func, T> GetPipelineInvoker(this IInstanceActivator activator, IComponentRegistry registry)
+ {
+ var services = new RegistryServices(registry);
+ var pipelineBuilder = new ResolvePipelineBuilder();
+
+ activator.ConfigurePipeline(services, pipelineBuilder);
+
+ var built = pipelineBuilder.Build();
+
+ return (scope, parameters) =>
+ {
+ // To get the sharing scope from what might be a container, we're going to resolve the lifetime scope.
+ var lifetimeScope = scope.Resolve() as ISharingLifetimeScope;
+
+ var request = new ResolveRequestContext(
+ new ResolveOperation(lifetimeScope),
+ new ResolveRequest(new TypedService(typeof(T)), Mocks.GetComponentRegistration(), parameters),
+ lifetimeScope,
+ null);
+
+ built.Invoke(request);
+
+ return (T)request.Instance;
+ };
+ }
+
+ private class RegistryServices : IComponentRegistryServices
+ {
+ private readonly IComponentRegistry _registry;
+
+ public RegistryServices(IComponentRegistry registry)
+ {
+ _registry = registry;
+ }
+
+ public bool IsRegistered(Service service)
+ {
+ return _registry.IsRegistered(service);
+ }
+
+ public IEnumerable RegistrationsFor(Service service)
+ {
+ return _registry.RegistrationsFor(service);
+ }
+
+ public bool TryGetRegistration(Service service, [NotNullWhen(true)] out IComponentRegistration registration)
+ {
+ return _registry.TryGetRegistration(service, out registration);
+ }
+ }
+ }
+}
diff --git a/test/Autofac.Test/ContainerBuilderTests.cs b/test/Autofac.Test/ContainerBuilderTests.cs
index 02e55f37f..4fc89710d 100644
--- a/test/Autofac.Test/ContainerBuilderTests.cs
+++ b/test/Autofac.Test/ContainerBuilderTests.cs
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
-
+using Autofac.Core.Resolving.Pipeline;
using Xunit;
namespace Autofac.Test
@@ -49,11 +49,16 @@ public void WhenComponentIsRegisteredDuringResolveItShouldRaiseTheRegisteredEven
var activatedInstances = new List();
var builder = new ContainerBuilder();
- builder.RegisterCallback(x =>
- x.Registered += (sender, args) =>
- {
- args.ComponentRegistration.Activating += (o, eventArgs) => activatedInstances.Add(eventArgs.Instance);
- });
+ builder.RegisterCallback(x => x.Registered += (o, registration) =>
+ {
+ registration.ComponentRegistration.PipelineBuilding += (o, builder) =>
+ builder.Use(PipelinePhase.Activation, (ctxt, next) =>
+ {
+ next(ctxt);
+
+ activatedInstances.Add(ctxt.Instance);
+ });
+ });
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>));
builder.RegisterType().PropertiesAutowired();
diff --git a/test/Autofac.Test/Core/Activators/Delegate/DelegateActivatorTests.cs b/test/Autofac.Test/Core/Activators/Delegate/DelegateActivatorTests.cs
index 25a725fd1..5283954ce 100644
--- a/test/Autofac.Test/Core/Activators/Delegate/DelegateActivatorTests.cs
+++ b/test/Autofac.Test/Core/Activators/Delegate/DelegateActivatorTests.cs
@@ -21,14 +21,17 @@ public void Constructor_DoesNotAcceptNullType()
}
[Fact]
- public void ActivateInstance_ReturnsResultOfInvokingSuppliedDelegate()
+ public void Pipeline_ReturnsResultOfInvokingSuppliedDelegate()
{
var instance = new object();
var target =
new DelegateActivator(typeof(object), (c, p) => instance);
- Assert.Same(instance, target.ActivateInstance(new ContainerBuilder().Build(), Enumerable.Empty()));
+ var container = Factory.CreateEmptyContainer();
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+
+ Assert.Same(instance, invoker(container, Factory.NoParameters));
}
[Fact]
@@ -36,8 +39,11 @@ public void WhenActivationDelegateReturnsNull_ExceptionDescribesLimitType()
{
var target = new DelegateActivator(typeof(string), (c, p) => null);
+ var container = Factory.CreateEmptyContainer();
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+
var ex = Assert.Throws(
- () => target.ActivateInstance(new ContainerBuilder().Build(), Enumerable.Empty()));
+ () => invoker(container, Factory.NoParameters));
Assert.Contains(typeof(string).ToString(), ex.Message);
}
diff --git a/test/Autofac.Test/Core/Activators/ProvidedInstance/ProvidedInstanceActivatorTests.cs b/test/Autofac.Test/Core/Activators/ProvidedInstance/ProvidedInstanceActivatorTests.cs
index e4b6b010d..a246e23c3 100644
--- a/test/Autofac.Test/Core/Activators/ProvidedInstance/ProvidedInstanceActivatorTests.cs
+++ b/test/Autofac.Test/Core/Activators/ProvidedInstance/ProvidedInstanceActivatorTests.cs
@@ -19,7 +19,11 @@ public void WhenInitializedWithInstance_ThatInstanceIsReturnedFromActivateInstan
ProvidedInstanceActivator target = new ProvidedInstanceActivator(instance);
- var actual = target.ActivateInstance(Factory.CreateEmptyContainer(), Factory.NoParameters);
+ var container = Factory.CreateEmptyContainer();
+
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+
+ var actual = invoker(container, Factory.NoParameters);
Assert.Same(instance, actual);
}
@@ -32,10 +36,14 @@ public void ActivatingAProvidedInstanceTwice_RaisesException()
ProvidedInstanceActivator target =
new ProvidedInstanceActivator(instance);
- target.ActivateInstance(Factory.CreateEmptyContainer(), Factory.NoParameters);
+ var container = Factory.CreateEmptyContainer();
+
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+
+ invoker(container, Factory.NoParameters);
Assert.Throws(() =>
- target.ActivateInstance(Factory.CreateEmptyContainer(), Factory.NoParameters));
+ invoker(container, Factory.NoParameters));
}
}
}
diff --git a/test/Autofac.Test/Core/Activators/Reflection/ReflectionActivatorTests.cs b/test/Autofac.Test/Core/Activators/Reflection/ReflectionActivatorTests.cs
index abc5499bf..4e859f07a 100644
--- a/test/Autofac.Test/Core/Activators/Reflection/ReflectionActivatorTests.cs
+++ b/test/Autofac.Test/Core/Activators/Reflection/ReflectionActivatorTests.cs
@@ -12,18 +12,22 @@ namespace Autofac.Test.Core.Activators.Reflection
public class ReflectionActivatorTests
{
[Fact]
- public void ActivateInstance_DependenciesNotAvailable_ThrowsException()
+ public void Pipeline_DependenciesNotAvailable_ThrowsException()
{
var target = Factory.CreateReflectionActivator(typeof(DependsByCtor));
+
+ var container = Factory.CreateEmptyContainer();
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+
var ex = Assert.Throws(
- () => target.ActivateInstance(Factory.CreateEmptyContext(), Factory.NoParameters));
+ () => invoker(container, Factory.NoParameters));
// I.e. the type of the missing dependency.
Assert.Contains(nameof(DependsByProp), ex.Message);
}
[Fact]
- public void ActivateInstance_ResolvesConstructorDependencies()
+ public void Pipeline_ResolvesConstructorDependencies()
{
var o = new object();
const string s = "s";
@@ -34,7 +38,8 @@ public void ActivateInstance_ResolvesConstructorDependencies()
var container = builder.Build();
var target = Factory.CreateReflectionActivator(typeof(Dependent));
- var instance = target.ActivateInstance(container, Factory.NoParameters);
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+ var instance = invoker(container, Factory.NoParameters);
Assert.NotNull(instance);
Assert.IsType(instance);
@@ -46,10 +51,12 @@ public void ActivateInstance_ResolvesConstructorDependencies()
}
[Fact]
- public void ActivateInstance_ReturnsInstanceOfTargetType()
+ public void Pipeline_ReturnsInstanceOfTargetType()
{
var target = Factory.CreateReflectionActivator(typeof(object));
- var instance = target.ActivateInstance(Factory.CreateEmptyContainer(), Factory.NoParameters);
+ var container = Factory.CreateEmptyContainer();
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+ var instance = invoker(Factory.CreateEmptyContainer(), Factory.NoParameters);
Assert.NotNull(instance);
Assert.IsType(instance);
@@ -63,7 +70,8 @@ public void ByDefault_ChoosesConstructorWithMostResolvableParameters()
var container = builder.Build();
var target = Factory.CreateReflectionActivator(typeof(MultipleConstructors));
- var instance = target.ActivateInstance(container, Factory.NoParameters);
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+ var instance = invoker(container, Factory.NoParameters);
Assert.NotNull(instance);
Assert.IsType(instance);
@@ -79,8 +87,9 @@ public void ByDefault_ChoosesMostParameterisedConstructor()
};
var target = Factory.CreateReflectionActivator(typeof(ThreeConstructors), parameters);
-
- var instance = target.ActivateInstance(Factory.CreateEmptyContainer(), Factory.NoParameters);
+ var container = Factory.CreateEmptyContainer();
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+ var instance = invoker(Factory.CreateEmptyContainer(), Factory.NoParameters);
Assert.NotNull(instance);
Assert.IsType(instance);
@@ -95,7 +104,9 @@ public void CanResolveConstructorsWithGenericParameters()
{
var activator = Factory.CreateReflectionActivator(typeof(WithGenericCtor));
var parameters = new Parameter[] { new NamedParameter("t", "Hello") };
- var instance = activator.ActivateInstance(Factory.CreateEmptyContainer(), parameters);
+ var container = Factory.CreateEmptyContainer();
+ var invoker = activator.GetPipelineInvoker(container.ComponentRegistry);
+ var instance = invoker(container, parameters);
Assert.IsType>(instance);
}
@@ -163,8 +174,12 @@ public void Constructor_DoesNotAcceptNullType()
public void NonPublicConstructorsIgnored()
{
var target = Factory.CreateReflectionActivator(typeof(InternalDefaultConstructor));
+
+ // Constructor finding happens at pipeline construction; not when the pipeline is invoked.
+ var invoker = target.GetPipelineInvoker(Factory.CreateEmptyComponentRegistry());
+
var dx = Assert.Throws(() =>
- target.ActivateInstance(Factory.CreateEmptyContainer(), Factory.NoParameters));
+ invoker(Factory.CreateEmptyContainer(), Factory.NoParameters));
Assert.Contains(typeof(DefaultConstructorFinder).Name, dx.Message);
}
@@ -174,7 +189,9 @@ public void PropertiesWithPrivateSetters_AreIgnored()
{
var setters = new Parameter[] { new NamedPropertyParameter("P", 1) };
var activator = Factory.CreateReflectionActivator(typeof(PrivateSetProperty), Factory.NoParameters, setters);
- var instance = activator.ActivateInstance(Factory.CreateEmptyContainer(), Factory.NoParameters);
+ var container = Factory.CreateEmptyContainer();
+ var invoker = activator.GetPipelineInvoker(container.ComponentRegistry);
+ var instance = invoker(container, Factory.NoParameters);
Assert.IsType(instance);
}
@@ -192,7 +209,9 @@ public void ProvidedParameters_OverrideThoseInContext()
var target = Factory.CreateReflectionActivator(typeof(AcceptsObjectParameter), parameters);
- var instance = (AcceptsObjectParameter)target.ActivateInstance(container, Factory.NoParameters);
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+
+ var instance = (AcceptsObjectParameter)invoker(container, Factory.NoParameters);
Assert.Same(parameterInstance, instance.P);
Assert.NotSame(containedInstance, instance.P);
@@ -209,7 +228,9 @@ public void SetsMultipleConfiguredProperties()
new NamedPropertyParameter("P2", p2),
};
var target = Factory.CreateReflectionActivator(typeof(R), Enumerable.Empty(), properties);
- var instance = (R)target.ActivateInstance(new ContainerBuilder().Build(), Enumerable.Empty());
+ var container = Factory.CreateEmptyContainer();
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+ var instance = (R)invoker(container, Enumerable.Empty());
Assert.Equal(1, instance.P1);
Assert.Equal(2, instance.P2);
}
@@ -219,7 +240,7 @@ public void ThrowsWhenNoPublicConstructors()
{
var target = Factory.CreateReflectionActivator(typeof(NoPublicConstructor));
var dx = Assert.Throws(
- () => target.ActivateInstance(Factory.CreateEmptyContainer(), Factory.NoParameters));
+ () => target.GetPipelineInvoker(Factory.CreateEmptyComponentRegistry()));
Assert.Contains(typeof(NoPublicConstructor).FullName, dx.Message);
Assert.Equal(typeof(NoPublicConstructor), dx.OffendingType);
@@ -232,7 +253,10 @@ public void WhenNullReferenceTypeParameterSupplied_ItIsPassedToTheComponent()
var target = Factory.CreateReflectionActivator(typeof(AcceptsObjectParameter), parameters);
- var instance = target.ActivateInstance(new ContainerBuilder().Build(), Factory.NoParameters);
+ var container = Factory.CreateEmptyContainer();
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+
+ var instance = invoker(container, Factory.NoParameters);
Assert.NotNull(instance);
Assert.IsType(instance);
@@ -250,7 +274,10 @@ public void WhenReferenceTypeParameterSupplied_ItIsProvidedToTheComponent()
var target = Factory.CreateReflectionActivator(typeof(AcceptsObjectParameter), parameters);
- var instance = target.ActivateInstance(new ContainerBuilder().Build(), Factory.NoParameters);
+ var container = Factory.CreateEmptyContainer();
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+
+ var instance = invoker(container, Factory.NoParameters);
Assert.NotNull(instance);
Assert.IsType(instance);
@@ -267,7 +294,10 @@ public void WhenValueTypeParameterIsSuppliedWithNull_TheDefaultForTheValueTypeIs
var target = Factory.CreateReflectionActivator(typeof(AcceptsIntParameter), parameters);
- var instance = target.ActivateInstance(Factory.CreateEmptyContainer(), Factory.NoParameters);
+ var container = Factory.CreateEmptyContainer();
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+
+ var instance = invoker(container, Factory.NoParameters);
Assert.NotNull(instance);
Assert.IsType(instance);
@@ -285,7 +315,10 @@ public void WhenValueTypeParameterSupplied_ItIsPassedToTheComponent()
var target = Factory.CreateReflectionActivator(typeof(AcceptsIntParameter), parameters);
- var instance = target.ActivateInstance(Factory.CreateEmptyContainer(), Factory.NoParameters);
+ var container = Factory.CreateEmptyContainer();
+ var invoker = target.GetPipelineInvoker(container.ComponentRegistry);
+
+ var instance = invoker(container, Factory.NoParameters);
Assert.NotNull(instance);
Assert.IsType(instance);
diff --git a/test/Autofac.Test/Core/ContainerTests.cs b/test/Autofac.Test/Core/ContainerTests.cs
index ed1a640f7..e7e74200b 100644
--- a/test/Autofac.Test/Core/ContainerTests.cs
+++ b/test/Autofac.Test/Core/ContainerTests.cs
@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Autofac.Core;
using Autofac.Core.Registration;
+using Autofac.Core.Resolving.Pipeline;
using Autofac.Test.Scenarios.Parameterisation;
using Autofac.Test.Util;
using Xunit;
@@ -206,10 +207,10 @@ private class ReplaceInstanceModule : Module
{
protected override void AttachToComponentRegistration(IComponentRegistryBuilder componentRegistry, IComponentRegistration registration)
{
- registration.Activating += (o, args) =>
+ registration.PipelineBuilding += (o, builder) => builder.Use(PipelinePhase.Activation, (ctxt, next) =>
{
- args.ReplaceInstance(new ReplaceableComponent { IsReplaced = true });
- };
+ ctxt.Instance = new ReplaceableComponent { IsReplaced = true };
+ });
}
}
diff --git a/test/Autofac.Test/Core/Lifetime/LifetimeScopeTests.cs b/test/Autofac.Test/Core/Lifetime/LifetimeScopeTests.cs
index 3d58ef903..69ac39d55 100644
--- a/test/Autofac.Test/Core/Lifetime/LifetimeScopeTests.cs
+++ b/test/Autofac.Test/Core/Lifetime/LifetimeScopeTests.cs
@@ -4,6 +4,7 @@
using Autofac.Core;
using Autofac.Core.Activators.Delegate;
using Autofac.Core.Lifetime;
+using Autofac.Core.Pipeline;
using Autofac.Core.Registration;
using Autofac.Features.Decorators;
using Autofac.Test.Scenarios.RegistrationSources;
diff --git a/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs
new file mode 100644
index 000000000..be9a2eb67
--- /dev/null
+++ b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs
@@ -0,0 +1,485 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Autofac.Core;
+using Autofac.Core.Diagnostics;
+using Autofac.Core.Lifetime;
+using Autofac.Core.Resolving;
+using Autofac.Core.Resolving.Middleware;
+using Autofac.Core.Resolving.Pipeline;
+using Xunit;
+
+namespace Autofac.Test.Core.Pipeline
+{
+ public class PipelineBuilderTests
+ {
+ [Fact]
+ public void CanHaveSingleStage()
+ {
+ var pipelineBuilder = new ResolvePipelineBuilder();
+ var order = new List();
+ pipelineBuilder.Use(PipelinePhase.RequestStart, (ctxt, next) =>
+ {
+ order.Add("1");
+ next(ctxt);
+ });
+
+ var built = pipelineBuilder.Build();
+
+ built.Invoke(new MockPipelineRequestContext());
+
+ Assert.Collection(
+ order,
+ e => Assert.Equal("1", e));
+ }
+
+ [Fact]
+ public void CanAddMiddlewareInPhaseOrder()
+ {
+ var pipelineBuilder = new ResolvePipelineBuilder();
+ var order = new List();
+ pipelineBuilder.Use("1", PipelinePhase.RequestStart, (ctxt, next) =>
+ {
+ order.Add("1");
+ next(ctxt);
+ });
+ pipelineBuilder.Use("2", PipelinePhase.ScopeSelection, (ctxt, next) =>
+ {
+ order.Add("2");
+ next(ctxt);
+ });
+ pipelineBuilder.Use("3", PipelinePhase.Activation, (ctxt, next) =>
+ {
+ order.Add("3");
+ next(ctxt);
+ });
+
+ var built = pipelineBuilder.Build();
+
+ built.Invoke(new MockPipelineRequestContext());
+
+ Assert.Collection(
+ order,
+ el => Assert.Equal("1", el.ToString()),
+ el => Assert.Equal("2", el.ToString()),
+ el => Assert.Equal("3", el.ToString()));
+ }
+
+ [Fact]
+ public void CanAddMiddlewareInReversePhaseOrder()
+ {
+ var pipelineBuilder = new ResolvePipelineBuilder();
+
+ var order = new List();
+ pipelineBuilder.Use("3", PipelinePhase.Activation, (ctxt, next) =>
+ {
+ order.Add("3");
+ next(ctxt);
+ });
+ pipelineBuilder.Use("2", PipelinePhase.ScopeSelection, (ctxt, next) =>
+ {
+ order.Add("2");
+ next(ctxt);
+ });
+ pipelineBuilder.Use("1", PipelinePhase.RequestStart, (ctxt, next) =>
+ {
+ order.Add("1");
+ next(ctxt);
+ });
+
+ var built = pipelineBuilder.Build();
+
+ built.Invoke(new MockPipelineRequestContext());
+
+ Assert.Collection(
+ order,
+ el => Assert.Equal("1", el.ToString()),
+ el => Assert.Equal("2", el.ToString()),
+ el => Assert.Equal("3", el.ToString()));
+ }
+
+ [Fact]
+ public void CanAddMiddlewareInMixedPhaseOrder()
+ {
+ var pipelineBuilder = new ResolvePipelineBuilder();
+
+ var order = new List();
+ pipelineBuilder.Use("3", PipelinePhase.ParameterSelection, (ctxt, next) =>
+ {
+ order.Add("3");
+ next(ctxt);
+ });
+ pipelineBuilder.Use("4", PipelinePhase.Activation, (ctxt, next) =>
+ {
+ order.Add("4");
+ next(ctxt);
+ });
+ pipelineBuilder.Use("1", PipelinePhase.RequestStart, (ctxt, next) =>
+ {
+ order.Add("1");
+ next(ctxt);
+ });
+ pipelineBuilder.Use("2", PipelinePhase.ScopeSelection, (ctxt, next) =>
+ {
+ order.Add("2");
+ next(ctxt);
+ });
+
+ var built = pipelineBuilder.Build();
+
+ built.Invoke(new MockPipelineRequestContext());
+
+ Assert.Collection(
+ order,
+ el => Assert.Equal("1", el.ToString()),
+ el => Assert.Equal("2", el.ToString()),
+ el => Assert.Equal("3", el.ToString()),
+ el => Assert.Equal("4", el.ToString()));
+ }
+
+ [Fact]
+ public void CanControlPhaseAddPriority()
+ {
+ var pipelineBuilder = new ResolvePipelineBuilder();
+ var order = new List();
+ pipelineBuilder.Use("2", PipelinePhase.Activation, (ctxt, next) =>
+ {
+ order.Add("2");
+ next(ctxt);
+ });
+ pipelineBuilder.Use("1", PipelinePhase.Activation, MiddlewareInsertionMode.StartOfPhase, (ctxt, next) =>
+ {
+ order.Add("1");
+ next(ctxt);
+ });
+ pipelineBuilder.Use("3", PipelinePhase.Activation, (ctxt, next) =>
+ {
+ order.Add("3");
+ next(ctxt);
+ });
+
+ var built = pipelineBuilder.Build();
+
+ built.Invoke(new MockPipelineRequestContext());
+
+ Assert.Collection(
+ order,
+ el => Assert.Equal("1", el.ToString()),
+ el => Assert.Equal("2", el.ToString()),
+ el => Assert.Equal("3", el.ToString()));
+ }
+
+ [Fact]
+ public void CanControlPhaseAddPriorityWithPrecedingPhase()
+ {
+ var pipelineBuilder = new ResolvePipelineBuilder();
+
+ var order = new List();
+ pipelineBuilder.Use("3", PipelinePhase.ScopeSelection, (ctxt, next) =>
+ {
+ order.Add("3");
+ next(ctxt);
+ });
+ pipelineBuilder.Use("1", PipelinePhase.RequestStart, (ctxt, next) =>
+ {
+ order.Add("1");
+ next(ctxt);
+ });
+ pipelineBuilder.Use("2", PipelinePhase.ScopeSelection, MiddlewareInsertionMode.StartOfPhase, (ctxt, next) =>
+ {
+ order.Add("2");
+ next(ctxt);
+ });
+
+ var built = pipelineBuilder.Build();
+
+ built.Invoke(new MockPipelineRequestContext());
+
+ Assert.Collection(
+ order,
+ el => Assert.Equal("1", el.ToString()),
+ el => Assert.Equal("2", el.ToString()),
+ el => Assert.Equal("3", el.ToString()));
+ }
+
+ [Fact]
+ public void CanAddMultipleMiddlewareToEmptyPipeline()
+ {
+ var pipelineBuilder = new ResolvePipelineBuilder();
+ var order = new List();
+
+ pipelineBuilder.UseRange(new[]
+ {
+ new DelegateMiddleware("1", PipelinePhase.RequestStart, (ctxt, next) =>
+ {
+ order.Add("1");
+ next(ctxt);
+ }),
+ new DelegateMiddleware("2", PipelinePhase.ScopeSelection, (ctxt, next) =>
+ {
+ order.Add("2");
+ next(ctxt);
+ }),
+ new DelegateMiddleware("4", PipelinePhase.Activation, (ctxt, next) =>
+ {
+ order.Add("3");
+ next(ctxt);
+ })
+ });
+
+ var built = pipelineBuilder.Build();
+
+ built.Invoke(new MockPipelineRequestContext());
+
+ Assert.Collection(
+ order,
+ el => Assert.Equal("1", el.ToString()),
+ el => Assert.Equal("2", el.ToString()),
+ el => Assert.Equal("3", el.ToString()));
+ }
+
+ [Fact]
+ public void AddMultipleMiddlewareOutOfOrderThrows()
+ {
+ var pipelineBuilder = new ResolvePipelineBuilder();
+ var order = new List();
+
+ pipelineBuilder.UseRange(new[]
+ {
+ new DelegateMiddleware("1", PipelinePhase.RequestStart, (ctxt, next) =>
+ {
+ order.Add("1");
+ next(ctxt);
+ }),
+ new DelegateMiddleware("2", PipelinePhase.ScopeSelection, (ctxt, next) =>
+ {
+ order.Add("2");
+ next(ctxt);
+ }),
+ new DelegateMiddleware("4", PipelinePhase.Activation, (ctxt, next) =>
+ {
+ order.Add("3");
+ next(ctxt);
+ })
+ });
+
+ Assert.Throws(() => pipelineBuilder.UseRange(new[]
+ {
+ new DelegateMiddleware("1", PipelinePhase.RequestStart, (ctxt, next) =>
+ {
+ order.Add("1");
+ next(ctxt);
+ }),
+ new DelegateMiddleware("4", PipelinePhase.Activation, (ctxt, next) =>
+ {
+ order.Add("4");
+ next(ctxt);
+ }),
+ new DelegateMiddleware("2", PipelinePhase.ScopeSelection, (ctxt, next) =>
+ {
+ order.Add("2");
+ next(ctxt);
+ }),
+ }));
+ }
+
+ [Fact]
+ public void AddMultipleMiddlewareToPopulatedPipelineOutOfOrderThrows()
+ {
+ var pipelineBuilder = new ResolvePipelineBuilder();
+ var order = new List();
+
+ Assert.Throws(() => pipelineBuilder.UseRange(new[]
+ {
+ new DelegateMiddleware("1", PipelinePhase.RequestStart, (ctxt, next) =>
+ {
+ order.Add("1");
+ next(ctxt);
+ }),
+ new DelegateMiddleware("4", PipelinePhase.Activation, (ctxt, next) =>
+ {
+ order.Add("4");
+ next(ctxt);
+ }),
+ new DelegateMiddleware("2", PipelinePhase.ScopeSelection, (ctxt, next) =>
+ {
+ order.Add("2");
+ next(ctxt);
+ }),
+ }));
+ }
+
+ [Fact]
+ public void CanAddMultipleMiddlewareToPipelineWithExistingMiddleware()
+ {
+ var pipelineBuilder = new ResolvePipelineBuilder();
+ var order = new List();
+
+ pipelineBuilder.UseRange(new[]
+ {
+ new DelegateMiddleware("1", PipelinePhase.RequestStart, (ctxt, next) =>
+ {
+ order.Add("1");
+ next(ctxt);
+ }),
+ new DelegateMiddleware("3", PipelinePhase.ScopeSelection, (ctxt, next) =>
+ {
+ order.Add("3");
+ next(ctxt);
+ }),
+ new DelegateMiddleware("5", PipelinePhase.Activation, (ctxt, next) =>
+ {
+ order.Add("5");
+ next(ctxt);
+ })
+ });
+
+ pipelineBuilder.UseRange(new[]
+ {
+ new DelegateMiddleware("2", PipelinePhase.RequestStart, (ctxt, next) =>
+ {
+ order.Add("2");
+ next(ctxt);
+ }),
+ new DelegateMiddleware("4", PipelinePhase.ScopeSelection, (ctxt, next) =>
+ {
+ order.Add("4");
+ next(ctxt);
+ }),
+ new DelegateMiddleware("6", PipelinePhase.Activation, (ctxt, next) =>
+ {
+ order.Add("6");
+ next(ctxt);
+ })
+ });
+
+ var built = pipelineBuilder.Build();
+
+ built.Invoke(new MockPipelineRequestContext());
+
+ Assert.Collection(
+ order,
+ el => Assert.Equal("1", el.ToString()),
+ el => Assert.Equal("2", el.ToString()),
+ el => Assert.Equal("3", el.ToString()),
+ el => Assert.Equal("4", el.ToString()),
+ el => Assert.Equal("5", el.ToString()),
+ el => Assert.Equal("6", el.ToString()));
+ }
+
+ private class MockPipelineRequestContext : ResolveRequestContextBase
+ {
+ public MockPipelineRequestContext()
+ : base(
+ new ResolveOperation(new MockLifetimeScope()),
+ new ResolveRequest(new TypedService(typeof(int)), Mocks.GetComponentRegistration(), Enumerable.Empty()),
+ new MockLifetimeScope(),
+ null)
+ {
+ }
+
+ public override object ResolveComponent(ResolveRequest request)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override object ResolveComponentWithNewOperation(ResolveRequest request)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private class MockLifetimeScope : ISharingLifetimeScope
+ {
+ public ISharingLifetimeScope RootLifetimeScope => throw new NotImplementedException();
+
+ public ISharingLifetimeScope ParentLifetimeScope => throw new NotImplementedException();
+
+ public IDisposer Disposer => throw new NotImplementedException();
+
+ public object Tag => throw new NotImplementedException();
+
+ public IComponentRegistry ComponentRegistry => throw new NotImplementedException();
+
+ public event EventHandler ChildLifetimeScopeBeginning
+ {
+ add { }
+ remove { }
+ }
+
+ public event EventHandler CurrentScopeEnding
+ {
+ add { }
+ remove { }
+ }
+
+ public event EventHandler ResolveOperationBeginning
+ {
+ add { }
+ remove { }
+ }
+
+ public void AttachTrace(IResolvePipelineTracer tracer)
+ {
+ throw new NotImplementedException();
+ }
+
+ public ILifetimeScope BeginLifetimeScope()
+ {
+ throw new NotImplementedException();
+ }
+
+ public ILifetimeScope BeginLifetimeScope(object tag)
+ {
+ throw new NotImplementedException();
+ }
+
+ public ILifetimeScope BeginLifetimeScope(Action configurationAction)
+ {
+ throw new NotImplementedException();
+ }
+
+ public ILifetimeScope BeginLifetimeScope(object tag, Action configurationAction)
+ {
+ throw new NotImplementedException();
+ }
+
+ public object CreateSharedInstance(Guid id, Func creator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public object CreateSharedInstance(Guid primaryId, Guid? qualifyingId, Func creator)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Dispose()
+ {
+ throw new NotImplementedException();
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ public object ResolveComponent(ResolveRequest request)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool TryGetSharedInstance(Guid id, out object value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool TryGetSharedInstance(Guid primaryId, Guid? qualifyingId, out object value)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/Autofac.Test/Core/Registration/ComponentRegistryTests.cs b/test/Autofac.Test/Core/Registration/ComponentRegistryTests.cs
index 66117710b..1805e76be 100644
--- a/test/Autofac.Test/Core/Registration/ComponentRegistryTests.cs
+++ b/test/Autofac.Test/Core/Registration/ComponentRegistryTests.cs
@@ -229,7 +229,9 @@ public void LastRegistrationSourceRegisteredIsTheDefault()
IComponentRegistration def;
registry.TryGetRegistration(new TypedService(typeof(object)), out def);
- var result = def.Activator.ActivateInstance(new ContainerBuilder().Build(), Enumerable.Empty());
+ var invoker = def.Activator.GetPipelineInvoker(registry);
+
+ var result = invoker(new ContainerBuilder().Build(), Enumerable.Empty());
Assert.Equal(result, second);
}
diff --git a/test/Autofac.Test/Features/Decorators/DecoratorTests.cs b/test/Autofac.Test/Features/Decorators/DecoratorTests.cs
index 31caefbd0..56d90588c 100644
--- a/test/Autofac.Test/Features/Decorators/DecoratorTests.cs
+++ b/test/Autofac.Test/Features/Decorators/DecoratorTests.cs
@@ -1,5 +1,7 @@
-using System.Linq;
+using System;
+using System.Linq;
using Autofac.Core;
+using Autofac.Core.Diagnostics;
using Xunit;
namespace Autofac.Test.Features.Decorators
diff --git a/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs b/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs
index 68eb787f2..c5f66f03f 100644
--- a/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs
+++ b/test/Autofac.Test/Features/Decorators/OpenGenericDecoratorTests.cs
@@ -2,13 +2,22 @@
using System.Collections.Generic;
using System.Linq;
using Autofac.Core;
+using Autofac.Core.Diagnostics;
using Autofac.Features.Decorators;
using Xunit;
+using Xunit.Abstractions;
namespace Autofac.Test.Features.Decorators
{
public class OpenGenericDecoratorTests
{
+ private readonly ITestOutputHelper _outputHelper;
+
+ public OpenGenericDecoratorTests(ITestOutputHelper outputHelper)
+ {
+ _outputHelper = outputHelper;
+ }
+
// ReSharper disable once UnusedTypeParameter
public interface IService
{
@@ -206,6 +215,15 @@ public void DecoratedInstancePerDependencyRegistrationCanIncludeOtherServices()
builder.RegisterGenericDecorator(typeof(DecoratorA<>), typeof(IService<>));
var container = builder.Build();
+ var tracer = new DefaultDiagnosticTracer();
+
+ container.AttachTrace(tracer);
+
+ tracer.OperationCompleted += (sender, args) =>
+ {
+ _outputHelper.WriteLine(args.TraceContent);
+ };
+
var serviceRegistration = container.RegistrationFor>();
var decoratedServiceRegistration = container.RegistrationFor>();
@@ -233,6 +251,15 @@ public void DecoratedInstancePerLifetimeScopeRegistrationCanIncludeOtherServices
builder.RegisterGenericDecorator(typeof(DecoratorA<>), typeof(IDecoratedService<>));
var container = builder.Build();
+ var tracer = new DefaultDiagnosticTracer();
+
+ container.AttachTrace(tracer);
+
+ tracer.OperationCompleted += (sender, args) =>
+ {
+ _outputHelper.WriteLine(args.TraceContent);
+ };
+
var serviceRegistration = container.RegistrationFor>();
var decoratedServiceRegistration = container.RegistrationFor>();
diff --git a/test/Autofac.Test/Features/OpenGenerics/OpenGenericRegistrationSourceTests.cs b/test/Autofac.Test/Features/OpenGenerics/OpenGenericRegistrationSourceTests.cs
index 760a5f282..9d9f58e17 100644
--- a/test/Autofac.Test/Features/OpenGenerics/OpenGenericRegistrationSourceTests.cs
+++ b/test/Autofac.Test/Features/OpenGenerics/OpenGenericRegistrationSourceTests.cs
@@ -3,6 +3,8 @@
using System.Reflection;
using Autofac.Builder;
using Autofac.Core;
+using Autofac.Core.Resolving;
+using Autofac.Core.Resolving.Pipeline;
using Autofac.Features.OpenGenerics;
using Autofac.Test.Util;
using Xunit;
@@ -32,7 +34,10 @@ public void GeneratesActivatorAndCorrectServices()
typeof(I),
r.Services.Cast().Single().ServiceType);
- var activatedInstance = r.Activator.ActivateInstance(new ContainerBuilder().Build(), Factory.NoParameters);
+ var container = new ContainerBuilder().Build();
+ var invoker = r.Activator.GetPipelineInvoker(container.ComponentRegistry);
+
+ var activatedInstance = invoker(container, Factory.NoParameters);
Assert.IsType>(activatedInstance);
}
@@ -239,7 +244,12 @@ private static bool SourceCanSupply(Type component)
return false;
var registration = registrations.Single();
- var instance = registration.Activator.ActivateInstance(new ContainerBuilder().Build(), Factory.NoParameters);
+
+ var container = new ContainerBuilder().Build();
+
+ var invoker = registration.Activator.GetPipelineInvoker(container.ComponentRegistry);
+
+ var instance = invoker(container, Factory.NoParameters);
Assert.True(closedServiceType.GetTypeInfo().IsAssignableFrom(instance.GetType().GetTypeInfo()));
return true;
@@ -249,6 +259,7 @@ private static OpenGenericRegistrationSource ConstructSource(Type component, Typ
{
return new OpenGenericRegistrationSource(
new RegistrationData(new TypedService(service ?? component)),
+ new ResolvePipelineBuilder(),
new ReflectionActivatorData(component));
}
}
diff --git a/test/Autofac.Test/Mocks.cs b/test/Autofac.Test/Mocks.cs
index a19d44ca9..116dda135 100644
--- a/test/Autofac.Test/Mocks.cs
+++ b/test/Autofac.Test/Mocks.cs
@@ -3,6 +3,9 @@
using System.Reflection;
using Autofac.Core;
using Autofac.Core.Activators.Reflection;
+using Autofac.Core.Diagnostics;
+using Autofac.Core.Resolving;
+using Autofac.Core.Resolving.Pipeline;
namespace Autofac.Test
{
@@ -66,23 +69,14 @@ public void Dispose()
public bool IsAdapterForIndividualComponent { get; }
- public event EventHandler Preparing = (sender, args) => { };
+ public event EventHandler PipelineBuilding;
- public void RaisePreparing(IComponentContext context, Service service, ref IEnumerable parameters)
- {
- }
-
- public event EventHandler> Activating = (sender, args) => { };
-
- public void RaiseActivating(IComponentContext context, IEnumerable parameters, Service service, ref object instance)
- {
- }
-
- public event EventHandler> Activated = (sender, args) => { };
+ public IResolvePipeline ResolvePipeline { get; } = new ResolvePipelineBuilder().Build();
- public void RaiseActivated(IComponentContext context, IEnumerable parameters, Service service, object instance)
+ public void BuildResolvePipeline(IComponentRegistryServices registryServices)
{
+ PipelineBuilding?.Invoke(this, new ResolvePipelineBuilder());
}
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Autofac.Test/ModuleTests.cs b/test/Autofac.Test/ModuleTests.cs
index e0bb621e0..52d4fe2d8 100644
--- a/test/Autofac.Test/ModuleTests.cs
+++ b/test/Autofac.Test/ModuleTests.cs
@@ -15,7 +15,7 @@ internal class ObjectModule : Module
{
protected override void Load(ContainerBuilder builder)
{
- builder.RegisterInstance(new object());
+ builder.RegisterInstance(new Service1());
}
}
@@ -26,7 +26,7 @@ public void LoadsRegistrations()
new ObjectModule().Configure(builder);
var registry = builder.Build();
- Assert.True(registry.IsRegistered(new TypedService(typeof(object))));
+ Assert.True(registry.IsRegistered(new TypedService(typeof(Service1))));
}
[Fact]
@@ -53,7 +53,7 @@ public void AttachesToRegistrations()
Assert.Equal(0, attachingModule.Registrations.Count);
var builder = new ContainerBuilder();
- builder.RegisterType(typeof(object));
+ builder.RegisterType(typeof(Service1));
builder.RegisterModule(attachingModule);
builder.RegisterInstance("Hello!");
@@ -72,7 +72,7 @@ public void AttachesToRegistrationsInScope()
builder.RegisterModule(attachingModule);
using (var container = builder.Build())
- using (var scope = container.BeginLifetimeScope(c => c.RegisterType(typeof(int))))
+ using (var scope = container.BeginLifetimeScope(c => c.RegisterType(typeof(Service1))))
{
var expected = container.ComponentRegistry.Registrations.Count() + scope.ComponentRegistry.Registrations.Count();
Assert.Equal(expected, attachingModule.Registrations.Count);
@@ -89,8 +89,8 @@ public void AttachesToRegistrationsInNestedScope()
builder.RegisterModule(attachingModule);
using (var container = builder.Build())
- using (var outerScope = container.BeginLifetimeScope(c => c.RegisterType(typeof(int))))
- using (var innerScope = outerScope.BeginLifetimeScope(c => c.RegisterType(typeof(double))))
+ using (var outerScope = container.BeginLifetimeScope(c => c.RegisterType(typeof(Service1))))
+ using (var innerScope = outerScope.BeginLifetimeScope(c => c.RegisterType(typeof(Service2))))
{
var expected = container.ComponentRegistry.Registrations.Count()
+ outerScope.ComponentRegistry.Registrations.Count() + innerScope.ComponentRegistry.Registrations.Count();
@@ -137,7 +137,7 @@ public void ModifiedScopesHaveTheirOwnDelegate()
outerBuilder.Properties[MetadataKeys.RegisteredPropertyKey]);
});
- c.RegisterType(typeof(int));
+ c.RegisterType(typeof(Service1));
}))
{
}
@@ -205,5 +205,17 @@ public void CanUseBuilderPropertyBag()
Assert.Equal(2, container.ComponentRegistry.Properties["count"]);
Assert.Equal("value", builder.Properties["prop"]);
}
+
+ private class Service1
+ {
+ }
+
+ private class Service2
+ {
+ }
+
+ private class Service3
+ {
+ }
}
}