From 93e158bcb73f2f172de4e31edca532f1023bc64e Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 18 Aug 2023 12:34:34 +0200 Subject: [PATCH 01/22] Whitespace and line break tweaks --- src/Scrutor/AttributeSelector.cs | 2 +- src/Scrutor/ClosedTypeDecorationStrategy.cs | 2 +- src/Scrutor/DecorationException.cs | 2 +- src/Scrutor/DecorationStrategy.cs | 14 +++++++------- src/Scrutor/IAssemblySelector.cs | 2 +- src/Scrutor/IFluentInterface.cs | 2 +- src/Scrutor/IImplementationTypeFilter.cs | 2 +- src/Scrutor/IImplementationTypeSelector.cs | 2 +- src/Scrutor/ILifetimeSelector.cs | 2 +- src/Scrutor/ISelector.cs | 2 +- src/Scrutor/ITypeSourceSelector.cs | 2 +- src/Scrutor/ImplementationTypeFilter.cs | 2 +- src/Scrutor/ImplementationTypeSelector.cs | 2 +- src/Scrutor/MissingTypeRegistrationException.cs | 2 +- src/Scrutor/Preconditions.cs | 2 +- src/Scrutor/ReflectionExtensions.cs | 2 +- src/Scrutor/RegistrationStrategy.cs | 2 +- src/Scrutor/ReplacementBehavior.cs | 2 +- .../ServiceCollectionExtensions.Decoration.cs | 8 ++++---- src/Scrutor/ServiceCollectionExtensions.cs | 2 +- src/Scrutor/ServiceDescriptorExtensions.cs | 2 +- src/Scrutor/TypeFactoryMap.cs | 2 +- src/Scrutor/TypeMap.cs | 2 +- test/Scrutor.Tests/ScanningTests.cs | 2 +- test/Scrutor.Tests/ServiceCollectionExtensions.cs | 2 +- test/Scrutor.Tests/TestBase.cs | 2 +- 26 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/Scrutor/AttributeSelector.cs b/src/Scrutor/AttributeSelector.cs index ed142d9d..1533a9ec 100644 --- a/src/Scrutor/AttributeSelector.cs +++ b/src/Scrutor/AttributeSelector.cs @@ -49,4 +49,4 @@ private static IEnumerable GetDuplicates(IEnumerable { return attributes.GroupBy(s => s.ServiceType).SelectMany(grp => grp.Skip(1)); } -} \ No newline at end of file +} diff --git a/src/Scrutor/ClosedTypeDecorationStrategy.cs b/src/Scrutor/ClosedTypeDecorationStrategy.cs index 2aa89f51..c2076ff9 100644 --- a/src/Scrutor/ClosedTypeDecorationStrategy.cs +++ b/src/Scrutor/ClosedTypeDecorationStrategy.cs @@ -29,5 +29,5 @@ public override Func CreateDecorator(Type serviceType) } throw new InvalidOperationException($"Both serviceType and decoratorFactory can not be null."); - } + } } diff --git a/src/Scrutor/DecorationException.cs b/src/Scrutor/DecorationException.cs index c3fd73b6..8f96427a 100644 --- a/src/Scrutor/DecorationException.cs +++ b/src/Scrutor/DecorationException.cs @@ -6,6 +6,6 @@ public DecorationException(DecorationStrategy strategy) : base(strategy.ServiceT { Strategy = strategy; } - + public DecorationStrategy Strategy { get; } } diff --git a/src/Scrutor/DecorationStrategy.cs b/src/Scrutor/DecorationStrategy.cs index 5d806eaa..ff6340b4 100644 --- a/src/Scrutor/DecorationStrategy.cs +++ b/src/Scrutor/DecorationStrategy.cs @@ -9,19 +9,19 @@ protected DecorationStrategy(Type serviceType) { ServiceType = serviceType; } - + public Type ServiceType { get; } - + public abstract bool CanDecorate(Type serviceType); - + public abstract Func CreateDecorator(Type serviceType); - - internal static DecorationStrategy WithType(Type serviceType, Type decoratorType) => + + internal static DecorationStrategy WithType(Type serviceType, Type decoratorType) => Create(serviceType, decoratorType, decoratorFactory: null); - internal static DecorationStrategy WithFactory(Type serviceType, Func decoratorFactory) => + internal static DecorationStrategy WithFactory(Type serviceType, Func decoratorFactory) => Create(serviceType, decoratorType: null, decoratorFactory); - + protected static Func TypeDecorator(Type serviceType, Type decoratorType) => serviceProvider => { var instanceToDecorate = serviceProvider.GetRequiredService(serviceType); diff --git a/src/Scrutor/IAssemblySelector.cs b/src/Scrutor/IAssemblySelector.cs index 2f26f54b..9624da56 100644 --- a/src/Scrutor/IAssemblySelector.cs +++ b/src/Scrutor/IAssemblySelector.cs @@ -95,4 +95,4 @@ public interface IAssemblySelector : IFluentInterface /// The assemblies to should be scanned. /// If the argument is null. IImplementationTypeSelector FromAssemblies(IEnumerable assemblies); -} \ No newline at end of file +} diff --git a/src/Scrutor/IFluentInterface.cs b/src/Scrutor/IFluentInterface.cs index f2af6e73..6604ba06 100644 --- a/src/Scrutor/IFluentInterface.cs +++ b/src/Scrutor/IFluentInterface.cs @@ -17,4 +17,4 @@ public interface IFluentInterface [EditorBrowsable(EditorBrowsableState.Never)] bool Equals(object? obj); -} \ No newline at end of file +} diff --git a/src/Scrutor/IImplementationTypeFilter.cs b/src/Scrutor/IImplementationTypeFilter.cs index c5b14ef8..15458765 100644 --- a/src/Scrutor/IImplementationTypeFilter.cs +++ b/src/Scrutor/IImplementationTypeFilter.cs @@ -155,4 +155,4 @@ public interface IImplementationTypeFilter : IFluentInterface /// The predicate to match types. /// If the argument is null. IImplementationTypeFilter Where(Func predicate); -} \ No newline at end of file +} diff --git a/src/Scrutor/IImplementationTypeSelector.cs b/src/Scrutor/IImplementationTypeSelector.cs index 785021e5..67b7fbc2 100644 --- a/src/Scrutor/IImplementationTypeSelector.cs +++ b/src/Scrutor/IImplementationTypeSelector.cs @@ -33,4 +33,4 @@ public interface IImplementationTypeSelector : IAssemblySelector /// If the argument is null. /// Specifies whether too add public types only. IServiceTypeSelector AddClasses(Action action, bool publicOnly); -} \ No newline at end of file +} diff --git a/src/Scrutor/ILifetimeSelector.cs b/src/Scrutor/ILifetimeSelector.cs index 50cad1d9..344424b1 100644 --- a/src/Scrutor/ILifetimeSelector.cs +++ b/src/Scrutor/ILifetimeSelector.cs @@ -23,4 +23,4 @@ public interface ILifetimeSelector : IServiceTypeSelector /// Registers each matching concrete type with the specified . /// IImplementationTypeSelector WithLifetime(ServiceLifetime lifetime); -} \ No newline at end of file +} diff --git a/src/Scrutor/ISelector.cs b/src/Scrutor/ISelector.cs index 39f4be70..62da015f 100644 --- a/src/Scrutor/ISelector.cs +++ b/src/Scrutor/ISelector.cs @@ -5,4 +5,4 @@ namespace Scrutor; public interface ISelector { void Populate(IServiceCollection services, RegistrationStrategy? options); -} \ No newline at end of file +} diff --git a/src/Scrutor/ITypeSourceSelector.cs b/src/Scrutor/ITypeSourceSelector.cs index 22b338fe..84ce9756 100644 --- a/src/Scrutor/ITypeSourceSelector.cs +++ b/src/Scrutor/ITypeSourceSelector.cs @@ -2,4 +2,4 @@ namespace Scrutor; public interface ITypeSourceSelector : IAssemblySelector, ITypeSelector { -} \ No newline at end of file +} diff --git a/src/Scrutor/ImplementationTypeFilter.cs b/src/Scrutor/ImplementationTypeFilter.cs index 54e5af61..85024341 100644 --- a/src/Scrutor/ImplementationTypeFilter.cs +++ b/src/Scrutor/ImplementationTypeFilter.cs @@ -154,4 +154,4 @@ public IImplementationTypeFilter Where(Func predicate) Types = Types.Where(predicate); return this; } -} \ No newline at end of file +} diff --git a/src/Scrutor/ImplementationTypeSelector.cs b/src/Scrutor/ImplementationTypeSelector.cs index d53220a9..952896a2 100644 --- a/src/Scrutor/ImplementationTypeSelector.cs +++ b/src/Scrutor/ImplementationTypeSelector.cs @@ -146,4 +146,4 @@ private IEnumerable GetNonAbstractClasses(bool publicOnly) { return Types.Where(t => t.IsNonAbstractClass(publicOnly)); } -} \ No newline at end of file +} diff --git a/src/Scrutor/MissingTypeRegistrationException.cs b/src/Scrutor/MissingTypeRegistrationException.cs index 385f056d..7bbd69c7 100644 --- a/src/Scrutor/MissingTypeRegistrationException.cs +++ b/src/Scrutor/MissingTypeRegistrationException.cs @@ -11,4 +11,4 @@ public MissingTypeRegistrationException(Type serviceType) } public Type ServiceType { get; } -} \ No newline at end of file +} diff --git a/src/Scrutor/Preconditions.cs b/src/Scrutor/Preconditions.cs index ae969125..24c759bf 100644 --- a/src/Scrutor/Preconditions.cs +++ b/src/Scrutor/Preconditions.cs @@ -52,4 +52,4 @@ public static TEnum IsDefined(TEnum value, [InvokerParameterName] string return value; } -} \ No newline at end of file +} diff --git a/src/Scrutor/ReflectionExtensions.cs b/src/Scrutor/ReflectionExtensions.cs index c34d1f8d..210925e4 100644 --- a/src/Scrutor/ReflectionExtensions.cs +++ b/src/Scrutor/ReflectionExtensions.cs @@ -245,7 +245,7 @@ public static bool HasMatchingGenericArity(this Type interfaceType, Type type) return true; } - + public static bool HasCompatibleGenericArguments(this Type type, Type genericTypeDefinition) { var genericArguments = type.GetGenericArguments(); diff --git a/src/Scrutor/RegistrationStrategy.cs b/src/Scrutor/RegistrationStrategy.cs index 48943849..d9625893 100644 --- a/src/Scrutor/RegistrationStrategy.cs +++ b/src/Scrutor/RegistrationStrategy.cs @@ -110,4 +110,4 @@ public override void Apply(IServiceCollection services, ServiceDescriptor descri services.Add(descriptor); } } -} \ No newline at end of file +} diff --git a/src/Scrutor/ReplacementBehavior.cs b/src/Scrutor/ReplacementBehavior.cs index 23aaff4f..caf24de8 100644 --- a/src/Scrutor/ReplacementBehavior.cs +++ b/src/Scrutor/ReplacementBehavior.cs @@ -24,4 +24,4 @@ public enum ReplacementBehavior /// Replace existing services by either service- or implementation type. /// All = ServiceType | ImplementationType -} \ No newline at end of file +} diff --git a/src/Scrutor/ServiceCollectionExtensions.Decoration.cs b/src/Scrutor/ServiceCollectionExtensions.Decoration.cs index 55375e5f..8d081744 100644 --- a/src/Scrutor/ServiceCollectionExtensions.Decoration.cs +++ b/src/Scrutor/ServiceCollectionExtensions.Decoration.cs @@ -73,7 +73,7 @@ public static bool TryDecorate(this IServiceCollection services, Type serviceTyp return services.TryDecorate(DecorationStrategy.WithType(serviceType, decoratorType)); } - + /// /// Decorates all registered services of type /// using the function. @@ -123,7 +123,7 @@ public static IServiceCollection Decorate(this IServiceCollection serv { Preconditions.NotNull(services, nameof(services)); Preconditions.NotNull(decorator, nameof(decorator)); - + return services.Decorate(typeof(TService), (service, provider) => decorator((TService)service, provider)); } @@ -140,10 +140,10 @@ public static bool TryDecorate(this IServiceCollection services, Func< { Preconditions.NotNull(services, nameof(services)); Preconditions.NotNull(decorator, nameof(decorator)); - + return services.TryDecorate(typeof(TService), (service, provider) => decorator((TService)service, provider)); } - + /// /// Decorates all registered services of the specified /// using the function. diff --git a/src/Scrutor/ServiceCollectionExtensions.cs b/src/Scrutor/ServiceCollectionExtensions.cs index d4002c61..e0ddda00 100644 --- a/src/Scrutor/ServiceCollectionExtensions.cs +++ b/src/Scrutor/ServiceCollectionExtensions.cs @@ -10,4 +10,4 @@ public static bool HasRegistration(this IServiceCollection services, Type servic { return services.Any(x => x.ServiceType == serviceType); } -} \ No newline at end of file +} diff --git a/src/Scrutor/ServiceDescriptorExtensions.cs b/src/Scrutor/ServiceDescriptorExtensions.cs index f734fe1d..57d2aac9 100644 --- a/src/Scrutor/ServiceDescriptorExtensions.cs +++ b/src/Scrutor/ServiceDescriptorExtensions.cs @@ -5,7 +5,7 @@ namespace Scrutor; internal static class ServiceDescriptorExtensions { - public static ServiceDescriptor WithImplementationFactory(this ServiceDescriptor descriptor, Func implementationFactory) => + public static ServiceDescriptor WithImplementationFactory(this ServiceDescriptor descriptor, Func implementationFactory) => new(descriptor.ServiceType, implementationFactory, descriptor.Lifetime); public static ServiceDescriptor WithServiceType(this ServiceDescriptor descriptor, Type serviceType) => descriptor switch diff --git a/src/Scrutor/TypeFactoryMap.cs b/src/Scrutor/TypeFactoryMap.cs index 6c81bbc2..d26e0273 100644 --- a/src/Scrutor/TypeFactoryMap.cs +++ b/src/Scrutor/TypeFactoryMap.cs @@ -14,4 +14,4 @@ public TypeFactoryMap(Func implementationFactory, IEnu public Func ImplementationFactory { get; } public IEnumerable ServiceTypes { get; } -} \ No newline at end of file +} diff --git a/src/Scrutor/TypeMap.cs b/src/Scrutor/TypeMap.cs index b63115b2..9e7998d2 100644 --- a/src/Scrutor/TypeMap.cs +++ b/src/Scrutor/TypeMap.cs @@ -14,4 +14,4 @@ public TypeMap(Type implementationType, IEnumerable serviceTypes) public Type ImplementationType { get; } public IEnumerable ServiceTypes { get; } -} \ No newline at end of file +} diff --git a/test/Scrutor.Tests/ScanningTests.cs b/test/Scrutor.Tests/ScanningTests.cs index 5cd05f21..34e3ca2c 100644 --- a/test/Scrutor.Tests/ScanningTests.cs +++ b/test/Scrutor.Tests/ScanningTests.cs @@ -611,7 +611,7 @@ public interface IOtherInheritance { } [ServiceDescriptor(typeof(IDuplicateInheritance))] [ServiceDescriptor(typeof(IDuplicateInheritance))] public class DuplicateInheritance : IDuplicateInheritance, IOtherInheritance { } - + public interface IDefault1 { } public interface IDefault2 { } diff --git a/test/Scrutor.Tests/ServiceCollectionExtensions.cs b/test/Scrutor.Tests/ServiceCollectionExtensions.cs index 58c5cf14..6a6cb841 100644 --- a/test/Scrutor.Tests/ServiceCollectionExtensions.cs +++ b/test/Scrutor.Tests/ServiceCollectionExtensions.cs @@ -20,4 +20,4 @@ public static ServiceDescriptor[] GetDescriptors(this IServiceCollection service { return services.Where(x => x.ServiceType == serviceType).ToArray(); } -} \ No newline at end of file +} diff --git a/test/Scrutor.Tests/TestBase.cs b/test/Scrutor.Tests/TestBase.cs index ca2972a1..c626e214 100644 --- a/test/Scrutor.Tests/TestBase.cs +++ b/test/Scrutor.Tests/TestBase.cs @@ -13,4 +13,4 @@ protected static ServiceProvider ConfigureProvider(Action co return services.BuildServiceProvider(); } -} \ No newline at end of file +} From ade71de58d7995f175a8a98819b02b48b9430524 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 18 Aug 2023 12:35:36 +0200 Subject: [PATCH 02/22] Bump target framework versions to match MS.Ext.DI --- NuGet.Config | 1 + src/Scrutor/Scrutor.csproj | 15 +++++---------- src/Scrutor/TypeSourceSelector.cs | 8 +++++++- test/Scrutor.Tests/ScanningTests.cs | 4 ++-- test/Scrutor.Tests/Scrutor.Tests.csproj | 19 ++++++------------- 5 files changed, 21 insertions(+), 26 deletions(-) diff --git a/NuGet.Config b/NuGet.Config index 05577e9b..dbf0180d 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -3,5 +3,6 @@ + diff --git a/src/Scrutor/Scrutor.csproj b/src/Scrutor/Scrutor.csproj index bf6bb1de..27601c18 100644 --- a/src/Scrutor/Scrutor.csproj +++ b/src/Scrutor/Scrutor.csproj @@ -3,7 +3,7 @@ Register services using assembly scanning and a fluent API. 4.2.2 Kristian Hellang - net461;netstandard2.0;netcoreapp3.1;net6.0 + net462;netstandard2.0;net8.0 $(NoWarn);CS1591 11.0 enable @@ -27,15 +27,10 @@ - + - - - - - - - - + + + diff --git a/src/Scrutor/TypeSourceSelector.cs b/src/Scrutor/TypeSourceSelector.cs index 4443ba9d..db226fbc 100644 --- a/src/Scrutor/TypeSourceSelector.cs +++ b/src/Scrutor/TypeSourceSelector.cs @@ -44,7 +44,13 @@ public IImplementationTypeSelector FromApplicationDependencies(Func t.AssignableTo()) .UsingAttributes()); - Assert.Equal(1, Collection.Count); + Assert.Single(Collection); var service = Collection.GetDescriptor(); @@ -266,7 +266,7 @@ public void CanFilterGenericAttributeTypes() .AddClasses(t => t.AssignableTo()) .UsingAttributes()); - Assert.Equal(1, Collection.Count); + Assert.Single(Collection); var service = Collection.GetDescriptor(); diff --git a/test/Scrutor.Tests/Scrutor.Tests.csproj b/test/Scrutor.Tests/Scrutor.Tests.csproj index 95a1afd4..fdfdef19 100644 --- a/test/Scrutor.Tests/Scrutor.Tests.csproj +++ b/test/Scrutor.Tests/Scrutor.Tests.csproj @@ -1,6 +1,6 @@  - netcoreapp3.1;net7.0 + net8.0 11.0 @@ -9,17 +9,10 @@ - - - - - - - - - - - - + + + + + From 84c456eb0d844de3ce8c5eb816cb92b1106b55ba Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 18 Aug 2023 14:04:19 +0200 Subject: [PATCH 03/22] Validate scopes on build in tests --- test/Scrutor.Tests/TestBase.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/Scrutor.Tests/TestBase.cs b/test/Scrutor.Tests/TestBase.cs index c626e214..74ee08ac 100644 --- a/test/Scrutor.Tests/TestBase.cs +++ b/test/Scrutor.Tests/TestBase.cs @@ -11,6 +11,10 @@ protected static ServiceProvider ConfigureProvider(Action co configure(services); - return services.BuildServiceProvider(); + return services.BuildServiceProvider(new ServiceProviderOptions + { + ValidateOnBuild = true, + ValidateScopes = true, + }); } } From 3cf6f7db26819fec49f3116e3822ab2a3eb827f2 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 18 Aug 2023 14:04:43 +0200 Subject: [PATCH 04/22] Leverage keyed services for decoration instead of DecoratedType hack --- src/Scrutor/ClosedTypeDecorationStrategy.cs | 6 +- src/Scrutor/DecoratedType.cs | 119 ------------------ src/Scrutor/DecorationStrategy.cs | 10 +- src/Scrutor/OpenGenericDecorationStrategy.cs | 6 +- .../ServiceCollectionExtensions.Decoration.cs | 39 ++++-- src/Scrutor/ServiceDescriptorExtensions.cs | 48 +++++-- .../ServiceCollectionExtensions.cs | 2 +- 7 files changed, 83 insertions(+), 147 deletions(-) delete mode 100644 src/Scrutor/DecoratedType.cs diff --git a/src/Scrutor/ClosedTypeDecorationStrategy.cs b/src/Scrutor/ClosedTypeDecorationStrategy.cs index c2076ff9..06c57682 100644 --- a/src/Scrutor/ClosedTypeDecorationStrategy.cs +++ b/src/Scrutor/ClosedTypeDecorationStrategy.cs @@ -16,16 +16,16 @@ public ClosedTypeDecorationStrategy(Type serviceType, Type? decoratorType, Func< public override bool CanDecorate(Type serviceType) => ServiceType == serviceType; - public override Func CreateDecorator(Type serviceType) + public override Func CreateDecorator(Type serviceType, string serviceKey) { if (DecoratorType is not null) { - return TypeDecorator(serviceType, DecoratorType); + return TypeDecorator(serviceType, serviceKey, DecoratorType); } if (DecoratorFactory is not null) { - return FactoryDecorator(serviceType, DecoratorFactory); + return FactoryDecorator(serviceType, serviceKey, DecoratorFactory); } throw new InvalidOperationException($"Both serviceType and decoratorFactory can not be null."); diff --git a/src/Scrutor/DecoratedType.cs b/src/Scrutor/DecoratedType.cs deleted file mode 100644 index 69ad63c5..00000000 --- a/src/Scrutor/DecoratedType.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Reflection; -using System.Runtime.InteropServices; - -namespace Scrutor; - -public class DecoratedType : Type -{ - public DecoratedType(Type type) => ProxiedType = type; - private Type ProxiedType { get; } - - // We use object reference equality here to ensure that only the decorating object can match. - public override bool Equals(Type? o) => ReferenceEquals(this, o); - public override bool Equals(object? o) => ReferenceEquals(this, o); - public override int GetHashCode() => ProxiedType.GetHashCode(); - public override string? Namespace => ProxiedType.Namespace; - public override string? AssemblyQualifiedName => ProxiedType.AssemblyQualifiedName; - public override string? FullName => ProxiedType.FullName; - public override Assembly Assembly => ProxiedType.Assembly; - public override Module Module => ProxiedType.Module; - public override Type? DeclaringType => ProxiedType.DeclaringType; - public override MethodBase? DeclaringMethod => ProxiedType.DeclaringMethod; - public override Type? ReflectedType => ProxiedType.ReflectedType; - public override Type UnderlyingSystemType => ProxiedType.UnderlyingSystemType; - -#if NETCOREAPP3_1_OR_GREATER - public override bool IsTypeDefinition => ProxiedType.IsTypeDefinition; -#endif - protected override bool IsArrayImpl() => ProxiedType.HasElementType; - protected override bool IsByRefImpl() => ProxiedType.IsByRef; - protected override bool IsPointerImpl() => ProxiedType.IsPointer; - public override bool IsConstructedGenericType => ProxiedType.IsConstructedGenericType; - public override bool IsGenericParameter => ProxiedType.IsGenericParameter; -#if NETCOREAPP3_1_OR_GREATER - public override bool IsGenericTypeParameter => ProxiedType.IsGenericTypeParameter; - public override bool IsGenericMethodParameter => ProxiedType.IsGenericMethodParameter; -#endif - public override bool IsGenericType => ProxiedType.IsGenericType; - public override bool IsGenericTypeDefinition => ProxiedType.IsGenericTypeDefinition; -#if NETCOREAPP3_1_OR_GREATER - public override bool IsSZArray => ProxiedType.IsSZArray; - public override bool IsVariableBoundArray => ProxiedType.IsVariableBoundArray; - public override bool IsByRefLike => ProxiedType.IsByRefLike; -#endif - protected override bool HasElementTypeImpl() => ProxiedType.HasElementType; - public override Type? GetElementType() => ProxiedType.GetElementType(); - public override int GetArrayRank() => ProxiedType.GetArrayRank(); - public override Type GetGenericTypeDefinition() => ProxiedType.GetGenericTypeDefinition(); - public override Type[] GetGenericArguments() => ProxiedType.GetGenericArguments(); - public override int GenericParameterPosition => ProxiedType.GenericParameterPosition; - public override GenericParameterAttributes GenericParameterAttributes => ProxiedType.GenericParameterAttributes; - public override Type[] GetGenericParameterConstraints() => ProxiedType.GetGenericParameterConstraints(); - protected override TypeAttributes GetAttributeFlagsImpl() => ProxiedType.Attributes; - protected override bool IsCOMObjectImpl() => ProxiedType.IsCOMObject; - protected override bool IsContextfulImpl() => ProxiedType.IsContextful; - public override bool IsEnum => ProxiedType.IsEnum; - protected override bool IsMarshalByRefImpl() => ProxiedType.IsMarshalByRef; - protected override bool IsPrimitiveImpl() => ProxiedType.IsPrimitive; - protected override bool IsValueTypeImpl() => ProxiedType.IsValueType; -#if NETCOREAPP3_1_OR_GREATER - public override bool IsSignatureType => ProxiedType.IsSignatureType; -#endif - public override bool IsSecurityCritical => ProxiedType.IsSecurityCritical; - public override bool IsSecuritySafeCritical => ProxiedType.IsSecuritySafeCritical; - public override bool IsSecurityTransparent => ProxiedType.IsSecurityTransparent; - public override StructLayoutAttribute? StructLayoutAttribute => ProxiedType.StructLayoutAttribute; - protected override ConstructorInfo? GetConstructorImpl(BindingFlags bindingAttr, Binder? binder, CallingConventions callConvention, Type[] types, ParameterModifier[]? modifiers) - => ProxiedType.GetConstructor(bindingAttr, binder, callConvention, types, modifiers); - public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) => ProxiedType.GetConstructors(bindingAttr); - public override EventInfo? GetEvent(string name, BindingFlags bindingAttr) => ProxiedType.GetEvent(name, bindingAttr); - public override EventInfo[] GetEvents() => ProxiedType.GetEvents(); - public override EventInfo[] GetEvents(BindingFlags bindingAttr) => ProxiedType.GetEvents(bindingAttr); - public override FieldInfo? GetField(string name, BindingFlags bindingAttr) => ProxiedType.GetField(name, bindingAttr); - public override FieldInfo[] GetFields(BindingFlags bindingAttr) => ProxiedType.GetFields(bindingAttr); - public override MemberInfo[] GetMember(string name, BindingFlags bindingAttr) => ProxiedType.GetMember(name, bindingAttr); - public override MemberInfo[] GetMember(string name, MemberTypes type, BindingFlags bindingAttr) => ProxiedType.GetMember(name, type, bindingAttr); -#if NET6_0 - public override MemberInfo GetMemberWithSameMetadataDefinitionAs(MemberInfo member) => ProxiedType.GetMemberWithSameMetadataDefinitionAs(member); -#endif - public override MemberInfo[] GetMembers(BindingFlags bindingAttr) => ProxiedType.GetMembers(bindingAttr); - protected override MethodInfo? GetMethodImpl(string name, BindingFlags bindingAttr, Binder? binder, CallingConventions callConvention, Type[]? types, ParameterModifier[]? modifiers) - => ProxiedType.GetMethod(name, bindingAttr, binder, callConvention, types!, modifiers); - public override MethodInfo[] GetMethods(BindingFlags bindingAttr) => ProxiedType.GetMethods(bindingAttr); - public override Type? GetNestedType(string name, BindingFlags bindingAttr) => ProxiedType.GetNestedType(name, bindingAttr); - public override Type[] GetNestedTypes(BindingFlags bindingAttr) => ProxiedType.GetNestedTypes(bindingAttr); - protected override PropertyInfo? GetPropertyImpl(string name, BindingFlags bindingAttr, Binder? binder, Type? returnType, Type[]? types, ParameterModifier[]? modifiers) - => ProxiedType.GetProperty(name, bindingAttr, binder, returnType, types!, modifiers); - public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) => ProxiedType.GetProperties(bindingAttr); - public override MemberInfo[] GetDefaultMembers() => ProxiedType.GetDefaultMembers(); - public override RuntimeTypeHandle TypeHandle => ProxiedType.TypeHandle; - protected override TypeCode GetTypeCodeImpl() => GetTypeCode(ProxiedType); - public override Guid GUID => ProxiedType.GUID; - public override Type? BaseType => ProxiedType.BaseType; - public override object? InvokeMember(string name, BindingFlags invokeAttr, Binder? binder, object? target, object?[]? args, ParameterModifier[]? modifiers, CultureInfo? culture, string[]? namedParameters) => - ProxiedType.InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters); - public override Type? GetInterface(string name, bool ignoreCase) => ProxiedType.GetInterface(name, ignoreCase); - public override Type[] GetInterfaces() => ProxiedType.GetInterfaces(); - public override InterfaceMapping GetInterfaceMap(Type interfaceType) => ProxiedType.GetInterfaceMap(interfaceType); - public override bool IsInstanceOfType(object? o) => ProxiedType.IsInstanceOfType(o); - public override bool IsEquivalentTo(Type? other) => ProxiedType.IsEquivalentTo(other); - public override Type GetEnumUnderlyingType() => ProxiedType.GetEnumUnderlyingType(); - public override Array GetEnumValues() => ProxiedType.GetEnumValues(); - public override Type MakeArrayType() => ProxiedType.MakeArrayType(); - public override Type MakeArrayType(int rank) => ProxiedType.MakeArrayType(rank); - public override Type MakeByRefType() => ProxiedType.MakeByRefType(); - public override Type MakeGenericType(params Type[] typeArguments) => ProxiedType.MakeGenericType(typeArguments); - public override Type MakePointerType() => ProxiedType.MakePointerType(); - public override string ToString() => "Type: " + Name; - public override MemberTypes MemberType => ProxiedType.MemberType; - public override string Name => $"{ProxiedType.Name}+Decorated"; - public override IEnumerable CustomAttributes => ProxiedType.CustomAttributes; - public override int MetadataToken => ProxiedType.MetadataToken; - public override object[] GetCustomAttributes(bool inherit) => ProxiedType.GetCustomAttributes(inherit); - public override object[] GetCustomAttributes(Type attributeType, bool inherit) => ProxiedType.GetCustomAttributes(attributeType, inherit); - public override bool IsDefined(Type attributeType, bool inherit) => ProxiedType.IsDefined(attributeType, inherit); - public override IList GetCustomAttributesData() => ProxiedType.GetCustomAttributesData(); -} diff --git a/src/Scrutor/DecorationStrategy.cs b/src/Scrutor/DecorationStrategy.cs index ff6340b4..746da946 100644 --- a/src/Scrutor/DecorationStrategy.cs +++ b/src/Scrutor/DecorationStrategy.cs @@ -14,7 +14,7 @@ protected DecorationStrategy(Type serviceType) public abstract bool CanDecorate(Type serviceType); - public abstract Func CreateDecorator(Type serviceType); + public abstract Func CreateDecorator(Type serviceType, string serviceKey); internal static DecorationStrategy WithType(Type serviceType, Type decoratorType) => Create(serviceType, decoratorType, decoratorFactory: null); @@ -22,15 +22,15 @@ internal static DecorationStrategy WithType(Type serviceType, Type decoratorType internal static DecorationStrategy WithFactory(Type serviceType, Func decoratorFactory) => Create(serviceType, decoratorType: null, decoratorFactory); - protected static Func TypeDecorator(Type serviceType, Type decoratorType) => serviceProvider => + protected static Func TypeDecorator(Type serviceType, string serviceKey, Type decoratorType) => (serviceProvider, _) => { - var instanceToDecorate = serviceProvider.GetRequiredService(serviceType); + var instanceToDecorate = serviceProvider.GetRequiredKeyedService(serviceType, serviceKey); return ActivatorUtilities.CreateInstance(serviceProvider, decoratorType, instanceToDecorate); }; - protected static Func FactoryDecorator(Type decorated, Func decoratorFactory) => serviceProvider => + protected static Func FactoryDecorator(Type serviceType, string serviceKey, Func decoratorFactory) => (serviceProvider, _) => { - var instanceToDecorate = serviceProvider.GetRequiredService(decorated); + var instanceToDecorate = serviceProvider.GetRequiredKeyedService(serviceType, serviceKey); return decoratorFactory(instanceToDecorate, serviceProvider); }; diff --git a/src/Scrutor/OpenGenericDecorationStrategy.cs b/src/Scrutor/OpenGenericDecorationStrategy.cs index 40a7ab75..62f014f2 100644 --- a/src/Scrutor/OpenGenericDecorationStrategy.cs +++ b/src/Scrutor/OpenGenericDecorationStrategy.cs @@ -20,19 +20,19 @@ public override bool CanDecorate(Type serviceType) => && serviceType.GetGenericTypeDefinition() == ServiceType.GetGenericTypeDefinition() && (DecoratorType is null || serviceType.HasCompatibleGenericArguments(DecoratorType)); - public override Func CreateDecorator(Type serviceType) + public override Func CreateDecorator(Type serviceType, string serviceKey) { if (DecoratorType is not null) { var genericArguments = serviceType.GetGenericArguments(); var closedDecorator = DecoratorType.MakeGenericType(genericArguments); - return TypeDecorator(serviceType, closedDecorator); + return TypeDecorator(serviceType, serviceKey, closedDecorator); } if (DecoratorFactory is not null) { - return FactoryDecorator(serviceType, DecoratorFactory); + return FactoryDecorator(serviceType, serviceKey, DecoratorFactory); } throw new InvalidOperationException($"Both serviceType and decoratorFactory can not be null."); diff --git a/src/Scrutor/ServiceCollectionExtensions.Decoration.cs b/src/Scrutor/ServiceCollectionExtensions.Decoration.cs index 8d081744..36f235b7 100644 --- a/src/Scrutor/ServiceCollectionExtensions.Decoration.cs +++ b/src/Scrutor/ServiceCollectionExtensions.Decoration.cs @@ -8,6 +8,8 @@ namespace Microsoft.Extensions.DependencyInjection; [PublicAPI] public static partial class ServiceCollectionExtensions { + private const string DecoratedServiceKeySuffix = "+Decorated"; + /// /// Decorates all registered services of type /// using the specified type . @@ -250,27 +252,48 @@ public static bool TryDecorate(this IServiceCollection services, DecorationStrat { var serviceDescriptor = services[i]; - if (serviceDescriptor.ServiceType is DecoratedType) + if (IsDecorated(serviceDescriptor) || !strategy.CanDecorate(serviceDescriptor.ServiceType)) { - continue; // Service has already been decorated. + continue; } - if (!strategy.CanDecorate(serviceDescriptor.ServiceType)) + var serviceKey = GetDecoratorKey(serviceDescriptor); + if (serviceKey is null) { - continue; // Unable to decorate using the specified strategy. + return false; } - var decoratedType = new DecoratedType(serviceDescriptor.ServiceType); - // Insert decorated - services.Add(serviceDescriptor.WithServiceType(decoratedType)); + services.Add(serviceDescriptor.WithServiceKey(serviceKey)); // Replace decorator - services[i] = serviceDescriptor.WithImplementationFactory(strategy.CreateDecorator(decoratedType)); + services[i] = serviceDescriptor.WithImplementationFactory(strategy.CreateDecorator(serviceDescriptor.ServiceType, serviceKey)); decorated = true; } return decorated; } + + private static string? GetDecoratorKey(ServiceDescriptor descriptor) + { + var uniqueId = Guid.NewGuid().ToString("n"); + + if (descriptor.ServiceKey is null) + { + return $"{descriptor.ServiceType.Name}+{uniqueId}{DecoratedServiceKeySuffix}"; + } + + if (descriptor.ServiceKey is string stringKey) + { + return $"{stringKey}+{uniqueId}{DecoratedServiceKeySuffix}"; + } + + return null; + } + + private static bool IsDecorated(ServiceDescriptor descriptor) + { + return descriptor.ServiceKey is string stringKey && stringKey.EndsWith(DecoratedServiceKeySuffix); + } } diff --git a/src/Scrutor/ServiceDescriptorExtensions.cs b/src/Scrutor/ServiceDescriptorExtensions.cs index 57d2aac9..8c2e90da 100644 --- a/src/Scrutor/ServiceDescriptorExtensions.cs +++ b/src/Scrutor/ServiceDescriptorExtensions.cs @@ -5,14 +5,46 @@ namespace Scrutor; internal static class ServiceDescriptorExtensions { - public static ServiceDescriptor WithImplementationFactory(this ServiceDescriptor descriptor, Func implementationFactory) => - new(descriptor.ServiceType, implementationFactory, descriptor.Lifetime); + public static ServiceDescriptor WithImplementationFactory(this ServiceDescriptor descriptor, Func implementationFactory) => + new(descriptor.ServiceType, descriptor.ServiceKey, implementationFactory, descriptor.Lifetime); - public static ServiceDescriptor WithServiceType(this ServiceDescriptor descriptor, Type serviceType) => descriptor switch + public static ServiceDescriptor WithServiceKey(this ServiceDescriptor descriptor, string serviceKey) { - { ImplementationType: not null } => new ServiceDescriptor(serviceType, descriptor.ImplementationType, descriptor.Lifetime), - { ImplementationFactory: not null } => new ServiceDescriptor(serviceType, descriptor.ImplementationFactory, descriptor.Lifetime), - { ImplementationInstance: not null } => new ServiceDescriptor(serviceType, descriptor.ImplementationInstance), - _ => throw new ArgumentException($"No implementation factory or instance or type found for {descriptor.ServiceType}.", nameof(descriptor)) - }; + if (descriptor.IsKeyedService) + { + if (descriptor.KeyedImplementationType is not null) + { + return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationType, descriptor.Lifetime); + } + + if (descriptor.KeyedImplementationInstance is not null) + { + return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationInstance); + } + + if (descriptor.KeyedImplementationFactory is not null) + { + return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationFactory, descriptor.Lifetime); + } + + throw new InvalidOperationException($"One of the following properties must be set: {nameof(ServiceDescriptor.KeyedImplementationType)}, {nameof(ServiceDescriptor.KeyedImplementationInstance)} or {nameof(ServiceDescriptor.KeyedImplementationFactory)}"); + } + + if (descriptor.ImplementationType is not null) + { + return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.ImplementationType, descriptor.Lifetime); + } + + if (descriptor.ImplementationInstance is not null) + { + return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.ImplementationInstance); + } + + if (descriptor.ImplementationFactory is not null) + { + return new ServiceDescriptor(descriptor.ServiceType, serviceKey, (sp, key) => descriptor.ImplementationFactory(sp), descriptor.Lifetime); + } + + throw new InvalidOperationException($"One of the following properties must be set: {nameof(ServiceDescriptor.ImplementationType)}, {nameof(ServiceDescriptor.ImplementationInstance)} or {nameof(ServiceDescriptor.ImplementationFactory)}"); + } } diff --git a/test/Scrutor.Tests/ServiceCollectionExtensions.cs b/test/Scrutor.Tests/ServiceCollectionExtensions.cs index 6a6cb841..011cb3f9 100644 --- a/test/Scrutor.Tests/ServiceCollectionExtensions.cs +++ b/test/Scrutor.Tests/ServiceCollectionExtensions.cs @@ -18,6 +18,6 @@ public static ServiceDescriptor[] GetDescriptors(this IServiceCollection serv public static ServiceDescriptor[] GetDescriptors(this IServiceCollection services, Type serviceType) { - return services.Where(x => x.ServiceType == serviceType).ToArray(); + return services.Where(x => x.ServiceType == serviceType && x.ServiceKey is null).ToArray(); } } From a6b35281f07f4adc5256771f509e1cfd8e188c26 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 18 Aug 2023 14:15:19 +0200 Subject: [PATCH 05/22] Bump .NET version in action --- .github/workflows/build.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 862bdf0d..4a948076 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,10 +21,11 @@ jobs: with: dotnet-version: "3.1.x" - - name: Setup .NET 7 SDK - uses: actions/setup-dotnet@v1 + - name: Setup .NET 8 SDK + uses: actions/setup-dotnet@v3 with: - dotnet-version: "7.0.x" + dotnet-version: "8.x" + dotnet-quality: "preview" - name: Test run: dotnet test --collect:"XPlat Code Coverage" From 17b0f66c0131392c7bbdee7018dc4e1f79fb0d87 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 18 Aug 2023 14:16:03 +0200 Subject: [PATCH 06/22] Remove traces of .NET Core --- .github/workflows/build.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4a948076..5edad59c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,11 +16,6 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v1 - with: - dotnet-version: "3.1.x" - - name: Setup .NET 8 SDK uses: actions/setup-dotnet@v3 with: From f60f908032f8cecc6500a13a27249a3d364f93b8 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 1 Sep 2023 09:12:36 +0200 Subject: [PATCH 07/22] Remove Obsolete methods --- src/Scrutor/ITypeSelector.cs | 16 ---------------- src/Scrutor/TypeSelectorExtensions.cs | 10 ---------- src/Scrutor/TypeSourceSelector.cs | 6 ------ 3 files changed, 32 deletions(-) diff --git a/src/Scrutor/ITypeSelector.cs b/src/Scrutor/ITypeSelector.cs index 60cc0f08..3df56f52 100644 --- a/src/Scrutor/ITypeSelector.cs +++ b/src/Scrutor/ITypeSelector.cs @@ -5,14 +5,6 @@ namespace Scrutor; public interface ITypeSelector : IFluentInterface { - /// - /// Will scan the types in . - /// - /// The types in which assemblies that should be scanned. - /// If the argument is null. - [Obsolete("This method has been marked obsolete and will be removed in the next major version. Use " + nameof(FromTypes) + " instead.")] - IServiceTypeSelector AddTypes(params Type[] types); - /// /// Will scan the types in . /// @@ -20,14 +12,6 @@ public interface ITypeSelector : IFluentInterface /// If the argument is null. IServiceTypeSelector FromTypes(params Type[] types); - /// - /// Will scan the types in . - /// - /// The types in which assemblies that should be scanned. - /// If the argument is null. - [Obsolete("This method has been marked obsolete and will be removed in the next major version. Use " + nameof(FromTypes) + " instead.")] - IServiceTypeSelector AddTypes(IEnumerable types); - /// /// Will scan the types in . /// diff --git a/src/Scrutor/TypeSelectorExtensions.cs b/src/Scrutor/TypeSelectorExtensions.cs index 2992f0df..1618e3d1 100644 --- a/src/Scrutor/TypeSelectorExtensions.cs +++ b/src/Scrutor/TypeSelectorExtensions.cs @@ -1,4 +1,3 @@ -using System; using JetBrains.Annotations; namespace Scrutor; @@ -6,9 +5,6 @@ namespace Scrutor; [PublicAPI] public static class TypeSelectorExtensions { - [Obsolete("This method has been marked obsolete and will be removed in the next major version. Use " + nameof(FromTypes) + " instead.")] - public static IServiceTypeSelector AddType(this ITypeSelector selector) => selector.FromType(); - public static IServiceTypeSelector FromType(this ITypeSelector selector) { Preconditions.NotNull(selector, nameof(selector)); @@ -16,9 +12,6 @@ public static IServiceTypeSelector FromType(this ITypeSelector selector) return selector.FromTypes(typeof(T)); } - [Obsolete("This method has been marked obsolete and will be removed in the next major version. Use " + nameof(FromTypes) + " instead.")] - public static IServiceTypeSelector AddTypes(this ITypeSelector selector) => selector.FromTypes(); - public static IServiceTypeSelector FromTypes(this ITypeSelector selector) { Preconditions.NotNull(selector, nameof(selector)); @@ -26,9 +19,6 @@ public static IServiceTypeSelector FromTypes(this ITypeSelector selector return selector.FromTypes(typeof(T1), typeof(T2)); } - [Obsolete("This method has been marked obsolete and will be removed in the next major version. Use " + nameof(FromTypes) + " instead.")] - public static IServiceTypeSelector AddTypes(this ITypeSelector selector) => selector.FromTypes(); - public static IServiceTypeSelector FromTypes(this ITypeSelector selector) { Preconditions.NotNull(selector, nameof(selector)); diff --git a/src/Scrutor/TypeSourceSelector.cs b/src/Scrutor/TypeSourceSelector.cs index db226fbc..777d2500 100644 --- a/src/Scrutor/TypeSourceSelector.cs +++ b/src/Scrutor/TypeSourceSelector.cs @@ -126,12 +126,6 @@ public IImplementationTypeSelector FromAssemblies(IEnumerable assembli return InternalFromAssemblies(assemblies); } - [Obsolete("This method has been marked obsolete and will be removed in the next major version. Use " + nameof(FromTypes) + " instead.")] - public IServiceTypeSelector AddTypes(params Type[] types) => FromTypes(types); - - [Obsolete("This method has been marked obsolete and will be removed in the next major version. Use " + nameof(FromTypes) + " instead.")] - public IServiceTypeSelector AddTypes(IEnumerable types) => FromTypes(types); - public IServiceTypeSelector FromTypes(params Type[] types) => FromTypes(types.AsEnumerable()); public IServiceTypeSelector FromTypes(IEnumerable types) From 63dea3f1e781f27d9b30cc7b3b75f076ed8e40db Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 1 Sep 2023 09:39:36 +0200 Subject: [PATCH 08/22] Use sets internally to avoid duplicate types and assemblies --- src/Scrutor/AttributeSelector.cs | 4 ++-- src/Scrutor/EnumerableExtensions.cs | 20 ++++++++++++++++ src/Scrutor/ImplementationTypeFilter.cs | 6 ++--- src/Scrutor/ImplementationTypeSelector.cs | 10 ++++---- src/Scrutor/ReflectionExtensions.cs | 4 ++-- src/Scrutor/ServiceTypeSelector.cs | 4 ++-- src/Scrutor/TypeSourceSelector.cs | 29 +++++++++++------------ 7 files changed, 48 insertions(+), 29 deletions(-) create mode 100644 src/Scrutor/EnumerableExtensions.cs diff --git a/src/Scrutor/AttributeSelector.cs b/src/Scrutor/AttributeSelector.cs index 1533a9ec..0026ded4 100644 --- a/src/Scrutor/AttributeSelector.cs +++ b/src/Scrutor/AttributeSelector.cs @@ -8,12 +8,12 @@ namespace Scrutor; internal class AttributeSelector : ISelector { - public AttributeSelector(IEnumerable types) + public AttributeSelector(ISet types) { Types = types; } - private IEnumerable Types { get; } + private ISet Types { get; } void ISelector.Populate(IServiceCollection services, RegistrationStrategy? registrationStrategy) { diff --git a/src/Scrutor/EnumerableExtensions.cs b/src/Scrutor/EnumerableExtensions.cs new file mode 100644 index 00000000..ae29a124 --- /dev/null +++ b/src/Scrutor/EnumerableExtensions.cs @@ -0,0 +1,20 @@ +#if !NET8_0_OR_GREATER + +using System.Collections.Generic; + +namespace Scrutor; + +internal static class EnumerableExtensions +{ + public static ISet ToHashSet(this IEnumerable source) + { + if (source is ISet set) + { + return set; + } + + return new HashSet(source); + } +} + +#endif diff --git a/src/Scrutor/ImplementationTypeFilter.cs b/src/Scrutor/ImplementationTypeFilter.cs index 85024341..243e64b8 100644 --- a/src/Scrutor/ImplementationTypeFilter.cs +++ b/src/Scrutor/ImplementationTypeFilter.cs @@ -6,12 +6,12 @@ namespace Scrutor; internal class ImplementationTypeFilter : IImplementationTypeFilter { - public ImplementationTypeFilter(IEnumerable types) + public ImplementationTypeFilter(ISet types) { Types = types; } - internal IEnumerable Types { get; private set; } + internal ISet Types { get; private set; } public IImplementationTypeFilter AssignableTo() { @@ -151,7 +151,7 @@ public IImplementationTypeFilter Where(Func predicate) { Preconditions.NotNull(predicate, nameof(predicate)); - Types = Types.Where(predicate); + Types.IntersectWith(Types.Where(predicate)); return this; } } diff --git a/src/Scrutor/ImplementationTypeSelector.cs b/src/Scrutor/ImplementationTypeSelector.cs index 952896a2..0db4cf1d 100644 --- a/src/Scrutor/ImplementationTypeSelector.cs +++ b/src/Scrutor/ImplementationTypeSelector.cs @@ -9,7 +9,7 @@ namespace Scrutor; internal class ImplementationTypeSelector : IImplementationTypeSelector, ISelector { - public ImplementationTypeSelector(ITypeSourceSelector inner, IEnumerable types) + public ImplementationTypeSelector(ITypeSourceSelector inner, ISet types) { Inner = inner; Types = types; @@ -17,7 +17,7 @@ public ImplementationTypeSelector(ITypeSourceSelector inner, IEnumerable t private ITypeSourceSelector Inner { get; } - private IEnumerable Types { get; } + private ISet Types { get; } private List Selectors { get; } = new(); @@ -135,15 +135,15 @@ void ISelector.Populate(IServiceCollection services, RegistrationStrategy? regis private IServiceTypeSelector AddSelector(IEnumerable types) { - var selector = new ServiceTypeSelector(this, types); + var selector = new ServiceTypeSelector(this, types.ToHashSet()); Selectors.Add(selector); return selector; } - private IEnumerable GetNonAbstractClasses(bool publicOnly) + private ISet GetNonAbstractClasses(bool publicOnly) { - return Types.Where(t => t.IsNonAbstractClass(publicOnly)); + return Types.Where(t => t.IsNonAbstractClass(publicOnly)).ToHashSet(); } } diff --git a/src/Scrutor/ReflectionExtensions.cs b/src/Scrutor/ReflectionExtensions.cs index 210925e4..a477df4e 100644 --- a/src/Scrutor/ReflectionExtensions.cs +++ b/src/Scrutor/ReflectionExtensions.cs @@ -143,9 +143,9 @@ public static IEnumerable FindMatchingInterface(this Type type, Action string.Equals(x.Name, matchingInterfaceName, StringComparison.Ordinal)) - .ToArray(); + .ToHashSet(); - if (matchedInterfaces.Length == 0) + if (matchedInterfaces.Count == 0) { yield break; } diff --git a/src/Scrutor/ServiceTypeSelector.cs b/src/Scrutor/ServiceTypeSelector.cs index 31fa18e6..465cd1a3 100644 --- a/src/Scrutor/ServiceTypeSelector.cs +++ b/src/Scrutor/ServiceTypeSelector.cs @@ -9,7 +9,7 @@ namespace Scrutor; internal class ServiceTypeSelector : IServiceTypeSelector, ISelector { - public ServiceTypeSelector(IImplementationTypeSelector inner, IEnumerable types) + public ServiceTypeSelector(IImplementationTypeSelector inner, ISet types) { Inner = inner; Types = types; @@ -17,7 +17,7 @@ public ServiceTypeSelector(IImplementationTypeSelector inner, IEnumerable private IImplementationTypeSelector Inner { get; } - private IEnumerable Types { get; } + private ISet Types { get; } private List Selectors { get; } = new(); diff --git a/src/Scrutor/TypeSourceSelector.cs b/src/Scrutor/TypeSourceSelector.cs index 777d2500..a511cb14 100644 --- a/src/Scrutor/TypeSourceSelector.cs +++ b/src/Scrutor/TypeSourceSelector.cs @@ -71,7 +71,8 @@ public IImplementationTypeSelector FromDependencyContext(DependencyContext conte Preconditions.NotNull(predicate, nameof(predicate)); var assemblyNames = context.RuntimeLibraries - .SelectMany(library => library.GetDefaultAssemblyNames(context)); + .SelectMany(library => library.GetDefaultAssemblyNames(context)) + .ToHashSet(); var assemblies = LoadAssemblies(assemblyNames); @@ -82,19 +83,21 @@ public IImplementationTypeSelector FromAssemblyDependencies(Assembly assembly) { Preconditions.NotNull(assembly, nameof(assembly)); - var assemblies = new List { assembly }; - try { - var dependencyNames = assembly.GetReferencedAssemblies(); + var dependencyNames = assembly + .GetReferencedAssemblies() + .ToHashSet(); + + var assemblies = LoadAssemblies(dependencyNames); - assemblies.AddRange(LoadAssemblies(dependencyNames)); + assemblies.Add(assembly); return InternalFromAssemblies(assemblies); } catch { - return InternalFromAssemblies(assemblies); + return FromAssemblies(assembly); } } @@ -132,11 +135,7 @@ public IServiceTypeSelector FromTypes(IEnumerable types) { Preconditions.NotNull(types, nameof(types)); - var selector = new ImplementationTypeSelector(this, types); - - Selectors.Add(selector); - - return selector.AddClasses(); + return AddSelector(types).AddClasses(); } public void Populate(IServiceCollection services, RegistrationStrategy? registrationStrategy) @@ -154,12 +153,12 @@ private IImplementationTypeSelector InternalFromAssembliesOf(IEnumerable t private IImplementationTypeSelector InternalFromAssemblies(IEnumerable assemblies) { - return AddSelector(assemblies.SelectMany(asm => asm.ExportedTypes)); + return AddSelector(assemblies.SelectMany(asm => asm.GetTypes())); } - private static IEnumerable LoadAssemblies(IEnumerable assemblyNames) + private static ISet LoadAssemblies(ISet assemblyNames) { - var assemblies = new List(); + var assemblies = new HashSet(); foreach (var assemblyName in assemblyNames) { @@ -179,7 +178,7 @@ private static IEnumerable LoadAssemblies(IEnumerable as private IImplementationTypeSelector AddSelector(IEnumerable types) { - var selector = new ImplementationTypeSelector(this, types); + var selector = new ImplementationTypeSelector(this, types.ToHashSet()); Selectors.Add(selector); From 40e9680cbae9e332d3e2576755956e60b66c6376 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 1 Sep 2023 09:42:04 +0200 Subject: [PATCH 09/22] Address PR feedback --- .github/workflows/build.yml | 2 +- src/Scrutor/ServiceCollectionExtensions.Decoration.cs | 7 +++---- src/Scrutor/ServiceDescriptorExtensions.cs | 3 ++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5edad59c..f6bd59b8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup .NET 8 SDK uses: actions/setup-dotnet@v3 diff --git a/src/Scrutor/ServiceCollectionExtensions.Decoration.cs b/src/Scrutor/ServiceCollectionExtensions.Decoration.cs index 36f235b7..644f7a24 100644 --- a/src/Scrutor/ServiceCollectionExtensions.Decoration.cs +++ b/src/Scrutor/ServiceCollectionExtensions.Decoration.cs @@ -292,8 +292,7 @@ public static bool TryDecorate(this IServiceCollection services, DecorationStrat return null; } - private static bool IsDecorated(ServiceDescriptor descriptor) - { - return descriptor.ServiceKey is string stringKey && stringKey.EndsWith(DecoratedServiceKeySuffix); - } + private static bool IsDecorated(ServiceDescriptor descriptor) => + descriptor.ServiceKey is string stringKey + && stringKey.EndsWith(DecoratedServiceKeySuffix, StringComparison.Ordinal); } diff --git a/src/Scrutor/ServiceDescriptorExtensions.cs b/src/Scrutor/ServiceDescriptorExtensions.cs index 8c2e90da..77ed479d 100644 --- a/src/Scrutor/ServiceDescriptorExtensions.cs +++ b/src/Scrutor/ServiceDescriptorExtensions.cs @@ -42,7 +42,8 @@ public static ServiceDescriptor WithServiceKey(this ServiceDescriptor descriptor if (descriptor.ImplementationFactory is not null) { - return new ServiceDescriptor(descriptor.ServiceType, serviceKey, (sp, key) => descriptor.ImplementationFactory(sp), descriptor.Lifetime); + var factory = descriptor.ImplementationFactory; // Local to avoid capturing descriptor in lambda below. + return new ServiceDescriptor(descriptor.ServiceType, serviceKey, (sp, key) => factory(sp), descriptor.Lifetime); } throw new InvalidOperationException($"One of the following properties must be set: {nameof(ServiceDescriptor.ImplementationType)}, {nameof(ServiceDescriptor.ImplementationInstance)} or {nameof(ServiceDescriptor.ImplementationFactory)}"); From 080891c18315633d17dae899e5ed06e84e953b9d Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 1 Sep 2023 09:52:25 +0200 Subject: [PATCH 10/22] Extract methods --- src/Scrutor/ServiceDescriptorExtensions.cs | 39 ++++++++++++---------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Scrutor/ServiceDescriptorExtensions.cs b/src/Scrutor/ServiceDescriptorExtensions.cs index 77ed479d..341a1f0f 100644 --- a/src/Scrutor/ServiceDescriptorExtensions.cs +++ b/src/Scrutor/ServiceDescriptorExtensions.cs @@ -8,28 +8,31 @@ internal static class ServiceDescriptorExtensions public static ServiceDescriptor WithImplementationFactory(this ServiceDescriptor descriptor, Func implementationFactory) => new(descriptor.ServiceType, descriptor.ServiceKey, implementationFactory, descriptor.Lifetime); - public static ServiceDescriptor WithServiceKey(this ServiceDescriptor descriptor, string serviceKey) + public static ServiceDescriptor WithServiceKey(this ServiceDescriptor descriptor, string serviceKey) => + descriptor.IsKeyedService ? ReplaceServiceKey(descriptor, serviceKey) : AddServiceKey(descriptor, serviceKey); + + private static ServiceDescriptor ReplaceServiceKey(ServiceDescriptor descriptor, string serviceKey) { - if (descriptor.IsKeyedService) + if (descriptor.KeyedImplementationType is not null) + { + return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationType, descriptor.Lifetime); + } + + if (descriptor.KeyedImplementationInstance is not null) + { + return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationInstance); + } + + if (descriptor.KeyedImplementationFactory is not null) { - if (descriptor.KeyedImplementationType is not null) - { - return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationType, descriptor.Lifetime); - } - - if (descriptor.KeyedImplementationInstance is not null) - { - return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationInstance); - } - - if (descriptor.KeyedImplementationFactory is not null) - { - return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationFactory, descriptor.Lifetime); - } - - throw new InvalidOperationException($"One of the following properties must be set: {nameof(ServiceDescriptor.KeyedImplementationType)}, {nameof(ServiceDescriptor.KeyedImplementationInstance)} or {nameof(ServiceDescriptor.KeyedImplementationFactory)}"); + return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationFactory, descriptor.Lifetime); } + throw new InvalidOperationException($"One of the following properties must be set: {nameof(ServiceDescriptor.KeyedImplementationType)}, {nameof(ServiceDescriptor.KeyedImplementationInstance)} or {nameof(ServiceDescriptor.KeyedImplementationFactory)}"); + } + + private static ServiceDescriptor AddServiceKey(ServiceDescriptor descriptor, string serviceKey) + { if (descriptor.ImplementationType is not null) { return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.ImplementationType, descriptor.Lifetime); From ad5c936f5b00c8ecdfac688a4f1598267d285642 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 1 Sep 2023 09:59:44 +0200 Subject: [PATCH 11/22] Use switch expressions --- src/Scrutor/ServiceDescriptorExtensions.cs | 49 ++++++---------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/src/Scrutor/ServiceDescriptorExtensions.cs b/src/Scrutor/ServiceDescriptorExtensions.cs index 341a1f0f..6388d3cc 100644 --- a/src/Scrutor/ServiceDescriptorExtensions.cs +++ b/src/Scrutor/ServiceDescriptorExtensions.cs @@ -11,44 +11,21 @@ public static ServiceDescriptor WithImplementationFactory(this ServiceDescriptor public static ServiceDescriptor WithServiceKey(this ServiceDescriptor descriptor, string serviceKey) => descriptor.IsKeyedService ? ReplaceServiceKey(descriptor, serviceKey) : AddServiceKey(descriptor, serviceKey); - private static ServiceDescriptor ReplaceServiceKey(ServiceDescriptor descriptor, string serviceKey) + private static ServiceDescriptor ReplaceServiceKey(ServiceDescriptor descriptor, string serviceKey) => descriptor switch { - if (descriptor.KeyedImplementationType is not null) - { - return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationType, descriptor.Lifetime); - } + { KeyedImplementationType: not null } => new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationType, descriptor.Lifetime), + { KeyedImplementationFactory: not null } => new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationFactory, descriptor.Lifetime), + { KeyedImplementationInstance: not null } => new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationInstance), + _ => throw new ArgumentException($"No implementation factory or instance or type found for {descriptor.ServiceType}.", nameof(descriptor)) + }; - if (descriptor.KeyedImplementationInstance is not null) - { - return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationInstance); - } - - if (descriptor.KeyedImplementationFactory is not null) - { - return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.KeyedImplementationFactory, descriptor.Lifetime); - } - - throw new InvalidOperationException($"One of the following properties must be set: {nameof(ServiceDescriptor.KeyedImplementationType)}, {nameof(ServiceDescriptor.KeyedImplementationInstance)} or {nameof(ServiceDescriptor.KeyedImplementationFactory)}"); - } - - private static ServiceDescriptor AddServiceKey(ServiceDescriptor descriptor, string serviceKey) + private static ServiceDescriptor AddServiceKey(ServiceDescriptor descriptor, string serviceKey) => descriptor switch { - if (descriptor.ImplementationType is not null) - { - return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.ImplementationType, descriptor.Lifetime); - } - - if (descriptor.ImplementationInstance is not null) - { - return new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.ImplementationInstance); - } - - if (descriptor.ImplementationFactory is not null) - { - var factory = descriptor.ImplementationFactory; // Local to avoid capturing descriptor in lambda below. - return new ServiceDescriptor(descriptor.ServiceType, serviceKey, (sp, key) => factory(sp), descriptor.Lifetime); - } + { ImplementationType: not null } => new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.ImplementationType, descriptor.Lifetime), + { ImplementationFactory: not null } => new ServiceDescriptor(descriptor.ServiceType, serviceKey, DiscardServiceKey(descriptor.ImplementationFactory), descriptor.Lifetime), + { ImplementationInstance: not null } => new ServiceDescriptor(descriptor.ServiceType, serviceKey, descriptor.ImplementationInstance), + _ => throw new ArgumentException($"No implementation factory or instance or type found for {descriptor.ServiceType}.", nameof(descriptor)) + }; - throw new InvalidOperationException($"One of the following properties must be set: {nameof(ServiceDescriptor.ImplementationType)}, {nameof(ServiceDescriptor.ImplementationInstance)} or {nameof(ServiceDescriptor.ImplementationFactory)}"); - } + private static Func DiscardServiceKey(Func factory) => (sp, key) => factory(sp); } From 67d09f867643e3c3235efa0d730d9c6db81aca8d Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 1 Sep 2023 10:10:19 +0200 Subject: [PATCH 12/22] Fix test to work with ServiceKey --- test/Scrutor.Tests/DecorationTests.cs | 15 +++++++++++---- test/Scrutor.Tests/ServiceCollectionExtensions.cs | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/test/Scrutor.Tests/DecorationTests.cs b/test/Scrutor.Tests/DecorationTests.cs index c75fdc6b..00fa295a 100644 --- a/test/Scrutor.Tests/DecorationTests.cs +++ b/test/Scrutor.Tests/DecorationTests.cs @@ -63,7 +63,7 @@ public void CanDecorateDifferentServices() } [Fact] - public void ShouldReplaceExistingServiceDescriptor() + public void ShouldAddServiceKeyToExistingServiceDescriptor() { var services = new ServiceCollection(); @@ -71,10 +71,17 @@ public void ShouldReplaceExistingServiceDescriptor() services.Decorate(); - var descriptor = services.GetDescriptor(); + var descriptors = services.GetDescriptors(); - Assert.Equal(typeof(IDecoratedService), descriptor.ServiceType); - Assert.NotNull(descriptor.ImplementationFactory); + Assert.Equal(2, descriptors.Length); + + var decorated = descriptors.SingleOrDefault(x => x.ServiceKey is not null); + + Assert.NotNull(decorated); + Assert.NotNull(decorated.KeyedImplementationType); + var key = Assert.IsType(decorated.ServiceKey); + Assert.StartsWith("IDecoratedService", key); + Assert.EndsWith("+Decorated", key); } [Fact] diff --git a/test/Scrutor.Tests/ServiceCollectionExtensions.cs b/test/Scrutor.Tests/ServiceCollectionExtensions.cs index 011cb3f9..6a6cb841 100644 --- a/test/Scrutor.Tests/ServiceCollectionExtensions.cs +++ b/test/Scrutor.Tests/ServiceCollectionExtensions.cs @@ -18,6 +18,6 @@ public static ServiceDescriptor[] GetDescriptors(this IServiceCollection serv public static ServiceDescriptor[] GetDescriptors(this IServiceCollection services, Type serviceType) { - return services.Where(x => x.ServiceType == serviceType && x.ServiceKey is null).ToArray(); + return services.Where(x => x.ServiceType == serviceType).ToArray(); } } From cd2018be60e2c18e354f0bf4caccd6716421935e Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 1 Sep 2023 10:21:23 +0200 Subject: [PATCH 13/22] Create activator factory outside of factory delegate --- src/Scrutor/DecorationStrategy.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Scrutor/DecorationStrategy.cs b/src/Scrutor/DecorationStrategy.cs index 746da946..59b93df4 100644 --- a/src/Scrutor/DecorationStrategy.cs +++ b/src/Scrutor/DecorationStrategy.cs @@ -22,11 +22,15 @@ internal static DecorationStrategy WithType(Type serviceType, Type decoratorType internal static DecorationStrategy WithFactory(Type serviceType, Func decoratorFactory) => Create(serviceType, decoratorType: null, decoratorFactory); - protected static Func TypeDecorator(Type serviceType, string serviceKey, Type decoratorType) => (serviceProvider, _) => + protected static Func TypeDecorator(Type serviceType, string serviceKey, Type decoratorType) { - var instanceToDecorate = serviceProvider.GetRequiredKeyedService(serviceType, serviceKey); - return ActivatorUtilities.CreateInstance(serviceProvider, decoratorType, instanceToDecorate); - }; + var factory = ActivatorUtilities.CreateFactory(decoratorType, new[] { serviceType }); + return (serviceProvider, _) => + { + var instanceToDecorate = serviceProvider.GetRequiredKeyedService(serviceType, serviceKey); + return factory(serviceProvider, new object[] { instanceToDecorate }); + }; + } protected static Func FactoryDecorator(Type serviceType, string serviceKey, Func decoratorFactory) => (serviceProvider, _) => { From acda74599d379c800cdf270a35eed27a309f88b4 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Fri, 1 Sep 2023 10:21:37 +0200 Subject: [PATCH 14/22] Fix exception message names --- src/Scrutor/ClosedTypeDecorationStrategy.cs | 2 +- src/Scrutor/OpenGenericDecorationStrategy.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Scrutor/ClosedTypeDecorationStrategy.cs b/src/Scrutor/ClosedTypeDecorationStrategy.cs index 06c57682..cb86a9d0 100644 --- a/src/Scrutor/ClosedTypeDecorationStrategy.cs +++ b/src/Scrutor/ClosedTypeDecorationStrategy.cs @@ -28,6 +28,6 @@ public ClosedTypeDecorationStrategy(Type serviceType, Type? decoratorType, Func< return FactoryDecorator(serviceType, serviceKey, DecoratorFactory); } - throw new InvalidOperationException($"Both serviceType and decoratorFactory can not be null."); + throw new InvalidOperationException($"Both {nameof(DecoratorType)} and {nameof(DecoratorFactory)} can not be null."); } } diff --git a/src/Scrutor/OpenGenericDecorationStrategy.cs b/src/Scrutor/OpenGenericDecorationStrategy.cs index 62f014f2..8d928551 100644 --- a/src/Scrutor/OpenGenericDecorationStrategy.cs +++ b/src/Scrutor/OpenGenericDecorationStrategy.cs @@ -35,6 +35,6 @@ public override bool CanDecorate(Type serviceType) => return FactoryDecorator(serviceType, serviceKey, DecoratorFactory); } - throw new InvalidOperationException($"Both serviceType and decoratorFactory can not be null."); + throw new InvalidOperationException($"Both {nameof(DecoratorType)} and {nameof(DecoratorFactory)} can not be null."); } } From 12c653c6b166615533edda2576b0402cdd9dff8b Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Tue, 5 Sep 2023 10:44:32 +0200 Subject: [PATCH 15/22] Filter out IEnumerables when registering as implemented interfaces. #65 --- src/Scrutor/ServiceTypeSelector.cs | 18 +++++++++++++++++- test/Scrutor.Tests/ScanningTests.cs | 15 ++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Scrutor/ServiceTypeSelector.cs b/src/Scrutor/ServiceTypeSelector.cs index 465cd1a3..30bb8f5a 100644 --- a/src/Scrutor/ServiceTypeSelector.cs +++ b/src/Scrutor/ServiceTypeSelector.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -57,7 +58,7 @@ public ILifetimeSelector AsImplementedInterfaces(Func predicate) Preconditions.NotNull(predicate, nameof(predicate)); return As(t => t.GetInterfaces() - .Where(x => x.HasMatchingGenericArity(t)) + .Where(x => ShouldRegister(x) && x.HasMatchingGenericArity(t)) .Select(x => x.GetRegistrationType(t)) .Where(predicate)); } @@ -235,6 +236,21 @@ void ISelector.Populate(IServiceCollection services, RegistrationStrategy? regis } } + private static bool ShouldRegister(Type type) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + return false; + } + + if (type == typeof(IEnumerable)) + { + return false; + } + + return true; + } + private ILifetimeSelector AddSelector(IEnumerable types, IEnumerable factories) { var selector = new LifetimeSelector(this, types, factories); diff --git a/test/Scrutor.Tests/ScanningTests.cs b/test/Scrutor.Tests/ScanningTests.cs index 8ab79eef..46f2d7d0 100644 --- a/test/Scrutor.Tests/ScanningTests.cs +++ b/test/Scrutor.Tests/ScanningTests.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.DependencyInjection; using Scrutor.Tests; using System; +using System.Collections; +using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using Xunit; @@ -565,7 +567,18 @@ public class TransientService1 : ITransientService { } public class TransientService2 : ITransientService, IOtherInheritance { } - public class TransientService : ITransientService { } + public class TransientService : ITransientService, IEnumerable + { + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } public interface IScopedService { } From d2bda342a5299b5409f31bb4457b50433f3c8242 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Tue, 5 Sep 2023 11:08:57 +0200 Subject: [PATCH 16/22] Reuse same logic for getting interfaces between AsImplementedInterfaces and AsSelfWithInterfaces --- src/Scrutor/ServiceTypeSelector.cs | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Scrutor/ServiceTypeSelector.cs b/src/Scrutor/ServiceTypeSelector.cs index 30bb8f5a..f9b3955f 100644 --- a/src/Scrutor/ServiceTypeSelector.cs +++ b/src/Scrutor/ServiceTypeSelector.cs @@ -57,20 +57,12 @@ public ILifetimeSelector AsImplementedInterfaces(Func predicate) { Preconditions.NotNull(predicate, nameof(predicate)); - return As(t => t.GetInterfaces() - .Where(x => ShouldRegister(x) && x.HasMatchingGenericArity(t)) - .Select(x => x.GetRegistrationType(t)) - .Where(predicate)); + return As(t => GetInterfaces(t).Where(predicate)); } public ILifetimeSelector AsSelfWithInterfaces() { - return AsSelfWithInterfaces(_ => true); - } - - public ILifetimeSelector AsSelfWithInterfaces(Func predicate) - { - IEnumerable Selector(Type type) + static IEnumerable Selector(Type type) { if (type.IsGenericTypeDefinition) { @@ -79,10 +71,7 @@ IEnumerable Selector(Type type) return Enumerable.Empty(); } - return type.GetInterfaces() - .Where(x => x.HasMatchingGenericArity(type)) - .Where(predicate) - .Select(x => x.GetRegistrationType(type)); + return GetInterfaces(type); } return AddSelector( @@ -236,14 +225,25 @@ void ISelector.Populate(IServiceCollection services, RegistrationStrategy? regis } } - private static bool ShouldRegister(Type type) + private static IEnumerable GetInterfaces(Type type) => + type.GetInterfaces() + .Where(x => ShouldRegister(x, type)) + .Select(x => x.GetRegistrationType(type)) + .ToList(); + + private static bool ShouldRegister(Type serviceType, Type implementationType) { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + if (!serviceType.HasMatchingGenericArity(implementationType)) + { + return false; + } + + if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { return false; } - if (type == typeof(IEnumerable)) + if (serviceType == typeof(IEnumerable)) { return false; } From 109fad6b1d7a5f85332c9d36c53dd7bb083ccfba Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Tue, 5 Sep 2023 11:12:19 +0200 Subject: [PATCH 17/22] Add AsSelfWithInterfaces overload with predicate --- src/Scrutor/ServiceTypeSelector.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Scrutor/ServiceTypeSelector.cs b/src/Scrutor/ServiceTypeSelector.cs index f9b3955f..c850d5e9 100644 --- a/src/Scrutor/ServiceTypeSelector.cs +++ b/src/Scrutor/ServiceTypeSelector.cs @@ -62,7 +62,18 @@ public ILifetimeSelector AsImplementedInterfaces(Func predicate) public ILifetimeSelector AsSelfWithInterfaces() { - static IEnumerable Selector(Type type) + return AsSelfWithInterfaces(_ => true); + } + + public ILifetimeSelector AsSelfWithInterfaces(Func predicate) + { + Preconditions.NotNull(predicate, nameof(predicate)); + + return AddSelector( + Types.Select(t => new TypeMap(t, new[] { t })), + Types.Select(t => new TypeFactoryMap(x => x.GetRequiredService(t), Selector(t, predicate)))); + + static IEnumerable Selector(Type type, Func predicate) { if (type.IsGenericTypeDefinition) { @@ -71,12 +82,8 @@ static IEnumerable Selector(Type type) return Enumerable.Empty(); } - return GetInterfaces(type); + return GetInterfaces(type).Where(predicate); } - - return AddSelector( - Types.Select(t => new TypeMap(t, new[] { t })), - Types.Select(t => new TypeFactoryMap(x => x.GetRequiredService(t), Selector(t)))); } public ILifetimeSelector AsMatchingInterface() From 14b851197793ddba27c84c303f3512f9b67a0b82 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 15 Nov 2023 15:56:17 +0100 Subject: [PATCH 18/22] Add plumbing to support decorating keyed services --- src/Scrutor/ClosedTypeDecorationStrategy.cs | 4 ++-- src/Scrutor/DecorationStrategy.cs | 24 ++++++++++++------- src/Scrutor/OpenGenericDecorationStrategy.cs | 4 ++-- .../ServiceCollectionExtensions.Decoration.cs | 10 ++++---- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/Scrutor/ClosedTypeDecorationStrategy.cs b/src/Scrutor/ClosedTypeDecorationStrategy.cs index cb86a9d0..b161e629 100644 --- a/src/Scrutor/ClosedTypeDecorationStrategy.cs +++ b/src/Scrutor/ClosedTypeDecorationStrategy.cs @@ -4,7 +4,7 @@ namespace Scrutor; internal sealed class ClosedTypeDecorationStrategy : DecorationStrategy { - public ClosedTypeDecorationStrategy(Type serviceType, Type? decoratorType, Func? decoratorFactory) : base(serviceType) + public ClosedTypeDecorationStrategy(Type serviceType, string? serviceKey, Type? decoratorType, Func? decoratorFactory) : base(serviceType, serviceKey) { DecoratorType = decoratorType; DecoratorFactory = decoratorFactory; @@ -14,7 +14,7 @@ public ClosedTypeDecorationStrategy(Type serviceType, Type? decoratorType, Func< private Func? DecoratorFactory { get; } - public override bool CanDecorate(Type serviceType) => ServiceType == serviceType; + protected override bool CanDecorate(Type serviceType) => ServiceType == serviceType; public override Func CreateDecorator(Type serviceType, string serviceKey) { diff --git a/src/Scrutor/DecorationStrategy.cs b/src/Scrutor/DecorationStrategy.cs index 59b93df4..b392ff85 100644 --- a/src/Scrutor/DecorationStrategy.cs +++ b/src/Scrutor/DecorationStrategy.cs @@ -5,22 +5,28 @@ namespace Scrutor; public abstract class DecorationStrategy { - protected DecorationStrategy(Type serviceType) + protected DecorationStrategy(Type serviceType, string? serviceKey) { ServiceType = serviceType; + ServiceKey = serviceKey; } public Type ServiceType { get; } - public abstract bool CanDecorate(Type serviceType); + public string? ServiceKey { get; } + + public virtual bool CanDecorate(ServiceDescriptor descriptor) => + string.Equals(ServiceKey, descriptor.ServiceKey) && CanDecorate(descriptor.ServiceType); + + protected abstract bool CanDecorate(Type serviceType); public abstract Func CreateDecorator(Type serviceType, string serviceKey); - internal static DecorationStrategy WithType(Type serviceType, Type decoratorType) => - Create(serviceType, decoratorType, decoratorFactory: null); + internal static DecorationStrategy WithType(Type serviceType, string? serviceKey, Type decoratorType) => + Create(serviceType, serviceKey, decoratorType, decoratorFactory: null); - internal static DecorationStrategy WithFactory(Type serviceType, Func decoratorFactory) => - Create(serviceType, decoratorType: null, decoratorFactory); + internal static DecorationStrategy WithFactory(Type serviceType, string? serviceKey, Func decoratorFactory) => + Create(serviceType, serviceKey, decoratorType: null, decoratorFactory); protected static Func TypeDecorator(Type serviceType, string serviceKey, Type decoratorType) { @@ -38,13 +44,13 @@ internal static DecorationStrategy WithFactory(Type serviceType, Func? decoratorFactory) + private static DecorationStrategy Create(Type serviceType, string? serviceKey, Type? decoratorType, Func? decoratorFactory) { if (serviceType.IsOpenGeneric()) { - return new OpenGenericDecorationStrategy(serviceType, decoratorType, decoratorFactory); + return new OpenGenericDecorationStrategy(serviceType, serviceKey, decoratorType, decoratorFactory); } - return new ClosedTypeDecorationStrategy(serviceType, decoratorType, decoratorFactory); + return new ClosedTypeDecorationStrategy(serviceType, serviceKey, decoratorType, decoratorFactory); } } diff --git a/src/Scrutor/OpenGenericDecorationStrategy.cs b/src/Scrutor/OpenGenericDecorationStrategy.cs index 8d928551..76f8345d 100644 --- a/src/Scrutor/OpenGenericDecorationStrategy.cs +++ b/src/Scrutor/OpenGenericDecorationStrategy.cs @@ -4,7 +4,7 @@ namespace Scrutor; public class OpenGenericDecorationStrategy : DecorationStrategy { - public OpenGenericDecorationStrategy(Type serviceType, Type? decoratorType, Func? decoratorFactory) : base(serviceType) + public OpenGenericDecorationStrategy(Type serviceType, string? serviceKey, Type? decoratorType, Func? decoratorFactory) : base(serviceType, serviceKey) { DecoratorType = decoratorType; DecoratorFactory = decoratorFactory; @@ -14,7 +14,7 @@ public OpenGenericDecorationStrategy(Type serviceType, Type? decoratorType, Func private Func? DecoratorFactory { get; } - public override bool CanDecorate(Type serviceType) => + protected override bool CanDecorate(Type serviceType) => serviceType.IsGenericType && !serviceType.IsGenericTypeDefinition && serviceType.GetGenericTypeDefinition() == ServiceType.GetGenericTypeDefinition() diff --git a/src/Scrutor/ServiceCollectionExtensions.Decoration.cs b/src/Scrutor/ServiceCollectionExtensions.Decoration.cs index 644f7a24..ada3ab3e 100644 --- a/src/Scrutor/ServiceCollectionExtensions.Decoration.cs +++ b/src/Scrutor/ServiceCollectionExtensions.Decoration.cs @@ -55,7 +55,7 @@ public static IServiceCollection Decorate(this IServiceCollection services, Type Preconditions.NotNull(serviceType, nameof(serviceType)); Preconditions.NotNull(decoratorType, nameof(decoratorType)); - return services.Decorate(DecorationStrategy.WithType(serviceType, decoratorType)); + return services.Decorate(DecorationStrategy.WithType(serviceType, serviceKey: null, decoratorType)); } /// @@ -73,7 +73,7 @@ public static bool TryDecorate(this IServiceCollection services, Type serviceTyp Preconditions.NotNull(serviceType, nameof(serviceType)); Preconditions.NotNull(decoratorType, nameof(decoratorType)); - return services.TryDecorate(DecorationStrategy.WithType(serviceType, decoratorType)); + return services.TryDecorate(DecorationStrategy.WithType(serviceType, serviceKey: null, decoratorType)); } /// @@ -199,7 +199,7 @@ public static IServiceCollection Decorate(this IServiceCollection services, Type Preconditions.NotNull(serviceType, nameof(serviceType)); Preconditions.NotNull(decorator, nameof(decorator)); - return services.Decorate(DecorationStrategy.WithFactory(serviceType, decorator)); + return services.Decorate(DecorationStrategy.WithFactory(serviceType, serviceKey: null, decorator)); } /// @@ -217,7 +217,7 @@ public static bool TryDecorate(this IServiceCollection services, Type serviceTyp Preconditions.NotNull(serviceType, nameof(serviceType)); Preconditions.NotNull(decorator, nameof(decorator)); - return services.TryDecorate(DecorationStrategy.WithFactory(serviceType, decorator)); + return services.TryDecorate(DecorationStrategy.WithFactory(serviceType, serviceKey: null, decorator)); } /// @@ -252,7 +252,7 @@ public static bool TryDecorate(this IServiceCollection services, DecorationStrat { var serviceDescriptor = services[i]; - if (IsDecorated(serviceDescriptor) || !strategy.CanDecorate(serviceDescriptor.ServiceType)) + if (IsDecorated(serviceDescriptor) || !strategy.CanDecorate(serviceDescriptor)) { continue; } From 6ebbfdf9d93a320b6da34d3e18102306e868785e Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 15 Nov 2023 15:57:28 +0100 Subject: [PATCH 19/22] Bump dependencies to RTM --- src/Scrutor/Scrutor.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Scrutor/Scrutor.csproj b/src/Scrutor/Scrutor.csproj index 27601c18..9d95cce7 100644 --- a/src/Scrutor/Scrutor.csproj +++ b/src/Scrutor/Scrutor.csproj @@ -30,7 +30,7 @@ - - + + From 4270df83d72445709b3170dfcecbc3e9c44c2883 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 15 Nov 2023 15:57:38 +0100 Subject: [PATCH 20/22] Bump major version --- src/Scrutor/Scrutor.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Scrutor/Scrutor.csproj b/src/Scrutor/Scrutor.csproj index 9d95cce7..62238172 100644 --- a/src/Scrutor/Scrutor.csproj +++ b/src/Scrutor/Scrutor.csproj @@ -1,7 +1,7 @@  Register services using assembly scanning and a fluent API. - 4.2.2 + 5.0.0 Kristian Hellang net462;netstandard2.0;net8.0 $(NoWarn);CS1591 @@ -23,7 +23,7 @@ true snupkg - 4.0.0.0 + 5.0.0.0 From 04a3f7e81471c5d6aa81475bb53fc6a2d78e0457 Mon Sep 17 00:00:00 2001 From: Simona Avornicesei Date: Mon, 5 Feb 2024 23:52:14 +0200 Subject: [PATCH 21/22] FromExecutingAssembly and FromCallingAssembly are misleading #92 --- src/Scrutor/TypeSourceSelector.cs | 37 ++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Scrutor/TypeSourceSelector.cs b/src/Scrutor/TypeSourceSelector.cs index a511cb14..a393afbc 100644 --- a/src/Scrutor/TypeSourceSelector.cs +++ b/src/Scrutor/TypeSourceSelector.cs @@ -20,11 +20,46 @@ public IImplementationTypeSelector FromAssemblyOf() return InternalFromAssembliesOf(new[] { typeof(T) }); } + /// + /// Uses the assembly that called Scrutor. + /// + /// + /// + /// Returned might not be the one expected do to (lack of) inlining. + /// To ensure proper assembly is resolved, use: + /// + /// services.Scan(s => s.FromAssemblies(Assembly.GetExecutingAssembly()) + /// + /// or + /// + /// var assembly = Assembly.GetCallingAssembly(); + /// services.Scan(s => s.FromAssemblies(assembly)); + /// + /// + /// + [Obsolete("Misleading, as it might not always determine the correct assembly that called Scrutor. Will be removed in a future release")] public IImplementationTypeSelector FromCallingAssembly() { return FromAssemblies(Assembly.GetCallingAssembly()); } - + + /// + /// Always uses the Scrutor assembly. + /// + /// + /// + /// To ensure proper assembly is resolved, use: + /// + /// services.Scan(s => s.FromAssemblies(Assembly.GetExecutingAssembly()) + /// + /// or + /// + /// var assembly = Assembly.GetCallingAssembly(); + /// services.Scan(s => s.FromAssemblies(assembly)); + /// + /// + /// + [Obsolete("Misleading, as it always uses Scrutor's assembly. Will be removed in a future release")] public IImplementationTypeSelector FromExecutingAssembly() { return FromAssemblies(Assembly.GetExecutingAssembly()); From 4d0ecf4ff85c111ba1692324e8d0cef0182042ec Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 25 Sep 2024 08:58:22 +0200 Subject: [PATCH 22/22] Update package versions --- src/Scrutor/Scrutor.csproj | 8 ++++---- test/Scrutor.Tests/Scrutor.Tests.csproj | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Scrutor/Scrutor.csproj b/src/Scrutor/Scrutor.csproj index 62238172..aee1d0b6 100644 --- a/src/Scrutor/Scrutor.csproj +++ b/src/Scrutor/Scrutor.csproj @@ -27,10 +27,10 @@ - - + + - - + + diff --git a/test/Scrutor.Tests/Scrutor.Tests.csproj b/test/Scrutor.Tests/Scrutor.Tests.csproj index fdfdef19..1d58602b 100644 --- a/test/Scrutor.Tests/Scrutor.Tests.csproj +++ b/test/Scrutor.Tests/Scrutor.Tests.csproj @@ -9,10 +9,10 @@ - - - - - + + + + +