diff --git a/src/Autofac/Builder/RegistrationBuilder.cs b/src/Autofac/Builder/RegistrationBuilder.cs index cbb20b96a..7955ec0e5 100644 --- a/src/Autofac/Builder/RegistrationBuilder.cs +++ b/src/Autofac/Builder/RegistrationBuilder.cs @@ -139,8 +139,7 @@ public static IComponentRegistration CreateRegistration @@ -171,7 +170,6 @@ public static IComponentRegistration CreateRegistration( /// 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. /// An IComponentRegistration. /// /// Thrown if or is . @@ -182,8 +180,7 @@ public static IComponentRegistration CreateRegistration( IInstanceActivator activator, IResolvePipelineBuilder pipelineBuilder, Service[] services, - IComponentRegistration? target, - bool isAdapterForIndividualComponent = false) + IComponentRegistration? target) { if (activator == null) throw new ArgumentNullException(nameof(activator)); if (data == null) throw new ArgumentNullException(nameof(data)); @@ -224,7 +221,8 @@ public static IComponentRegistration CreateRegistration( data.Ownership, clonedPipelineBuilder, services, - data.Metadata); + data.Metadata, + data.Options); } else { @@ -238,7 +236,7 @@ public static IComponentRegistration CreateRegistration( services, data.Metadata, target, - isAdapterForIndividualComponent); + data.Options); } return registration; diff --git a/src/Autofac/Builder/RegistrationData.cs b/src/Autofac/Builder/RegistrationData.cs index c3731c466..3d6f45d58 100644 --- a/src/Autofac/Builder/RegistrationData.cs +++ b/src/Autofac/Builder/RegistrationData.cs @@ -137,6 +137,11 @@ public IComponentLifetime Lifetime /// public IDictionary Metadata { get; } + /// + /// Gets or sets the options for the registration. + /// + public RegistrationOptions Options { get; set; } + /// /// Gets or sets the callback used to register this component. /// diff --git a/src/Autofac/Builder/SingleRegistrationStyle.cs b/src/Autofac/Builder/SingleRegistrationStyle.cs index b2da14cfb..808fa6730 100644 --- a/src/Autofac/Builder/SingleRegistrationStyle.cs +++ b/src/Autofac/Builder/SingleRegistrationStyle.cs @@ -26,6 +26,7 @@ using System; using System.Collections.Generic; using Autofac.Core; +using Autofac.Core.Registration; namespace Autofac.Builder { @@ -55,11 +56,5 @@ public class SingleRegistrationStyle /// Gets or sets the component upon which this registration is based. /// public IComponentRegistration? Target { get; set; } - - /// - /// Gets or sets a value indicating whether the registration is a 1:1 adapters on top - /// of another component (e.g., Meta, Func, or Owned). - /// - public bool IsAdapterForIndividualComponent { get; set; } } } diff --git a/src/Autofac/Core/IComponentRegistration.cs b/src/Autofac/Core/IComponentRegistration.cs index 7ad4fce0a..84bc5231f 100644 --- a/src/Autofac/Core/IComponentRegistration.cs +++ b/src/Autofac/Core/IComponentRegistration.cs @@ -83,10 +83,9 @@ public interface IComponentRegistration : IDisposable IResolvePipeline ResolvePipeline { get; } /// - /// Gets a value indicating whether the registration is a 1:1 adapter on top - /// of another component (e.g., Meta, Func, or Owned). + /// Gets the options for the registration. /// - bool IsAdapterForIndividualComponent { get; } + RegistrationOptions Options { get; } /// /// Provides an event that will be invoked just before a pipeline is built, and can be used to add additional middleware diff --git a/src/Autofac/Core/ImplicitRegistrationSource.cs b/src/Autofac/Core/ImplicitRegistrationSource.cs index e2e5009fe..f7adaa215 100644 --- a/src/Autofac/Core/ImplicitRegistrationSource.cs +++ b/src/Autofac/Core/ImplicitRegistrationSource.cs @@ -132,7 +132,7 @@ private IComponentRegistration CreateRegistration(Service providedService, Se var rb = BuildRegistration(registrationDelegate) .As(providedService) - .Targeting(serviceRegistration.Registration, IsAdapterForIndividualComponents) + .Targeting(serviceRegistration.Registration) .InheritRegistrationOrderFrom(serviceRegistration.Registration); return rb.CreateRegistration(); diff --git a/src/Autofac/Core/Registration/ComponentRegistration.cs b/src/Autofac/Core/Registration/ComponentRegistration.cs index c27cdcb33..ea977202b 100644 --- a/src/Autofac/Core/Registration/ComponentRegistration.cs +++ b/src/Autofac/Core/Registration/ComponentRegistration.cs @@ -44,6 +44,13 @@ public class ComponentRegistration : Disposable, IComponentRegistration private readonly IResolvePipelineBuilder _lateBuildPipeline; private IResolvePipeline? _builtComponentPipeline; + /// + /// Defines the options copied from a target registration onto this one. + /// + private const RegistrationOptions OptionsCopiedFromTargetRegistration = RegistrationOptions.Fixed | + RegistrationOptions.ExcludeFromCollections | + RegistrationOptions.DisableDecoration; + /// /// Initializes a new instance of the class. /// @@ -55,7 +62,7 @@ public class ComponentRegistration : Disposable, IComponentRegistration /// The set of services provided by the registration. /// Any metadata associated with the registration. /// The target/inner registration. - /// Indicates whether this registration is an adapter for individual components (Meta, Func, etc). + /// Contains options for the registration. public ComponentRegistration( Guid id, IInstanceActivator activator, @@ -65,8 +72,8 @@ public ComponentRegistration( IEnumerable services, IDictionary metadata, IComponentRegistration target, - bool isAdapterForIndividualComponents) - : this(id, activator, lifetime, sharing, ownership, new ResolvePipelineBuilder(PipelineType.Registration), services, metadata, target, isAdapterForIndividualComponents) + RegistrationOptions options = RegistrationOptions.None) + : this(id, activator, lifetime, sharing, ownership, new ResolvePipelineBuilder(PipelineType.Registration), services, metadata, target, options) { } @@ -80,6 +87,7 @@ public ComponentRegistration( /// Whether the component instances are disposed at the end of their lifetimes. /// Services the component provides. /// Data associated with the component. + /// Contains options for the registration. public ComponentRegistration( Guid id, IInstanceActivator activator, @@ -87,8 +95,9 @@ public ComponentRegistration( InstanceSharing sharing, InstanceOwnership ownership, IEnumerable services, - IDictionary metadata) - : this(id, activator, lifetime, sharing, ownership, new ResolvePipelineBuilder(PipelineType.Registration), services, metadata) + IDictionary metadata, + RegistrationOptions options = RegistrationOptions.None) + : this(id, activator, lifetime, sharing, ownership, new ResolvePipelineBuilder(PipelineType.Registration), services, metadata, options) { } @@ -103,6 +112,7 @@ public ComponentRegistration( /// The resolve pipeline builder for the registration. /// Services the component provides. /// Data associated with the component. + /// The additional registration options. public ComponentRegistration( Guid id, IInstanceActivator activator, @@ -111,7 +121,8 @@ public ComponentRegistration( InstanceOwnership ownership, IResolvePipelineBuilder pipelineBuilder, IEnumerable services, - IDictionary metadata) + IDictionary metadata, + RegistrationOptions options = RegistrationOptions.None) { if (activator == null) throw new ArgumentNullException(nameof(activator)); if (lifetime == null) throw new ArgumentNullException(nameof(lifetime)); @@ -128,7 +139,7 @@ public ComponentRegistration( Services = Enforce.ArgumentElementNotNull(services, nameof(services)); Metadata = metadata; - IsAdapterForIndividualComponent = false; + Options = options; } /// @@ -143,7 +154,7 @@ public ComponentRegistration( /// Services the component provides. /// Data associated with the component. /// The component registration upon which this registration is based. - /// Whether the registration is a 1:1 adapters on top of another component. + /// Registration options. public ComponentRegistration( Guid id, IInstanceActivator activator, @@ -154,12 +165,14 @@ public ComponentRegistration( IEnumerable services, IDictionary metadata, IComponentRegistration target, - bool isAdapterForIndividualComponents) - : this(id, activator, lifetime, sharing, ownership, pipelineBuilder, services, metadata) + RegistrationOptions options = RegistrationOptions.None) + : this(id, activator, lifetime, sharing, ownership, pipelineBuilder, services, metadata, options) { if (target == null) throw new ArgumentNullException(nameof(target)); _target = target; - IsAdapterForIndividualComponent = isAdapterForIndividualComponents; + + // Certain flags carry over from the target. + Options = options | (_target.Options & OptionsCopiedFromTargetRegistration); } /// @@ -204,6 +217,11 @@ public ComponentRegistration( /// public IDictionary Metadata { get; } + /// + /// Gets the options for the registration. + /// + public RegistrationOptions Options { get; } + /// public event EventHandler? PipelineBuilding; @@ -214,9 +232,6 @@ public IResolvePipeline ResolvePipeline protected set => _builtComponentPipeline = value; } - /// - public bool IsAdapterForIndividualComponent { get; } - /// public void BuildResolvePipeline(IComponentRegistryServices registryServices) { diff --git a/src/Autofac/Core/Registration/ComponentRegistrationLifetimeDecorator.cs b/src/Autofac/Core/Registration/ComponentRegistrationLifetimeDecorator.cs index f12071384..dccc04e1b 100644 --- a/src/Autofac/Core/Registration/ComponentRegistrationLifetimeDecorator.cs +++ b/src/Autofac/Core/Registration/ComponentRegistrationLifetimeDecorator.cs @@ -75,10 +75,10 @@ public ComponentRegistrationLifetimeDecorator(IComponentRegistration inner, ICom public IComponentRegistration Target => _inner.IsAdapting() ? _inner.Target : this; /// - public bool IsAdapterForIndividualComponent => _inner.IsAdapterForIndividualComponent; + public IResolvePipeline ResolvePipeline => _inner.ResolvePipeline; /// - public IResolvePipeline ResolvePipeline => _inner.ResolvePipeline; + public RegistrationOptions Options => _inner.Options; /// public event EventHandler PipelineBuilding diff --git a/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs b/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs index 756b04513..189789d75 100644 --- a/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs +++ b/src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs @@ -183,13 +183,7 @@ public bool TryGetServiceRegistration(Service service, out ServiceRegistration s { var info = GetInitializedServiceInfo(service); - // There is a 'virtual' registration; use it. - if (info.RedirectionTargetRegistration is object) - { - serviceData = new ServiceRegistration(info.ServicePipeline, info.RedirectionTargetRegistration); - return true; - } - else if (info.TryGetRegistration(out var registration)) + if (info.TryGetRegistration(out var registration)) { serviceData = new ServiceRegistration(info.ServicePipeline, registration); return true; diff --git a/src/Autofac/Core/Registration/ExternalComponentRegistration.cs b/src/Autofac/Core/Registration/ExternalComponentRegistration.cs index 6155ce966..73720d66a 100644 --- a/src/Autofac/Core/Registration/ExternalComponentRegistration.cs +++ b/src/Autofac/Core/Registration/ExternalComponentRegistration.cs @@ -39,7 +39,7 @@ internal class ExternalComponentRegistration : ComponentRegistration /// The service to register for. /// The target registration ID. public ExternalComponentRegistration(Service service, IComponentRegistration target) - : base(target.Id, new NoOpActivator(target.Activator.LimitType), target.Lifetime, target.Sharing, target.Ownership, new[] { service }, target.Metadata, target, false) + : base(target.Id, new NoOpActivator(target.Activator.LimitType), target.Lifetime, target.Sharing, target.Ownership, new[] { service }, target.Metadata, target) { } diff --git a/src/Autofac/Core/Registration/RegistrationOptions.cs b/src/Autofac/Core/Registration/RegistrationOptions.cs new file mode 100644 index 000000000..e03c0dd7d --- /dev/null +++ b/src/Autofac/Core/Registration/RegistrationOptions.cs @@ -0,0 +1,63 @@ +// 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.Registration +{ + /// + /// Defines options for a registration. + /// + [Flags] + public enum RegistrationOptions + { + /// + /// No special options; default behaviour. + /// + None = 0, + + /// + /// Indicates that this registration is 'fixed' as the default, ignoring all other registrations when determining the default registration for + /// a service. + /// + Fixed = 2, + + /// + /// Registrations with this flag will not be decorated. + /// + DisableDecoration = 4, + + /// + /// Registrations with this flag will not be included in any collection resolves (i.e. and other collection types). + /// + ExcludeFromCollections = 8, + + /// + /// Flag combination for composite registrations. + /// + Composite = Fixed | DisableDecoration | ExcludeFromCollections, + } +} diff --git a/src/Autofac/Core/Registration/RegistrationOptionsExtensions.cs b/src/Autofac/Core/Registration/RegistrationOptionsExtensions.cs new file mode 100644 index 000000000..f884be846 --- /dev/null +++ b/src/Autofac/Core/Registration/RegistrationOptionsExtensions.cs @@ -0,0 +1,45 @@ +// 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.Registration +{ + /// + /// Extension methods for registration options. + /// + public static class RegistrationOptionsExtensions + { + /// + /// Tests whether a given flag (or combined set of flags) is present in the specified + /// options enumeration. + /// + /// The option to test. + /// The flag (or flags) to test for. + /// True if the specified flag (or flags) are enabled for the registration. + public static bool HasOption(this RegistrationOptions options, RegistrationOptions flag) + { + return (options & flag) == flag; + } + } +} diff --git a/src/Autofac/Core/Registration/ServiceRegistrationInfo.cs b/src/Autofac/Core/Registration/ServiceRegistrationInfo.cs index b63efaf6d..7149b065a 100644 --- a/src/Autofac/Core/Registration/ServiceRegistrationInfo.cs +++ b/src/Autofac/Core/Registration/ServiceRegistrationInfo.cs @@ -45,6 +45,8 @@ internal class ServiceRegistrationInfo : IResolvePipelineBuilder [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "The _service field is useful in debugging and diagnostics.")] private readonly Service _service; + private IComponentRegistration? _fixedRegistration = null; + /// /// List of implicit default service implementations. Overriding default implementations are appended to the end, /// so the enumeration should begin from the end too, and the most default implementation comes last. @@ -111,7 +113,15 @@ public IEnumerable Implementations { RequiresInitialization(); - return _registeredImplementations!.Value; + if (_fixedRegistration is object) + { + yield return _fixedRegistration; + } + + foreach (var item in _registeredImplementations!.Value) + { + yield return item; + } } } @@ -170,7 +180,14 @@ RedirectionTargetRegistration is object || /// Whether the registration originated from a dynamic source. public void AddImplementation(IComponentRegistration registration, bool preserveDefaults, bool originatedFromSource) { - if (preserveDefaults) + if (registration.Options.HasOption(RegistrationOptions.Fixed)) + { + if (_fixedRegistration is null || !originatedFromSource) + { + _fixedRegistration = registration; + } + } + else if (preserveDefaults) { if (originatedFromSource) { @@ -218,23 +235,6 @@ public void UseServiceMiddleware(IResolveMiddleware middleware, MiddlewareInsert } _customPipelineBuilder.Use(middleware, insertionMode); - - if (middleware is IRedirectingMiddleware redirectMiddleware) - { - var target = redirectMiddleware.TargetRegistration; - - if (target is null) - { - throw new InvalidOperationException( - string.Format( - CultureInfo.CurrentCulture, - ServiceRegistrationInfoResources.RedirectingMiddlewareHasNullRegistration, - redirectMiddleware.ToString())); - } - - // This middleware provides a redirect registration for the registration. Store it. - RedirectionTargetRegistration = redirectMiddleware.TargetRegistration; - } } /// @@ -266,7 +266,8 @@ public bool TryGetRegistration([NotNullWhen(returnValue: true)] out IComponentRe { RequiresInitialization(); - registration = _defaultImplementation ??= _defaultImplementations.LastOrDefault() ?? + registration = _defaultImplementation ??= _fixedRegistration ?? + _defaultImplementations.LastOrDefault() ?? _sourceImplementations?.First() ?? _preserveDefaultImplementations?.First(); diff --git a/src/Autofac/Core/Registration/ServiceRegistrationInfoResources.Designer.cs b/src/Autofac/Core/Registration/ServiceRegistrationInfoResources.Designer.cs index 872517dde..0c19a8f4a 100644 --- a/src/Autofac/Core/Registration/ServiceRegistrationInfoResources.Designer.cs +++ b/src/Autofac/Core/Registration/ServiceRegistrationInfoResources.Designer.cs @@ -78,15 +78,6 @@ internal static string NotInitialized { } } - /// - /// Looks up a localized string similar to Service middleware '{0}' implements IRedirectingMiddleware but returns a null value for TargetRegistration.. - /// - internal static string RedirectingMiddlewareHasNullRegistration { - get { - return ResourceManager.GetString("RedirectingMiddlewareHasNullRegistration", resourceCulture); - } - } - /// /// Looks up a localized string similar to The service pipeline cannot be built until the service has finished initialization.. /// diff --git a/src/Autofac/Core/Registration/ServiceRegistrationInfoResources.resx b/src/Autofac/Core/Registration/ServiceRegistrationInfoResources.resx index 3e7975afa..53fa2757c 100644 --- a/src/Autofac/Core/Registration/ServiceRegistrationInfoResources.resx +++ b/src/Autofac/Core/Registration/ServiceRegistrationInfoResources.resx @@ -123,9 +123,6 @@ The operation is not valid until the object is initialized. - - Service middleware '{0}' implements IRedirectingMiddleware but returns a null value for TargetRegistration. - The service pipeline cannot be built until the service has finished initialization. diff --git a/src/Autofac/Core/Resolving/Pipeline/IRedirectingMiddleware.cs b/src/Autofac/Core/Resolving/Pipeline/IRedirectingMiddleware.cs deleted file mode 100644 index 1d694e091..000000000 --- a/src/Autofac/Core/Resolving/Pipeline/IRedirectingMiddleware.cs +++ /dev/null @@ -1,51 +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. - -namespace Autofac.Core.Resolving.Pipeline -{ - /// - /// Defines a category of service resolve middleware that can 'redirect' a service to a custom registration. - /// - /// - /// Middleware implementing this interface can 'override' the registration reported for a service when the - /// default registration is requested, but not have that registration reported in the set of concrete registrations for the service. - /// - /// The best use-case for this type of middleware is when you want to add some custom behaviour to a service, and resolve - /// a registration that does not implement that service (but might consume it), e.g. composites. - /// - /// You can also use this mechanism to provide a service that has no 'real' backing registrations, but performs some other behaviour. In this - /// case you still need to supply a target registration, but only for metadata and consistency. - /// - /// A service pipeline containing a will not invoke the registration's pipeline. It is expected that the implementation - /// invokes the required registration pipeline as needed. - /// - public interface IRedirectingMiddleware : IResolveMiddleware - { - /// - /// Gets the target registration of the redirect. - /// - IComponentRegistration TargetRegistration { get; } - } -} diff --git a/src/Autofac/Features/Collections/CollectionRegistrationSource.cs b/src/Autofac/Features/Collections/CollectionRegistrationSource.cs index 6f1aa03bc..8b77b3051 100644 --- a/src/Autofac/Features/Collections/CollectionRegistrationSource.cs +++ b/src/Autofac/Features/Collections/CollectionRegistrationSource.cs @@ -122,6 +122,7 @@ public IEnumerable RegistrationsFor(Service service, Fun { var itemRegistrations = c.ComponentRegistry .ServiceRegistrationsFor(elementTypeService) + .Where(cr => !cr.Registration.Options.HasOption(RegistrationOptions.ExcludeFromCollections)) .OrderBy(cr => cr.Registration.GetRegistrationOrder()) .ToList(); diff --git a/src/Autofac/Features/Decorators/DecoratorMiddleware.cs b/src/Autofac/Features/Decorators/DecoratorMiddleware.cs index 89105a2a8..076ae242e 100644 --- a/src/Autofac/Features/Decorators/DecoratorMiddleware.cs +++ b/src/Autofac/Features/Decorators/DecoratorMiddleware.cs @@ -26,6 +26,7 @@ using System; using System.Linq; using Autofac.Core; +using Autofac.Core.Registration; using Autofac.Core.Resolving.Pipeline; namespace Autofac.Features.Decorators @@ -64,6 +65,12 @@ public void Execute(ResolveRequestContextBase context, Action RegistrationsFor(Service service, Fun .InstancePerLifetimeScope() .ExternallyOwned() .As(service) - .Targeting(r.Registration, IsAdapterForIndividualComponents) + .Targeting(r.Registration) .InheritRegistrationOrderFrom(r.Registration); return rb.CreateRegistration(); diff --git a/src/Autofac/Features/LazyDependencies/LazyWithMetadataRegistrationSource.cs b/src/Autofac/Features/LazyDependencies/LazyWithMetadataRegistrationSource.cs index 3ec23ed62..a9e2c80e3 100644 --- a/src/Autofac/Features/LazyDependencies/LazyWithMetadataRegistrationSource.cs +++ b/src/Autofac/Features/LazyDependencies/LazyWithMetadataRegistrationSource.cs @@ -73,7 +73,7 @@ public IEnumerable RegistrationsFor(Service service, Fun var registrationCreator = _methodCache.GetOrAdd((valueType, metaType), types => { - return CreateLazyRegistrationMethod.MakeGenericMethod(types.ValueType, types.MetaType).CreateDelegate(this); + return CreateLazyRegistrationMethod.MakeGenericMethod(types.ValueType, types.MetaType).CreateDelegate(null); }); return registrationAccessor(valueService) @@ -89,7 +89,7 @@ public override string ToString() return LazyWithMetadataRegistrationSourceResources.LazyWithMetadataRegistrationSourceDescription; } - private IComponentRegistration CreateLazyRegistration(Service providedService, Service valueService, ServiceRegistration registrationResolveInfo) + private static IComponentRegistration CreateLazyRegistration(Service providedService, Service valueService, ServiceRegistration registrationResolveInfo) { var metadataProvider = MetadataViewProvider.GetMetadataViewProvider(); @@ -104,7 +104,7 @@ private IComponentRegistration CreateLazyRegistration(Service provided return Activator.CreateInstance(lazyType, valueFactory, metadata); }) .As(providedService) - .Targeting(registrationResolveInfo.Registration, IsAdapterForIndividualComponents); + .Targeting(registrationResolveInfo.Registration); return rb.CreateRegistration(); } diff --git a/src/Autofac/Features/LightweightAdapters/LightweightAdapterRegistrationSource.cs b/src/Autofac/Features/LightweightAdapters/LightweightAdapterRegistrationSource.cs index 13b253684..6f75457fa 100644 --- a/src/Autofac/Features/LightweightAdapters/LightweightAdapterRegistrationSource.cs +++ b/src/Autofac/Features/LightweightAdapters/LightweightAdapterRegistrationSource.cs @@ -73,7 +73,7 @@ public IEnumerable RegistrationsFor(Service service, Fun var rb = RegistrationBuilder .ForDelegate((c, p) => _activatorData.Adapter( c, Enumerable.Empty(), c.ResolveComponent(new ResolveRequest(_activatorData.FromService, r, p)))) - .Targeting(r.Registration, IsAdapterForIndividualComponents) + .Targeting(r.Registration) .InheritRegistrationOrderFrom(r.Registration); rb.RegistrationData.CopyFrom(_registrationData, true); @@ -102,7 +102,7 @@ public IEnumerable RegistrationsFor(Service service, Fun var rb = RegistrationBuilder .ForDelegate((c, p) => _activatorData.Adapter( c, p, c.ResolveComponent(new ResolveRequest(serviceToFind, r, Enumerable.Empty())))) - .Targeting(r.Registration, IsAdapterForIndividualComponents); + .Targeting(r.Registration); rb.RegistrationData.CopyFrom(_registrationData, true); diff --git a/src/Autofac/Features/Metadata/StronglyTypedMetaRegistrationSource.cs b/src/Autofac/Features/Metadata/StronglyTypedMetaRegistrationSource.cs index 831450d90..2f44e4b82 100644 --- a/src/Autofac/Features/Metadata/StronglyTypedMetaRegistrationSource.cs +++ b/src/Autofac/Features/Metadata/StronglyTypedMetaRegistrationSource.cs @@ -67,7 +67,7 @@ public IEnumerable RegistrationsFor(Service service, Fun var registrationCreator = _methodCache.GetOrAdd((valueType, metaType), t => { - return CreateMetaRegistrationMethod.MakeGenericMethod(t.ValueType, t.MetaType).CreateDelegate(this); + return CreateMetaRegistrationMethod.MakeGenericMethod(t.ValueType, t.MetaType).CreateDelegate(null); }); return registrationAccessor(valueService) @@ -83,7 +83,7 @@ public override string ToString() return MetaRegistrationSourceResources.StronglyTypedMetaRegistrationSourceDescription; } - private IComponentRegistration CreateMetaRegistration(Service providedService, Service valueService, ServiceRegistration implementation) + private static IComponentRegistration CreateMetaRegistration(Service providedService, Service valueService, ServiceRegistration implementation) { var metadataProvider = MetadataViewProvider.GetMetadataViewProvider(); @@ -94,7 +94,7 @@ private IComponentRegistration CreateMetaRegistration(Service prov return new Meta((T)c.ResolveComponent(new ResolveRequest(valueService, implementation, p)), metadata); }) .As(providedService) - .Targeting(implementation.Registration, IsAdapterForIndividualComponents); + .Targeting(implementation.Registration); return rb.CreateRegistration(); } diff --git a/src/Autofac/Features/Variance/ContravariantRegistrationSource.cs b/src/Autofac/Features/Variance/ContravariantRegistrationSource.cs index 50ba82614..4be099e3b 100644 --- a/src/Autofac/Features/Variance/ContravariantRegistrationSource.cs +++ b/src/Autofac/Features/Variance/ContravariantRegistrationSource.cs @@ -108,7 +108,7 @@ public IEnumerable RegistrationsFor( return variantRegistrations .Select(vr => RegistrationBuilder .ForDelegate((c, p) => c.ResolveComponent(new ResolveRequest(service, vr, p))) - .Targeting(vr.Registration, IsAdapterForIndividualComponents) + .Targeting(vr.Registration) .As(service) .WithMetadata(IsContravariantAdapter, true) .CreateRegistration()); diff --git a/src/Autofac/RegistrationExtensions.cs b/src/Autofac/RegistrationExtensions.cs index b553ee586..d5f1dfbf4 100644 --- a/src/Autofac/RegistrationExtensions.cs +++ b/src/Autofac/RegistrationExtensions.cs @@ -966,7 +966,6 @@ public static IRegistrationBuilder /// Registration style. /// Registration to set target for. /// The target. - /// Optional; whether the registration is a 1:1 adapter on top of another component. /// /// Registration builder allowing the registration to be configured. /// @@ -976,15 +975,14 @@ public static IRegistrationBuilder public static IRegistrationBuilder Targeting( this IRegistrationBuilder registration, - IComponentRegistration target, - bool isAdapterForIndividualComponent) + IComponentRegistration target) where TSingleRegistrationStyle : SingleRegistrationStyle { if (registration == null) throw new ArgumentNullException(nameof(registration)); if (target == null) throw new ArgumentNullException(nameof(target)); registration.RegistrationStyle.Target = target.Target; - registration.RegistrationStyle.IsAdapterForIndividualComponent = isAdapterForIndividualComponent; + return registration; } @@ -1689,5 +1687,170 @@ public static IRegistrationBuilder return registration.OnlyIf(reg => !reg.IsRegistered(new TypedService(serviceType))); } + + /// + /// + /// Register a composite type that should always provide the instance of when it is resolved, + /// regardless of what other registrations for are available. + /// + /// + /// Composite registrations are not included when resolving a collection of . + /// + /// + /// + /// Service type of the composite. Must accept a parameter + /// of type , or , + /// which will be set to collection of registered implementations. + /// + /// Service type to provide a composite for. + /// Container builder. + public static IRegistrationBuilder RegisterComposite(this ContainerBuilder builder) + where TComposite : notnull, TService + where TService : notnull + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + var rb = builder.RegisterType().As(); + + ApplyCompositeConfiguration(builder, rb); + + return rb; + } + + /// + /// + /// Register a composite type that should always provide the instance of when it is resolved, + /// regardless of what other registrations for are available. + /// + /// + /// Composite registrations are not included when resolving a collection of . + /// + /// + /// Container builder. + /// + /// Service type of the composite. Must accept a parameter + /// of type , or an array of , + /// which will be set to the collection of registered implementations. + /// + /// Service type to provide a composite for. + public static IRegistrationBuilder RegisterComposite( + this ContainerBuilder builder, + Type compositeType, + Type serviceType) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + var rb = builder.RegisterType(compositeType).As(serviceType); + + ApplyCompositeConfiguration(builder, rb); + + return rb; + } + + /// + /// + /// Register a delegate that should always provide the composite instance of a service type when it is resolved, + /// regardless of what other registrations for are available. + /// + /// + /// Composite registrations are not included when resolving a collection of . + /// + /// + /// Container builder. + /// + /// Callback to create a new instance of the composite, which takes the set of concrete implementations. + /// + /// Service type to provide a composite for. + public static IRegistrationBuilder RegisterComposite( + this ContainerBuilder builder, + Func, IEnumerable, TService> compositeDelegate) + where TService : notnull + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + var rb = builder.Register((ctxt, p) => + { + var concreteItems = ctxt.Resolve>(); + + return compositeDelegate(ctxt, p, concreteItems); + }); + + ApplyCompositeConfiguration(builder, rb); + + return rb; + } + + /// + /// + /// Register a delegate that should always provide the composite instance of a service type when it is resolved, + /// regardless of what other registrations for are available. + /// + /// + /// Composite registrations are not included when resolving a collection of . + /// + /// + /// Container builder. + /// + /// Callback to create a new instance of the composite, which takes the set of concrete implementations. + /// + /// Service type to provide a composite for. + public static IRegistrationBuilder RegisterComposite( + this ContainerBuilder builder, + Func, TService> compositeDelegate) + where TService : notnull + { + return builder.RegisterComposite((ctxt, p, concrete) => compositeDelegate(ctxt, concrete)); + } + + /// + /// + /// Register an un-parameterised generic type, e.g. Composite<> to function as a composite + /// for an open generic service, e.g. IRepository<>. Composites will be made as they are requested, + /// e.g. with Resolve<IRepository<int>>(). + /// + /// + /// Composite registrations are not included when resolving a collection of . + /// + /// + /// Container builder. + /// + /// Service type of the composite. Must accept a parameter + /// of type , or an array of , + /// which will be set to the collection of registered implementations. + /// + /// Service type to provide a composite for. + public static IRegistrationBuilder RegisterGenericComposite( + this ContainerBuilder builder, + Type compositeType, + Type serviceType) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + var rb = builder.RegisterGeneric(compositeType).As(serviceType); + + ApplyCompositeConfiguration(builder, rb); + + return rb; + } + + private static void ApplyCompositeConfiguration(ContainerBuilder builder, IRegistrationBuilder registration) + { + // Set the options for a composite. + registration.RegistrationData.Options |= RegistrationOptions.Composite; + + builder.RegisterCallback(crb => + { + // Validate that we are only behaving as a composite for a single service. + if (registration.RegistrationData.Services.Count() > 1) + { + // Cannot have a multi-service composite. + throw new InvalidOperationException( + string.Format( + CultureInfo.CurrentCulture, + RegistrationExtensionsResources.CompositesCannotProvideMultipleServices, + registration)); + } + }); + } } } diff --git a/src/Autofac/RegistrationExtensionsResources.Designer.cs b/src/Autofac/RegistrationExtensionsResources.Designer.cs index 1b0cd5b59..408e1a9f9 100644 --- a/src/Autofac/RegistrationExtensionsResources.Designer.cs +++ b/src/Autofac/RegistrationExtensionsResources.Designer.cs @@ -19,7 +19,7 @@ namespace Autofac { // 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", "4.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 RegistrationExtensionsResources { @@ -60,6 +60,15 @@ internal RegistrationExtensionsResources() { } } + /// + /// Looks up a localized string similar to A composite registration ({0}) is registered against multiple services. Composites can only be registered as providing a single service.. + /// + internal static string CompositesCannotProvideMultipleServices { + get { + return ResourceManager.GetString("CompositesCannotProvideMultipleServices", resourceCulture); + } + } + /// /// Looks up a localized string similar to A decorator for '{0}' was not provided with an instance parameter.. /// @@ -70,7 +79,7 @@ internal static string DecoratorRequiresInstanceParameter { } /// - /// Looks up a localized string similar to Expression does not refer to a property. + /// Looks up a localized string similar to Expression does not refer to a property.. /// internal static string ExpressionDoesNotReferToProperty { get { diff --git a/src/Autofac/RegistrationExtensionsResources.resx b/src/Autofac/RegistrationExtensionsResources.resx index 04f31b154..095b9bb8c 100644 --- a/src/Autofac/RegistrationExtensionsResources.resx +++ b/src/Autofac/RegistrationExtensionsResources.resx @@ -1,17 +1,17 @@  - @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + A composite registration ({0}) is registered against multiple services. Composites can only be registered as providing a single service. + A decorator for '{0}' was not provided with an instance parameter. @@ -138,4 +141,4 @@ You can only attach a registration predicate to a registration that has a callback container attached (e.g., one that was made with a standard ContainerBuilder extension method). - + \ No newline at end of file diff --git a/src/Autofac/Util/ReflectionExtensions.cs b/src/Autofac/Util/ReflectionExtensions.cs index a28a1ae62..9b2969fe7 100644 --- a/src/Autofac/Util/ReflectionExtensions.cs +++ b/src/Autofac/Util/ReflectionExtensions.cs @@ -43,7 +43,7 @@ internal static class ReflectionExtensions /// The method. /// The target object for the delegate. /// A constructed delegate. - public static TDelegate CreateDelegate(this MethodInfo method, object target) + public static TDelegate CreateDelegate(this MethodInfo method, object? target) where TDelegate : Delegate => (TDelegate)method.CreateDelegate(typeof(TDelegate), target); diff --git a/test/Autofac.Specification.Test/Features/CompositeTests.cs b/test/Autofac.Specification.Test/Features/CompositeTests.cs new file mode 100644 index 000000000..6acc1490f --- /dev/null +++ b/test/Autofac.Specification.Test/Features/CompositeTests.cs @@ -0,0 +1,732 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Autofac.Builder; +using Autofac.Core; +using Autofac.Core.Registration; +using Autofac.Features.Metadata; +using Autofac.Features.OwnedInstances; +using Autofac.Specification.Test.Lifetime; +using Autofac.Specification.Test.Util; +using Xunit; + +namespace Autofac.Specification.Test.Features +{ + public class CompositeTests + { + [Fact] + public void CanRegisterComposite() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite(); + + var container = builder.Build(); + + var comp = container.Resolve(); + + Assert.IsType(comp); + + var actualComp = (MyComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i)); + } + + [Fact] + public void CompositeRegistrationOrderIrrelevant() + { + var builder = new ContainerBuilder(); + + builder.Register(ctx => new S1()).As(); + builder.RegisterComposite(); + builder.Register(ctx => new S2()).As(); + + var container = builder.Build(); + + var comp = container.Resolve(); + + Assert.IsType(comp); + + var actualComp = (MyComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i)); + } + + [Fact] + public void CanRegisterCompositeWithDirectType() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite(typeof(MyComposite), typeof(I1)); + + var container = builder.Build(); + + var comp = container.Resolve(); + + Assert.IsType(comp); + + var actualComp = (MyComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i)); + } + + [Fact] + public void CanRegisterCompositeWithDelegate() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite((ctxt, concrete) => new MyComposite(concrete)); + + var container = builder.Build(); + + var comp = container.Resolve(); + + Assert.IsType(comp); + + var actualComp = (MyComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i)); + } + + [Fact] + public void CanRegisterGenericComposite() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new GenericImplInt()).As>(); + builder.Register(ctx => new GenericImplInt2()).As>(); + builder.RegisterGeneric(typeof(OpenGenericImpl<>)).As(typeof(IGenericService<>)); + + builder.RegisterGenericComposite(typeof(GenericComposite<>), typeof(IGenericService<>)); + + var container = builder.Build(); + + var comp = container.Resolve>(); + + Assert.IsType>(comp); + + var actualComp = (GenericComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i), + i => Assert.IsType>(i)); + } + + [Fact] + public void CanRegisterCompositeInNestedScope() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + var container = builder.Build(); + + Assert.IsType(container.Resolve()); + + var nested = container.BeginLifetimeScope(cfg => + { + cfg.RegisterComposite(); + }); + + var comp = nested.Resolve(); + + Assert.IsType(comp); + + var actualComp = (MyComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i)); + + // Check that outer scope is still composite-less. + Assert.IsType(container.Resolve()); + } + + [Fact] + public void CompositeForNamedScopeThrowsExceptionIfServiceResolvedInDifferentScope() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + var activatedCount = 0; + + builder.RegisterComposite() + .InstancePerMatchingLifetimeScope("tag1") + .OnActivated(args => activatedCount++); + + var container = builder.Build(); + + var nested = container.BeginLifetimeScope(); + + Assert.Throws(() => nested.Resolve()); + } + + [Fact] + public void CompositeForNamedScopeResolvedInNestedScope() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + var activatedCount = 0; + + builder.RegisterComposite() + .InstancePerMatchingLifetimeScope("tag1") + .OnActivated(args => activatedCount++); + + var container = builder.Build(); + + var nested = container.BeginLifetimeScope("tag1").BeginLifetimeScope(cfg => { }); + + Assert.IsType(nested.Resolve()); + } + + [Fact] + public void AdditionalRegistrationsInNestedScopeAreIncludedInComposite() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite(); + + var container = builder.Build(); + + var nested = container.BeginLifetimeScope(cfg => + { + cfg.Register(ctx => new S3()).As(); + }); + + var comp = nested.Resolve(); + + Assert.IsType(comp); + + var actualComp = (MyComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i), + i => Assert.IsType(i)); + } + + [Fact] + public void CompositeWithCircularDependencyThrows() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite(); + + var container = builder.Build(); + + Assert.Throws(() => container.Resolve()); + } + + [Fact] + public void CompositeCanAccessMetaOfRegistrations() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite>, I1>(); + + var container = builder.Build(); + + var comp = container.Resolve(); + + Assert.IsType>>(comp); + + var actualComp = (MyComposite>)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i.Value), + i => Assert.IsType(i.Value)); + } + + [Fact] + public void CompositeCanNestAdaptersOnResolvedImplementations() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite>>, I1>(); + + var container = builder.Build(); + + var comp = container.Resolve(); + + Assert.IsType>>>(comp); + + var actualComp = (MyComposite>>)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i().Value), + i => Assert.IsType(i().Value)); + } + + [Fact] + public void CompositeCanHaveOwnMeta() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite() + .WithMetadata("data", "value"); + + var container = builder.Build(); + + var comp = container.Resolve>(); + + Assert.IsType(comp.Value); + + Assert.Equal("value", comp.Metadata["data"]); + } + + [Fact] + public void CompositeCanBeResolveByFunc() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite(); + + var container = builder.Build(); + + var comp = container.Resolve>(); + + Assert.IsType(comp()); + } + + [Fact] + public void CompositeCanBeResolvedAsOwned() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite(); + + var container = builder.Build(); + + var comp = container.Resolve>(); + + Assert.IsType(comp.Value); + } + + [Fact] + public void CompositeAsOwnedDependenciesDisposed() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + var didDisposeTracker = 0; + + builder.RegisterType().OnActivated(args => args.Instance.Disposing += (s, e) => didDisposeTracker++); + + builder.RegisterComposite(); + + var container = builder.Build(); + + var comp = container.Resolve>(); + + comp.Dispose(); + + Assert.Equal(1, didDisposeTracker); + } + + [Fact] + public void CompositeCanLazyResolveIndividualRegistrations() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite(); + + var container = builder.Build(); + + var comp = (MyLazyComposite)container.Resolve(); + + Assert.Equal(2, comp.Implementations.Value.Count()); + } + + [Fact] + public void CompositeCanHaveOwnLifetime() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + var activatedCount = 0; + + builder.RegisterComposite() + .OnActivated(args => + { + activatedCount++; + }) + .SingleInstance(); + + var container = builder.Build(); + + var comp = container.Resolve(); + + Assert.IsType(comp); + + comp = container.Resolve(); + + Assert.Equal(1, activatedCount); + } + + [Fact] + public void CompositeWithSingletonLifetimeIgnoresRegistrationsInChildScopes() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite() + .SingleInstance(); + + var container = builder.Build(); + + var nested = container.BeginLifetimeScope(cfg => cfg.Register(ctxt => new S3()).As()); + + var comp = (MyComposite)nested.Resolve(); + + Assert.Equal(2, comp.Implementations.Count); + } + + [Fact] + public void CompositeCannotBeCompositeForMultipleServices() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite().As(); + + Assert.Throws(() => builder.Build()); + } + + [Fact] + public void CompositeIsNotDecoratedButConcreteImplementationsAre() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + builder.RegisterDecorator(); + + builder.RegisterComposite((ctxt, concrete) => new MyComposite(concrete)); + + var container = builder.Build(); + + var comp = container.Resolve(); + + Assert.IsType(comp); + + var actualComp = (MyComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(Assert.IsType(i).Instance), + i => Assert.IsType(Assert.IsType(i).Instance)); + } + + [Fact] + public void SecondCompositeForSameServiceOverridesFirst() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite(); + + builder.RegisterComposite(); + + var container = builder.Build(); + + var comp = container.Resolve(); + + Assert.IsType(comp); + + var actualComp = (MyCompositeTheSecond)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i)); + } + + [Fact] + public void SecondCompositeForSameServiceInNestedScopeOverridesFirst() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + + builder.RegisterComposite(); + + var container = builder.Build(); + + var nested = container.BeginLifetimeScope(cfg => cfg.RegisterComposite()); + + var comp = nested.Resolve(); + + Assert.IsType(comp); + + var actualComp = (MyCompositeTheSecond)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i)); + } + + [Fact] + public void RegistrationSourceCanProvideComposite() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + builder.RegisterSource(new CompositeSupplierSource()); + + var container = builder.Build(); + + var comp = container.Resolve(); + + Assert.IsType(comp); + + var actualComp = (MyComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i)); + } + + [Fact] + public void RegistrationSourceCanProvideCompositeInNestedScope() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + builder.RegisterSource(new CompositeSupplierSource()); + + var container = builder.Build(); + + var comp = container.BeginLifetimeScope(cfg => { }).Resolve(); + + Assert.IsType(comp); + + var actualComp = (MyComposite)comp; + + Assert.Collection( + actualComp.Implementations, + i => Assert.IsType(i), + i => Assert.IsType(i)); + } + + [Fact] + public void ManualCompositeOverridesSourcedComposite() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + builder.RegisterSource(new CompositeSupplierSource()); + + builder.RegisterComposite(); + + var container = builder.Build(); + + var comp = container.Resolve(); + + Assert.IsType(comp); + } + + [Fact] + public void SourceInNestedScopeOverridesManualComposite() + { + var builder = new ContainerBuilder(); + builder.Register(ctx => new S1()).As(); + builder.Register(ctx => new S2()).As(); + builder.RegisterComposite(); + + var container = builder.Build(); + + var comp = container.BeginLifetimeScope(cfg => cfg.RegisterSource(new CompositeSupplierSource())).Resolve(); + + Assert.IsType(comp); + } + + private class CompositeSupplierSource : IRegistrationSource + { + public bool IsAdapterForIndividualComponents => true; + + public IEnumerable RegistrationsFor(Service service, Func> registrationAccessor) + { + if (service is IServiceWithType swt && swt.ServiceType == typeof(I1)) + { + var rb = RegistrationBuilder.ForType().As(); + + rb.RegistrationData.Options |= RegistrationOptions.Composite; + + yield return rb.CreateRegistration(); + } + } + } + + private class MyComposite : MyComposite, I1 + { + public MyComposite(IEnumerable implementations) + : base(implementations.ToList()) + { + } + } + + private class MyCompositeNeedsDisposeTracker : MyComposite, I1 + { + public MyCompositeNeedsDisposeTracker(DisposeTracker tracker, IEnumerable implementations) + : base(implementations.ToList()) + { + } + } + + private class MyCompositeTheSecond : MyComposite + { + public MyCompositeTheSecond(IEnumerable implementations) + : base(implementations.ToList()) + { + } + } + + private class MyComposite : I1 + { + public MyComposite(IList implementations) + { + Implementations = implementations; + } + + public IList Implementations { get; } + } + + private class MultiComposite : I1, I2 + { + public MultiComposite(IEnumerable composite1, IEnumerable composite2) + { + Composite1 = composite1; + Composite2 = composite2; + } + + public IEnumerable Composite1 { get; } + + public IEnumerable Composite2 { get; } + } + + private class MyLazyComposite : I1 + { + public MyLazyComposite(Lazy> implementations) + { + Implementations = implementations; + } + + public Lazy> Implementations { get; } + } + + private class CircularComposite : I1 + { + public CircularComposite(I1 circular) + { + } + } + + private class DecoratorForI1 : I1 + { + public DecoratorForI1(I1 instance) + { + Instance = instance; + } + + public I1 Instance { get; } + } + + private interface IGenericService + { + } + + private class GenericImplInt : IGenericService + { + } + + private class GenericImplInt2 : IGenericService + { + } + + private class OpenGenericImpl : IGenericService + { + } + + private class GenericComposite : IGenericService + { + public GenericComposite(IList> implementations) + { + Implementations = implementations; + } + + public IList> Implementations { get; } + } + + private interface I1 + { + } + + private interface I2 + { + } + + private class S1 : I1 + { + } + + private class S2 : I1 + { + } + + private class S3 : I1 + { + } + + private class S4 : I1 + { + } + } +} diff --git a/test/Autofac.Specification.Test/Registration/ModuleRegistrationTests.cs b/test/Autofac.Specification.Test/Registration/ModuleRegistrationTests.cs index 32809db6b..432c851a7 100644 --- a/test/Autofac.Specification.Test/Registration/ModuleRegistrationTests.cs +++ b/test/Autofac.Specification.Test/Registration/ModuleRegistrationTests.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Reflection; using Autofac.Test.Scenarios.ScannedAssembly; using Xunit; @@ -101,6 +103,28 @@ public void RegisterModule() Assert.True(container.IsRegistered()); } + [Fact] + public void ModuleCanContainDecorators() + { + var mod = new DecoratingModule(); + var target = new ContainerBuilder(); + target.RegisterType().As(); + target.RegisterModule(mod); + var container = target.Build(); + Assert.IsType(container.Resolve()); + } + + [Fact] + public void ModuleCanContainComposites() + { + var mod = new CompositingModule(); + var target = new ContainerBuilder(); + target.RegisterType().As(); + target.RegisterModule(mod); + var container = target.Build(); + Assert.IsType(container.Resolve()); + } + private class Module1 : Module { protected override void Load(ContainerBuilder builder) @@ -117,6 +141,44 @@ protected override void Load(ContainerBuilder builder) } } + private class DecoratingModule : Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterDecorator(); + } + } + + private class CompositingModule : Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterComposite(); + } + } + + private interface I1 + { + } + + private class S1 : I1 + { + } + + private class Decorator1 : I1 + { + public Decorator1(I1 instance) + { + } + } + + private class Composite1 : I1 + { + public Composite1(IEnumerable instance) + { + } + } + internal class ObjectModule : Module { public bool ConfigureCalled { get; private set; } diff --git a/test/Autofac.Test/Mocks.cs b/test/Autofac.Test/Mocks.cs index 86a5393de..5d3a8b598 100644 --- a/test/Autofac.Test/Mocks.cs +++ b/test/Autofac.Test/Mocks.cs @@ -4,6 +4,7 @@ using Autofac.Core; using Autofac.Core.Activators.Reflection; using Autofac.Core.Diagnostics; +using Autofac.Core.Registration; using Autofac.Core.Resolving; using Autofac.Core.Resolving.Middleware; using Autofac.Core.Resolving.Pipeline; @@ -93,6 +94,10 @@ public void Dispose() public IResolvePipeline ResolvePipeline { get; } = new ResolvePipelineBuilder(PipelineType.Registration).Build(); + public bool IsServiceOverride { get; set; } + + public RegistrationOptions Options { get; set; } + public void BuildResolvePipeline(IComponentRegistryServices registryServices) { PipelineBuilding?.Invoke(this, new ResolvePipelineBuilder(PipelineType.Registration));