diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 862bdf0d..f6bd59b8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,17 +14,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - - name: Setup .NET Core SDK - uses: actions/setup-dotnet@v1 + - name: Setup .NET 8 SDK + uses: actions/setup-dotnet@v3 with: - dotnet-version: "3.1.x" - - - name: Setup .NET 7 SDK - uses: actions/setup-dotnet@v1 - with: - dotnet-version: "7.0.x" + dotnet-version: "8.x" + dotnet-quality: "preview" - name: Test run: dotnet test --collect:"XPlat Code Coverage" 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/AttributeSelector.cs b/src/Scrutor/AttributeSelector.cs index ed142d9d..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) { @@ -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..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,20 +14,20 @@ 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) + 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."); - } + throw new InvalidOperationException($"Both {nameof(DecoratorType)} and {nameof(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/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..b392ff85 100644 --- a/src/Scrutor/DecorationStrategy.cs +++ b/src/Scrutor/DecorationStrategy.cs @@ -5,42 +5,52 @@ 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 abstract Func CreateDecorator(Type serviceType); - - internal static DecorationStrategy WithType(Type serviceType, Type decoratorType) => - Create(serviceType, decoratorType, decoratorFactory: null); - - internal static DecorationStrategy WithFactory(Type serviceType, Func decoratorFactory) => - Create(serviceType, decoratorType: null, decoratorFactory); - - protected static Func TypeDecorator(Type serviceType, Type decoratorType) => serviceProvider => + + 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, string? serviceKey, Type decoratorType) => + Create(serviceType, serviceKey, decoratorType, decoratorFactory: null); + + 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) { - var instanceToDecorate = serviceProvider.GetRequiredService(serviceType); - 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 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); }; - private static DecorationStrategy Create(Type serviceType, Type? decoratorType, 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/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/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/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/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..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; } -} \ No newline at end of file +} diff --git a/src/Scrutor/ImplementationTypeSelector.cs b/src/Scrutor/ImplementationTypeSelector.cs index d53220a9..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(); } -} \ 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/OpenGenericDecorationStrategy.cs b/src/Scrutor/OpenGenericDecorationStrategy.cs index 40a7ab75..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,27 +14,27 @@ 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() && (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."); + throw new InvalidOperationException($"Both {nameof(DecoratorType)} and {nameof(DecoratorFactory)} can not be null."); } } 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..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; } @@ -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/Scrutor.csproj b/src/Scrutor/Scrutor.csproj index bf6bb1de..aee1d0b6 100644 --- a/src/Scrutor/Scrutor.csproj +++ b/src/Scrutor/Scrutor.csproj @@ -1,9 +1,9 @@  Register services using assembly scanning and a fluent API. - 4.2.2 + 5.0.0 Kristian Hellang - net461;netstandard2.0;netcoreapp3.1;net6.0 + net462;netstandard2.0;net8.0 $(NoWarn);CS1591 11.0 enable @@ -23,19 +23,14 @@ true snupkg - 4.0.0.0 + 5.0.0.0 - - - - - - - - - - + + + + + diff --git a/src/Scrutor/ServiceCollectionExtensions.Decoration.cs b/src/Scrutor/ServiceCollectionExtensions.Decoration.cs index 55375e5f..ada3ab3e 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 . @@ -53,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)); } /// @@ -71,9 +73,9 @@ 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)); } - + /// /// Decorates all registered services of type /// using the function. @@ -123,7 +125,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 +142,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. @@ -197,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)); } /// @@ -215,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)); } /// @@ -250,27 +252,47 @@ public static bool TryDecorate(this IServiceCollection services, DecorationStrat { var serviceDescriptor = services[i]; - if (serviceDescriptor.ServiceType is DecoratedType) + if (IsDecorated(serviceDescriptor) || !strategy.CanDecorate(serviceDescriptor)) { - 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) => + descriptor.ServiceKey is string stringKey + && stringKey.EndsWith(DecoratedServiceKeySuffix, StringComparison.Ordinal); } 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..6388d3cc 100644 --- a/src/Scrutor/ServiceDescriptorExtensions.cs +++ b/src/Scrutor/ServiceDescriptorExtensions.cs @@ -5,14 +5,27 @@ 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) => + descriptor.IsKeyedService ? ReplaceServiceKey(descriptor, serviceKey) : AddServiceKey(descriptor, serviceKey); + + private static ServiceDescriptor ReplaceServiceKey(ServiceDescriptor descriptor, string serviceKey) => descriptor switch + { + { 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)) + }; + + private static ServiceDescriptor AddServiceKey(ServiceDescriptor descriptor, string serviceKey) => descriptor switch { - { 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), + { 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)) }; + + private static Func DiscardServiceKey(Func factory) => (sp, key) => factory(sp); } diff --git a/src/Scrutor/ServiceTypeSelector.cs b/src/Scrutor/ServiceTypeSelector.cs index 31fa18e6..c850d5e9 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; @@ -9,7 +10,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 +18,7 @@ public ServiceTypeSelector(IImplementationTypeSelector inner, IEnumerable private IImplementationTypeSelector Inner { get; } - private IEnumerable Types { get; } + private ISet Types { get; } private List Selectors { get; } = new(); @@ -56,10 +57,7 @@ public ILifetimeSelector AsImplementedInterfaces(Func predicate) { Preconditions.NotNull(predicate, nameof(predicate)); - return As(t => t.GetInterfaces() - .Where(x => x.HasMatchingGenericArity(t)) - .Select(x => x.GetRegistrationType(t)) - .Where(predicate)); + return As(t => GetInterfaces(t).Where(predicate)); } public ILifetimeSelector AsSelfWithInterfaces() @@ -69,7 +67,13 @@ public ILifetimeSelector AsSelfWithInterfaces() public ILifetimeSelector AsSelfWithInterfaces(Func predicate) { - IEnumerable Selector(Type type) + 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) { @@ -78,15 +82,8 @@ 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).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() @@ -235,6 +232,32 @@ void ISelector.Populate(IServiceCollection services, RegistrationStrategy? regis } } + 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 (!serviceType.HasMatchingGenericArity(implementationType)) + { + return false; + } + + if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + return false; + } + + if (serviceType == typeof(IEnumerable)) + { + return false; + } + + return true; + } + private ILifetimeSelector AddSelector(IEnumerable types, IEnumerable factories) { var selector = new LifetimeSelector(this, types, factories); 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/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 4443ba9d..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()); @@ -44,7 +79,13 @@ public IImplementationTypeSelector FromApplicationDependencies(Func library.GetDefaultAssemblyNames(context)); + .SelectMany(library => library.GetDefaultAssemblyNames(context)) + .ToHashSet(); var assemblies = LoadAssemblies(assemblyNames); @@ -76,19 +118,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); } } @@ -120,23 +164,13 @@ 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) { 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 +188,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 +213,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); 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/ScanningTests.cs b/test/Scrutor.Tests/ScanningTests.cs index 5cd05f21..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; @@ -250,7 +252,7 @@ public void CanFilterAttributeTypes() .AddClasses(t => t.AssignableTo()) .UsingAttributes()); - Assert.Equal(1, Collection.Count); + Assert.Single(Collection); var service = Collection.GetDescriptor(); @@ -266,7 +268,7 @@ public void CanFilterGenericAttributeTypes() .AddClasses(t => t.AssignableTo()) .UsingAttributes()); - Assert.Equal(1, Collection.Count); + Assert.Single(Collection); var service = Collection.GetDescriptor(); @@ -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 { } @@ -611,7 +624,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/Scrutor.Tests.csproj b/test/Scrutor.Tests/Scrutor.Tests.csproj index 95a1afd4..1d58602b 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 @@ - - - - - - - - - - - - + + + + + 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..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, + }); } -} \ No newline at end of file +}