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(context, this, parameters, instance, service); - handler(this, args); - instance = args.Instance; - } + if (PipelineBuilding is object) + { + PipelineBuilding.Invoke(this, _lateBuildPipeline); + } - /// - /// Fired when the activation process for a new instance is complete. - /// - public event EventHandler>? Activated; + _lateBuildPipeline.UseRange(_defaultStages); - /// - /// Called by the container once an instance has been fully constructed, including - /// any requested objects that depend on the instance. - /// - /// The context in which the instance was activated. - /// The parameters supplied to the activator. - /// The service being resolved. - /// The instance. - public void RaiseActivated(IComponentContext context, IEnumerable parameters, Service service, object instance) + if (HasStartableService()) + { + _lateBuildPipeline.Use(StartableMiddleware.Instance); + } + + if (Ownership == InstanceOwnership.OwnedByLifetimeScope) + { + // Add the disposal tracking stage. + _lateBuildPipeline.Use(DisposalTrackingMiddleware.Instance); + } + + // Add activator error propagation (want it to run outer-most in the Activator phase). + _lateBuildPipeline.Use(ActivatorErrorHandlingMiddleware.Instance, MiddlewareInsertionMode.StartOfPhase); + + // Allow the activator to configure the pipeline. + Activator.ConfigurePipeline(registryServices, _lateBuildPipeline); + + ResolvePipeline = _lateBuildPipeline.Build(); + } + + private bool HasStartableService() { - var handler = Activated; - handler?.Invoke(this, new ActivatedEventArgs(context, this, parameters, instance, service)); + foreach (var service in Services) + { + if ((service is TypedService typed) && typed.ServiceType == typeof(IStartable)) + { + return true; + } + } + + return false; } /// @@ -220,7 +255,7 @@ public void RaiseActivated(IComponentContext context, IEnumerable par /// A description of the component. public override string ToString() { - // Activator = {0}, Services = [{1}], Lifetime = {2}, Sharing = {3}, Ownership = {4} + // Activator = {0}, Services = [{1}], Lifetime = {2}, Sharing = {3}, Ownership = {4}, Pipeline = {5} return string.Format( CultureInfo.CurrentCulture, ComponentRegistrationResources.ToStringFormat, @@ -228,7 +263,8 @@ public override string ToString() Services.Select(s => s.Description).JoinWith(", "), Lifetime, Sharing, - Ownership); + Ownership, + _builtComponentPipeline is null ? ComponentRegistrationResources.PipelineNotBuilt : _builtComponentPipeline.ToString()); } /// @@ -239,6 +275,7 @@ protected override void Dispose(bool disposing) { if (disposing) Activator.Dispose(); + base.Dispose(disposing); } } diff --git a/src/Autofac/Core/Registration/ComponentRegistrationLifetimeDecorator.cs b/src/Autofac/Core/Registration/ComponentRegistrationLifetimeDecorator.cs index 7eea5e745..89a077d4e 100644 --- a/src/Autofac/Core/Registration/ComponentRegistrationLifetimeDecorator.cs +++ b/src/Autofac/Core/Registration/ComponentRegistrationLifetimeDecorator.cs @@ -26,6 +26,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using Autofac.Core.Resolving.Pipeline; using Autofac.Util; namespace Autofac.Core.Registration @@ -33,7 +34,7 @@ namespace Autofac.Core.Registration /// /// Wraps a component registration, switching its lifetime. /// - [SuppressMessage("Microsoft.ApiDesignGuidelines", "CA2213", Justification = "The creator of the inner registration is responsible for disposal.")] + [SuppressMessage("Microsoft.ApiDesignGuidelines", "CA2215", Justification = "The creator of the inner registration is responsible for disposal.")] internal class ComponentRegistrationLifetimeDecorator : Disposable, IComponentRegistration { private readonly IComponentRegistration _inner; @@ -62,37 +63,17 @@ public ComponentRegistrationLifetimeDecorator(IComponentRegistration inner, ICom public bool IsAdapterForIndividualComponent => _inner.IsAdapterForIndividualComponent; - public event EventHandler Preparing - { - add => _inner.Preparing += value; - remove => _inner.Preparing -= value; - } - - public void RaisePreparing(IComponentContext context, Service service, ref IEnumerable parameters) - { - _inner.RaisePreparing(context, service, ref parameters); - } - - public event EventHandler> Activating - { - add => _inner.Activating += value; - remove => _inner.Activating -= value; - } - - public void RaiseActivating(IComponentContext context, IEnumerable parameters, Service service, ref object instance) - { - _inner.RaiseActivating(context, parameters, service, ref instance); - } + public IResolvePipeline ResolvePipeline => _inner.ResolvePipeline; - public event EventHandler> Activated + public event EventHandler PipelineBuilding { - add => _inner.Activated += value; - remove => _inner.Activated -= value; + add => _inner.PipelineBuilding += value; + remove => _inner.PipelineBuilding -= value; } - public void RaiseActivated(IComponentContext context, IEnumerable parameters, Service service, object instance) + public void BuildResolvePipeline(IComponentRegistryServices registryServices) { - _inner.RaiseActivated(context, parameters, service, instance); + _inner.BuildResolvePipeline(registryServices); } protected override void Dispose(bool disposing) diff --git a/src/Autofac/Core/Registration/ComponentRegistrationResources.Designer.cs b/src/Autofac/Core/Registration/ComponentRegistrationResources.Designer.cs index 1263dc896..dfd473bd6 100644 --- a/src/Autofac/Core/Registration/ComponentRegistrationResources.Designer.cs +++ b/src/Autofac/Core/Registration/ComponentRegistrationResources.Designer.cs @@ -10,7 +10,6 @@ namespace Autofac.Core.Registration { using System; - using System.Reflection; /// @@ -20,7 +19,7 @@ namespace Autofac.Core.Registration { // 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", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class ComponentRegistrationResources { @@ -40,7 +39,7 @@ internal ComponentRegistrationResources() { 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.Registration.ComponentRegistrationResources", typeof(ComponentRegistrationResources).GetTypeInfo().Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Autofac.Core.Registration.ComponentRegistrationResources", typeof(ComponentRegistrationResources).Assembly); resourceMan = temp; } return resourceMan; @@ -62,7 +61,25 @@ internal ComponentRegistrationResources() { } /// - /// Looks up a localized string similar to Activator = {0}, Services = [{1}], Lifetime = {2}, Sharing = {3}, Ownership = {4}. + /// Looks up a localized string similar to Component pipeline has not yet been built.. + /// + internal static string ComponentPipelineHasNotBeenBuilt { + get { + return ResourceManager.GetString("ComponentPipelineHasNotBeenBuilt", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not Built. + /// + internal static string PipelineNotBuilt { + get { + return ResourceManager.GetString("PipelineNotBuilt", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Activator = {0}, Services = [{1}], Lifetime = {2}, Sharing = {3}, Ownership = {4}, Pipeline = {5}. /// internal static string ToStringFormat { get { diff --git a/src/Autofac/Core/Registration/ComponentRegistrationResources.resx b/src/Autofac/Core/Registration/ComponentRegistrationResources.resx index 58288e9a3..449faad6e 100644 --- a/src/Autofac/Core/Registration/ComponentRegistrationResources.resx +++ b/src/Autofac/Core/Registration/ComponentRegistrationResources.resx @@ -112,12 +112,18 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Component pipeline has not yet been built. + + + Not Built + - Activator = {0}, Services = [{1}], Lifetime = {2}, Sharing = {3}, Ownership = {4} + Activator = {0}, Services = [{1}], Lifetime = {2}, Sharing = {3}, Ownership = {4}, Pipeline = {5} \ No newline at end of file diff --git a/src/Autofac/Core/Registration/ComponentRegistryBuilder.cs b/src/Autofac/Core/Registration/ComponentRegistryBuilder.cs index 367a4d440..deb9838d6 100644 --- a/src/Autofac/Core/Registration/ComponentRegistryBuilder.cs +++ b/src/Autofac/Core/Registration/ComponentRegistryBuilder.cs @@ -3,6 +3,8 @@ using System.Runtime.CompilerServices; using Autofac.Builder; +using Autofac.Core.Pipeline; +using Autofac.Core.Resolving; using Autofac.Util; namespace Autofac.Core.Registration @@ -72,7 +74,15 @@ protected override void Dispose(bool disposing) /// A new component registry with the configured component registrations. public IComponentRegistry Build() { - return new ComponentRegistry(_registeredServicesTracker, Properties); + // Go through all our registrations and build the component pipeline for each one. + foreach (var registration in _registeredServicesTracker.Registrations) + { + registration.BuildResolvePipeline(_registeredServicesTracker); + } + + var componentRegistry = new ComponentRegistry(_registeredServicesTracker, Properties); + + return componentRegistry; } /// @@ -179,4 +189,4 @@ public event EventHandler RegistrationSourceAd return null; } } -} \ No newline at end of file +} diff --git a/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs b/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs index 7e8893d6e..9627d1648 100644 --- a/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs +++ b/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using Autofac.Builder; +using Autofac.Core.Pipeline; using Autofac.Features.Decorators; using Autofac.Util; @@ -15,10 +17,13 @@ namespace Autofac.Core.Registration /// internal class DefaultRegisteredServicesTracker : Disposable, IRegisteredServicesTracker { + private readonly Func> _registrationAccessor; + private readonly Func> _decoratorRegistrationsAccessor; + /// /// Keeps track of the status of registered services. /// - private readonly ConcurrentDictionary _serviceInfo = new ConcurrentDictionary(); + private readonly Dictionary _serviceInfo = new Dictionary(); /// /// External registration sources. @@ -33,39 +38,27 @@ internal class DefaultRegisteredServicesTracker : Disposable, IRegisteredService private readonly ConcurrentDictionary> _decorators = new ConcurrentDictionary>(); - /// - /// Gets the set of properties used during component registration. - /// - /// - /// An that can be used to share context across registrations. - /// - private readonly IDictionary _properties = new Dictionary(); - /// /// Protects instance variables from concurrent access. /// private readonly object _synchRoot = new object(); + public DefaultRegisteredServicesTracker() + { + _registrationAccessor = RegistrationsFor; + _decoratorRegistrationsAccessor = InternalDecoratorRegistrationsFor; + } + /// /// Fired whenever a component is registered - either explicitly or via a /// . /// - public event EventHandler Registered - { - add => _properties[MetadataKeys.InternalRegisteredPropertyKey] = GetRegistered() + value; - - remove => _properties[MetadataKeys.InternalRegisteredPropertyKey] = GetRegistered() - value; - } + public event EventHandler? Registered; /// /// Fired when an is added to the registry. /// - public event EventHandler RegistrationSourceAdded - { - add => _properties[MetadataKeys.InternalRegistrationSourceAddedPropertyKey] = GetRegistrationSourceAdded() + value; - - remove => _properties[MetadataKeys.InternalRegistrationSourceAddedPropertyKey] = GetRegistrationSourceAdded() - value; - } + public event EventHandler? RegistrationSourceAdded; /// public IEnumerable Registrations @@ -73,7 +66,7 @@ public IEnumerable Registrations get { lock (_synchRoot) - return _registrations.ToArray(); + return _registrations.ToList(); } } @@ -84,22 +77,28 @@ public IEnumerable Sources { lock (_synchRoot) { - return _dynamicRegistrationSources.ToArray(); + return _dynamicRegistrationSources.ToList(); } } } /// - public virtual void AddRegistration(IComponentRegistration registration, bool preserveDefaults, bool originatedFromSource = false) + public virtual void AddRegistration(IComponentRegistration registration, bool preserveDefaults, bool originatedFromDynamicSource = false) { foreach (var service in registration.Services) { var info = GetServiceInfo(service); - info.AddImplementation(registration, preserveDefaults, originatedFromSource); + info.AddImplementation(registration, preserveDefaults, originatedFromDynamicSource); } _registrations.Add(registration); - GetRegistered()?.Invoke(this, registration); + var handler = Registered; + handler?.Invoke(this, registration); + + if (originatedFromDynamicSource) + { + registration.BuildResolvePipeline(this); + } } /// @@ -113,7 +112,7 @@ public void AddRegistrationSource(IRegistrationSource source) foreach (var serviceRegistrationInfo in _serviceInfo) serviceRegistrationInfo.Value.Include(source); - var handler = GetRegistrationSourceAdded(); + var handler = RegistrationSourceAdded; handler?.Invoke(this, source); } } @@ -149,7 +148,7 @@ public IEnumerable RegistrationsFor(Service service) lock (_synchRoot) { var info = GetInitializedServiceInfo(service); - return info.Implementations.ToArray(); + return info.Implementations.ToList(); } } @@ -158,11 +157,15 @@ public IReadOnlyList DecoratorsFor(IServiceWithType serv { if (service == null) throw new ArgumentNullException(nameof(service)); - return _decorators.GetOrAdd(service, s => - RegistrationsFor(new DecoratorService(s.ServiceType)) + return _decorators.GetOrAdd(service, _decoratorRegistrationsAccessor); + } + + private IReadOnlyList InternalDecoratorRegistrationsFor(IServiceWithType service) + { + return RegistrationsFor(new DecoratorService(service.ServiceType)) .Where(r => !r.IsAdapterForIndividualComponent) .OrderBy(r => r.GetRegistrationOrder()) - .ToArray()); + .ToList(); } /// @@ -189,7 +192,7 @@ private ServiceRegistrationInfo GetInitializedServiceInfo(Service service) while (info.HasSourcesToQuery) { var next = info.DequeueNextSource(); - foreach (var provided in next.RegistrationsFor(service, RegistrationsFor)) + foreach (var provided in next.RegistrationsFor(service, _registrationAccessor)) { // This ensures that multiple services provided by the same // component share a single component (we don't re-query for them) @@ -199,7 +202,7 @@ private ServiceRegistrationInfo GetInitializedServiceInfo(Service service) if (additionalInfo.IsInitialized || additionalInfo == info) continue; if (!additionalInfo.IsInitializing) - additionalInfo.BeginInitialization(_dynamicRegistrationSources.Where(src => src != next)); + additionalInfo.BeginInitialization(ExcludeSource(_dynamicRegistrationSources, next)); else additionalInfo.SkipSource(next); } @@ -212,6 +215,18 @@ private ServiceRegistrationInfo GetInitializedServiceInfo(Service service) return info; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static IEnumerable ExcludeSource(IEnumerable sources, IRegistrationSource exclude) + { + foreach (var item in sources) + { + if (item != exclude) + { + yield return item; + } + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private ServiceRegistrationInfo GetServiceInfo(Service service) { @@ -219,20 +234,8 @@ private ServiceRegistrationInfo GetServiceInfo(Service service) return existing; var info = new ServiceRegistrationInfo(service); - _serviceInfo.TryAdd(service, info); + _serviceInfo.Add(service, info); return info; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private EventHandler? GetRegistered() => - _properties.TryGetValue(MetadataKeys.InternalRegisteredPropertyKey, out var registered) - ? (EventHandler?)registered - : null; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private EventHandler? GetRegistrationSourceAdded() => - _properties.TryGetValue(MetadataKeys.InternalRegistrationSourceAddedPropertyKey, out var registrationSourceAdded) - ? (EventHandler?)registrationSourceAdded - : null; } -} \ No newline at end of file +} diff --git a/src/Autofac/Core/Registration/ExternalComponentRegistration.cs b/src/Autofac/Core/Registration/ExternalComponentRegistration.cs index 9529908a3..b1dfc4459 100644 --- a/src/Autofac/Core/Registration/ExternalComponentRegistration.cs +++ b/src/Autofac/Core/Registration/ExternalComponentRegistration.cs @@ -33,9 +33,18 @@ namespace Autofac.Core.Registration /// internal class ExternalComponentRegistration : ComponentRegistration { - public ExternalComponentRegistration(Guid id, IInstanceActivator activator, IComponentLifetime lifetime, InstanceSharing sharing, InstanceOwnership ownership, IEnumerable services, IDictionary metadata, IComponentRegistration target, bool isAdapterForIndividualComponent) + public ExternalComponentRegistration( + Guid id, + IInstanceActivator activator, + IComponentLifetime lifetime, + InstanceSharing sharing, + InstanceOwnership ownership, + IEnumerable services, + IDictionary metadata, + IComponentRegistration target, + bool isAdapterForIndividualComponent) : base(id, activator, lifetime, sharing, ownership, services, metadata, target, isAdapterForIndividualComponent) { } } -} \ No newline at end of file +} diff --git a/src/Autofac/Core/Registration/IComponentRegistryBuilder.cs b/src/Autofac/Core/Registration/IComponentRegistryBuilder.cs index 4b36d127c..4f2628753 100644 --- a/src/Autofac/Core/Registration/IComponentRegistryBuilder.cs +++ b/src/Autofac/Core/Registration/IComponentRegistryBuilder.cs @@ -86,4 +86,4 @@ public interface IComponentRegistryBuilder : IDisposable /// event EventHandler RegistrationSourceAdded; } -} \ No newline at end of file +} diff --git a/src/Autofac/Core/Registration/IRegisteredServicesTracker.cs b/src/Autofac/Core/Registration/IRegisteredServicesTracker.cs index 63c7d0530..b0bfc0dcb 100644 --- a/src/Autofac/Core/Registration/IRegisteredServicesTracker.cs +++ b/src/Autofac/Core/Registration/IRegisteredServicesTracker.cs @@ -1,21 +1,20 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; namespace Autofac.Core.Registration { /// /// Keeps track of the status of registered services. /// - internal interface IRegisteredServicesTracker : IDisposable + internal interface IRegisteredServicesTracker : IDisposable, IComponentRegistryServices { /// /// Adds a registration to the list of registered services. /// /// The registration to add. /// Indicates whether the defaults should be preserved. - /// Indicates whether this is an explicitly added registration or that it has been added by a different source. - void AddRegistration(IComponentRegistration registration, bool preserveDefaults, bool originatedFromSource = false); + /// Indicates whether this is an explicitly added registration or that it has been added by a dynamic registration source. + void AddRegistration(IComponentRegistration registration, bool preserveDefaults, bool originatedFromDynamicSource = false); /// /// Add a registration source that will provide registrations on-the-fly. @@ -23,14 +22,6 @@ internal interface IRegisteredServicesTracker : IDisposable /// The source to register. void AddRegistrationSource(IRegistrationSource source); - /// - /// Attempts to find a default registration for the specified service. - /// - /// 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); - /// /// Fired whenever a component is registered - either explicitly or via an . /// @@ -51,22 +42,6 @@ internal interface IRegisteredServicesTracker : IDisposable /// IEnumerable Sources { get; } - /// - /// Determines whether the specified service is registered. - /// - /// The service to test. - /// True if the service is registered. - bool IsRegistered(Service service); - - /// - /// Selects from the available registrations after ensuring that any - /// dynamic registration sources that may provide - /// have been invoked. - /// - /// The service for which registrations are sought. - /// Registrations supporting . - IEnumerable RegistrationsFor(Service service); - /// /// Selects all available decorator registrations that can be applied to the specified service. /// @@ -74,4 +49,4 @@ internal interface IRegisteredServicesTracker : IDisposable /// Decorator registrations applicable to . IReadOnlyList DecoratorsFor(IServiceWithType service); } -} \ No newline at end of file +} diff --git a/src/Autofac/Core/Registration/ScopeRestrictedRegisteredServicesTracker.cs b/src/Autofac/Core/Registration/ScopeRestrictedRegisteredServicesTracker.cs index 203d6cba2..753c32b11 100644 --- a/src/Autofac/Core/Registration/ScopeRestrictedRegisteredServicesTracker.cs +++ b/src/Autofac/Core/Registration/ScopeRestrictedRegisteredServicesTracker.cs @@ -36,4 +36,4 @@ public override void AddRegistration(IComponentRegistration registration, bool p base.AddRegistration(toRegister, preserveDefaults, originatedFromSource); } } -} \ No newline at end of file +} diff --git a/src/Autofac/Core/Resolving/CircularDependencyDetector.cs b/src/Autofac/Core/Resolving/CircularDependencyDetector.cs deleted file mode 100644 index d83902b58..000000000 --- a/src/Autofac/Core/Resolving/CircularDependencyDetector.cs +++ /dev/null @@ -1,75 +0,0 @@ -// 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; - -namespace Autofac.Core.Resolving -{ - internal class CircularDependencyDetector - { - /// - /// Catch circular dependencies that are triggered by post-resolve processing (e.g. 'OnActivated'). - /// - [SuppressMessage("SA1306", "SA1306", Justification = "Changed const to static on temporary basis until we can solve circular dependencies without a limit.")] - private static int MaxResolveDepth = 50; - - private static string CreateDependencyGraphTo(IComponentRegistration registration, Stack activationStack) - { - if (registration == null) throw new ArgumentNullException(nameof(registration)); - if (activationStack == null) throw new ArgumentNullException(nameof(activationStack)); - - var dependencyGraph = Display(registration); - - return activationStack.Select(a => a.ComponentRegistration) - .Aggregate(dependencyGraph, (current, requestor) => Display(requestor) + " -> " + current); - } - - private static string Display(IComponentRegistration registration) - { - return registration.Activator.DisplayName(); - } - - public static void CheckForCircularDependency(IComponentRegistration registration, Stack activationStack, int callDepth) - { - if (registration == null) throw new ArgumentNullException(nameof(registration)); - - if (callDepth > MaxResolveDepth) - throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, CircularDependencyDetectorResources.MaxDepthExceeded, registration)); - - // Checks for circular dependency - foreach (var a in activationStack) - { - if (a.ComponentRegistration == registration) - { - throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, CircularDependencyDetectorResources.CircularDependency, CreateDependencyGraphTo(registration, activationStack))); - } - } - } - } -} diff --git a/src/Autofac/Core/Resolving/ComponentActivationResources.Designer.cs b/src/Autofac/Core/Resolving/ComponentActivationResources.Designer.cs index d8ce5035b..009f9c5e2 100644 --- a/src/Autofac/Core/Resolving/ComponentActivationResources.Designer.cs +++ b/src/Autofac/Core/Resolving/ComponentActivationResources.Designer.cs @@ -8,8 +8,6 @@ // //------------------------------------------------------------------------------ -using System.Reflection; - namespace Autofac.Core.Resolving { using System; @@ -21,7 +19,7 @@ namespace Autofac.Core.Resolving { // 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", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class ComponentActivationResources { @@ -41,7 +39,7 @@ internal ComponentActivationResources() { 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.Resolving.ComponentActivationResources", typeof(ComponentActivationResources).GetTypeInfo().Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Autofac.Core.Resolving.ComponentActivationResources", typeof(ComponentActivationResources).Assembly); resourceMan = temp; } return resourceMan; @@ -79,16 +77,5 @@ internal static string ErrorDuringActivation { return ResourceManager.GetString("ErrorDuringActivation", resourceCulture); } } - - /// - /// Looks up a localized string similar to Unable to resolve the type '{0}' because the lifetime scope it belongs in can't be located. The following services are exposed by this registration: - ///{1} - ///Details. - /// - internal static string UnableToLocateLifetimeScope { - get { - return ResourceManager.GetString("UnableToLocateLifetimeScope", resourceCulture); - } - } } } diff --git a/src/Autofac/Core/Resolving/ComponentActivationResources.resx b/src/Autofac/Core/Resolving/ComponentActivationResources.resx index 28895a5b7..f8a5c0b92 100644 --- a/src/Autofac/Core/Resolving/ComponentActivationResources.resx +++ b/src/Autofac/Core/Resolving/ComponentActivationResources.resx @@ -123,9 +123,4 @@ An exception was thrown while activating {0}. - - Unable to resolve the type '{0}' because the lifetime scope it belongs in can't be located. The following services are exposed by this registration: -{1} -Details - \ No newline at end of file diff --git a/src/Autofac/Core/Resolving/IResolveOperation.cs b/src/Autofac/Core/Resolving/IResolveOperation.cs index 87e444d72..593098bc8 100644 --- a/src/Autofac/Core/Resolving/IResolveOperation.cs +++ b/src/Autofac/Core/Resolving/IResolveOperation.cs @@ -24,6 +24,7 @@ // OTHER DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; namespace Autofac.Core.Resolving { @@ -44,11 +45,11 @@ public interface IResolveOperation /// /// Raised when the entire operation is complete. /// - event EventHandler CurrentOperationEnding; + event EventHandler? CurrentOperationEnding; /// - /// Raised when an instance is looked up within the operation. + /// Raised when a resolve request starts. /// - event EventHandler InstanceLookupBeginning; + event EventHandler? ResolveRequestBeginning; } } diff --git a/src/Autofac/Core/Resolving/InstanceLookup.cs b/src/Autofac/Core/Resolving/InstanceLookup.cs deleted file mode 100644 index d9884cdc4..000000000 --- a/src/Autofac/Core/Resolving/InstanceLookup.cs +++ /dev/null @@ -1,220 +0,0 @@ -// 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 System.Text; -using Autofac.Builder; -using Autofac.Features.Decorators; - -namespace Autofac.Core.Resolving -{ - // Is a component context that pins resolution to a point in the context hierarchy - [SuppressMessage("Microsoft.ApiDesignGuidelines", "CA2213", Justification = "The instance lookup activation scope gets disposed of by the creator of the scope.")] - internal class InstanceLookup : IComponentContext, IInstanceLookup - { - private readonly IResolveOperation _context; - private readonly ISharingLifetimeScope _activationScope; - private readonly IComponentRegistration? _decoratorTargetComponent; - private readonly Service _service; - private object? _newInstance; - private bool _executed; - private const string ActivatorChainExceptionData = "ActivatorChain"; - - public InstanceLookup( - IResolveOperation context, - ISharingLifetimeScope mostNestedVisibleScope, - ResolveRequest request) - { - _context = context; - _service = request.Service; - _decoratorTargetComponent = request.DecoratorTarget; - ComponentRegistration = request.Registration; - Parameters = request.Parameters; - - try - { - _activationScope = ComponentRegistration.Lifetime.FindScope(mostNestedVisibleScope); - } - catch (DependencyResolutionException ex) - { - var services = new StringBuilder(); - foreach (var s in ComponentRegistration.Services) - { - services.Append("- "); - services.AppendLine(s.Description); - } - - var message = string.Format(CultureInfo.CurrentCulture, ComponentActivationResources.UnableToLocateLifetimeScope, ComponentRegistration.Activator.LimitType, services); - throw new DependencyResolutionException(message, ex); - } - } - - public object Execute() - { - if (_executed) - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ComponentActivationResources.ActivationAlreadyExecuted, this.ComponentRegistration)); - - _executed = true; - - var sharing = _decoratorTargetComponent?.Sharing ?? ComponentRegistration.Sharing; - - var resolveParameters = Parameters as Parameter[] ?? Parameters.ToArray(); - - if (!_activationScope.TryGetSharedInstance(ComponentRegistration.Id, _decoratorTargetComponent?.Id, out var instance)) - { - instance = sharing == InstanceSharing.Shared - ? _activationScope.CreateSharedInstance(ComponentRegistration.Id, _decoratorTargetComponent?.Id, () => CreateInstance(Parameters)) - : CreateInstance(Parameters); - } - - var decoratorTarget = instance; - - instance = InstanceDecorator.TryDecorateRegistration( - _service, - ComponentRegistration, - instance, - _activationScope, - resolveParameters); - - var handler = InstanceLookupEnding; - handler?.Invoke(this, new InstanceLookupEndingEventArgs(this, NewInstanceActivated)); - - StartStartableComponent(decoratorTarget); - - return instance; - } - - private void StartStartableComponent(object instance) - { - if (instance is IStartable startable - && ComponentRegistration.Services.Any(s => (s is TypedService typed) && typed.ServiceType == typeof(IStartable)) - && !ComponentRegistration.Metadata.ContainsKey(MetadataKeys.AutoActivated) - && ComponentRegistry.Properties.ContainsKey(MetadataKeys.StartOnActivatePropertyKey)) - { - // Issue #916: Set the startable as "done starting" BEFORE calling Start - // so you don't get a StackOverflow if the component creates a child scope - // during Start. You don't want the startable trying to activate itself. - ComponentRegistration.Metadata[MetadataKeys.AutoActivated] = true; - startable.Start(); - } - } - - private bool NewInstanceActivated => _newInstance != null; - - [SuppressMessage("CA1031", "CA1031", Justification = "General exception gets rethrown in a PropagateActivationException.")] - private object CreateInstance(IEnumerable parameters) - { - ComponentRegistration.RaisePreparing(this, _service, ref parameters); - - var resolveParameters = parameters as Parameter[] ?? parameters.ToArray(); - - try - { - _newInstance = ComponentRegistration.Activator.ActivateInstance(this, resolveParameters); - - ComponentRegistration.RaiseActivating(this, resolveParameters, _service, ref _newInstance); - } - catch (ObjectDisposedException) - { - throw; - } - catch (Exception ex) - { - throw PropagateActivationException(this.ComponentRegistration.Activator, ex); - } - - if (ComponentRegistration.Ownership == InstanceOwnership.OwnedByLifetimeScope) - { - // The fact this adds instances for disposal agnostic of the activator is - // important. The ProvidedInstanceActivator will NOT dispose of the provided - // instance once the instance has been activated - assuming that it will be - // done during the lifetime scope's Disposer executing. - if (_newInstance is IDisposable instanceAsDisposable) - { - _activationScope.Disposer.AddInstanceForDisposal(instanceAsDisposable); - } - else if (_newInstance is IAsyncDisposable asyncDisposableInstance) - { - _activationScope.Disposer.AddInstanceForAsyncDisposal(asyncDisposableInstance); - } - } - - return _newInstance; - } - - private static DependencyResolutionException PropagateActivationException(IInstanceActivator activator, Exception exception) - { - var activatorChain = activator.DisplayName(); - var innerException = exception; - - if (exception.Data.Contains(ActivatorChainExceptionData) && - exception.Data[ActivatorChainExceptionData] is string innerChain) - { - activatorChain = activatorChain + " -> " + innerChain; - innerException = exception.InnerException; - } - - var result = new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, ComponentActivationResources.ErrorDuringActivation, activatorChain), innerException); - result.Data[ActivatorChainExceptionData] = activatorChain; - return result; - } - - public void Complete() - { - if (!NewInstanceActivated) return; - - var beginningHandler = CompletionBeginning; - beginningHandler?.Invoke(this, new InstanceLookupCompletionBeginningEventArgs(this)); - - ComponentRegistration.RaiseActivated(this, Parameters, _service, _newInstance!); - - var endingHandler = CompletionEnding; - endingHandler?.Invoke(this, new InstanceLookupCompletionEndingEventArgs(this)); - } - - public IComponentRegistry ComponentRegistry => _activationScope.ComponentRegistry; - - public object ResolveComponent(ResolveRequest request) - { - return _context.GetOrCreateInstance(_activationScope, request); - } - - public IComponentRegistration ComponentRegistration { get; } - - public ILifetimeScope ActivationScope => _activationScope; - - public IEnumerable Parameters { get; } - - public event EventHandler? InstanceLookupEnding; - - public event EventHandler? CompletionBeginning; - - public event EventHandler? CompletionEnding; - } -} diff --git a/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs new file mode 100644 index 000000000..1a8ca0c7c --- /dev/null +++ b/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs @@ -0,0 +1,94 @@ +// 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; +using System.Globalization; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Resolving.Middleware +{ + /// + /// Provides middleware to wrap propagating activator exceptions. + /// + internal class ActivatorErrorHandlingMiddleware : IResolveMiddleware + { + private const string ActivatorChainExceptionData = "ActivatorChain"; + + /// + /// Gets a singleton instance of the middleware. + /// + public static ActivatorErrorHandlingMiddleware Instance { get; } = new ActivatorErrorHandlingMiddleware(); + + private ActivatorErrorHandlingMiddleware() + { + } + + /// + public PipelinePhase Phase => PipelinePhase.Activation; + + /// + public void Execute(ResolveRequestContextBase context, Action next) + { + try + { + next(context); + + if (context.Instance is null) + { + // Exited the Activation Stage without creating an instance. + throw new DependencyResolutionException(MiddlewareMessages.ActivatorDidNotPopulateInstance); + } + } + catch (ObjectDisposedException) + { + throw; + } + catch (Exception ex) + { + throw PropagateActivationException(context.Registration.Activator, ex); + } + } + + private static DependencyResolutionException PropagateActivationException(IInstanceActivator activator, Exception exception) + { + var activatorChain = activator.DisplayName(); + var innerException = exception; + + if (exception.Data.Contains(ActivatorChainExceptionData) && + exception.Data[ActivatorChainExceptionData] is string innerChain) + { + activatorChain = activatorChain + " -> " + innerChain; + innerException = exception.InnerException; + } + + var result = new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, ComponentActivationResources.ErrorDuringActivation, activatorChain), innerException); + result.Data[ActivatorChainExceptionData] = activatorChain; + return result; + } + + /// + public override string ToString() => nameof(ActivatorErrorHandlingMiddleware); + } +} diff --git a/src/Autofac/Core/Resolving/CircularDependencyDetectorResources.Designer.cs b/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMessages.Designer.cs similarity index 90% rename from src/Autofac/Core/Resolving/CircularDependencyDetectorResources.Designer.cs rename to src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMessages.Designer.cs index 8463da88b..db3c46800 100644 --- a/src/Autofac/Core/Resolving/CircularDependencyDetectorResources.Designer.cs +++ b/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMessages.Designer.cs @@ -8,9 +8,8 @@ // //------------------------------------------------------------------------------ -namespace Autofac.Core.Resolving { +namespace Autofac.Core.Resolving.Middleware { using System; - using System.Reflection; /// @@ -20,17 +19,17 @@ namespace Autofac.Core.Resolving { // 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", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class CircularDependencyDetectorResources { + internal class CircularDependencyDetectorMessages { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal CircularDependencyDetectorResources() { + internal CircularDependencyDetectorMessages() { } /// @@ -40,7 +39,7 @@ internal CircularDependencyDetectorResources() { 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.Resolving.CircularDependencyDetectorResources", typeof(CircularDependencyDetectorResources).GetTypeInfo().Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Autofac.Core.Resolving.Middleware.CircularDependencyDetectorMessages", typeof(CircularDependencyDetectorMessages).Assembly); resourceMan = temp; } return resourceMan; diff --git a/src/Autofac/Core/Resolving/CircularDependencyDetectorResources.resx b/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMessages.resx similarity index 100% rename from src/Autofac/Core/Resolving/CircularDependencyDetectorResources.resx rename to src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMessages.resx diff --git a/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs new file mode 100644 index 000000000..f255fe7c7 --- /dev/null +++ b/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs @@ -0,0 +1,113 @@ +// 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; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Resolving.Middleware +{ + /// + /// Common stage. Added to the start of all component pipelines. + /// + internal class CircularDependencyDetectorMiddleware : IResolveMiddleware + { + public const int DefaultMaxResolveDepth = 50; + + public static CircularDependencyDetectorMiddleware Default { get; } = new CircularDependencyDetectorMiddleware(DefaultMaxResolveDepth); + + private readonly int _maxResolveDepth; + + public CircularDependencyDetectorMiddleware(int maxResolveDepth) + { + _maxResolveDepth = maxResolveDepth; + } + + public PipelinePhase Phase => PipelinePhase.RequestStart; + + public void Execute(ResolveRequestContextBase context, Action next) + { + var activationDepth = context.Operation.RequestDepth; + + if (activationDepth > _maxResolveDepth) + { + throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, CircularDependencyDetectorMessages.MaxDepthExceeded, context.Service)); + } + + var requestStack = context.Operation.RequestStack; + + // The first one is the current resolve request. + // Do our circular dependency check. + if (activationDepth > 1) + { + var registration = context.Registration; + + // Only check the stack for shared components. + foreach (var requestEntry in requestStack) + { + if (requestEntry.Registration == registration) + { + throw new DependencyResolutionException(string.Format( + CultureInfo.CurrentCulture, + CircularDependencyDetectorMessages.CircularDependency, + CreateDependencyGraphTo(registration, requestStack))); + } + } + } + + requestStack.Push(context); + + try + { + // Circular dependency check is done, move to the next stage. + next(context); + } + finally + { + requestStack.Pop(); + } + } + + public override string ToString() => nameof(CircularDependencyDetectorMiddleware); + + private static string CreateDependencyGraphTo(IComponentRegistration registration, IEnumerable requestStack) + { + if (registration == null) throw new ArgumentNullException(nameof(registration)); + if (requestStack == null) throw new ArgumentNullException(nameof(requestStack)); + + var dependencyGraph = Display(registration); + + return requestStack.Select(a => a.Registration) + .Aggregate(dependencyGraph, (current, requestor) => Display(requestor) + " -> " + current); + } + + private static string Display(IComponentRegistration registration) + { + return registration.Activator.DisplayName(); + } + } +} diff --git a/src/Autofac/Features/Decorators/InstanceDecorator.cs b/src/Autofac/Core/Resolving/Middleware/DecoratorMiddleware.cs similarity index 58% rename from src/Autofac/Features/Decorators/InstanceDecorator.cs rename to src/Autofac/Core/Resolving/Middleware/DecoratorMiddleware.cs index fa45dc9db..3b5998fe3 100644 --- a/src/Autofac/Features/Decorators/InstanceDecorator.cs +++ b/src/Autofac/Core/Resolving/Middleware/DecoratorMiddleware.cs @@ -1,5 +1,5 @@ // This software is part of the Autofac IoC container -// Copyright © 2018 Autofac Contributors +// Copyright © 2020 Autofac Contributors // https://autofac.org // // Permission is hereby granted, free of charge, to any person @@ -23,31 +23,71 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. -using System.Collections.Generic; +using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; -using Autofac.Core; using Autofac.Core.Registration; +using Autofac.Core.Resolving.Pipeline; +using Autofac.Features.Decorators; -namespace Autofac.Features.Decorators +namespace Autofac.Core.Resolving.Middleware { - internal static class InstanceDecorator + /// + /// Provides resolve middleware for locating decorators. + /// + internal class DecoratorMiddleware : IResolveMiddleware { - internal static object TryDecorateRegistration( - Service service, - IComponentRegistration registration, - object instance, - IComponentContext context, - IEnumerable parameters) + /// + /// Gets a singleton instance of the middleware. + /// + public static DecoratorMiddleware Instance { get; } = new DecoratorMiddleware(); + + private DecoratorMiddleware() + { + } + + /// + public PipelinePhase Phase => PipelinePhase.Decoration; + + /// + public void Execute(ResolveRequestContextBase context, Action next) + { + // Proceed down the pipeline. + next(context); + + // If we can get a decorated instance, then use it. + if (TryDecorateRegistration(context, out var newInstance)) + { + context.Instance = newInstance; + } + } + + /// + public override string ToString() => nameof(DecoratorMiddleware); + + private static bool TryDecorateRegistration(ResolveRequestContextBase context, [NotNullWhen(true)] out object? instance) { + var service = context.Service; + if (service is DecoratorService || !(service is IServiceWithType serviceWithType) - || registration is ExternalComponentRegistration) return instance; + || context.Registration is ExternalComponentRegistration) + { + instance = null; + return false; + } var decoratorRegistrations = context.ComponentRegistry.DecoratorsFor(serviceWithType); - if (decoratorRegistrations.Count == 0) return instance; + if (decoratorRegistrations.Count == 0) + { + instance = null; + return false; + } + + instance = context.Instance!; var serviceType = serviceWithType.ServiceType; - var resolveParameters = parameters as Parameter[] ?? parameters.ToArray(); + var resolveParameters = context.Parameters as Parameter[] ?? context.Parameters.ToArray(); var instanceType = instance.GetType(); var decoratorContext = DecoratorContext.Create(instanceType, serviceType, instance); @@ -69,14 +109,14 @@ internal static object TryDecorateRegistration( invokeParameters[invokeParameters.Length - 2] = serviceParameter; invokeParameters[invokeParameters.Length - 1] = contextParameter; - var resolveRequest = new ResolveRequest(decoratorService, decoratorRegistration, invokeParameters, registration); - instance = context.ResolveComponent(resolveRequest); + var resolveRequest = new ResolveRequest(decoratorService, decoratorRegistration, invokeParameters, context.Registration); + instance = context.ResolveComponentWithNewOperation(resolveRequest); if (index < decoratorCount - 1) decoratorContext = decoratorContext.UpdateContext(instance); } - return instance; + return true; } } } diff --git a/src/Autofac/Core/Resolving/Middleware/DelegateMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/DelegateMiddleware.cs new file mode 100644 index 000000000..73e92475b --- /dev/null +++ b/src/Autofac/Core/Resolving/Middleware/DelegateMiddleware.cs @@ -0,0 +1,64 @@ +// 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; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Resolving.Middleware +{ + /// + /// Wraps pipeline delegates from the Use* methods in . + /// + internal class DelegateMiddleware : IResolveMiddleware + { + private readonly string _name; + private readonly Action> _callback; + + /// + /// Initializes a new instance of the class. + /// + /// The middleware description. + /// The pipeline phase. + /// The callback to execute. + public DelegateMiddleware(string description, PipelinePhase phase, Action> callback) + { + _name = description; + Phase = phase; + _callback = callback; + } + + /// + public PipelinePhase Phase { get; } + + /// + public void Execute(ResolveRequestContextBase context, Action next) + { + _callback(context, next); + } + + /// + public override string ToString() => _name; + } +} diff --git a/src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs new file mode 100644 index 000000000..20f5ed0b8 --- /dev/null +++ b/src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs @@ -0,0 +1,70 @@ +// 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; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Resolving.Middleware +{ + /// + /// Ensures activated instances are tracked. + /// + internal class DisposalTrackingMiddleware : IResolveMiddleware + { + public static DisposalTrackingMiddleware Instance { get; } = new DisposalTrackingMiddleware(); + + private DisposalTrackingMiddleware() + { + } + + /// + public PipelinePhase Phase => PipelinePhase.Activation; + + /// + public void Execute(ResolveRequestContextBase context, Action next) + { + next(context); + + if (context.Registration.Ownership == InstanceOwnership.OwnedByLifetimeScope) + { + // The fact this adds instances for disposal agnostic of the activator is + // important. The ProvidedInstanceActivator will NOT dispose of the provided + // instance once the instance has been activated - assuming that it will be + // done during the lifetime scope's Disposer executing. + if (context.Instance is IDisposable instanceAsDisposable) + { + context.ActivationScope.Disposer.AddInstanceForDisposal(instanceAsDisposable); + } + else if (context.Instance is IAsyncDisposable asyncDisposableInstance) + { + context.ActivationScope.Disposer.AddInstanceForAsyncDisposal(asyncDisposableInstance); + } + } + } + + /// + public override string ToString() => nameof(DisposalTrackingMiddleware); + } +} diff --git a/src/Autofac/Core/Resolving/Middleware/MiddlewareMessages.Designer.cs b/src/Autofac/Core/Resolving/Middleware/MiddlewareMessages.Designer.cs new file mode 100644 index 000000000..0ae4f5d32 --- /dev/null +++ b/src/Autofac/Core/Resolving/Middleware/MiddlewareMessages.Designer.cs @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// +// 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.Resolving.Middleware { + 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 MiddlewareMessages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal MiddlewareMessages() { + } + + /// + /// 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.Resolving.Middleware.MiddlewareMessages", typeof(MiddlewareMessages).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 The Activation phase of the resolve pipeline did not populate the request context's Instance.. + /// + internal static string ActivatorDidNotPopulateInstance { + get { + return ResourceManager.GetString("ActivatorDidNotPopulateInstance", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to resolve the type '{0}' because the lifetime scope it belongs in can't be located. The following services are exposed by this registration: + ///{1} + ///Details. + /// + internal static string UnableToLocateLifetimeScope { + get { + return ResourceManager.GetString("UnableToLocateLifetimeScope", resourceCulture); + } + } + } +} diff --git a/src/Autofac/Core/Resolving/Middleware/MiddlewareMessages.resx b/src/Autofac/Core/Resolving/Middleware/MiddlewareMessages.resx new file mode 100644 index 000000000..e55949b98 --- /dev/null +++ b/src/Autofac/Core/Resolving/Middleware/MiddlewareMessages.resx @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + The Activation phase of the resolve pipeline did not populate the request context's Instance. + + + Unable to resolve the type '{0}' because the lifetime scope it belongs in can't be located. The following services are exposed by this registration: +{1} +Details + + \ No newline at end of file diff --git a/src/Autofac/Core/Resolving/Middleware/ScopeSelectionMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/ScopeSelectionMiddleware.cs new file mode 100644 index 000000000..fc51d66c3 --- /dev/null +++ b/src/Autofac/Core/Resolving/Middleware/ScopeSelectionMiddleware.cs @@ -0,0 +1,71 @@ +// 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; +using System.Globalization; +using System.Text; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Resolving.Middleware +{ + /// + /// Selects the correct activation scope based on the registration's lifetime. + /// + internal class ScopeSelectionMiddleware : IResolveMiddleware + { + public static ScopeSelectionMiddleware Instance => new ScopeSelectionMiddleware(); + + private ScopeSelectionMiddleware() + { + // Only want to use the static instance. + } + + public PipelinePhase Phase => PipelinePhase.ScopeSelection; + + public void Execute(ResolveRequestContextBase context, Action next) + { + try + { + context.ChangeScope(context.Registration.Lifetime.FindScope(context.ActivationScope)); + } + catch (DependencyResolutionException ex) + { + var services = new StringBuilder(); + foreach (var s in context.Registration.Services) + { + services.Append("- "); + services.AppendLine(s.Description); + } + + var message = string.Format(CultureInfo.CurrentCulture, MiddlewareMessages.UnableToLocateLifetimeScope, context.Registration.Activator.LimitType, services); + throw new DependencyResolutionException(message, ex); + } + + next(context); + } + + public override string ToString() => nameof(ScopeSelectionMiddleware); + } +} diff --git a/src/Autofac/Core/Resolving/Middleware/SharingMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/SharingMiddleware.cs new file mode 100644 index 000000000..b4500b557 --- /dev/null +++ b/src/Autofac/Core/Resolving/Middleware/SharingMiddleware.cs @@ -0,0 +1,85 @@ +// 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; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Resolving.Middleware +{ + /// + /// Checks for a shared instance of the requested registration. + /// + internal class SharingMiddleware : IResolveMiddleware + { + /// + /// Gets the singleton instance of the middleware. + /// + public static SharingMiddleware Instance { get; } = new SharingMiddleware(); + + /// + public PipelinePhase Phase => PipelinePhase.Sharing; + + /// + public void Execute(ResolveRequestContextBase context, Action next) + { + var registration = context.Registration; + var decoratorRegistration = context.DecoratorTarget; + + var sharing = decoratorRegistration?.Sharing ?? registration.Sharing; + + if (context.ActivationScope.TryGetSharedInstance(registration.Id, decoratorRegistration?.Id, out var instance)) + { + // Assign instance; do not continue the pipeline. + context.Instance = instance; + } + else + { + if (sharing == InstanceSharing.Shared) + { + // Assign the result of CreateSharedInstance onto the context, because if concurrency causes CreateSharedInstance to return + // the existing instance, the rest of the pipeline shouldn't run. + context.Instance = context.ActivationScope.CreateSharedInstance(registration.Id, decoratorRegistration?.Id, () => + { + next(context); + + if (context.Instance is null) + { + throw new InvalidOperationException("Instance null after pipeline invoke."); + } + + return context.Instance; + }); + } + else + { + next(context); + } + } + } + + /// + public override string ToString() => nameof(SharingMiddleware); + } +} diff --git a/src/Autofac/Core/Resolving/Middleware/StartableMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/StartableMiddleware.cs new file mode 100644 index 000000000..f09d99914 --- /dev/null +++ b/src/Autofac/Core/Resolving/Middleware/StartableMiddleware.cs @@ -0,0 +1,64 @@ +// 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; +using Autofac.Builder; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Resolving.Middleware +{ + /// + /// Middleware that starts startable components. + /// + internal class StartableMiddleware : IResolveMiddleware + { + public static StartableMiddleware Instance { get; } = new StartableMiddleware(); + + private StartableMiddleware() + { + } + + public PipelinePhase Phase => PipelinePhase.Activation; + + /// + public void Execute(ResolveRequestContextBase context, Action next) + { + next(context); + + if (context.Instance is IStartable startable + && !context.Registration.Metadata.ContainsKey(MetadataKeys.AutoActivated) + && context.ComponentRegistry.Properties.ContainsKey(MetadataKeys.StartOnActivatePropertyKey)) + { + // Issue #916: Set the startable as "done starting" BEFORE calling Start + // so you don't get a StackOverflow if the component creates a child scope + // during Start. You don't want the startable trying to activate itself. + context.Registration.Metadata[MetadataKeys.AutoActivated] = true; + startable.Start(); + } + } + + public override string ToString() => nameof(StartableMiddleware); + } +} diff --git a/src/Autofac/Core/Resolving/Pipeline/IResolveMiddleware.cs b/src/Autofac/Core/Resolving/Pipeline/IResolveMiddleware.cs new file mode 100644 index 000000000..2fbd95639 --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/IResolveMiddleware.cs @@ -0,0 +1,48 @@ +// 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; + +namespace Autofac.Core.Resolving.Pipeline +{ + /// + /// Defines an executable resolve middleware. + /// + public interface IResolveMiddleware + { + /// + /// Gets the phase of the resolve pipeline at which to execute. + /// + PipelinePhase Phase { get; } + + /// + /// Invoked when this middleware is executed as part of an active . The middleware should usually call + /// the method in order to continue the pipeline, unless the middleware fully satisfies the request. + /// + /// The context for the resolve request. + /// The method to invoke to continue the pipeline execution; pass this method the argument. + void Execute(ResolveRequestContextBase context, Action next); + } +} diff --git a/src/Autofac/Core/Resolving/Pipeline/IResolvePipeline.cs b/src/Autofac/Core/Resolving/Pipeline/IResolvePipeline.cs new file mode 100644 index 000000000..8c5cdcaab --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/IResolvePipeline.cs @@ -0,0 +1,39 @@ +// 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. + +namespace Autofac.Core.Resolving.Pipeline +{ + /// + /// Represents a pipeline that can be invoked to resolve an instance of a service. + /// + public interface IResolvePipeline + { + /// + /// Invoke the pipeline to the end, or until an exception is thrown. + /// + /// The request context. + void Invoke(ResolveRequestContextBase context); + } +} diff --git a/src/Autofac/Core/Resolving/Pipeline/IResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/IResolvePipelineBuilder.cs new file mode 100644 index 000000000..0567819f3 --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/IResolvePipelineBuilder.cs @@ -0,0 +1,121 @@ +// 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; +using System.Collections.Generic; + +namespace Autofac.Core.Resolving.Pipeline +{ + /// + /// Provides the ability to build a resolve pipeline from a set of middleware. + /// + public interface IResolvePipelineBuilder + { + /// + /// Construct a concrete resolve pipeline from this builder. + /// + /// A built pipeline. + IResolvePipeline Build(); + + /// + /// Use a piece of middleware in a resolve pipeline. + /// + /// The middleware instance. + /// The insertion mode specifying whether to add at the start or end of the phase. + /// The same builder instance. + IResolvePipelineBuilder Use(IResolveMiddleware middleware, MiddlewareInsertionMode insertionMode = MiddlewareInsertionMode.EndOfPhase); + + /// + /// Use a middleware callback in a resolve pipeline. + /// + /// The phase of the pipeline the middleware should run at. + /// + /// A callback invoked to run your middleware. + /// This callback takes a , containing the context for the resolve request, plus + /// a callback to invoke to continue the pipeline. + /// + /// The same builder instance. + IResolvePipelineBuilder Use(PipelinePhase phase, Action> callback); + + /// + /// Use a middleware callback in a resolve pipeline. + /// + /// A description for the middleware; this will show up in any resolve tracing. + /// The phase of the pipeline the middleware should run at. + /// + /// A callback invoked to run your middleware. + /// This callback takes a , containing the context for the resolve request, plus + /// a callback to invoke to continue the pipeline. + /// + /// The same builder instance. + IResolvePipelineBuilder Use(string name, PipelinePhase phase, Action> callback); + + /// + /// Use a middleware callback in a resolve pipeline. + /// + /// The phase of the pipeline the middleware should run at. + /// The insertion mode specifying whether to add at the start or end of the phase. + /// + /// A callback invoked to run your middleware. + /// This callback takes a , containing the context for the resolve request, plus + /// a callback to invoke to continue the pipeline. + /// + /// The same builder instance. + IResolvePipelineBuilder Use(PipelinePhase phase, MiddlewareInsertionMode insertionMode, Action> callback); + + /// + /// Use a middleware callback in a resolve pipeline. + /// + /// A description for the middleware; this will show up in any resolve tracing. + /// The phase of the pipeline the middleware should run at. + /// The insertion mode specifying whether to add at the start or end of the phase. + /// + /// A callback invoked to run your middleware. + /// This callback takes a , containing the context for the resolve request, plus + /// a callback to invoke to continue the pipeline. + /// + /// The same builder instance. + IResolvePipelineBuilder Use(string name, PipelinePhase phase, MiddlewareInsertionMode insertionMode, Action> callback); + + /// + /// Use a set of multiple, ordered middleware instances in a resolve pipeline. + /// + /// The set of middleware items to add to the pipelne. The set of middleware must be pre-ordered by phase. + /// The insertion mode specifying whether to add at the start or end of the phase. + /// The same builder instance. + IResolvePipelineBuilder UseRange(IEnumerable middleware, MiddlewareInsertionMode insertionMode = MiddlewareInsertionMode.EndOfPhase); + + /// + /// Gets the set of middleware currently registered. + /// + IEnumerable Middleware { get; } + + /// + /// Clone this builder, returning a new builder containing the set of middleware already added. + /// + /// A new builder instance. + IResolvePipelineBuilder Clone(); + } +} diff --git a/src/Autofac/Core/Resolving/InstanceLookupEndingEventArgs.cs b/src/Autofac/Core/Resolving/Pipeline/MiddlewareDeclaration.cs similarity index 58% rename from src/Autofac/Core/Resolving/InstanceLookupEndingEventArgs.cs rename to src/Autofac/Core/Resolving/Pipeline/MiddlewareDeclaration.cs index b283baebb..c1e965612 100644 --- a/src/Autofac/Core/Resolving/InstanceLookupEndingEventArgs.cs +++ b/src/Autofac/Core/Resolving/Pipeline/MiddlewareDeclaration.cs @@ -23,36 +23,37 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. -using System; - -namespace Autofac.Core.Resolving +namespace Autofac.Core.Resolving.Pipeline { /// - /// Fired when an instance is looked up. + /// Defines a declared piece of middleware in a pipeline builder. /// - public class InstanceLookupEndingEventArgs : EventArgs + internal sealed class MiddlewareDeclaration { + public MiddlewareDeclaration(IResolveMiddleware middleware) + { + Middleware = middleware; + Phase = middleware.Phase; + } + /// - /// Initializes a new instance of the class. + /// Gets or sets the next node in a pipeline set. /// - /// The instance lookup that is ending. - /// True if a new instance was created as part of the operation. - public InstanceLookupEndingEventArgs(IInstanceLookup instanceLookup, bool newInstanceActivated) - { - if (instanceLookup == null) throw new ArgumentNullException(nameof(instanceLookup)); + public MiddlewareDeclaration? Next { get; set; } - InstanceLookup = instanceLookup; - NewInstanceActivated = newInstanceActivated; - } + /// + /// Gets or sets the previous node in a pipeline set. + /// + public MiddlewareDeclaration? Previous { get; set; } /// - /// Gets a value indicating whether a new instance was created as part of the operation. + /// Gets the middleware for this declaration. /// - public bool NewInstanceActivated { get; } + public IResolveMiddleware Middleware { get; } /// - /// Gets the instance lookup operation that is ending. + /// Gets the declared phase of the middleware. /// - public IInstanceLookup InstanceLookup { get; } + public PipelinePhase Phase { get; } } -} \ No newline at end of file +} diff --git a/src/Autofac/Core/Resolving/InstanceLookupCompletionBeginningEventArgs.cs b/src/Autofac/Core/Resolving/Pipeline/MiddlewareInsertionMode.cs similarity index 62% rename from src/Autofac/Core/Resolving/InstanceLookupCompletionBeginningEventArgs.cs rename to src/Autofac/Core/Resolving/Pipeline/MiddlewareInsertionMode.cs index 0759765f5..c69718d88 100644 --- a/src/Autofac/Core/Resolving/InstanceLookupCompletionBeginningEventArgs.cs +++ b/src/Autofac/Core/Resolving/Pipeline/MiddlewareInsertionMode.cs @@ -23,29 +23,23 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. -using System; - -namespace Autofac.Core.Resolving +namespace Autofac.Core.Resolving.Pipeline { /// - /// Raised when the completion phase of an instance lookup operation begins. + /// Provides the modes of insertion when adding middleware to an . /// - public class InstanceLookupCompletionBeginningEventArgs : EventArgs + public enum MiddlewareInsertionMode { /// - /// Initializes a new instance of the class. + /// The new middleware should be added at the end of the middleware's declared phase. The added middleware will run after any middleware already added + /// at the same phase. /// - /// The instance lookup that is beginning the completion phase. - public InstanceLookupCompletionBeginningEventArgs(IInstanceLookup instanceLookup) - { - if (instanceLookup == null) throw new ArgumentNullException(nameof(instanceLookup)); - - InstanceLookup = instanceLookup; - } + EndOfPhase, /// - /// Gets the instance lookup operation that is beginning the completion phase. + /// The new middleware should be added at the beginning of the middleware's declared phase. The added middleware will run before any middleware + /// already added at the same phase. /// - public IInstanceLookup InstanceLookup { get; } + StartOfPhase, } -} \ No newline at end of file +} diff --git a/src/Autofac/Core/Resolving/Pipeline/PipelineBuilderEnumerator.cs b/src/Autofac/Core/Resolving/Pipeline/PipelineBuilderEnumerator.cs new file mode 100644 index 000000000..31165e1f1 --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/PipelineBuilderEnumerator.cs @@ -0,0 +1,93 @@ +// 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; +using System.Collections.Generic; + +namespace Autofac.Core.Resolving.Pipeline +{ + /// + /// Enumerator for a pipeline builder. + /// + internal sealed class PipelineBuilderEnumerator : IEnumerator, IEnumerator + { + private readonly MiddlewareDeclaration? _first; + private MiddlewareDeclaration? _current; + private bool _ended; + + public PipelineBuilderEnumerator(MiddlewareDeclaration? first) + { + _first = first; + } + + /// + object IEnumerator.Current => _current?.Middleware ?? throw new InvalidOperationException(); + + /// + public IResolveMiddleware Current => _current?.Middleware ?? throw new InvalidOperationException(); + + /// + public bool MoveNext() + { + if (_ended) + { + return false; + } + + if (_current is object) + { + _current = _current.Next; + + _ended = !(_current is object); + + return !_ended; + } + + if (_first is object) + { + _current = _first; + + return true; + } + + _ended = true; + return false; + } + + /// + public void Reset() + { + _current = null; + _ended = false; + } + + /// + public void Dispose() + { + // Nothing to dispose here. + } + } +} diff --git a/src/Autofac/Core/Resolving/Pipeline/PipelinePhase.cs b/src/Autofac/Core/Resolving/Pipeline/PipelinePhase.cs new file mode 100644 index 000000000..167f5be59 --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/PipelinePhase.cs @@ -0,0 +1,75 @@ +// 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.Collections.Generic; + +namespace Autofac.Core.Resolving.Pipeline +{ + /// + /// Defines the possibles phases of the resolve pipeline. + /// + /// + /// A resolve pipeline is split into these distinct phases, that control general ordering + /// of middlewares and allow integrations and consuming code to specify what point in the pipeline to run their middleware. + /// + /// As a general principle, order between phases is strict, and always executes in the same order, but order within a phase should + /// not be important for most cases, and handlers should be able to run in any order. + /// + public enum PipelinePhase : int + { + /// + /// The start of a resolve request. Custom middleware added to this phase executes before circular dependency detection. + /// + RequestStart = 0, + + /// + /// In this phase, the lifetime scope selection takes place. If some middleware needs to change the lifetime scope for resolving against, + /// it happens here (but bear in mind that the configured Autofac lifetime for the registration will take effect). + /// + ScopeSelection = 10, + + /// + /// In this phase, instance decoration will take place (on the way 'out' of the pipeline). + /// + Decoration = 75, + + /// + /// At the end of this phase, if a shared instance satisfies the request, the pipeline will stop executing and exit. Add custom + /// middleware to this phase to choose your own shared instance. + /// + Sharing = 100, + + /// + /// This phase runs just before Activation, is the recommended point at which the resolve parameters should be replaced + /// (using ). + /// + ParameterSelection = 125, + + /// + /// The Activation phase is the last phase of a pipeline, where a new instance of a component is created. + /// + Activation = 200, + } +} diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs new file mode 100644 index 000000000..282fd3d09 --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -0,0 +1,331 @@ +// 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; +using System.Collections.Generic; +using Autofac.Core.Pipeline; +using Autofac.Core.Resolving.Middleware; + +namespace Autofac.Core.Resolving.Pipeline +{ + /// + /// Provides the functionality to construct a resolve pipeline. + /// + /// + /// + /// The pipeline builder is built as a doubly-linked list; each node in that list is a + /// , that holds the middleware instance, and the reference to the next and previous nodes. + /// + /// + /// + /// When you call one of the Use* methods, we find the appropriate node in the linked list based on the phase of the new middleware + /// and insert it into the list. + /// + /// + /// + /// When you build a pipeline, we walk back through that set of middleware and generate the concrete call chain so that when you execute the pipeline, + /// we don't iterate over any nodes, but just invoke the built set of methods. + /// + /// + internal class ResolvePipelineBuilder : IResolvePipelineBuilder, IEnumerable + { + /// + /// Termination action for the end of pipelines, that will execute the specified continuation (if there is one). + /// + private static readonly Action _terminateAction = ctxt => ctxt.Continuation?.Invoke(ctxt); + + private const string AnonymousName = "unnamed"; + + private MiddlewareDeclaration? _first; + private MiddlewareDeclaration? _last; + + public IEnumerable Middleware => this; + + public IResolvePipelineBuilder Use(IResolveMiddleware stage, MiddlewareInsertionMode insertionMode = MiddlewareInsertionMode.EndOfPhase) + { + if (stage is null) + { + throw new ArgumentNullException(nameof(stage)); + } + + AddStage(stage, insertionMode); + + return this; + } + + public IResolvePipelineBuilder Use(PipelinePhase phase, Action> callback) + { + Use(phase, MiddlewareInsertionMode.EndOfPhase, callback); + + return this; + } + + public IResolvePipelineBuilder Use(PipelinePhase phase, MiddlewareInsertionMode insertionMode, Action> callback) + { + Use(AnonymousName, phase, insertionMode, callback); + + return this; + } + + public IResolvePipelineBuilder Use(string name, PipelinePhase phase, Action> callback) + { + Use(new DelegateMiddleware(name, phase, callback), MiddlewareInsertionMode.EndOfPhase); + + return this; + } + + public IResolvePipelineBuilder Use(string name, PipelinePhase phase, MiddlewareInsertionMode insertionMode, Action> callback) + { + Use(new DelegateMiddleware(name, phase, callback), insertionMode); + + return this; + } + + public IResolvePipelineBuilder UseRange(IEnumerable stages, MiddlewareInsertionMode insertionMode = MiddlewareInsertionMode.EndOfPhase) + { + // Use multiple stages. + // Start at the beginning. + var currentStage = _first; + using var enumerator = stages.GetEnumerator(); + + if (!enumerator.MoveNext()) + { + return this; + } + + var nextNewStage = enumerator.Current; + var lastPhase = nextNewStage.Phase; + + while (currentStage is object) + { + if (insertionMode == MiddlewareInsertionMode.StartOfPhase ? + currentStage.Middleware.Phase >= nextNewStage.Phase : + currentStage.Middleware.Phase > nextNewStage.Phase) + { + var newDecl = new MiddlewareDeclaration(enumerator.Current); + + if (currentStage.Previous is object) + { + // Insert the node. + currentStage.Previous.Next = newDecl; + newDecl.Next = currentStage; + newDecl.Previous = currentStage.Previous; + currentStage.Previous = newDecl; + } + else + { + _first!.Previous = newDecl; + newDecl.Next = _first; + _first = newDecl; + } + + currentStage = newDecl; + + if (!enumerator.MoveNext()) + { + // Done. + return this; + } + + nextNewStage = enumerator.Current; + + if (nextNewStage.Phase < lastPhase) + { + throw new InvalidOperationException(ResolvePipelineBuilderMessages.MiddlewareMustBeInPhaseOrder); + } + + lastPhase = nextNewStage.Phase; + } + + currentStage = currentStage.Next; + } + + do + { + nextNewStage = enumerator.Current; + + if (nextNewStage.Phase < lastPhase) + { + throw new InvalidOperationException(ResolvePipelineBuilderMessages.MiddlewareMustBeInPhaseOrder); + } + + lastPhase = nextNewStage.Phase; + + var newStageDecl = new MiddlewareDeclaration(nextNewStage); + + if (_last is null) + { + _first = _last = newStageDecl; + } + else + { + newStageDecl.Previous = _last; + _last.Next = newStageDecl; + _last = newStageDecl; + } + } + while (enumerator.MoveNext()); + + return this; + } + + private void AddStage(IResolveMiddleware stage, MiddlewareInsertionMode insertionLocation) + { + // Start at the beginning. + var currentStage = _first; + + var newStageDecl = new MiddlewareDeclaration(stage); + + if (_first is null) + { + _first = _last = newStageDecl; + return; + } + + while (currentStage is object) + { + if (insertionLocation == MiddlewareInsertionMode.StartOfPhase ? currentStage.Middleware.Phase >= stage.Phase : currentStage.Middleware.Phase > stage.Phase) + { + if (currentStage.Previous is object) + { + // Insert the node. + currentStage.Previous.Next = newStageDecl; + newStageDecl.Next = currentStage; + newStageDecl.Previous = currentStage.Previous; + currentStage.Previous = newStageDecl; + } + else + { + _first.Previous = newStageDecl; + newStageDecl.Next = _first; + _first = newStageDecl; + } + + return; + } + + currentStage = currentStage.Next; + } + + // Add at the end. + newStageDecl.Previous = _last; + _last!.Next = newStageDecl; + _last = newStageDecl; + } + + private void AppendStage(IResolveMiddleware stage) + { + var newDecl = new MiddlewareDeclaration(stage); + + if (_last is null) + { + _first = _last = newDecl; + } + else + { + newDecl.Previous = _last; + _last.Next = newDecl; + _last = newDecl; + } + } + + /// + public IResolvePipeline Build() + { + return BuildPipeline(_last); + } + + private static IResolvePipeline BuildPipeline(MiddlewareDeclaration? lastDecl) + { + // When we build, we go through the set and construct a single call stack, starting from the end. + var current = lastDecl; + Action? currentInvoke = _terminateAction; + + Action Chain(Action next, IResolveMiddleware stage) + { + return (ctxt) => + { + // Optimise the path depending on whether a tracer is attached. + if (ctxt.Tracer is null) + { + ctxt.PhaseReached = stage.Phase; + stage.Execute(ctxt, next); + } + else + { + ctxt.Tracer.MiddlewareEntry(ctxt.Operation, ctxt, stage); + var succeeded = false; + try + { + ctxt.PhaseReached = stage.Phase; + stage.Execute(ctxt, next); + succeeded = true; + } + finally + { + ctxt.Tracer.MiddlewareExit(ctxt.Operation, ctxt, stage, succeeded); + } + } + }; + } + + while (current is object) + { + var stage = current.Middleware; + currentInvoke = Chain(currentInvoke, stage); + current = current.Previous; + } + + return new ResolvePipeline(currentInvoke); + } + + public IResolvePipelineBuilder Clone() + { + // To clone a pipeline, we create a new instance, then insert the same stage + // objects in the same order. + var newPipeline = new ResolvePipelineBuilder(); + var currentStage = _first; + + while (currentStage is object) + { + newPipeline.AppendStage(currentStage.Middleware); + currentStage = currentStage.Next; + } + + return newPipeline; + } + + public IEnumerator GetEnumerator() + { + return new PipelineBuilderEnumerator(_first); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilderMessages.Designer.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilderMessages.Designer.cs new file mode 100644 index 000000000..d2039023a --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilderMessages.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// 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.Resolving.Pipeline { + 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 ResolvePipelineBuilderMessages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ResolvePipelineBuilderMessages() { + } + + /// + /// 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.Resolving.Pipeline.ResolvePipelineBuilderMessages", typeof(ResolvePipelineBuilderMessages).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 Middleware provided to the UseRange method must be in phase order.. + /// + internal static string MiddlewareMustBeInPhaseOrder { + get { + return ResourceManager.GetString("MiddlewareMustBeInPhaseOrder", resourceCulture); + } + } + } +} diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilderMessages.resx b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilderMessages.resx new file mode 100644 index 000000000..abaf31ae3 --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilderMessages.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Middleware provided to the UseRange method must be in phase order. + + \ No newline at end of file diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs new file mode 100644 index 000000000..caf0e03e7 --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs @@ -0,0 +1,70 @@ +// 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 Autofac.Core.Diagnostics; + +namespace Autofac.Core.Resolving.Pipeline +{ + /// + /// Context area for a resolve request. + /// + internal sealed class ResolveRequestContext : ResolveRequestContextBase + { + /// + /// Initializes a new instance of the class. + /// + /// The owning resolve operation. + /// The initiating resolve request. + /// The lifetime scope. + /// An optional tracer. + internal ResolveRequestContext( + ResolveOperationBase owningOperation, + ResolveRequest request, + ISharingLifetimeScope scope, + IResolvePipelineTracer? tracer) + : base(owningOperation, request, scope, tracer) + { + } + + public override object ResolveComponent(ResolveRequest request) + { + return Operation.GetOrCreateInstance(ActivationScope, request); + } + + public override object ResolveComponentWithNewOperation(ResolveRequest request) + { + // Create a new operation, with the current ActivationScope and Tracer. + // Pass in the current operation as a tracing reference. + var operation = new ResolveOperation(ActivationScope, Tracer, Operation); + return operation.Execute(request); + } + + public void Complete() + { + // Let the base class raise events. + CompleteRequest(); + } + } +} diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs new file mode 100644 index 000000000..f006c1872 --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs @@ -0,0 +1,183 @@ +// 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; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Autofac.Core.Diagnostics; +using Autofac.Core.Registration; + +namespace Autofac.Core.Resolving.Pipeline +{ + /// + /// Defines the context object for a single resolve request. Provides access to the in-flight status of the operation, + /// and ways to manipulate the contents. + /// + public abstract class ResolveRequestContextBase : IComponentContext + { + private readonly ResolveRequest _resolveRequest; + private object? _instance; + + /// + /// Initializes a new instance of the class. + /// + /// The owning resolve operation. + /// The initiating resolve request. + /// The lifetime scope. + /// An optional tracer. + internal ResolveRequestContextBase( + ResolveOperationBase owningOperation, + ResolveRequest request, + ISharingLifetimeScope scope, + IResolvePipelineTracer? tracer) + { + Operation = owningOperation; + ActivationScope = scope; + Parameters = request.Parameters; + PhaseReached = PipelinePhase.RequestStart; + Tracer = tracer; + _resolveRequest = request; + } + + /// + /// Gets a reference to the owning resolve operation (which might emcompass multiple nested requests). + /// + public ResolveOperationBase Operation { get; } + + /// + /// Gets the lifetime scope that will be used for the activation of any components later in the pipeline. + /// Avoid resolving instances directly from this scope; they will not be traced as part of the same operation. + /// + public ISharingLifetimeScope ActivationScope { get; private set; } + + /// + /// Gets the component registration that is being resolved in the current request. + /// + public IComponentRegistration Registration => _resolveRequest.Registration; + + /// + /// Gets the service that is being resolved in the current request. + /// + public Service Service => _resolveRequest.Service; + + /// + /// Gets the target registration for decorator requests. + /// + public IComponentRegistration? DecoratorTarget => _resolveRequest.DecoratorTarget; + + /// + /// Gets or sets the instance that will be returned as the result of the resolve request. + /// On the way back up the pipeline, after calling next(ctxt), this value will be populated + /// with the resolved instance. Check the property to determine + /// whether the object here was a newly activated instance, or a shared instance previously activated. + /// + [DisallowNull] + public object? Instance + { + get => _instance; + set => _instance = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// Gets a value indicating whether the resolved is a new instance of a component has been activated during this request, + /// or an existing shared instance that has been retrieved. + /// + public bool NewInstanceActivated => Instance is object && PhaseReached == PipelinePhase.Activation; + + /// + /// Gets the active for the request. + /// + public IResolvePipelineTracer? Tracer { get; } + + /// + /// Gets the current resolve parameters. These can be changed using the method. + /// + public IEnumerable Parameters { get; private set; } + + /// + /// Gets the phase of the pipeline reached by this request. + /// + public PipelinePhase PhaseReached { get; internal set; } + + /// + /// Gets or sets an optional pipeline to invoke at the end of the current request's pipeline. + /// + public IResolvePipeline? Continuation { get; set; } + + /// + public IComponentRegistry ComponentRegistry => ActivationScope.ComponentRegistry; + + /// + /// Provides an event that will fire when the current request completes. + /// Requests will only be considered 'complete' when the overall is completing. + /// + public event EventHandler? RequestCompleting; + + /// + /// Use this method to change the that is used in this request. Changing this scope will + /// also change the available in this context. + /// + /// The new lifetime scope. + public void ChangeScope(ISharingLifetimeScope newScope) + { + ActivationScope = newScope ?? throw new ArgumentNullException(nameof(newScope)); + } + + /// + /// Change the set of parameters being used in the processing of this request. + /// + /// The new set of parameters. + public void ChangeParameters(IEnumerable newParameters) + { + Parameters = newParameters ?? throw new ArgumentNullException(nameof(newParameters)); + } + + /// + public abstract object ResolveComponent(ResolveRequest request); + + /// + /// Resolve an instance of the provided registration within the context, but isolated inside a new + /// . + /// This method should only be used instead of + /// if you need to resolve a component with a completely separate operation and circular dependency verification stack. + /// + /// The resolve request. + /// + /// The component instance. + /// + /// + /// + public abstract object ResolveComponentWithNewOperation(ResolveRequest request); + + /// + /// Complete the request, raising any appropriate events. + /// + protected void CompleteRequest() + { + var handler = RequestCompleting; + handler?.Invoke(this, new ResolveRequestCompletingEventArgs(this)); + } + } +} diff --git a/src/Autofac/Core/Resolving/ResolveOperation.cs b/src/Autofac/Core/Resolving/ResolveOperation.cs index 224f5b8c6..284e1a79c 100644 --- a/src/Autofac/Core/Resolving/ResolveOperation.cs +++ b/src/Autofac/Core/Resolving/ResolveOperation.cs @@ -23,9 +23,9 @@ // 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 Autofac.Core.Diagnostics; +using Autofac.Core.Resolving.Pipeline; namespace Autofac.Core.Resolving { @@ -33,148 +33,58 @@ namespace Autofac.Core.Resolving /// A is a component context that sequences and monitors the multiple /// activations that go into producing a single requested object graph. /// - [SuppressMessage("Microsoft.ApiDesignGuidelines", "CA2213", Justification = "The creator of the most nested lifetime scope is responsible for disposal.")] - internal class ResolveOperation : IComponentContext, IResolveOperation + internal sealed class ResolveOperation : ResolveOperationBase { - private readonly Stack _activationStack = new Stack(); - - // _successfulActivations can never be null, but the roslyn compiler doesn't look deeper than - // the initial constructor methods yet. - private List _successfulActivations = default!; - private readonly ISharingLifetimeScope _mostNestedLifetimeScope; - private int _callDepth; - private bool _ended; - /// /// Initializes a new instance of the class. /// /// The most nested scope in which to begin the operation. The operation /// can move upward to less nested scopes as components with wider sharing scopes are activated. public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope) + : base(mostNestedLifetimeScope) { - _mostNestedLifetimeScope = mostNestedLifetimeScope; - - // Initialise _successfulActivations. - ResetSuccessfulActivations(); - } - - /// - public object ResolveComponent(ResolveRequest request) - { - return GetOrCreateInstance(_mostNestedLifetimeScope, request); } /// - /// Execute the complete resolve operation. + /// Initializes a new instance of the class. /// - /// The resolution context. - [SuppressMessage("CA1031", "CA1031", Justification = "General exception gets rethrown in a DependencyResolutionException.")] - public object Execute(ResolveRequest request) + /// The most nested scope in which to begin the operation. The operation + /// can move upward to less nested scopes as components with wider sharing scopes are activated. + /// A pipeline tracer for the operation. + public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, IResolvePipelineTracer? pipelineTracer) + : base(mostNestedLifetimeScope, pipelineTracer) { - object result; - - try - { - result = ResolveComponent(request); - } - catch (ObjectDisposedException) - { - throw; - } - catch (DependencyResolutionException dependencyResolutionException) - { - End(dependencyResolutionException); - throw; - } - catch (Exception exception) - { - End(exception); - throw new DependencyResolutionException(ResolveOperationResources.ExceptionDuringResolve, exception); - } - - End(); - return result; } /// - /// Continue building the object graph by instantiating in the - /// current . + /// Initializes a new instance of the class. /// - /// The current scope of the operation. - /// The resolve request. - /// The resolved instance. - /// - public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request) - { - if (_ended) throw new ObjectDisposedException(ResolveOperationResources.TemporaryContextDisposed, innerException: null); - - ++_callDepth; - - if (_activationStack.Count > 0) - CircularDependencyDetector.CheckForCircularDependency(request.Registration, _activationStack, _callDepth); - - var activation = new InstanceLookup(this, currentOperationScope, request); - - _activationStack.Push(activation); - - var handler = InstanceLookupBeginning; - handler?.Invoke(this, new InstanceLookupBeginningEventArgs(activation)); - - try - { - var instance = activation.Execute(); - _successfulActivations.Add(activation); - - return instance; - } - finally - { - // Issue #929: Allow the activation stack to be popped even if the activation failed. - // This allows try/catch to happen in lambda registrations without corrupting the stack. - _activationStack.Pop(); - - if (_activationStack.Count == 0) - { - CompleteActivations(); - } - - --_callDepth; - } - } - - public event EventHandler? CurrentOperationEnding; - - public event EventHandler? InstanceLookupBeginning; - - private void CompleteActivations() - { - List completed = _successfulActivations; - int count = completed.Count; - ResetSuccessfulActivations(); - - for (int i = 0; i < count; i++) - { - completed[i].Complete(); - } - } - - private void ResetSuccessfulActivations() + /// The most nested scope in which to begin the operation. The operation + /// can move upward to less nested scopes as components with wider sharing scopes are activated. + /// An optional pipeline tracer. + /// A parent resolve operation, used to maintain tracing between related operations. + public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, IResolvePipelineTracer? pipelineTracer, ResolveOperationBase parentOperation) + : base(mostNestedLifetimeScope, pipelineTracer, parentOperation) { - _successfulActivations = new List(); } /// - /// Gets the services associated with the components that provide them. + /// Execute the complete resolve operation. /// - public IComponentRegistry ComponentRegistry => _mostNestedLifetimeScope.ComponentRegistry; + /// The resolution context. + [SuppressMessage("CA1031", "CA1031", Justification = "General exception gets rethrown in a DependencyResolutionException.")] + public object Execute(ResolveRequest request) + { + return ExecuteOperation(request); + } - private void End(Exception? exception = null) + protected override void ExecuteRequest(ResolveRequestContextBase requestContext) { - if (_ended) return; + // Get pipeline from the registration. + var registrationPipeline = requestContext.Registration.ResolvePipeline; - _ended = true; - var handler = CurrentOperationEnding; - handler?.Invoke(this, new ResolveOperationEndingEventArgs(this, exception)); + // Invoke the pipeline. + registrationPipeline.Invoke(requestContext); } } } diff --git a/src/Autofac/Core/Resolving/ResolveOperationBase.cs b/src/Autofac/Core/Resolving/ResolveOperationBase.cs new file mode 100644 index 000000000..43b32c532 --- /dev/null +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -0,0 +1,263 @@ +// 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; +using System.Collections.Generic; +using Autofac.Core.Diagnostics; +using Autofac.Core.Resolving.Middleware; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Resolving +{ + /// + /// Defines the base properties and behaviour of a resolve operation. + /// + public abstract class ResolveOperationBase : IResolveOperation, ITracingIdentifer + { + private bool _ended; + private IResolvePipelineTracer? _pipelineTracer; + private List _successfulRequests = new List(); + + /// + /// Initializes a new instance of the class. + /// + /// The most nested scope in which to begin the operation. The operation + /// can move upward to less nested scopes as components with wider sharing scopes are activated. + protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope) + : this(mostNestedLifetimeScope, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The most nested scope in which to begin the operation. The operation + /// can move upward to less nested scopes as components with wider sharing scopes are activated. + /// A pipeline tracer for the operation. + protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, IResolvePipelineTracer? pipelineTracer) + { + TracingId = this; + CurrentScope = mostNestedLifetimeScope; + _pipelineTracer = pipelineTracer; + } + + /// + /// Initializes a new instance of the class. + /// + /// The most nested scope in which to begin the operation. The operation + /// can move upward to less nested scopes as components with wider sharing scopes are activated. + /// A pipeline tracer for the operation. + /// A tracing ID for the operation. + protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, IResolvePipelineTracer? pipelineTracer, ITracingIdentifer tracingId) + : this(mostNestedLifetimeScope, pipelineTracer) + { + TracingId = tracingId; + } + + /// + /// Gets the active resolve request. + /// + public ResolveRequestContextBase? ActiveRequestContext { get; private set; } + + public ISharingLifetimeScope CurrentScope { get; private set; } + + /// + /// Gets the set of all in-progress requests on the request stack. + /// + public IEnumerable InProgressRequests => RequestStack; + + /// + /// Gets the tracing identifier for the operation. + /// + public ITracingIdentifer TracingId { get; } + + /// + /// Gets or sets the current request depth. + /// + public int RequestDepth { get; protected set; } + + /// + /// Gets a value indicating whether this operation is a top-level operation (as opposed to one initiated from inside an existing operation). + /// + public bool IsTopLevelOperation { get; } + + /// + /// Gets or sets the that initiated the operation. Other nested requests may have been issued as a result of this one. + /// + public ResolveRequest? InitiatingRequest { get; protected set; } + + /// + /// Gets the modifiable active request stack. + /// + /// + /// Don't want this exposed to the outside world, but we do want it available in the , + /// hence it's internal. + /// + internal Stack RequestStack { get; } = new Stack(); + + /// + public event EventHandler? ResolveRequestBeginning; + + /// + public event EventHandler? CurrentOperationEnding; + + /// + /// Invoke this method to execute the operation for a given request. + /// + /// The resolve request. + /// The resolved instance. + protected object ExecuteOperation(ResolveRequest request) + { + object result; + + try + { + InitiatingRequest = request; + + _pipelineTracer?.OperationStart(this, request); + + result = GetOrCreateInstance(CurrentScope, request); + } + catch (ObjectDisposedException disposeException) + { + _pipelineTracer?.OperationFailure(this, disposeException); + + throw; + } + catch (DependencyResolutionException dependencyResolutionException) + { + _pipelineTracer?.OperationFailure(this, dependencyResolutionException); + End(dependencyResolutionException); + throw; + } + catch (Exception exception) + { + End(exception); + _pipelineTracer?.OperationFailure(this, exception); + throw new DependencyResolutionException(ResolveOperationResources.ExceptionDuringResolve, exception); + } + finally + { + ResetSuccessfulRequests(); + } + + End(); + + _pipelineTracer?.OperationSuccess(this, result); + + return result; + } + + /// + public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request) + { + if (_ended) throw new ObjectDisposedException(ResolveOperationResources.TemporaryContextDisposed, innerException: null); + + // Create a new request context. + var requestContext = new ResolveRequestContext(this, request, currentOperationScope, _pipelineTracer); + + // Raise our request-beginning event. + var handler = ResolveRequestBeginning; + handler?.Invoke(this, new ResolveRequestBeginningEventArgs(requestContext)); + + RequestDepth++; + + // Track the last active request and scope in the call stack. + var lastActiveRequest = ActiveRequestContext; + var lastScope = CurrentScope; + + ActiveRequestContext = requestContext; + CurrentScope = currentOperationScope; + + try + { + _pipelineTracer?.RequestStart(this, requestContext); + + ExecuteRequest(requestContext); + + if (requestContext.Instance == null) + { + // No exception, but was null; this shouldn't happen. + throw new DependencyResolutionException(ResolveOperationResources.PipelineCompletedWithNoInstance); + } + + _successfulRequests.Add(requestContext); + _pipelineTracer?.RequestSuccess(this, requestContext); + } + catch (Exception ex) + { + _pipelineTracer?.RequestFailure(this, requestContext, ex); + throw; + } + finally + { + ActiveRequestContext = lastActiveRequest; + CurrentScope = lastScope; + + // Raise the appropriate completion events. + if (RequestStack.Count == 0) + { + CompleteRequests(); + } + + RequestDepth--; + } + + return requestContext.Instance; + } + + /// + /// An implementation must implement this member to execute the specified request context. + /// + /// The request context. + protected abstract void ExecuteRequest(ResolveRequestContextBase requestContext); + + private void CompleteRequests() + { + var completed = _successfulRequests; + int count = completed.Count; + ResetSuccessfulRequests(); + + for (int i = 0; i < count; i++) + { + completed[i].Complete(); + } + } + + private void ResetSuccessfulRequests() + { + _successfulRequests = new List(); + } + + private void End(Exception? exception = null) + { + if (_ended) return; + + _ended = true; + var handler = CurrentOperationEnding; + handler?.Invoke(this, new ResolveOperationEndingEventArgs(this, exception)); + } + } +} diff --git a/src/Autofac/Core/Resolving/ResolveOperationBeginningEventArgs.cs b/src/Autofac/Core/Resolving/ResolveOperationBeginningEventArgs.cs index 1c7edb01e..1a4b09b49 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationBeginningEventArgs.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationBeginningEventArgs.cs @@ -30,7 +30,7 @@ namespace Autofac.Core.Resolving /// /// Describes the commencement of a new resolve operation. /// - public class ResolveOperationBeginningEventArgs : EventArgs + public sealed class ResolveOperationBeginningEventArgs : EventArgs { /// /// Initializes a new instance of the class. @@ -46,4 +46,4 @@ public ResolveOperationBeginningEventArgs(IResolveOperation resolveOperation) /// public IResolveOperation ResolveOperation { get; } } -} \ No newline at end of file +} diff --git a/src/Autofac/Core/Resolving/ResolveOperationEndingEventArgs.cs b/src/Autofac/Core/Resolving/ResolveOperationEndingEventArgs.cs index 73bf7a1cb..aab497d3e 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationEndingEventArgs.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationEndingEventArgs.cs @@ -30,7 +30,7 @@ namespace Autofac.Core.Resolving /// /// Describes the commencement of a new resolve operation. /// - public class ResolveOperationEndingEventArgs : EventArgs + public sealed class ResolveOperationEndingEventArgs : EventArgs { /// /// Initializes a new instance of the class. @@ -53,4 +53,4 @@ public ResolveOperationEndingEventArgs(IResolveOperation resolveOperation, Excep /// public IResolveOperation ResolveOperation { get; } } -} \ No newline at end of file +} diff --git a/src/Autofac/Core/Resolving/ResolveOperationResources.Designer.cs b/src/Autofac/Core/Resolving/ResolveOperationResources.Designer.cs index 7c3273a3c..5900a75ae 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationResources.Designer.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationResources.Designer.cs @@ -10,7 +10,6 @@ namespace Autofac.Core.Resolving { using System; - using System.Reflection; /// @@ -20,7 +19,7 @@ namespace Autofac.Core.Resolving { // 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", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class ResolveOperationResources { @@ -40,7 +39,7 @@ internal ResolveOperationResources() { 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.Resolving.ResolveOperationResources", typeof(ResolveOperationResources).GetTypeInfo().Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Autofac.Core.Resolving.ResolveOperationResources", typeof(ResolveOperationResources).Assembly); resourceMan = temp; } return resourceMan; @@ -79,6 +78,15 @@ internal static string MaxDepthExceeded { } } + /// + /// Looks up a localized string similar to Resolve pipeline completed with a null value for the resolved instance.. + /// + internal static string PipelineCompletedWithNoInstance { + get { + return ResourceManager.GetString("PipelineCompletedWithNoInstance", resourceCulture); + } + } + /// /// Looks up a localized string similar to This resolve operation has already ended. When registering components using lambdas, the IComponentContext 'c' parameter to the lambda cannot be stored. Instead, either resolve IComponentContext again from 'c', or resolve a Func<> based factory to create subsequent components from.. /// diff --git a/src/Autofac/Core/Resolving/ResolveOperationResources.resx b/src/Autofac/Core/Resolving/ResolveOperationResources.resx index b4cd106be..970a59ec1 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationResources.resx +++ b/src/Autofac/Core/Resolving/ResolveOperationResources.resx @@ -123,6 +123,9 @@ Probable circular dependency between factory-scoped components. Chain includes '{0}' + + Resolve pipeline completed with a null value for the resolved instance. + This resolve operation has already ended. When registering components using lambdas, the IComponentContext 'c' parameter to the lambda cannot be stored. Instead, either resolve IComponentContext again from 'c', or resolve a Func<> based factory to create subsequent components from. diff --git a/src/Autofac/Core/Resolving/ResolvePipeline.cs b/src/Autofac/Core/Resolving/ResolvePipeline.cs new file mode 100644 index 000000000..c058f482a --- /dev/null +++ b/src/Autofac/Core/Resolving/ResolvePipeline.cs @@ -0,0 +1,28 @@ +using System; +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Pipeline +{ + /// + /// Encapsulates the call back that represents the entry point of a pipeline. + /// + internal class ResolvePipeline : IResolvePipeline + { + private readonly Action? _entryPoint; + + /// + /// Initializes a new instance of the class. + /// + /// Callback to invoke. + public ResolvePipeline(Action? entryPoint) + { + _entryPoint = entryPoint; + } + + /// + public void Invoke(ResolveRequestContextBase ctxt) + { + _entryPoint?.Invoke(ctxt); + } + } +} diff --git a/src/Autofac/Core/Resolving/InstanceLookupCompletionEndingEventArgs.cs b/src/Autofac/Core/Resolving/ResolveRequestBeginningEventArgs.cs similarity index 66% rename from src/Autofac/Core/Resolving/InstanceLookupCompletionEndingEventArgs.cs rename to src/Autofac/Core/Resolving/ResolveRequestBeginningEventArgs.cs index 21dbab93c..e724ba3c6 100644 --- a/src/Autofac/Core/Resolving/InstanceLookupCompletionEndingEventArgs.cs +++ b/src/Autofac/Core/Resolving/ResolveRequestBeginningEventArgs.cs @@ -24,28 +24,27 @@ // OTHER DEALINGS IN THE SOFTWARE. using System; +using Autofac.Core.Resolving.Pipeline; namespace Autofac.Core.Resolving { /// - /// Raised when the completion phase of an instance lookup operation ends. + /// Fired when a resolve request is starting. /// - public class InstanceLookupCompletionEndingEventArgs : EventArgs + public sealed class ResolveRequestBeginningEventArgs : EventArgs { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The instance lookup that is ending the completion phase. - public InstanceLookupCompletionEndingEventArgs(IInstanceLookup instanceLookup) + /// The resolve request context that is starting. + public ResolveRequestBeginningEventArgs(ResolveRequestContextBase requestContext) { - if (instanceLookup == null) throw new ArgumentNullException(nameof(instanceLookup)); - - InstanceLookup = instanceLookup; + RequestContext = requestContext ?? throw new ArgumentNullException(nameof(requestContext)); } /// - /// Gets the instance lookup operation that is ending the completion phase. + /// Gets the resolve request that is beginning. /// - public IInstanceLookup InstanceLookup { get; } + public ResolveRequestContextBase RequestContext { get; } } -} \ No newline at end of file +} diff --git a/src/Autofac/Core/Resolving/InstanceLookupBeginningEventArgs.cs b/src/Autofac/Core/Resolving/ResolveRequestCompletingEventArgs.cs similarity index 69% rename from src/Autofac/Core/Resolving/InstanceLookupBeginningEventArgs.cs rename to src/Autofac/Core/Resolving/ResolveRequestCompletingEventArgs.cs index 443bb1bee..c306f0e88 100644 --- a/src/Autofac/Core/Resolving/InstanceLookupBeginningEventArgs.cs +++ b/src/Autofac/Core/Resolving/ResolveRequestCompletingEventArgs.cs @@ -24,28 +24,29 @@ // OTHER DEALINGS IN THE SOFTWARE. using System; +using Autofac.Core.Resolving.Pipeline; namespace Autofac.Core.Resolving { /// - /// Fired when instance lookup is complete. + /// Fired when a resolve request is starting. /// - public class InstanceLookupBeginningEventArgs : EventArgs + public class ResolveRequestCompletingEventArgs : EventArgs { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The instance lookup that is ending. - public InstanceLookupBeginningEventArgs(IInstanceLookup instanceLookup) + /// The resolve request context that is completing. + public ResolveRequestCompletingEventArgs(ResolveRequestContextBase requestContext) { - if (instanceLookup == null) throw new ArgumentNullException(nameof(instanceLookup)); + if (requestContext is null) throw new ArgumentNullException(nameof(requestContext)); - InstanceLookup = instanceLookup; + RequestContext = requestContext; } /// /// Gets the instance lookup operation that is beginning. /// - public IInstanceLookup InstanceLookup { get; } + public ResolveRequestContextBase RequestContext { get; } } -} \ No newline at end of file +} diff --git a/src/Autofac/Core/SelfComponentRegistration.cs b/src/Autofac/Core/SelfComponentRegistration.cs index 9b0ec38a8..cb12358fa 100644 --- a/src/Autofac/Core/SelfComponentRegistration.cs +++ b/src/Autofac/Core/SelfComponentRegistration.cs @@ -4,6 +4,7 @@ using Autofac.Core.Activators.Delegate; using Autofac.Core.Lifetime; using Autofac.Core.Registration; +using Autofac.Core.Resolving.Pipeline; namespace Autofac.Core { @@ -19,9 +20,10 @@ public SelfComponentRegistration() new CurrentScopeLifetime(), InstanceSharing.Shared, InstanceOwnership.ExternallyOwned, + new ResolvePipelineBuilder(), new Service[] { new TypedService(typeof(ILifetimeScope)), new TypedService(typeof(IComponentContext)) }, new Dictionary()) { } } -} \ No newline at end of file +} diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs b/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs index 7d3376fe9..6e166312c 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs @@ -30,16 +30,23 @@ using Autofac.Builder; using Autofac.Core; using Autofac.Core.Activators.Reflection; +using Autofac.Core.Pipeline; +using Autofac.Core.Resolving.Pipeline; namespace Autofac.Features.OpenGenerics { + /// + /// TODO: Replace this with a service pipeline source. + /// internal class OpenGenericDecoratorRegistrationSource : IRegistrationSource { private readonly RegistrationData _registrationData; private readonly OpenGenericDecoratorActivatorData _activatorData; + private readonly IResolvePipelineBuilder _existingPipeline; public OpenGenericDecoratorRegistrationSource( RegistrationData registrationData, + IResolvePipelineBuilder existingPipelineBuilder, OpenGenericDecoratorActivatorData activatorData) { if (registrationData == null) throw new ArgumentNullException(nameof(registrationData)); @@ -52,6 +59,7 @@ public OpenGenericDecoratorRegistrationSource( _registrationData = registrationData; _activatorData = activatorData; + _existingPipeline = existingPipelineBuilder; } public IEnumerable RegistrationsFor(Service service, Func> registrationAccessor) @@ -71,6 +79,7 @@ public IEnumerable RegistrationsFor(Service service, Fun Guid.NewGuid(), _registrationData, new ReflectionActivator(constructedImplementationType, _activatorData.ConstructorFinder, _activatorData.ConstructorSelector, AddDecoratedComponentParameter(fromService, swt.ServiceType, cr, _activatorData.ConfiguredParameters), _activatorData.ConfiguredProperties), + _existingPipeline, services)); } diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationExtensions.cs b/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationExtensions.cs index 55a2e8f5d..c9803de10 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationExtensions.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationExtensions.cs @@ -48,7 +48,7 @@ public static IRegistrationBuilder cr.AddRegistrationSource( - new OpenGenericRegistrationSource(rb.RegistrationData, rb.ActivatorData))); + new OpenGenericRegistrationSource(rb.RegistrationData, rb.ResolvePipeline.Clone(), rb.ActivatorData))); return rb; } @@ -66,7 +66,7 @@ public static IRegistrationBuilder cr.AddRegistrationSource( - new OpenGenericDecoratorRegistrationSource(rb.RegistrationData, rb.ActivatorData))); + new OpenGenericDecoratorRegistrationSource(rb.RegistrationData, rb.ResolvePipeline.Clone(), rb.ActivatorData))); return rb; } diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationSource.cs b/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationSource.cs index 68d7f2f60..b5591e7ce 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationSource.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationSource.cs @@ -30,6 +30,7 @@ using Autofac.Builder; using Autofac.Core; using Autofac.Core.Activators.Reflection; +using Autofac.Core.Resolving.Pipeline; namespace Autofac.Features.OpenGenerics { @@ -39,10 +40,12 @@ namespace Autofac.Features.OpenGenerics internal class OpenGenericRegistrationSource : IRegistrationSource { private readonly RegistrationData _registrationData; + private readonly IResolvePipelineBuilder _existingPipelineBuilder; private readonly ReflectionActivatorData _activatorData; public OpenGenericRegistrationSource( RegistrationData registrationData, + IResolvePipelineBuilder existingPipelineBuilder, ReflectionActivatorData activatorData) { if (registrationData == null) throw new ArgumentNullException(nameof(registrationData)); @@ -51,6 +54,7 @@ public OpenGenericRegistrationSource( OpenGenericServiceBinder.EnforceBindable(activatorData.ImplementationType, registrationData.Services); _registrationData = registrationData; + _existingPipelineBuilder = existingPipelineBuilder; _activatorData = activatorData; } @@ -63,10 +67,13 @@ public IEnumerable RegistrationsFor(Service service, Fun Service[]? services; if (OpenGenericServiceBinder.TryBindServiceType(service, _registrationData.Services, _activatorData.ImplementationType, out constructedImplementationType, out services)) { + // Pass the pipeline builder from the original registration to the 'CreateRegistration'. + // So the original registration will contain all of the pipeline stages originally added, plus anything we want to add due to the lifetim yield return RegistrationBuilder.CreateRegistration( Guid.NewGuid(), _registrationData, new ReflectionActivator(constructedImplementationType, _activatorData.ConstructorFinder, _activatorData.ConstructorSelector, _activatorData.ConfiguredParameters, _activatorData.ConfiguredProperties), + _existingPipelineBuilder, services); } } diff --git a/src/Autofac/Features/OwnedInstances/Owned.cs b/src/Autofac/Features/OwnedInstances/Owned.cs index c08365c6c..77b732f8b 100644 --- a/src/Autofac/Features/OwnedInstances/Owned.cs +++ b/src/Autofac/Features/OwnedInstances/Owned.cs @@ -143,7 +143,7 @@ protected override async ValueTask DisposeAsync(bool disposing) Value = default!; if (lt is IAsyncDisposable asyncDisposable) { - await asyncDisposable.DisposeAsync(); + await asyncDisposable.DisposeAsync().ConfigureAwait(false); } else { diff --git a/src/Autofac/Features/ResolveAnything/AnyConcreteTypeNotAlreadyRegisteredSource.cs b/src/Autofac/Features/ResolveAnything/AnyConcreteTypeNotAlreadyRegisteredSource.cs index 52f445c57..c7d6a65eb 100644 --- a/src/Autofac/Features/ResolveAnything/AnyConcreteTypeNotAlreadyRegisteredSource.cs +++ b/src/Autofac/Features/ResolveAnything/AnyConcreteTypeNotAlreadyRegisteredSource.cs @@ -128,7 +128,7 @@ public override string ToString() return AnyConcreteTypeNotAlreadyRegisteredSourceResources.AnyConcreteTypeNotAlreadyRegisteredSourceDescription; } - private bool ShouldRegisterGenericService(TypeInfo type) + private static bool ShouldRegisterGenericService(TypeInfo type) { var genericType = type.GetGenericTypeDefinition(); diff --git a/src/Autofac/ILifetimeScope.cs b/src/Autofac/ILifetimeScope.cs index a1d5d6607..da9d1fe92 100644 --- a/src/Autofac/ILifetimeScope.cs +++ b/src/Autofac/ILifetimeScope.cs @@ -26,6 +26,7 @@ using System; using Autofac.Builder; using Autofac.Core; +using Autofac.Core.Diagnostics; using Autofac.Core.Lifetime; using Autofac.Core.Resolving; @@ -117,6 +118,17 @@ public interface ILifetimeScope : IComponentContext, IDisposable, IAsyncDisposab /// A new lifetime scope. ILifetimeScope BeginLifetimeScope(object tag, Action configurationAction); + /// + /// Enable tracing (or replace existing tracing) on this scope, routing trace events to the specified tracer. + /// All lifetime scopes created from this one will inherit this tracer as well. + /// + /// The implementation. + /// + /// Only one tracer is supported at once, so calling this will replace any prior tracer on this scope. Nested scopes + /// will continue to retain the same trace behaviour. + /// + void AttachTrace(IResolvePipelineTracer tracer); + /// /// Gets the disposer associated with this . /// Component instances can be associated with it manually if required. diff --git a/src/Autofac/LifetimeScopeExtensions.cs b/src/Autofac/LifetimeScopeExtensions.cs new file mode 100644 index 000000000..b10ab1bae --- /dev/null +++ b/src/Autofac/LifetimeScopeExtensions.cs @@ -0,0 +1,62 @@ +// 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; +using Autofac.Core.Diagnostics; + +namespace Autofac +{ + /// + /// Extensions to the lifetime scope to provide convenience methods for tracing. + /// + public static class LifetimeScopeExtensions + { + /// + /// Enable tracing on this scope, routing trace events to the specified tracer. + /// All lifetime scopes created from this one will inherit this tracer as well. + /// + /// The lifetime scope. + /// A callback that will receive the trace output. + /// + /// Only one tracer is supported at once, so calling this will replace any prior tracer on this scope. Existing nested scopes + /// will continue to retain their original trace behaviour. + /// + public static void AttachTrace(this ILifetimeScope scope, Action newTraceCallback) + { + if (scope is null) throw new ArgumentNullException(nameof(scope)); + if (newTraceCallback is null) throw new ArgumentNullException(nameof(newTraceCallback)); + + // Create a new default tracer and attach the callback. + var tracer = new DefaultDiagnosticTracer(); + tracer.OperationCompleted += (sender, args) => + { + // The initiating request will always be non-null by the time an operation completes. + newTraceCallback(args.Operation.InitiatingRequest!, args.TraceContent); + }; + + scope.AttachTrace(tracer); + } + } +} diff --git a/src/Autofac/RegistrationExtensions.cs b/src/Autofac/RegistrationExtensions.cs index 85eeb046f..3979ef687 100644 --- a/src/Autofac/RegistrationExtensions.cs +++ b/src/Autofac/RegistrationExtensions.cs @@ -37,6 +37,7 @@ using Autofac.Core.Activators.Reflection; using Autofac.Core.Lifetime; using Autofac.Core.Registration; +using Autofac.Core.Resolving.Pipeline; using Autofac.Features.Decorators; using Autofac.Features.LightweightAdapters; using Autofac.Features.OpenGenerics; @@ -99,13 +100,13 @@ public static IRegistrationBuilder s.Phase == PipelinePhase.Activation)) { var autoStartService = rb.RegistrationData.Services.First(); - // https://github.com/autofac/Autofac/issues/1102 - // Single instance registrations with activation handlers need to be auto-activated, - // so that other behaviour (such as OnRelease) that expects 'normal' object lifetime behaviour works as expected. var activationRegistration = new RegistrationBuilder( new AutoActivateService(), new SimpleActivatorData(new DelegateActivator(typeof(T), (c, p) => c.ResolveService(autoStartService))), @@ -1467,10 +1468,12 @@ public static IRegistrationBuilder // .OnActivating() handler may call .ReplaceInstance() and we'll // have closed over the wrong thing. registration.ExternallyOwned(); - registration.RegistrationData.ActivatingHandlers.Add((s, e) => + registration.ResolvePipeline.Use(nameof(OnRelease), PipelinePhase.Activation, MiddlewareInsertionMode.StartOfPhase, (ctxt, next) => { - var ra = new ReleaseAction(releaseAction, () => (TLimit)e.Instance); - e.Context.Resolve().Disposer.AddInstanceForDisposal(ra); + // Continue down the pipeline. + next(ctxt); + + ctxt.ActivationScope.Disposer.AddInstanceForAsyncDisposal(new ReleaseAction(releaseAction, () => (TLimit)ctxt.Instance!)); }); return registration; } diff --git a/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs b/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs index b1070ec85..f3aa49680 100644 --- a/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs +++ b/test/Autofac.Specification.Test/Features/CircularDependencyTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Autofac.Core; +using Autofac.Core.Diagnostics; using Autofac.Specification.Test.Features.CircularDependency; using Xunit; @@ -61,7 +62,10 @@ public void DetectsCircularDependencies() var container = builder.Build(); - var de = Assert.Throws(() => container.Resolve()); + var ex = Assert.Throws(() => container.Resolve()); + + // Make sure we're getting the detected exception, not the depth one. + Assert.Contains("component dependency", ex.ToString()); } [Fact] @@ -87,7 +91,7 @@ public void InstancePerDependencyDoesNotAllowCircularDependencies_PropertyOwnerR cb.RegisterType().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies); var c = cb.Build(); - Assert.Throws(() => c.Resolve()); + var de = Assert.Throws(() => c.Resolve()); } [Fact] @@ -98,7 +102,7 @@ public void InstancePerLifetimeScopeServiceCannotCreateSecondInstanceOfSelfDurin builder.RegisterType().InstancePerLifetimeScope(); var container = builder.Build(); - var exception = Assert.Throws(() => container.Resolve()); + var ex = Assert.Throws(() => container.Resolve()); } [Fact] diff --git a/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs b/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs index 86b3ce675..70a09f8ce 100644 --- a/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs +++ b/test/Autofac.Specification.Test/Lifetime/InstancePerLifetimeScopeTests.cs @@ -1,11 +1,20 @@ using System; +using Autofac.Core.Diagnostics; using Autofac.Specification.Test.Util; using Xunit; +using Xunit.Abstractions; namespace Autofac.Specification.Test.Lifetime { public class InstancePerLifetimeScopeTests { + private readonly ITestOutputHelper _output; + + public InstancePerLifetimeScopeTests(ITestOutputHelper output) + { + _output = output; + } + [Fact] public void TypeAsInstancePerScope() { @@ -16,6 +25,14 @@ public void TypeAsInstancePerScope() var lifetime = container.BeginLifetimeScope(); + var tracer = new DefaultDiagnosticTracer(); + tracer.OperationCompleted += (sender, args) => + { + _output.WriteLine(args.TraceContent); + }; + + lifetime.AttachTrace(tracer); + var ctxA = lifetime.Resolve(); var ctxA2 = lifetime.Resolve(); diff --git a/test/Autofac.Specification.Test/Lifetime/LifetimeEventTests.cs b/test/Autofac.Specification.Test/Lifetime/LifetimeEventTests.cs index d86b76e21..4850280d8 100644 --- a/test/Autofac.Specification.Test/Lifetime/LifetimeEventTests.cs +++ b/test/Autofac.Specification.Test/Lifetime/LifetimeEventTests.cs @@ -74,7 +74,7 @@ public void ChainedOnActivatingEventsAreInvokedWithinASingleResolveOperation() } [Fact] - public void MultilpeOnActivatingEventsCanPassReplacementOnward() + public void MultipleOnActivatingEventsCanPassReplacementOnward() { var builder = new ContainerBuilder(); @@ -276,14 +276,12 @@ public void ActivatingRaisedForFirstResolveInEachLifetimeScope() public void ActivatingOnlyRaisedForAttachedRegistrations() { var activatingRaised = new List(); - var activatingInstances = new List(); var cb = new ContainerBuilder(); cb.RegisterType() .As() .OnActivating(e => { activatingRaised.Add(e.Component); - activatingInstances.Add(e.Instance); }); cb.RegisterType() .As(); @@ -291,8 +289,6 @@ public void ActivatingOnlyRaisedForAttachedRegistrations() container.Resolve>(); Assert.Single(activatingRaised); Assert.Equal(typeof(AService), activatingRaised[0].Activator.LimitType); - Assert.Single(activatingInstances); - Assert.IsType(activatingInstances[0]); } [Fact] diff --git a/test/Autofac.Test.Compilation/Autofac.Test.Compilation.csproj b/test/Autofac.Test.Compilation/Autofac.Test.Compilation.csproj index 48b0933a3..167c46495 100644 --- a/test/Autofac.Test.Compilation/Autofac.Test.Compilation.csproj +++ b/test/Autofac.Test.Compilation/Autofac.Test.Compilation.csproj @@ -24,7 +24,7 @@ - + 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 + { + } } }