From 7feb53bef5266d66a5e609553a09b12f6c068f7b Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Mon, 11 May 2020 12:10:43 +0100 Subject: [PATCH 01/11] Moving Autofac to a middleware and pipeline-based resolve process. Squashed commit of previous related work because there was a lot of discarded changes early on that won't make sense. --- .editorconfig | 12 + src/Autofac/Autofac.csproj | 44 +- src/Autofac/Builder/IRegistrationBuilder.cs | 12 +- src/Autofac/Builder/RegistrationBuilder.cs | 26 +- ...imit,TActivatorData,TRegistrationStyle}.cs | 87 +++- src/Autofac/Builder/RegistrationData.cs | 20 +- src/Autofac/ContainerBuilder.cs | 4 +- src/Autofac/Core/ActivatingEventArgs.cs | 10 +- .../Activators/Delegate/DelegateActivator.cs | 25 +- .../ProvidedInstanceActivator.cs | 35 +- .../Reflection/ReflectionActivator.cs | 46 +- src/Autofac/Core/Container.cs | 12 +- .../Diagnostics/DefaultDiagnosticTracer.cs | 230 ++++++++++ .../Diagnostics/IResolvePipelineTracer.cs | 85 ++++ .../Core/Diagnostics/ITracingIdentifer.cs | 40 ++ .../OperationTraceCompletedArgs.cs | 17 + .../Diagnostics/TracerMessages.Designer.cs | 216 +++++++++ .../Core/Diagnostics/TracerMessages.resx | 172 +++++++ src/Autofac/Core/IActivatingEventArgs.cs | 2 +- src/Autofac/Core/IComponentRegistration.cs | 58 +-- ...ookup.cs => IComponentRegistryServices.cs} | 47 +- src/Autofac/Core/IInstanceActivator.cs | 15 +- src/Autofac/Core/Lifetime/LifetimeScope.cs | 42 +- .../ComponentPipelineBuildingArgs.cs | 50 +++ .../Registration/ComponentRegistration.cs | 161 ++++--- .../ComponentRegistrationLifetimeDecorator.cs | 35 +- ...ComponentRegistrationResources.Designer.cs | 25 +- .../ComponentRegistrationResources.resx | 12 +- .../Registration/ComponentRegistryBuilder.cs | 14 +- .../DefaultRegisteredServicesTracker.cs | 54 ++- .../ExternalComponentRegistration.cs | 13 +- .../Registration/IComponentRegistryBuilder.cs | 2 +- .../IRegisteredServicesTracker.cs | 29 +- ...copeRestrictedRegisteredServicesTracker.cs | 2 +- .../Resolving/CircularDependencyDetector.cs | 75 ---- .../ComponentActivationResources.Designer.cs | 17 +- .../ComponentActivationResources.resx | 5 - .../Core/Resolving/IResolveOperation.cs | 7 +- src/Autofac/Core/Resolving/InstanceLookup.cs | 220 --------- .../ActivatorErrorHandlingMiddleware.cs | 94 ++++ ...larDependencyDetectorMessages.Designer.cs} | 11 +- .../CircularDependencyDetectorMessages.resx} | 0 .../CircularDependencyDetectorMiddleware.cs | 113 +++++ .../Middleware/DecoratorMiddleware.cs} | 74 +++- .../Middleware/DelegateMiddleware.cs | 64 +++ .../Middleware/DisposalTrackingMiddleware.cs | 70 +++ .../Middleware/MiddlewareMessages.Designer.cs | 83 ++++ .../Middleware/MiddlewareMessages.resx | 128 ++++++ .../Middleware/ScopeSelectionMiddleware.cs | 71 +++ .../Resolving/Middleware/SharingMiddleware.cs | 85 ++++ .../Middleware/StartableMiddleware.cs | 64 +++ .../Pipeline/IPipelineResolveOperation.cs | 73 +++ .../Resolving/Pipeline/IResolveMiddleware.cs | 48 ++ .../Resolving/Pipeline/IResolvePipeline.cs | 39 ++ .../Pipeline/IResolvePipelineBuilder.cs | 121 +++++ .../Pipeline/IResolveRequestContext.cs | 140 ++++++ .../MiddlewareDeclaration.cs} | 39 +- .../MiddlewareInsertionMode.cs} | 26 +- .../Pipeline/PipelineBuilderEnumerator.cs | 93 ++++ .../Core/Resolving/Pipeline/PipelinePhase.cs | 75 ++++ .../Pipeline/ResolvePipelineBuilder.cs | 325 ++++++++++++++ ...ResolvePipelineBuilderMessages.Designer.cs | 72 +++ .../ResolvePipelineBuilderMessages.resx | 123 +++++ .../Pipeline/ResolveRequestContext.cs | 145 ++++++ .../Core/Resolving/ResolveOperation.cs | 167 +++++-- .../ResolveOperationBeginningEventArgs.cs | 4 +- .../ResolveOperationEndingEventArgs.cs | 4 +- .../ResolveOperationResources.Designer.cs | 14 +- .../Resolving/ResolveOperationResources.resx | 3 + src/Autofac/Core/Resolving/ResolvePipeline.cs | 28 ++ ...cs => ResolveRequestBeginningEventArgs.cs} | 21 +- ...s => ResolveRequestCompletingEventArgs.cs} | 19 +- src/Autofac/Core/SelfComponentRegistration.cs | 4 +- .../OpenGenericDecoratorRegistrationSource.cs | 9 + .../OpenGenericRegistrationExtensions.cs | 4 +- .../OpenGenericRegistrationSource.cs | 7 + src/Autofac/Features/OwnedInstances/Owned.cs | 2 +- ...yConcreteTypeNotAlreadyRegisteredSource.cs | 2 +- src/Autofac/ILifetimeScope.cs | 12 + src/Autofac/LifetimeScopeExtensions.cs | 62 +++ src/Autofac/RegistrationExtensions.cs | 17 +- .../Features/CircularDependencyTests.cs | 10 +- .../Lifetime/InstancePerLifetimeScopeTests.cs | 17 + .../Lifetime/LifetimeEventTests.cs | 6 +- .../Autofac.Test.Compilation.csproj | 2 +- .../ActivatorPipelineExtensions.cs | 82 ++++ test/Autofac.Test/ContainerBuilderTests.cs | 17 +- .../Delegate/DelegateActivatorTests.cs | 12 +- .../ProvidedInstanceActivatorTests.cs | 14 +- .../Reflection/ReflectionActivatorTests.cs | 71 ++- test/Autofac.Test/Core/ContainerTests.cs | 7 +- .../Core/Lifetime/LifetimeScopeTests.cs | 1 + .../Core/Pipeline/PipelineBuilderTests.cs | 419 ++++++++++++++++++ .../Registration/ComponentRegistryTests.cs | 4 +- .../Features/Decorators/DecoratorTests.cs | 4 +- .../Decorators/OpenGenericDecoratorTests.cs | 27 ++ .../OpenGenericRegistrationSourceTests.cs | 15 +- test/Autofac.Test/Mocks.cs | 54 ++- test/Autofac.Test/ModuleTests.cs | 26 +- 99 files changed, 4459 insertions(+), 862 deletions(-) create mode 100644 src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs create mode 100644 src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs create mode 100644 src/Autofac/Core/Diagnostics/ITracingIdentifer.cs create mode 100644 src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs create mode 100644 src/Autofac/Core/Diagnostics/TracerMessages.Designer.cs create mode 100644 src/Autofac/Core/Diagnostics/TracerMessages.resx rename src/Autofac/Core/{Resolving/IInstanceLookup.cs => IComponentRegistryServices.cs} (53%) create mode 100644 src/Autofac/Core/Registration/ComponentPipelineBuildingArgs.cs delete mode 100644 src/Autofac/Core/Resolving/CircularDependencyDetector.cs delete mode 100644 src/Autofac/Core/Resolving/InstanceLookup.cs create mode 100644 src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs rename src/Autofac/Core/Resolving/{CircularDependencyDetectorResources.Designer.cs => Middleware/CircularDependencyDetectorMessages.Designer.cs} (90%) rename src/Autofac/Core/Resolving/{CircularDependencyDetectorResources.resx => Middleware/CircularDependencyDetectorMessages.resx} (100%) create mode 100644 src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs rename src/Autofac/{Features/Decorators/InstanceDecorator.cs => Core/Resolving/Middleware/DecoratorMiddleware.cs} (58%) create mode 100644 src/Autofac/Core/Resolving/Middleware/DelegateMiddleware.cs create mode 100644 src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs create mode 100644 src/Autofac/Core/Resolving/Middleware/MiddlewareMessages.Designer.cs create mode 100644 src/Autofac/Core/Resolving/Middleware/MiddlewareMessages.resx create mode 100644 src/Autofac/Core/Resolving/Middleware/ScopeSelectionMiddleware.cs create mode 100644 src/Autofac/Core/Resolving/Middleware/SharingMiddleware.cs create mode 100644 src/Autofac/Core/Resolving/Middleware/StartableMiddleware.cs create mode 100644 src/Autofac/Core/Resolving/Pipeline/IPipelineResolveOperation.cs create mode 100644 src/Autofac/Core/Resolving/Pipeline/IResolveMiddleware.cs create mode 100644 src/Autofac/Core/Resolving/Pipeline/IResolvePipeline.cs create mode 100644 src/Autofac/Core/Resolving/Pipeline/IResolvePipelineBuilder.cs create mode 100644 src/Autofac/Core/Resolving/Pipeline/IResolveRequestContext.cs rename src/Autofac/Core/Resolving/{InstanceLookupEndingEventArgs.cs => Pipeline/MiddlewareDeclaration.cs} (58%) rename src/Autofac/Core/Resolving/{InstanceLookupCompletionBeginningEventArgs.cs => Pipeline/MiddlewareInsertionMode.cs} (62%) create mode 100644 src/Autofac/Core/Resolving/Pipeline/PipelineBuilderEnumerator.cs create mode 100644 src/Autofac/Core/Resolving/Pipeline/PipelinePhase.cs create mode 100644 src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs create mode 100644 src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilderMessages.Designer.cs create mode 100644 src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilderMessages.resx create mode 100644 src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs create mode 100644 src/Autofac/Core/Resolving/ResolvePipeline.cs rename src/Autofac/Core/Resolving/{InstanceLookupCompletionEndingEventArgs.cs => ResolveRequestBeginningEventArgs.cs} (66%) rename src/Autofac/Core/Resolving/{InstanceLookupBeginningEventArgs.cs => ResolveRequestCompletingEventArgs.cs} (69%) create mode 100644 src/Autofac/LifetimeScopeExtensions.cs create mode 100644 test/Autofac.Test/ActivatorPipelineExtensions.cs create mode 100644 test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs 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..64846291f 100644 --- a/src/Autofac/Autofac.csproj +++ b/src/Autofac/Autofac.csproj @@ -40,16 +40,21 @@ $(NoWarn);8600;8601;8602;8603;8604 + + + + + - + All - + All @@ -129,6 +134,11 @@ True ContainerResources.resx + + True + True + TracerMessages.resx + True True @@ -154,16 +164,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 +350,10 @@ ResXFileCodeGenerator ContainerResources.Designer.cs + + ResXFileCodeGenerator + TracerMessages.Designer.cs + ResXFileCodeGenerator LifetimeScopeResources.Designer.cs @@ -350,14 +374,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..ee11c3205 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.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.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.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/ActivatingEventArgs.cs b/src/Autofac/Core/ActivatingEventArgs.cs index 3565fd1c8..2b106b12a 100644 --- a/src/Autofac/Core/ActivatingEventArgs.cs +++ b/src/Autofac/Core/ActivatingEventArgs.cs @@ -44,27 +44,19 @@ public class ActivatingEventArgs : EventArgs, IActivatingEventArgs /// 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, 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)); Context = context; Component = component; Parameters = parameters; _instance = instance; - Service = service; } - /// - /// Gets the service being resolved. - /// - public Service Service { get; } - /// /// Gets the context in which the activation occurred. /// 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..c2d07b2ea --- /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(IPipelineResolveOperation 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(IPipelineResolveOperation operation, IResolveRequestContext 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(IPipelineResolveOperation operation, IResolveRequestContext requestContext, IResolveMiddleware middleware) + { + if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + { + builder.AppendFormattedLine(TracerMessages.EnterMiddleware, middleware.ToString()); + builder.Indent(); + } + } + + /// + void IResolvePipelineTracer.MiddlewareExit(IPipelineResolveOperation operation, IResolveRequestContext 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(IPipelineResolveOperation operation, IResolveRequestContext 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(IPipelineResolveOperation operation, IResolveRequestContext requestContext) + { + if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) + { + builder.Outdent(); + builder.AppendLine(TracerMessages.ExitBrace); + builder.AppendFormattedLine(TracerMessages.ResolveRequestSucceeded, requestContext.Instance); + } + } + + /// + void IResolvePipelineTracer.OperationFailure(IPipelineResolveOperation 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(IPipelineResolveOperation 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..e9dcc228f --- /dev/null +++ b/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs @@ -0,0 +1,85 @@ +using System; +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(IPipelineResolveOperation 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(IPipelineResolveOperation operation, IResolveRequestContext 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(IPipelineResolveOperation operation, IResolveRequestContext 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(IPipelineResolveOperation operation, IResolveRequestContext 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(IPipelineResolveOperation operation, IResolveRequestContext 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(IPipelineResolveOperation operation, IResolveRequestContext requestContext); + + /// + /// Invoked when a resolve operation fails. + /// + /// The resolve operation that failed. + /// The exception that caused the operation failure. + void OperationFailure(IPipelineResolveOperation 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(IPipelineResolveOperation 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..0fa882e94 --- /dev/null +++ b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs @@ -0,0 +1,17 @@ +using Autofac.Core.Resolving.Pipeline; + +namespace Autofac.Core.Diagnostics +{ + public sealed class OperationTraceCompletedArgs + { + public OperationTraceCompletedArgs(IPipelineResolveOperation operation, string traceContent) + { + Operation = operation; + TraceContent = traceContent; + } + + public IPipelineResolveOperation 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..8273cba90 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. + /// Configure a registrations resolve pipeline just before the pipeline is built, and with the available services known to you. /// - /// 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 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..a553409eb 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,11 @@ 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 +222,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 +294,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 +303,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 +403,7 @@ protected override void Dispose(bool disposing) // ReSharper disable once InconsistentlySynchronizedField _sharedInstances.Clear(); - parentScope = null; + _parentScope = null; } base.Dispose(disposing); @@ -401,12 +421,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 +446,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/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..9b8378b26 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. @@ -46,6 +51,12 @@ private readonly ConcurrentDictionary private readonly object _synchRoot = new object(); + public DefaultRegisteredServicesTracker() + { + _registrationAccessor = RegistrationsFor; + _decoratorRegistrationsAccessor = InternalDecoratorRegistrationsFor; + } + /// /// Fired whenever a component is registered - either explicitly or via a /// . @@ -73,7 +84,7 @@ public IEnumerable Registrations get { lock (_synchRoot) - return _registrations.ToArray(); + return _registrations.ToList(); } } @@ -84,7 +95,7 @@ public IEnumerable Sources { lock (_synchRoot) { - return _dynamicRegistrationSources.ToArray(); + return _dynamicRegistrationSources.ToList(); } } } @@ -100,6 +111,11 @@ public virtual void AddRegistration(IComponentRegistration registration, bool pr _registrations.Add(registration); GetRegistered()?.Invoke(this, registration); + + if (originatedFromSource) + { + registration.BuildResolvePipeline(this); + } } /// @@ -149,7 +165,7 @@ public IEnumerable RegistrationsFor(Service service) lock (_synchRoot) { var info = GetInitializedServiceInfo(service); - return info.Implementations.ToArray(); + return info.Implementations.ToList(); } } @@ -158,11 +174,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 +209,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 +219,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 +232,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,7 +251,7 @@ private ServiceRegistrationInfo GetServiceInfo(Service service) return existing; var info = new ServiceRegistrationInfo(service); - _serviceInfo.TryAdd(service, info); + _serviceInfo.Add(service, info); return info; } @@ -235,4 +267,4 @@ private ServiceRegistrationInfo GetServiceInfo(Service service) ? (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..0496b26a9 100644 --- a/src/Autofac/Core/Registration/IRegisteredServicesTracker.cs +++ b/src/Autofac/Core/Registration/IRegisteredServicesTracker.cs @@ -1,13 +1,12 @@ 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. @@ -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..aed5bfd97 --- /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(IResolveRequestContext 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..fbc0eb1a2 --- /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(IResolveRequestContext 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..13c593a30 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(IResolveRequestContext 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(IResolveRequestContext 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..780ca15a5 --- /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(IResolveRequestContext 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..65ab2e5f6 --- /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(IResolveRequestContext 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..76ff06a15 --- /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(IResolveRequestContext 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..1403c1746 --- /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(IResolveRequestContext 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..2a98bbb28 --- /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(IResolveRequestContext 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/IPipelineResolveOperation.cs b/src/Autofac/Core/Resolving/Pipeline/IPipelineResolveOperation.cs new file mode 100644 index 000000000..1e456a9a9 --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/IPipelineResolveOperation.cs @@ -0,0 +1,73 @@ +// 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.Collections.Generic; +using Autofac.Core.Diagnostics; +using Autofac.Core.Resolving.Middleware; + +namespace Autofac.Core.Resolving.Pipeline +{ + public interface IPipelineResolveOperation : IResolveOperation, IComponentContext, ITracingIdentifer + { + /// + /// Gets the active resolve request. + /// + IResolveRequestContext? ActiveRequestContext { get; } + + /// + /// Gets the set of all in-progress requests on the request stack. + /// + IEnumerable InProgressRequests { get; } + + /// + /// Gets the tracing identifier for the operation. + /// + ITracingIdentifer TracingId { get; } + + /// + /// Gets the current request depth. + /// + int RequestDepth { get; } + + /// + /// Gets a value indicating whether this operation is a top-level operation (as opposed to one initiated from inside an existing operation). + /// + bool IsTopLevelOperation { get; } + + /// + /// Gets the that initiated the operation. Other nested requests may have been issued as a result of this one. + /// + ResolveRequest? InitiatingRequest { get; } + + /// + /// 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; } + } +} diff --git a/src/Autofac/Core/Resolving/Pipeline/IResolveMiddleware.cs b/src/Autofac/Core/Resolving/Pipeline/IResolveMiddleware.cs new file mode 100644 index 000000000..54f3f5f6b --- /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(IResolveRequestContext 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..8ca799ce6 --- /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(IResolveRequestContext 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..661a67cdd --- /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/Pipeline/IResolveRequestContext.cs b/src/Autofac/Core/Resolving/Pipeline/IResolveRequestContext.cs new file mode 100644 index 000000000..581bc76cb --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/IResolveRequestContext.cs @@ -0,0 +1,140 @@ +// 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 interface IResolveRequestContext : IComponentContext + { + /// + /// Gets a reference to the owning resolve operation (which might emcompass multiple nested requests). + /// + IPipelineResolveOperation 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. + /// + ISharingLifetimeScope ActivationScope { get; } + + /// + /// Gets the component registration that is being resolved in the current request. + /// + IComponentRegistration Registration { get; } + + /// + /// Gets the service that is being resolved in the current request. + /// + Service Service { get; } + + /// + /// Gets the target registration for decorator requests. + /// + IComponentRegistration? DecoratorTarget { get; } + + /// + /// 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] + object? Instance { get; set; } + + /// + /// 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. + /// + bool NewInstanceActivated { get; } + + /// + /// Gets the active for the request. + /// + IResolvePipelineTracer? Tracer { get; } + + /// + /// Gets the current resolve parameters. These can be changed using the method. + /// + IEnumerable Parameters { get; } + + /// + /// Gets the phase of the pipeline reached by this request. + /// + public PipelinePhase PhaseReached { get; } + + /// + /// Gets or sets an optional pipeline to invoke at the end of the current request's pipeline. + /// + IResolvePipeline? Continuation { get; set; } + + /// + /// Provides an event that will fire when the current request completes. + /// Requests will only be considered 'complete' when the overall is completing. + /// + 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. + void ChangeScope(ISharingLifetimeScope newScope); + + /// + /// Change the set of parameters being used in the processing of this request. + /// + /// The new set of parameters. + void ChangeParameters(IEnumerable newParameters); + + /// + /// 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. + /// + /// + /// + object ResolveComponentWithNewOperation(ResolveRequest request); + + /// + /// Set the phase of the pipeline we are in. Only used internally in ; should not be used elsewhere. + /// + /// The pipeline phase. + internal void SetPhase(PipelinePhase phase); + } +} 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..12f69262d --- /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..04f6dec67 --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -0,0 +1,325 @@ +// 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.StartOfPhase, 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.SetPhase(stage.Phase); + stage.Execute(ctxt, next); + } + else + { + ctxt.Tracer.MiddlewareEntry(ctxt.Operation, ctxt, stage); + var succeeded = false; + try + { + ctxt.SetPhase(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..a1f142180 --- /dev/null +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs @@ -0,0 +1,145 @@ +// 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; + +namespace Autofac.Core.Resolving.Pipeline +{ + /// + /// Context area for a resolve request. + /// + internal sealed class ResolveRequestContext : IResolveRequestContext + { + 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 ResolveRequestContext( + IPipelineResolveOperation owningOperation, + ResolveRequest request, + ISharingLifetimeScope scope, + IResolvePipelineTracer? tracer) + { + Operation = owningOperation; + ActivationScope = scope; + Parameters = request.Parameters; + PhaseReached = PipelinePhase.RequestStart; + Tracer = tracer; + _resolveRequest = request; + } + + /// + public IPipelineResolveOperation Operation { get; } + + /// + public IComponentRegistration Registration => _resolveRequest.Registration; + + /// + public Service Service => _resolveRequest.Service; + + /// + public IComponentRegistration? DecoratorTarget => _resolveRequest.DecoratorTarget; + + /// + public ISharingLifetimeScope ActivationScope { get; private set; } + + /// + [DisallowNull] + public object? Instance + { + get => _instance; + set => _instance = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + public IEnumerable Parameters { get; private set; } + + /// + public IComponentRegistry ComponentRegistry => ActivationScope.ComponentRegistry; + + /// + public PipelinePhase PhaseReached { get; set; } + + /// + public IResolvePipelineTracer? Tracer { get; } + + /// + public bool NewInstanceActivated => Instance is object && PhaseReached == PipelinePhase.Activation; + + /// + public IResolvePipeline? Continuation { get; set; } + + /// + public event EventHandler? RequestCompleting; + + /// + public object ResolveComponent(ResolveRequest request) + { + return Operation.GetOrCreateInstance(ActivationScope, request); + } + + /// + public void ChangeParameters(IEnumerable newParameters) + { + Parameters = newParameters ?? throw new ArgumentNullException(nameof(newParameters)); + } + + /// + public void ChangeScope(ISharingLifetimeScope newScope) + { + ActivationScope = newScope ?? throw new ArgumentNullException(nameof(newScope)); + } + + public void Complete() + { + var handler = RequestCompleting; + handler?.Invoke(this, new ResolveRequestCompletingEventArgs(this)); + } + + /// + void IResolveRequestContext.SetPhase(PipelinePhase phase) + { + PhaseReached = phase; + } + + /// + public 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); + } + } +} diff --git a/src/Autofac/Core/Resolving/ResolveOperation.cs b/src/Autofac/Core/Resolving/ResolveOperation.cs index 224f5b8c6..684a69aa2 100644 --- a/src/Autofac/Core/Resolving/ResolveOperation.cs +++ b/src/Autofac/Core/Resolving/ResolveOperation.cs @@ -26,6 +26,8 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using Autofac.Core.Diagnostics; +using Autofac.Core.Resolving.Pipeline; namespace Autofac.Core.Resolving { @@ -33,16 +35,11 @@ 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 : IPipelineResolveOperation { - 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 readonly Stack _requestStack; + private readonly IResolvePipelineTracer? _pipelineTracer; + private List _successfulRequests; private bool _ended; /// @@ -51,17 +48,70 @@ internal class ResolveOperation : IComponentContext, IResolveOperation /// 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) + : 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. + public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, IResolvePipelineTracer? pipelineTracer) { - _mostNestedLifetimeScope = mostNestedLifetimeScope; + CurrentScope = mostNestedLifetimeScope; + TracingId = this; + IsTopLevelOperation = true; + _pipelineTracer = pipelineTracer; - // Initialise _successfulActivations. - ResetSuccessfulActivations(); + _requestStack = new Stack(); + _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. + /// An optional pipeline tracer. + /// A parent resolve operation, used to maintain tracing between related operations. + public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, IResolvePipelineTracer? pipelineTracer, IPipelineResolveOperation parentOperation) + { + CurrentScope = mostNestedLifetimeScope; + TracingId = parentOperation.TracingId; + _pipelineTracer = pipelineTracer; + + _requestStack = new Stack(); + _successfulRequests = new List(); + } + + public IResolveRequestContext? ActiveRequestContext { get; set; } + + public ISharingLifetimeScope CurrentScope { get; set; } + + public IComponentRegistry ComponentRegistry => CurrentScope.ComponentRegistry; + + public IEnumerable InProgressRequests => _requestStack; + + public ResolveRequest? InitiatingRequest { get; private set; } + + public int RequestDepth { get; private set; } + + Stack IPipelineResolveOperation.RequestStack => _requestStack; + + public ITracingIdentifer TracingId { get; } + + public bool IsTopLevelOperation { get; } + + public event EventHandler? CurrentOperationEnding; + + public event EventHandler? ResolveRequestBeginning; + /// public object ResolveComponent(ResolveRequest request) { - return GetOrCreateInstance(_mostNestedLifetimeScope, request); + return GetOrCreateInstance(CurrentScope, request); } /// @@ -75,24 +125,39 @@ public object Execute(ResolveRequest request) try { + InitiatingRequest = request; + + _pipelineTracer?.OperationStart(this, request); + result = ResolveComponent(request); } - catch (ObjectDisposedException) + 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; } @@ -108,49 +173,68 @@ public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, R { if (_ended) throw new ObjectDisposedException(ResolveOperationResources.TemporaryContextDisposed, innerException: null); - ++_callDepth; + // Resolve pipeline from the registration. + var registrationPipeline = request.Registration.ResolvePipeline; + + // Create a new request context. + var requestContext = new ResolveRequestContext(this, request, currentOperationScope, _pipelineTracer); - if (_activationStack.Count > 0) - CircularDependencyDetector.CheckForCircularDependency(request.Registration, _activationStack, _callDepth); + // Raise our request-beginning event. + var handler = ResolveRequestBeginning; + handler?.Invoke(this, new ResolveRequestBeginningEventArgs(requestContext)); - var activation = new InstanceLookup(this, currentOperationScope, request); + RequestDepth++; - _activationStack.Push(activation); + // Track the last active request and scope in the call stack. + var lastActiveRequest = ActiveRequestContext; + var lastScope = CurrentScope; - var handler = InstanceLookupBeginning; - handler?.Invoke(this, new InstanceLookupBeginningEventArgs(activation)); + ActiveRequestContext = requestContext; + CurrentScope = currentOperationScope; try { - var instance = activation.Execute(); - _successfulActivations.Add(activation); + _pipelineTracer?.RequestStart(this, requestContext); + + // Invoke the pipeline. + registrationPipeline.Invoke(requestContext); - return instance; + 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 { - // 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(); + ActiveRequestContext = lastActiveRequest; + CurrentScope = lastScope; - if (_activationStack.Count == 0) + // Raise the appropriate completion events. + if (_requestStack.Count == 0) { - CompleteActivations(); + CompleteRequests(); } - --_callDepth; + RequestDepth--; } - } - public event EventHandler? CurrentOperationEnding; - - public event EventHandler? InstanceLookupBeginning; + return requestContext.Instance; + } - private void CompleteActivations() + private void CompleteRequests() { - List completed = _successfulActivations; + var completed = _successfulRequests; int count = completed.Count; - ResetSuccessfulActivations(); + ResetSuccessfulRequests(); for (int i = 0; i < count; i++) { @@ -158,16 +242,11 @@ private void CompleteActivations() } } - private void ResetSuccessfulActivations() + private void ResetSuccessfulRequests() { - _successfulActivations = new List(); + _successfulRequests = new List(); } - /// - /// Gets the services associated with the components that provide them. - /// - public IComponentRegistry ComponentRegistry => _mostNestedLifetimeScope.ComponentRegistry; - private void End(Exception? exception = null) { if (_ended) return; 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..2be9642ca --- /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(IResolveRequestContext 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..a528d048d 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(IResolveRequestContext 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 IResolveRequestContext 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..a9faa941a 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(IResolveRequestContext 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 IResolveRequestContext 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..8ba64d440 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 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..bf17b9111 --- /dev/null +++ b/test/Autofac.Test/ActivatorPipelineExtensions.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Autofac.Core; +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( + Mocks.GetPipelineOperation(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..de028a800 --- /dev/null +++ b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs @@ -0,0 +1,419 @@ +using System; +using System.Collections.Generic; +using Autofac.Core; +using Autofac.Core.Diagnostics; +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 : IResolveRequestContext + { + public event EventHandler RequestCompleting; + + public IPipelineResolveOperation Operation => throw new NotImplementedException(); + + public ISharingLifetimeScope ActivationScope => throw new NotImplementedException(); + + public IComponentRegistration Registration => throw new NotImplementedException(); + + public Service Service => throw new NotImplementedException(); + + public IComponentRegistration DecoratorTarget => throw new NotImplementedException(); + + public object Instance { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public bool NewInstanceActivated => throw new NotImplementedException(); + + public IResolvePipelineTracer Tracer { get; set; } + + public IEnumerable Parameters => throw new NotImplementedException(); + + public PipelinePhase PhaseReached { get; set; } + + public IComponentRegistry ComponentRegistry => throw new NotImplementedException(); + + public IResolvePipeline Continuation { get; set; } + + void IResolveRequestContext.SetPhase(PipelinePhase phase) => PhaseReached = phase; + + public void ChangeParameters(IEnumerable newParameters) + { + throw new NotImplementedException(); + } + + public void ChangeScope(ISharingLifetimeScope newScope) + { + throw new NotImplementedException(); + } + + public object ResolveComponent(ResolveRequest request) + { + throw new NotImplementedException(); + } + + public object ResolveComponentWithNewOperation(ResolveRequest request) + { + 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..c4af78d26 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 { @@ -23,6 +26,11 @@ public static MockComponentRegistration GetComponentRegistration() return new MockComponentRegistration(); } + public static MockPipelineOperation GetPipelineOperation(ISharingLifetimeScope scope) + { + return new MockPipelineOperation(scope); + } + internal class MockConstructorFinder : IConstructorFinder { public ConstructorInfo[] FindConstructors(Type targetType) @@ -66,23 +74,53 @@ 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 IResolvePipeline ResolvePipeline { get; } = new ResolvePipelineBuilder().Build(); + + public void BuildResolvePipeline(IComponentRegistryServices registryServices) { } + } - public event EventHandler> Activating = (sender, args) => { }; - - public void RaiseActivating(IComponentContext context, IEnumerable parameters, Service service, ref object instance) + public class MockPipelineOperation : IPipelineResolveOperation + { + public MockPipelineOperation(ISharingLifetimeScope scope) { + CurrentScope = scope; } - public event EventHandler> Activated = (sender, args) => { }; + public ISharingLifetimeScope CurrentScope { get; } + + public IComponentRegistry ComponentRegistry => CurrentScope.ComponentRegistry; + + public IResolveRequestContext ActiveRequestContext => throw new NotImplementedException(); + + public IEnumerable InProgressRequests => throw new NotImplementedException(); + + Stack IPipelineResolveOperation.RequestStack => throw new NotImplementedException(); + + public int RequestDepth => throw new NotImplementedException(); + + public ITracingIdentifer TracingId => this; + + public bool IsTopLevelOperation => true; + + public ResolveRequest InitiatingRequest { get; set; } + + public event EventHandler CurrentOperationEnding; + + public event EventHandler ResolveRequestBeginning; + + public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request) + { + return currentOperationScope.ResolveComponent(request); + } - public void RaiseActivated(IComponentContext context, IEnumerable parameters, Service service, object instance) + public object ResolveComponent(ResolveRequest request) { + return CurrentScope.ResolveComponent(request); } } } -} \ 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 + { + } } } From 12140e9a7cb8cbea6b522e25e1dde6ccec9bac22 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Wed, 20 May 2020 16:04:13 +0100 Subject: [PATCH 02/11] Integrate changes from v6 branch --- ...nBuilder{TLimit,TActivatorData,TRegistrationStyle}.cs | 6 +++--- src/Autofac/Core/ActivatedEventArgs.cs | 4 ++-- src/Autofac/Core/ActivatingEventArgs.cs | 9 ++++++++- src/Autofac/Core/PreparingEventArgs.cs | 6 +++--- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Autofac/Builder/RegistrationBuilder{TLimit,TActivatorData,TRegistrationStyle}.cs b/src/Autofac/Builder/RegistrationBuilder{TLimit,TActivatorData,TRegistrationStyle}.cs index ee11c3205..49312044e 100644 --- a/src/Autofac/Builder/RegistrationBuilder{TLimit,TActivatorData,TRegistrationStyle}.cs +++ b/src/Autofac/Builder/RegistrationBuilder{TLimit,TActivatorData,TRegistrationStyle}.cs @@ -386,7 +386,7 @@ public IRegistrationBuilder OnPrepar ResolvePipeline.Use(nameof(OnPreparing), PipelinePhase.ParameterSelection, (ctxt, next) => { - var args = new PreparingEventArgs(ctxt, ctxt.Registration, ctxt.Parameters); + var args = new PreparingEventArgs(ctxt, ctxt.Service, ctxt.Registration, ctxt.Parameters); handler(args); @@ -414,7 +414,7 @@ public IRegistrationBuilder OnActiva { next(ctxt); - var args = new ActivatingEventArgs(ctxt, ctxt.Registration, ctxt.Parameters, (TLimit)ctxt.Instance!); + var args = new ActivatingEventArgs(ctxt, ctxt.Service, ctxt.Registration, ctxt.Parameters, (TLimit)ctxt.Instance!); handler(args); ctxt.Instance = args.Instance; @@ -452,7 +452,7 @@ public IRegistrationBuilder OnActiva ctxt.RequestCompleting += (sender, evArgs) => { var ctxt = evArgs.RequestContext; - var args = new ActivatedEventArgs(ctxt, ctxt.Registration, ctxt.Parameters, newInstance); + var args = new ActivatedEventArgs(ctxt, ctxt.Service, ctxt.Registration, ctxt.Parameters, newInstance); handler(args); }; 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 2b106b12a..bdd3c20a6 100644 --- a/src/Autofac/Core/ActivatingEventArgs.cs +++ b/src/Autofac/Core/ActivatingEventArgs.cs @@ -41,22 +41,29 @@ public class ActivatingEventArgs : EventArgs, IActivatingEventArgs /// Initializes a new instance of the class. /// /// The context. + /// The service. /// The component. /// The parameters. /// The instance. - public ActivatingEventArgs(IComponentContext context, IComponentRegistration component, IEnumerable parameters, T instance) + 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)); + Service = service; Context = context; Component = component; Parameters = parameters; _instance = instance; } + /// + /// Gets the service being resolved. + /// + public Service Service { get; } + /// /// Gets the context in which the activation occurred. /// 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; } /// From 717cad4af674d605087b2e8cb4b788c0d24e735f Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Thu, 21 May 2020 22:03:08 +0100 Subject: [PATCH 03/11] Correct default phase. --- src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs index 04f6dec67..74f1a92a8 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -72,7 +72,7 @@ public IResolvePipelineBuilder Use(IResolveMiddleware stage, MiddlewareInsertion public IResolvePipelineBuilder Use(PipelinePhase phase, Action> callback) { - Use(phase, MiddlewareInsertionMode.StartOfPhase, callback); + Use(phase, MiddlewareInsertionMode.EndOfPhase, callback); return this; } From 9c3f4d75aaadd73f3b7a74730e81007d7c33bbf6 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Thu, 21 May 2020 22:05:58 +0100 Subject: [PATCH 04/11] Resolve operation should no longer implement IComponentContext It confuses matters when multiple objects in a middleware function as slighty different component contexts. --- .../Core/Resolving/Pipeline/IPipelineResolveOperation.cs | 2 +- src/Autofac/Core/Resolving/ResolveOperation.cs | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Autofac/Core/Resolving/Pipeline/IPipelineResolveOperation.cs b/src/Autofac/Core/Resolving/Pipeline/IPipelineResolveOperation.cs index 1e456a9a9..acf63520b 100644 --- a/src/Autofac/Core/Resolving/Pipeline/IPipelineResolveOperation.cs +++ b/src/Autofac/Core/Resolving/Pipeline/IPipelineResolveOperation.cs @@ -29,7 +29,7 @@ namespace Autofac.Core.Resolving.Pipeline { - public interface IPipelineResolveOperation : IResolveOperation, IComponentContext, ITracingIdentifer + public interface IPipelineResolveOperation : IResolveOperation, ITracingIdentifer { /// /// Gets the active resolve request. diff --git a/src/Autofac/Core/Resolving/ResolveOperation.cs b/src/Autofac/Core/Resolving/ResolveOperation.cs index 684a69aa2..624166544 100644 --- a/src/Autofac/Core/Resolving/ResolveOperation.cs +++ b/src/Autofac/Core/Resolving/ResolveOperation.cs @@ -108,12 +108,6 @@ public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, IResolveP public event EventHandler? ResolveRequestBeginning; - /// - public object ResolveComponent(ResolveRequest request) - { - return GetOrCreateInstance(CurrentScope, request); - } - /// /// Execute the complete resolve operation. /// @@ -129,7 +123,7 @@ public object Execute(ResolveRequest request) _pipelineTracer?.OperationStart(this, request); - result = ResolveComponent(request); + result = GetOrCreateInstance(CurrentScope, request); } catch (ObjectDisposedException disposeException) { From 888a171aba913df9e2c7c62fc60602a9b43b69bd Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Thu, 21 May 2020 22:06:10 +0100 Subject: [PATCH 05/11] Add comment paragraphs. --- .../Core/Resolving/Pipeline/ResolvePipelineBuilder.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs index 74f1a92a8..5207f74a2 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -35,14 +35,20 @@ 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 { From a0d078bc89f896c660bdc44e62c19506c4088e9b Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Fri, 22 May 2020 11:15:33 +0100 Subject: [PATCH 06/11] Switch the operation and request contexts from interfaces to abstract classes. --- .../Diagnostics/DefaultDiagnosticTracer.cs | 16 +- .../Diagnostics/IResolvePipelineTracer.cs | 22 +- .../OperationTraceCompletedArgs.cs | 4 +- .../ActivatorErrorHandlingMiddleware.cs | 2 +- .../CircularDependencyDetectorMiddleware.cs | 4 +- .../Middleware/DecoratorMiddleware.cs | 4 +- .../Middleware/DelegateMiddleware.cs | 6 +- .../Middleware/DisposalTrackingMiddleware.cs | 2 +- .../Middleware/ScopeSelectionMiddleware.cs | 2 +- .../Resolving/Middleware/SharingMiddleware.cs | 2 +- .../Middleware/StartableMiddleware.cs | 2 +- .../Pipeline/IPipelineResolveOperation.cs | 73 ----- .../Resolving/Pipeline/IResolveMiddleware.cs | 2 +- .../Resolving/Pipeline/IResolvePipeline.cs | 2 +- .../Pipeline/IResolvePipelineBuilder.cs | 16 +- .../Core/Resolving/Pipeline/PipelinePhase.cs | 2 +- .../Pipeline/ResolvePipelineBuilder.cs | 18 +- .../Pipeline/ResolveRequestContext.cs | 97 +------ ...ontext.cs => ResolveRequestContextBase.cs} | 83 ++++-- .../Core/Resolving/ResolveOperation.cs | 185 +------------ .../Core/Resolving/ResolveOperationBase.cs | 262 ++++++++++++++++++ src/Autofac/Core/Resolving/ResolvePipeline.cs | 6 +- .../ResolveRequestBeginningEventArgs.cs | 4 +- .../ResolveRequestCompletingEventArgs.cs | 4 +- .../ActivatorPipelineExtensions.cs | 3 +- .../Core/Pipeline/PipelineBuilderTests.cs | 102 +++++-- test/Autofac.Test/Mocks.cs | 46 +-- 27 files changed, 494 insertions(+), 477 deletions(-) delete mode 100644 src/Autofac/Core/Resolving/Pipeline/IPipelineResolveOperation.cs rename src/Autofac/Core/Resolving/Pipeline/{IResolveRequestContext.cs => ResolveRequestContextBase.cs} (63%) create mode 100644 src/Autofac/Core/Resolving/ResolveOperationBase.cs diff --git a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs index c2d07b2ea..ef9a74263 100644 --- a/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs +++ b/src/Autofac/Core/Diagnostics/DefaultDiagnosticTracer.cs @@ -25,7 +25,7 @@ public class DefaultDiagnosticTracer : IResolvePipelineTracer public event EventHandler? OperationCompleted; /// - void IResolvePipelineTracer.OperationStart(IPipelineResolveOperation operation, ResolveRequest initiatingRequest) + void IResolvePipelineTracer.OperationStart(ResolveOperationBase operation, ResolveRequest initiatingRequest) { var builder = _operationBuilders.GetOrAdd(operation.TracingId, k => new IndentingStringBuilder()); @@ -35,7 +35,7 @@ void IResolvePipelineTracer.OperationStart(IPipelineResolveOperation operation, } /// - void IResolvePipelineTracer.RequestStart(IPipelineResolveOperation operation, IResolveRequestContext requestContext) + void IResolvePipelineTracer.RequestStart(ResolveOperationBase operation, ResolveRequestContextBase requestContext) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { @@ -56,7 +56,7 @@ void IResolvePipelineTracer.RequestStart(IPipelineResolveOperation operation, IR } /// - void IResolvePipelineTracer.MiddlewareEntry(IPipelineResolveOperation operation, IResolveRequestContext requestContext, IResolveMiddleware middleware) + void IResolvePipelineTracer.MiddlewareEntry(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { @@ -66,7 +66,7 @@ void IResolvePipelineTracer.MiddlewareEntry(IPipelineResolveOperation operation, } /// - void IResolvePipelineTracer.MiddlewareExit(IPipelineResolveOperation operation, IResolveRequestContext requestContext, IResolveMiddleware middleware, bool succeeded) + void IResolvePipelineTracer.MiddlewareExit(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware, bool succeeded) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { @@ -84,7 +84,7 @@ void IResolvePipelineTracer.MiddlewareExit(IPipelineResolveOperation operation, } /// - void IResolvePipelineTracer.RequestFailure(IPipelineResolveOperation operation, IResolveRequestContext requestContext, Exception requestException) + void IResolvePipelineTracer.RequestFailure(ResolveOperationBase operation, ResolveRequestContextBase requestContext, Exception requestException) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { @@ -110,7 +110,7 @@ void IResolvePipelineTracer.RequestFailure(IPipelineResolveOperation operation, } /// - void IResolvePipelineTracer.RequestSuccess(IPipelineResolveOperation operation, IResolveRequestContext requestContext) + void IResolvePipelineTracer.RequestSuccess(ResolveOperationBase operation, ResolveRequestContextBase requestContext) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { @@ -121,7 +121,7 @@ void IResolvePipelineTracer.RequestSuccess(IPipelineResolveOperation operation, } /// - void IResolvePipelineTracer.OperationFailure(IPipelineResolveOperation operation, Exception operationException) + void IResolvePipelineTracer.OperationFailure(ResolveOperationBase operation, Exception operationException) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { @@ -134,7 +134,7 @@ void IResolvePipelineTracer.OperationFailure(IPipelineResolveOperation operation } /// - void IResolvePipelineTracer.OperationSuccess(IPipelineResolveOperation operation, object resolvedInstance) + void IResolvePipelineTracer.OperationSuccess(ResolveOperationBase operation, object resolvedInstance) { if (_operationBuilders.TryGetValue(operation.TracingId, out var builder)) { diff --git a/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs b/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs index e9dcc228f..772214688 100644 --- a/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs +++ b/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs @@ -8,7 +8,7 @@ namespace Autofac.Core.Diagnostics /// 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 + /// You can get a 'tracing ID' object from that can be used as a dictionary tracking key /// to associate related operations. /// /// @@ -20,17 +20,17 @@ public interface IResolvePipelineTracer /// 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 + /// 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(IPipelineResolveOperation operation, ResolveRequest initiatingRequest); + 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(IPipelineResolveOperation operation, IResolveRequestContext requestContext); + void RequestStart(ResolveOperationBase operation, ResolveRequestContextBase requestContext); /// /// Invoked when an individual middleware item is about to execute (just before the method executes). @@ -38,7 +38,7 @@ public interface IResolvePipelineTracer /// 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(IPipelineResolveOperation operation, IResolveRequestContext requestContext, IResolveMiddleware middleware); + void MiddlewareEntry(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware); /// /// Invoked when an individual middleware item has finished executing (when the method returns). @@ -50,7 +50,7 @@ public interface IResolvePipelineTracer /// 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(IPipelineResolveOperation operation, IResolveRequestContext requestContext, IResolveMiddleware middleware, bool succeeded); + void MiddlewareExit(ResolveOperationBase operation, ResolveRequestContextBase requestContext, IResolveMiddleware middleware, bool succeeded); /// /// Invoked when a resolve request fails. @@ -58,28 +58,28 @@ public interface IResolvePipelineTracer /// 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(IPipelineResolveOperation operation, IResolveRequestContext requestContext, Exception requestException); + 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(IPipelineResolveOperation operation, IResolveRequestContext requestContext); + 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(IPipelineResolveOperation operation, Exception operationException); + 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(IPipelineResolveOperation operation, object resolvedInstance); + void OperationSuccess(ResolveOperationBase operation, object resolvedInstance); } } diff --git a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs index 0fa882e94..fb99e1202 100644 --- a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs +++ b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs @@ -4,13 +4,13 @@ namespace Autofac.Core.Diagnostics { public sealed class OperationTraceCompletedArgs { - public OperationTraceCompletedArgs(IPipelineResolveOperation operation, string traceContent) + public OperationTraceCompletedArgs(ResolveOperationBase operation, string traceContent) { Operation = operation; TraceContent = traceContent; } - public IPipelineResolveOperation Operation { get; } + public ResolveOperationBase Operation { get; } public string TraceContent { get; } } diff --git a/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs index aed5bfd97..1a8ca0c7c 100644 --- a/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/ActivatorErrorHandlingMiddleware.cs @@ -49,7 +49,7 @@ private ActivatorErrorHandlingMiddleware() public PipelinePhase Phase => PipelinePhase.Activation; /// - public void Execute(IResolveRequestContext context, Action next) + public void Execute(ResolveRequestContextBase context, Action next) { try { diff --git a/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs index fbc0eb1a2..f255fe7c7 100644 --- a/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs @@ -49,7 +49,7 @@ public CircularDependencyDetectorMiddleware(int maxResolveDepth) public PipelinePhase Phase => PipelinePhase.RequestStart; - public void Execute(IResolveRequestContext context, Action next) + public void Execute(ResolveRequestContextBase context, Action next) { var activationDepth = context.Operation.RequestDepth; @@ -94,7 +94,7 @@ public void Execute(IResolveRequestContext context, Action nameof(CircularDependencyDetectorMiddleware); - private static string CreateDependencyGraphTo(IComponentRegistration registration, IEnumerable requestStack) + private static string CreateDependencyGraphTo(IComponentRegistration registration, IEnumerable requestStack) { if (registration == null) throw new ArgumentNullException(nameof(registration)); if (requestStack == null) throw new ArgumentNullException(nameof(requestStack)); diff --git a/src/Autofac/Core/Resolving/Middleware/DecoratorMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/DecoratorMiddleware.cs index 13c593a30..3b5998fe3 100644 --- a/src/Autofac/Core/Resolving/Middleware/DecoratorMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/DecoratorMiddleware.cs @@ -50,7 +50,7 @@ private DecoratorMiddleware() public PipelinePhase Phase => PipelinePhase.Decoration; /// - public void Execute(IResolveRequestContext context, Action next) + public void Execute(ResolveRequestContextBase context, Action next) { // Proceed down the pipeline. next(context); @@ -65,7 +65,7 @@ public void Execute(IResolveRequestContext context, Action public override string ToString() => nameof(DecoratorMiddleware); - private static bool TryDecorateRegistration(IResolveRequestContext context, [NotNullWhen(true)] out object? instance) + private static bool TryDecorateRegistration(ResolveRequestContextBase context, [NotNullWhen(true)] out object? instance) { var service = context.Service; diff --git a/src/Autofac/Core/Resolving/Middleware/DelegateMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/DelegateMiddleware.cs index 780ca15a5..73e92475b 100644 --- a/src/Autofac/Core/Resolving/Middleware/DelegateMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/DelegateMiddleware.cs @@ -34,7 +34,7 @@ namespace Autofac.Core.Resolving.Middleware internal class DelegateMiddleware : IResolveMiddleware { private readonly string _name; - private readonly Action> _callback; + private readonly Action> _callback; /// /// Initializes a new instance of the class. @@ -42,7 +42,7 @@ internal class DelegateMiddleware : IResolveMiddleware /// The middleware description. /// The pipeline phase. /// The callback to execute. - public DelegateMiddleware(string description, PipelinePhase phase, Action> callback) + public DelegateMiddleware(string description, PipelinePhase phase, Action> callback) { _name = description; Phase = phase; @@ -53,7 +53,7 @@ public DelegateMiddleware(string description, PipelinePhase phase, Action - public void Execute(IResolveRequestContext context, Action next) + public void Execute(ResolveRequestContextBase context, Action next) { _callback(context, next); } diff --git a/src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs index 65ab2e5f6..20f5ed0b8 100644 --- a/src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs @@ -43,7 +43,7 @@ private DisposalTrackingMiddleware() public PipelinePhase Phase => PipelinePhase.Activation; /// - public void Execute(IResolveRequestContext context, Action next) + public void Execute(ResolveRequestContextBase context, Action next) { next(context); diff --git a/src/Autofac/Core/Resolving/Middleware/ScopeSelectionMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/ScopeSelectionMiddleware.cs index 76ff06a15..fc51d66c3 100644 --- a/src/Autofac/Core/Resolving/Middleware/ScopeSelectionMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/ScopeSelectionMiddleware.cs @@ -44,7 +44,7 @@ private ScopeSelectionMiddleware() public PipelinePhase Phase => PipelinePhase.ScopeSelection; - public void Execute(IResolveRequestContext context, Action next) + public void Execute(ResolveRequestContextBase context, Action next) { try { diff --git a/src/Autofac/Core/Resolving/Middleware/SharingMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/SharingMiddleware.cs index 1403c1746..b4500b557 100644 --- a/src/Autofac/Core/Resolving/Middleware/SharingMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/SharingMiddleware.cs @@ -42,7 +42,7 @@ internal class SharingMiddleware : IResolveMiddleware public PipelinePhase Phase => PipelinePhase.Sharing; /// - public void Execute(IResolveRequestContext context, Action next) + public void Execute(ResolveRequestContextBase context, Action next) { var registration = context.Registration; var decoratorRegistration = context.DecoratorTarget; diff --git a/src/Autofac/Core/Resolving/Middleware/StartableMiddleware.cs b/src/Autofac/Core/Resolving/Middleware/StartableMiddleware.cs index 2a98bbb28..f09d99914 100644 --- a/src/Autofac/Core/Resolving/Middleware/StartableMiddleware.cs +++ b/src/Autofac/Core/Resolving/Middleware/StartableMiddleware.cs @@ -43,7 +43,7 @@ private StartableMiddleware() public PipelinePhase Phase => PipelinePhase.Activation; /// - public void Execute(IResolveRequestContext context, Action next) + public void Execute(ResolveRequestContextBase context, Action next) { next(context); diff --git a/src/Autofac/Core/Resolving/Pipeline/IPipelineResolveOperation.cs b/src/Autofac/Core/Resolving/Pipeline/IPipelineResolveOperation.cs deleted file mode 100644 index acf63520b..000000000 --- a/src/Autofac/Core/Resolving/Pipeline/IPipelineResolveOperation.cs +++ /dev/null @@ -1,73 +0,0 @@ -// 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.Collections.Generic; -using Autofac.Core.Diagnostics; -using Autofac.Core.Resolving.Middleware; - -namespace Autofac.Core.Resolving.Pipeline -{ - public interface IPipelineResolveOperation : IResolveOperation, ITracingIdentifer - { - /// - /// Gets the active resolve request. - /// - IResolveRequestContext? ActiveRequestContext { get; } - - /// - /// Gets the set of all in-progress requests on the request stack. - /// - IEnumerable InProgressRequests { get; } - - /// - /// Gets the tracing identifier for the operation. - /// - ITracingIdentifer TracingId { get; } - - /// - /// Gets the current request depth. - /// - int RequestDepth { get; } - - /// - /// Gets a value indicating whether this operation is a top-level operation (as opposed to one initiated from inside an existing operation). - /// - bool IsTopLevelOperation { get; } - - /// - /// Gets the that initiated the operation. Other nested requests may have been issued as a result of this one. - /// - ResolveRequest? InitiatingRequest { get; } - - /// - /// 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; } - } -} diff --git a/src/Autofac/Core/Resolving/Pipeline/IResolveMiddleware.cs b/src/Autofac/Core/Resolving/Pipeline/IResolveMiddleware.cs index 54f3f5f6b..2fbd95639 100644 --- a/src/Autofac/Core/Resolving/Pipeline/IResolveMiddleware.cs +++ b/src/Autofac/Core/Resolving/Pipeline/IResolveMiddleware.cs @@ -43,6 +43,6 @@ public interface IResolveMiddleware /// /// The context for the resolve request. /// The method to invoke to continue the pipeline execution; pass this method the argument. - void Execute(IResolveRequestContext context, Action next); + void Execute(ResolveRequestContextBase context, Action next); } } diff --git a/src/Autofac/Core/Resolving/Pipeline/IResolvePipeline.cs b/src/Autofac/Core/Resolving/Pipeline/IResolvePipeline.cs index 8ca799ce6..8c5cdcaab 100644 --- a/src/Autofac/Core/Resolving/Pipeline/IResolvePipeline.cs +++ b/src/Autofac/Core/Resolving/Pipeline/IResolvePipeline.cs @@ -34,6 +34,6 @@ public interface IResolvePipeline /// Invoke the pipeline to the end, or until an exception is thrown. /// /// The request context. - void Invoke(IResolveRequestContext context); + void Invoke(ResolveRequestContextBase context); } } diff --git a/src/Autofac/Core/Resolving/Pipeline/IResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/IResolvePipelineBuilder.cs index 661a67cdd..0567819f3 100644 --- a/src/Autofac/Core/Resolving/Pipeline/IResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/IResolvePipelineBuilder.cs @@ -53,11 +53,11 @@ public interface IResolvePipelineBuilder /// 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 + /// 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); + IResolvePipelineBuilder Use(PipelinePhase phase, Action> callback); /// /// Use a middleware callback in a resolve pipeline. @@ -66,11 +66,11 @@ public interface IResolvePipelineBuilder /// 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 + /// 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); + IResolvePipelineBuilder Use(string name, PipelinePhase phase, Action> callback); /// /// Use a middleware callback in a resolve pipeline. @@ -79,11 +79,11 @@ public interface IResolvePipelineBuilder /// 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 + /// 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); + IResolvePipelineBuilder Use(PipelinePhase phase, MiddlewareInsertionMode insertionMode, Action> callback); /// /// Use a middleware callback in a resolve pipeline. @@ -93,11 +93,11 @@ public interface IResolvePipelineBuilder /// 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 + /// 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); + IResolvePipelineBuilder Use(string name, PipelinePhase phase, MiddlewareInsertionMode insertionMode, Action> callback); /// /// Use a set of multiple, ordered middleware instances in a resolve pipeline. diff --git a/src/Autofac/Core/Resolving/Pipeline/PipelinePhase.cs b/src/Autofac/Core/Resolving/Pipeline/PipelinePhase.cs index 12f69262d..167f5be59 100644 --- a/src/Autofac/Core/Resolving/Pipeline/PipelinePhase.cs +++ b/src/Autofac/Core/Resolving/Pipeline/PipelinePhase.cs @@ -63,7 +63,7 @@ public enum PipelinePhase : int /// /// This phase runs just before Activation, is the recommended point at which the resolve parameters should be replaced - /// (using ). + /// (using ). /// ParameterSelection = 125, diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs index 5207f74a2..282fd3d09 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -55,7 +55,7 @@ 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 static readonly Action _terminateAction = ctxt => ctxt.Continuation?.Invoke(ctxt); private const string AnonymousName = "unnamed"; @@ -76,28 +76,28 @@ public IResolvePipelineBuilder Use(IResolveMiddleware stage, MiddlewareInsertion return this; } - public IResolvePipelineBuilder Use(PipelinePhase phase, Action> callback) + public IResolvePipelineBuilder Use(PipelinePhase phase, Action> callback) { Use(phase, MiddlewareInsertionMode.EndOfPhase, callback); return this; } - public IResolvePipelineBuilder Use(PipelinePhase phase, MiddlewareInsertionMode insertionMode, Action> callback) + 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) + 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) + public IResolvePipelineBuilder Use(string name, PipelinePhase phase, MiddlewareInsertionMode insertionMode, Action> callback) { Use(new DelegateMiddleware(name, phase, callback), insertionMode); @@ -262,16 +262,16 @@ 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? currentInvoke = _terminateAction; - Action Chain(Action next, IResolveMiddleware stage) + Action Chain(Action next, IResolveMiddleware stage) { return (ctxt) => { // Optimise the path depending on whether a tracer is attached. if (ctxt.Tracer is null) { - ctxt.SetPhase(stage.Phase); + ctxt.PhaseReached = stage.Phase; stage.Execute(ctxt, next); } else @@ -280,7 +280,7 @@ Action Chain(Action next, IResol var succeeded = false; try { - ctxt.SetPhase(stage.Phase); + ctxt.PhaseReached = stage.Phase; stage.Execute(ctxt, next); succeeded = true; } diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs index a1f142180..caf0e03e7 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs @@ -23,9 +23,6 @@ // 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; namespace Autofac.Core.Resolving.Pipeline @@ -33,11 +30,8 @@ namespace Autofac.Core.Resolving.Pipeline /// /// Context area for a resolve request. /// - internal sealed class ResolveRequestContext : IResolveRequestContext + internal sealed class ResolveRequestContext : ResolveRequestContextBase { - private readonly ResolveRequest _resolveRequest; - private object? _instance; - /// /// Initializes a new instance of the class. /// @@ -46,100 +40,31 @@ internal sealed class ResolveRequestContext : IResolveRequestContext /// The lifetime scope. /// An optional tracer. internal ResolveRequestContext( - IPipelineResolveOperation owningOperation, + ResolveOperationBase owningOperation, ResolveRequest request, ISharingLifetimeScope scope, IResolvePipelineTracer? tracer) + : base(owningOperation, request, scope, tracer) { - Operation = owningOperation; - ActivationScope = scope; - Parameters = request.Parameters; - PhaseReached = PipelinePhase.RequestStart; - Tracer = tracer; - _resolveRequest = request; } - /// - public IPipelineResolveOperation Operation { get; } - - /// - public IComponentRegistration Registration => _resolveRequest.Registration; - - /// - public Service Service => _resolveRequest.Service; - - /// - public IComponentRegistration? DecoratorTarget => _resolveRequest.DecoratorTarget; - - /// - public ISharingLifetimeScope ActivationScope { get; private set; } - - /// - [DisallowNull] - public object? Instance - { - get => _instance; - set => _instance = value ?? throw new ArgumentNullException(nameof(value)); - } - - /// - public IEnumerable Parameters { get; private set; } - - /// - public IComponentRegistry ComponentRegistry => ActivationScope.ComponentRegistry; - - /// - public PipelinePhase PhaseReached { get; set; } - - /// - public IResolvePipelineTracer? Tracer { get; } - - /// - public bool NewInstanceActivated => Instance is object && PhaseReached == PipelinePhase.Activation; - - /// - public IResolvePipeline? Continuation { get; set; } - - /// - public event EventHandler? RequestCompleting; - - /// - public object ResolveComponent(ResolveRequest request) + public override object ResolveComponent(ResolveRequest request) { return Operation.GetOrCreateInstance(ActivationScope, request); } - /// - public void ChangeParameters(IEnumerable newParameters) - { - Parameters = newParameters ?? throw new ArgumentNullException(nameof(newParameters)); - } - - /// - public void ChangeScope(ISharingLifetimeScope newScope) - { - ActivationScope = newScope ?? throw new ArgumentNullException(nameof(newScope)); - } - - public void Complete() - { - var handler = RequestCompleting; - handler?.Invoke(this, new ResolveRequestCompletingEventArgs(this)); - } - - /// - void IResolveRequestContext.SetPhase(PipelinePhase phase) - { - PhaseReached = phase; - } - - /// - public object ResolveComponentWithNewOperation(ResolveRequest 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/IResolveRequestContext.cs b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs similarity index 63% rename from src/Autofac/Core/Resolving/Pipeline/IResolveRequestContext.cs rename to src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs index 581bc76cb..f006c1872 100644 --- a/src/Autofac/Core/Resolving/Pipeline/IResolveRequestContext.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs @@ -35,33 +35,57 @@ 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 interface IResolveRequestContext : IComponentContext + 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). /// - IPipelineResolveOperation Operation { get; } + 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. /// - ISharingLifetimeScope ActivationScope { get; } + public ISharingLifetimeScope ActivationScope { get; private set; } /// /// Gets the component registration that is being resolved in the current request. /// - IComponentRegistration Registration { get; } + public IComponentRegistration Registration => _resolveRequest.Registration; /// /// Gets the service that is being resolved in the current request. /// - Service Service { get; } + public Service Service => _resolveRequest.Service; /// /// Gets the target registration for decorator requests. /// - IComponentRegistration? DecoratorTarget { get; } + public IComponentRegistration? DecoratorTarget => _resolveRequest.DecoratorTarget; /// /// Gets or sets the instance that will be returned as the result of the resolve request. @@ -70,56 +94,72 @@ public interface IResolveRequestContext : IComponentContext /// whether the object here was a newly activated instance, or a shared instance previously activated. /// [DisallowNull] - object? Instance { get; set; } + 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. /// - bool NewInstanceActivated { get; } + public bool NewInstanceActivated => Instance is object && PhaseReached == PipelinePhase.Activation; /// /// Gets the active for the request. /// - IResolvePipelineTracer? Tracer { get; } + public IResolvePipelineTracer? Tracer { get; } /// /// Gets the current resolve parameters. These can be changed using the method. /// - IEnumerable Parameters { get; } + public IEnumerable Parameters { get; private set; } /// /// Gets the phase of the pipeline reached by this request. /// - public PipelinePhase PhaseReached { get; } + public PipelinePhase PhaseReached { get; internal set; } /// /// Gets or sets an optional pipeline to invoke at the end of the current request's pipeline. /// - IResolvePipeline? Continuation { get; set; } + 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. /// - event EventHandler? RequestCompleting; + 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. - void ChangeScope(ISharingLifetimeScope newScope); + 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. - void ChangeParameters(IEnumerable newParameters); + 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. /// @@ -129,12 +169,15 @@ public interface IResolveRequestContext : IComponentContext /// /// /// - object ResolveComponentWithNewOperation(ResolveRequest request); + public abstract object ResolveComponentWithNewOperation(ResolveRequest request); /// - /// Set the phase of the pipeline we are in. Only used internally in ; should not be used elsewhere. + /// Complete the request, raising any appropriate events. /// - /// The pipeline phase. - internal void SetPhase(PipelinePhase phase); + 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 624166544..284e1a79c 100644 --- a/src/Autofac/Core/Resolving/ResolveOperation.cs +++ b/src/Autofac/Core/Resolving/ResolveOperation.cs @@ -23,8 +23,6 @@ // 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; @@ -35,20 +33,15 @@ 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. /// - internal sealed class ResolveOperation : IPipelineResolveOperation + internal sealed class ResolveOperation : ResolveOperationBase { - private readonly Stack _requestStack; - private readonly IResolvePipelineTracer? _pipelineTracer; - private List _successfulRequests; - 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) - : this(mostNestedLifetimeScope, null) + : base(mostNestedLifetimeScope) { } @@ -59,14 +52,8 @@ public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope) /// 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) { - CurrentScope = mostNestedLifetimeScope; - TracingId = this; - IsTopLevelOperation = true; - _pipelineTracer = pipelineTracer; - - _requestStack = new Stack(); - _successfulRequests = new List(); } /// @@ -76,38 +63,11 @@ public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, IResolveP /// 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, IPipelineResolveOperation parentOperation) + public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, IResolvePipelineTracer? pipelineTracer, ResolveOperationBase parentOperation) + : base(mostNestedLifetimeScope, pipelineTracer, parentOperation) { - CurrentScope = mostNestedLifetimeScope; - TracingId = parentOperation.TracingId; - _pipelineTracer = pipelineTracer; - - _requestStack = new Stack(); - _successfulRequests = new List(); } - public IResolveRequestContext? ActiveRequestContext { get; set; } - - public ISharingLifetimeScope CurrentScope { get; set; } - - public IComponentRegistry ComponentRegistry => CurrentScope.ComponentRegistry; - - public IEnumerable InProgressRequests => _requestStack; - - public ResolveRequest? InitiatingRequest { get; private set; } - - public int RequestDepth { get; private set; } - - Stack IPipelineResolveOperation.RequestStack => _requestStack; - - public ITracingIdentifer TracingId { get; } - - public bool IsTopLevelOperation { get; } - - public event EventHandler? CurrentOperationEnding; - - public event EventHandler? ResolveRequestBeginning; - /// /// Execute the complete resolve operation. /// @@ -115,139 +75,16 @@ public ResolveOperation(ISharingLifetimeScope mostNestedLifetimeScope, IResolveP [SuppressMessage("CA1031", "CA1031", Justification = "General exception gets rethrown in a DependencyResolutionException.")] public object Execute(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; - } - - /// - /// Continue building the object graph by instantiating in the - /// current . - /// - /// 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); - - // Resolve pipeline from the registration. - var registrationPipeline = request.Registration.ResolvePipeline; - - // 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); - - // Invoke the pipeline. - registrationPipeline.Invoke(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; - } - - 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(); + 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..9ee2146c6 --- /dev/null +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -0,0 +1,262 @@ +// 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; + +namespace Autofac.Core.Resolving.Pipeline +{ + /// + /// 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/ResolvePipeline.cs b/src/Autofac/Core/Resolving/ResolvePipeline.cs index 2be9642ca..c058f482a 100644 --- a/src/Autofac/Core/Resolving/ResolvePipeline.cs +++ b/src/Autofac/Core/Resolving/ResolvePipeline.cs @@ -8,19 +8,19 @@ namespace Autofac.Core.Pipeline /// internal class ResolvePipeline : IResolvePipeline { - private readonly Action? _entryPoint; + private readonly Action? _entryPoint; /// /// Initializes a new instance of the class. /// /// Callback to invoke. - public ResolvePipeline(Action? entryPoint) + public ResolvePipeline(Action? entryPoint) { _entryPoint = entryPoint; } /// - public void Invoke(IResolveRequestContext ctxt) + public void Invoke(ResolveRequestContextBase ctxt) { _entryPoint?.Invoke(ctxt); } diff --git a/src/Autofac/Core/Resolving/ResolveRequestBeginningEventArgs.cs b/src/Autofac/Core/Resolving/ResolveRequestBeginningEventArgs.cs index a528d048d..e724ba3c6 100644 --- a/src/Autofac/Core/Resolving/ResolveRequestBeginningEventArgs.cs +++ b/src/Autofac/Core/Resolving/ResolveRequestBeginningEventArgs.cs @@ -37,7 +37,7 @@ public sealed class ResolveRequestBeginningEventArgs : EventArgs /// Initializes a new instance of the class. /// /// The resolve request context that is starting. - public ResolveRequestBeginningEventArgs(IResolveRequestContext requestContext) + public ResolveRequestBeginningEventArgs(ResolveRequestContextBase requestContext) { RequestContext = requestContext ?? throw new ArgumentNullException(nameof(requestContext)); } @@ -45,6 +45,6 @@ public ResolveRequestBeginningEventArgs(IResolveRequestContext requestContext) /// /// Gets the resolve request that is beginning. /// - public IResolveRequestContext RequestContext { get; } + public ResolveRequestContextBase RequestContext { get; } } } diff --git a/src/Autofac/Core/Resolving/ResolveRequestCompletingEventArgs.cs b/src/Autofac/Core/Resolving/ResolveRequestCompletingEventArgs.cs index a9faa941a..c306f0e88 100644 --- a/src/Autofac/Core/Resolving/ResolveRequestCompletingEventArgs.cs +++ b/src/Autofac/Core/Resolving/ResolveRequestCompletingEventArgs.cs @@ -37,7 +37,7 @@ public class ResolveRequestCompletingEventArgs : EventArgs /// Initializes a new instance of the class. /// /// The resolve request context that is completing. - public ResolveRequestCompletingEventArgs(IResolveRequestContext requestContext) + public ResolveRequestCompletingEventArgs(ResolveRequestContextBase requestContext) { if (requestContext is null) throw new ArgumentNullException(nameof(requestContext)); @@ -47,6 +47,6 @@ public ResolveRequestCompletingEventArgs(IResolveRequestContext requestContext) /// /// Gets the instance lookup operation that is beginning. /// - public IResolveRequestContext RequestContext { get; } + public ResolveRequestContextBase RequestContext { get; } } } diff --git a/test/Autofac.Test/ActivatorPipelineExtensions.cs b/test/Autofac.Test/ActivatorPipelineExtensions.cs index bf17b9111..51a239577 100644 --- a/test/Autofac.Test/ActivatorPipelineExtensions.cs +++ b/test/Autofac.Test/ActivatorPipelineExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Autofac.Core; +using Autofac.Core.Resolving; using Autofac.Core.Resolving.Pipeline; namespace Autofac.Test @@ -43,7 +44,7 @@ public static Func, T> GetPipelineInvoker var lifetimeScope = scope.Resolve() as ISharingLifetimeScope; var request = new ResolveRequestContext( - Mocks.GetPipelineOperation(lifetimeScope), + new ResolveOperation(lifetimeScope), new ResolveRequest(new TypedService(typeof(T)), Mocks.GetComponentRegistration(), parameters), lifetimeScope, null); diff --git a/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs index de028a800..be9a2eb67 100644 --- a/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs +++ b/test/Autofac.Test/Core/Pipeline/PipelineBuilderTests.cs @@ -1,7 +1,11 @@ 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; @@ -365,42 +369,99 @@ public void CanAddMultipleMiddlewareToPipelineWithExistingMiddleware() el => Assert.Equal("6", el.ToString())); } - private class MockPipelineRequestContext : IResolveRequestContext + private class MockPipelineRequestContext : ResolveRequestContextBase { - public event EventHandler RequestCompleting; + public MockPipelineRequestContext() + : base( + new ResolveOperation(new MockLifetimeScope()), + new ResolveRequest(new TypedService(typeof(int)), Mocks.GetComponentRegistration(), Enumerable.Empty()), + new MockLifetimeScope(), + null) + { + } - public IPipelineResolveOperation Operation => throw new NotImplementedException(); + public override object ResolveComponent(ResolveRequest request) + { + throw new NotImplementedException(); + } - public ISharingLifetimeScope ActivationScope => throw new NotImplementedException(); + public override object ResolveComponentWithNewOperation(ResolveRequest request) + { + throw new NotImplementedException(); + } + } - public IComponentRegistration Registration => throw new NotImplementedException(); + private class MockLifetimeScope : ISharingLifetimeScope + { + public ISharingLifetimeScope RootLifetimeScope => throw new NotImplementedException(); - public Service Service => throw new NotImplementedException(); + public ISharingLifetimeScope ParentLifetimeScope => throw new NotImplementedException(); - public IComponentRegistration DecoratorTarget => throw new NotImplementedException(); + public IDisposer Disposer => throw new NotImplementedException(); - public object Instance { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public object Tag => throw new NotImplementedException(); - public bool NewInstanceActivated => throw new NotImplementedException(); + public IComponentRegistry ComponentRegistry => throw new NotImplementedException(); - public IResolvePipelineTracer Tracer { get; set; } + public event EventHandler ChildLifetimeScopeBeginning + { + add { } + remove { } + } - public IEnumerable Parameters => throw new NotImplementedException(); + public event EventHandler CurrentScopeEnding + { + add { } + remove { } + } - public PipelinePhase PhaseReached { get; set; } + public event EventHandler ResolveOperationBeginning + { + add { } + remove { } + } - public IComponentRegistry ComponentRegistry => throw new NotImplementedException(); + public void AttachTrace(IResolvePipelineTracer tracer) + { + throw new NotImplementedException(); + } - public IResolvePipeline Continuation { get; set; } + public ILifetimeScope BeginLifetimeScope() + { + throw new NotImplementedException(); + } - void IResolveRequestContext.SetPhase(PipelinePhase phase) => PhaseReached = phase; + 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 void ChangeParameters(IEnumerable newParameters) + public object CreateSharedInstance(Guid id, Func creator) { throw new NotImplementedException(); } - public void ChangeScope(ISharingLifetimeScope newScope) + public object CreateSharedInstance(Guid primaryId, Guid? qualifyingId, Func creator) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public ValueTask DisposeAsync() { throw new NotImplementedException(); } @@ -410,7 +471,12 @@ public object ResolveComponent(ResolveRequest request) throw new NotImplementedException(); } - public object ResolveComponentWithNewOperation(ResolveRequest request) + 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/Mocks.cs b/test/Autofac.Test/Mocks.cs index c4af78d26..116dda135 100644 --- a/test/Autofac.Test/Mocks.cs +++ b/test/Autofac.Test/Mocks.cs @@ -26,11 +26,6 @@ public static MockComponentRegistration GetComponentRegistration() return new MockComponentRegistration(); } - public static MockPipelineOperation GetPipelineOperation(ISharingLifetimeScope scope) - { - return new MockPipelineOperation(scope); - } - internal class MockConstructorFinder : IConstructorFinder { public ConstructorInfo[] FindConstructors(Type targetType) @@ -80,46 +75,7 @@ public void Dispose() public void BuildResolvePipeline(IComponentRegistryServices registryServices) { - } - } - - public class MockPipelineOperation : IPipelineResolveOperation - { - public MockPipelineOperation(ISharingLifetimeScope scope) - { - CurrentScope = scope; - } - - public ISharingLifetimeScope CurrentScope { get; } - - public IComponentRegistry ComponentRegistry => CurrentScope.ComponentRegistry; - - public IResolveRequestContext ActiveRequestContext => throw new NotImplementedException(); - - public IEnumerable InProgressRequests => throw new NotImplementedException(); - - Stack IPipelineResolveOperation.RequestStack => throw new NotImplementedException(); - - public int RequestDepth => throw new NotImplementedException(); - - public ITracingIdentifer TracingId => this; - - public bool IsTopLevelOperation => true; - - public ResolveRequest InitiatingRequest { get; set; } - - public event EventHandler CurrentOperationEnding; - - public event EventHandler ResolveRequestBeginning; - - public object GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request) - { - return currentOperationScope.ResolveComponent(request); - } - - public object ResolveComponent(ResolveRequest request) - { - return CurrentScope.ResolveComponent(request); + PipelineBuilding?.Invoke(this, new ResolvePipelineBuilder()); } } } From 18961cb29b5baeaed559516271aebaf90d9a1143 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Fri, 22 May 2020 11:22:29 +0100 Subject: [PATCH 07/11] Correct namespace of ResolveOperationBase. --- src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs | 1 + src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs | 2 +- src/Autofac/Core/Resolving/ResolveOperationBase.cs | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs b/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs index 772214688..eb1ec5b67 100644 --- a/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs +++ b/src/Autofac/Core/Diagnostics/IResolvePipelineTracer.cs @@ -1,4 +1,5 @@ using System; +using Autofac.Core.Resolving; using Autofac.Core.Resolving.Pipeline; namespace Autofac.Core.Diagnostics diff --git a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs index fb99e1202..3db330187 100644 --- a/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs +++ b/src/Autofac/Core/Diagnostics/OperationTraceCompletedArgs.cs @@ -1,4 +1,4 @@ -using Autofac.Core.Resolving.Pipeline; +using Autofac.Core.Resolving; namespace Autofac.Core.Diagnostics { diff --git a/src/Autofac/Core/Resolving/ResolveOperationBase.cs b/src/Autofac/Core/Resolving/ResolveOperationBase.cs index 9ee2146c6..43b32c532 100644 --- a/src/Autofac/Core/Resolving/ResolveOperationBase.cs +++ b/src/Autofac/Core/Resolving/ResolveOperationBase.cs @@ -27,8 +27,9 @@ using System.Collections.Generic; using Autofac.Core.Diagnostics; using Autofac.Core.Resolving.Middleware; +using Autofac.Core.Resolving.Pipeline; -namespace Autofac.Core.Resolving.Pipeline +namespace Autofac.Core.Resolving { /// /// Defines the base properties and behaviour of a resolve operation. From 3cb998f37c4dc098540ba7f5d4f7aef00176cf59 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Fri, 22 May 2020 11:30:40 +0100 Subject: [PATCH 08/11] Switch events in the services tracker to regular event objects. --- .../DefaultRegisteredServicesTracker.cs | 39 +++---------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs b/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs index 9b8378b26..9e9564b0d 100644 --- a/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs +++ b/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs @@ -38,14 +38,6 @@ 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. /// @@ -61,22 +53,12 @@ public DefaultRegisteredServicesTracker() /// 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 @@ -110,7 +92,8 @@ public virtual void AddRegistration(IComponentRegistration registration, bool pr } _registrations.Add(registration); - GetRegistered()?.Invoke(this, registration); + var handler = Registered; + handler?.Invoke(this, registration); if (originatedFromSource) { @@ -129,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); } } @@ -254,17 +237,5 @@ private ServiceRegistrationInfo GetServiceInfo(Service service) _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; } } From ae6c2848ff8efb67bda5467bc8d0062676088387 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Fri, 22 May 2020 11:39:35 +0100 Subject: [PATCH 09/11] Renamed originatedFromSource argument. --- .../Core/Registration/DefaultRegisteredServicesTracker.cs | 6 +++--- src/Autofac/Core/Registration/IRegisteredServicesTracker.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs b/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs index 9e9564b0d..9627d1648 100644 --- a/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs +++ b/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs @@ -83,19 +83,19 @@ public IEnumerable Sources } /// - 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); var handler = Registered; handler?.Invoke(this, registration); - if (originatedFromSource) + if (originatedFromDynamicSource) { registration.BuildResolvePipeline(this); } diff --git a/src/Autofac/Core/Registration/IRegisteredServicesTracker.cs b/src/Autofac/Core/Registration/IRegisteredServicesTracker.cs index 0496b26a9..b0bfc0dcb 100644 --- a/src/Autofac/Core/Registration/IRegisteredServicesTracker.cs +++ b/src/Autofac/Core/Registration/IRegisteredServicesTracker.cs @@ -13,8 +13,8 @@ internal interface IRegisteredServicesTracker : IDisposable, IComponentRegistryS /// /// 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. From c12d3ef62dea1dd044ef74d2060b4bc1114b847e Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Wed, 27 May 2020 09:00:19 +0100 Subject: [PATCH 10/11] Improve IInstanceActivator comment and remove erroneous project Excludes. --- src/Autofac/Autofac.csproj | 5 ----- src/Autofac/Core/IInstanceActivator.cs | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Autofac/Autofac.csproj b/src/Autofac/Autofac.csproj index 64846291f..85891e25d 100644 --- a/src/Autofac/Autofac.csproj +++ b/src/Autofac/Autofac.csproj @@ -40,11 +40,6 @@ $(NoWarn);8600;8601;8602;8603;8604 - - - - - diff --git a/src/Autofac/Core/IInstanceActivator.cs b/src/Autofac/Core/IInstanceActivator.cs index 8273cba90..24952b518 100644 --- a/src/Autofac/Core/IInstanceActivator.cs +++ b/src/Autofac/Core/IInstanceActivator.cs @@ -34,9 +34,9 @@ namespace Autofac.Core public interface IInstanceActivator : IDisposable { /// - /// Configure a registrations resolve pipeline just before the pipeline is built, and with the available services known to you. + /// Allows an implementation to add middleware to a registration's resolve pipeline. /// - /// Provides access to the set of available services. + /// Provides access to the set of all available services. /// The registration's pipeline builder. void ConfigurePipeline(IComponentRegistryServices componentRegistryServices, IResolvePipelineBuilder pipelineBuilder); From c558977a5a9b6281f4a4c0208af25a7ca7c44dd7 Mon Sep 17 00:00:00 2001 From: Alistair Evans Date: Wed, 27 May 2020 09:07:47 +0100 Subject: [PATCH 11/11] Put some indication of the replacement of tracing in AttachTrace summary. --- src/Autofac/Core/Lifetime/LifetimeScope.cs | 1 + src/Autofac/ILifetimeScope.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Autofac/Core/Lifetime/LifetimeScope.cs b/src/Autofac/Core/Lifetime/LifetimeScope.cs index a553409eb..ba908a538 100644 --- a/src/Autofac/Core/Lifetime/LifetimeScope.cs +++ b/src/Autofac/Core/Lifetime/LifetimeScope.cs @@ -165,6 +165,7 @@ private void RaiseBeginning(ILifetimeScope scope) handler?.Invoke(this, new LifetimeScopeBeginningEventArgs(scope)); } + /// public void AttachTrace(IResolvePipelineTracer tracer) { _tracer = tracer ?? throw new ArgumentNullException(nameof(tracer)); diff --git a/src/Autofac/ILifetimeScope.cs b/src/Autofac/ILifetimeScope.cs index 8ba64d440..da9d1fe92 100644 --- a/src/Autofac/ILifetimeScope.cs +++ b/src/Autofac/ILifetimeScope.cs @@ -119,7 +119,7 @@ public interface ILifetimeScope : IComponentContext, IDisposable, IAsyncDisposab ILifetimeScope BeginLifetimeScope(object tag, Action configurationAction); /// - /// Enable tracing on this scope, routing trace events to the specified tracer. + /// 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.