From 6acf50692deddbff8d1e27464edc5d6246be51eb Mon Sep 17 00:00:00 2001 From: raymondhuy177 Date: Sun, 17 Jan 2021 21:18:20 +0700 Subject: [PATCH 1/5] add AsOpenTypesOf method --- ...enGenericScanningRegistrationExtensions.cs | 76 +++++++++++++++++++ ...nExtensions.OpenGenericAssemblyScanning.cs | 50 ++++++++++++ .../OpenGenericScanningRegistrationTests.cs | 54 +++++++++++++ 3 files changed, 180 insertions(+) diff --git a/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs b/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs index c70bb9902..1cd9c2122 100644 --- a/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs +++ b/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs @@ -169,5 +169,81 @@ private static void ConfigureFrom + /// Configures the scanning registration builder to register all open types of the specified open generic. + /// + /// The limit type. + /// The registration style. + /// The registration builder. + /// The open generic to register open types of. + /// The registration builder. + public static IRegistrationBuilder + AsOpenTypesOf( + IRegistrationBuilder registration, + Type openGenericServiceType) + { + if (openGenericServiceType == null) + { + throw new ArgumentNullException(nameof(openGenericServiceType)); + } + + return registration + .Where(candidateType => candidateType.IsOpenGenericTypeOf(openGenericServiceType)) + .As(candidateType => (Service)new TypedService(candidateType)); + } + + /// + /// Configures the scanning registration builder to register all open types of the specified open generic as a keyed service. + /// + /// The limit type. + /// The registration style. + /// The registration builder. + /// The open generic to register open types of. + /// The service key. + /// The registration builder. + public static IRegistrationBuilder + AsOpenTypesOf( + IRegistrationBuilder registration, + Type openGenericServiceType, + object serviceKey) + { + if (openGenericServiceType == null) + { + throw new ArgumentNullException(nameof(openGenericServiceType)); + } + + if (serviceKey == null) + { + throw new ArgumentNullException(nameof(serviceKey)); + } + + return AsOpenTypesOf(registration, openGenericServiceType, t => serviceKey); + } + + /// + /// Configures the scanning registration builder to register all open types of the specified open generic as a keyed service. + /// + /// The limit type. + /// The registration style. + /// The registration builder. + /// The open generic to register open types of. + /// A function to determine the service key for a given type. + /// The registration builder. + public static IRegistrationBuilder + AsOpenTypesOf( + IRegistrationBuilder registration, + Type openGenericServiceType, + Func serviceKeyMapping) + { + if (openGenericServiceType == null) + { + throw new ArgumentNullException(nameof(openGenericServiceType)); + } + + return registration + .Where(candidateType => candidateType.IsOpenGenericTypeOf(openGenericServiceType)) + .As(candidateType => (Service)new KeyedService(serviceKeyMapping(candidateType), candidateType)); + } } } diff --git a/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs b/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs index 7ce130fed..9ef3b4473 100644 --- a/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs +++ b/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs @@ -202,5 +202,55 @@ public static IRegistrationBuilder + /// Specifies that an open generic type from a scanned assembly is registered if it implements an interface + /// that opens the provided open generic interface type. + /// + /// Registration limit type. + /// Registration style. + /// Registration to set service mapping on. + /// The open generic interface or base class type for which implementations will be found. + /// Registration builder allowing the registration to be configured. + public static IRegistrationBuilder + AsOpenTypesOf( + this IRegistrationBuilder registration, Type openGenericServiceType) + { + return ScanningRegistrationExtensions.AsOpenTypesOf(registration, openGenericServiceType); + } + + /// + /// Specifies that an open generic type from a scanned assembly is registered if it implements an interface + /// that opens the provided open generic interface type. + /// + /// Registration limit type. + /// Registration style. + /// Registration to set service mapping on. + /// The open generic interface or base class type for which implementations will be found. + /// Key of the service. + /// Registration builder allowing the registration to be configured. + public static IRegistrationBuilder + AsOpenTypesOf( + this IRegistrationBuilder registration, Type openGenericServiceType, object serviceKey) + { + return ScanningRegistrationExtensions.AsOpenTypesOf(registration, openGenericServiceType, serviceKey); + } + + /// + /// Specifies that an open generic type from a scanned assembly is registered if it implements an interface + /// that opens the provided open generic interface type. + /// + /// Registration limit type. + /// Registration style. + /// Registration to set service mapping on. + /// The open generic interface or base class type for which implementations will be found. + /// Function mapping types to service keys. + /// Registration builder allowing the registration to be configured. + public static IRegistrationBuilder + AsOpenTypesOf( + this IRegistrationBuilder registration, Type openGenericServiceType, Func serviceKeyMapping) + { + return ScanningRegistrationExtensions.AsOpenTypesOf(registration, openGenericServiceType, serviceKeyMapping); + } } } diff --git a/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs b/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs index 620e3f723..1a5a89b66 100644 --- a/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs +++ b/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -97,5 +98,58 @@ public void WhenExceptionsProvideConfigurationComponentConfiguredAppropriately() var a2 = c.Resolve>(); Assert.Same(a1, a2); } + + [Fact] + public void AsOpenTypesOfNullTypeProvidedThrowsException() + { + var cb = new ContainerBuilder(); + Assert.Throws(() => cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly). + AsOpenTypesOf(null)); + } + + [Fact] + public void AsOpenTypesOfOpenGenericInterfaceTypeProvidedOpenGenericTypesRegistered() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .AsOpenTypesOf(typeof(ICommand<>)); + var c = cb.Build(); + + Assert.NotNull(c.Resolve>()); + } + + [Fact] + public void AsOpenTypesOfOpenGenericAbstractClassTypeProvidedOpenGenericTypesRegistered() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .AsOpenTypesOf(typeof(CommandBase<>)); + var c = cb.Build(); + + Assert.NotNull(c.Resolve>()); + } + + [Fact] + public void AsOpenTypesOfWithServiceKeyShouldAssignKeyToAllRegistrations() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .AsOpenTypesOf(typeof(ICommand<>), "command"); + var c = cb.Build(); + + Assert.Throws(() => c.Resolve>()); + Assert.NotNull(c.ResolveKeyed>("command")); + } + + [Fact] + public void AsOpenTypesOfWithServiceKeyMappingShouldAssignKeyResultToEachRegistration() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .AsOpenTypesOf(typeof(ICommand<>), t => t); + var c = cb.Build(); + + Assert.NotNull(c.ResolveKeyed>(typeof(RedoOpenGenericCommand<>))); + } } } From 5f13fe20d69e1678bd09fae66cb773fe9dde1a33 Mon Sep 17 00:00:00 2001 From: raymondhuy177 Date: Sat, 23 Jan 2021 16:54:06 +0700 Subject: [PATCH 2/5] add WithMetadata, WithMetadataFrom, AsImplementedInterfaces method --- ...enGenericScanningRegistrationExtensions.cs | 60 ++++++++++++ ...RegistrationExtensions.AssemblyScanning.cs | 8 ++ ...nExtensions.OpenGenericAssemblyScanning.cs | 64 +++++++++++++ src/Autofac/Util/TypeExtensions.cs | 1 - .../OpenGenericScannedComponentWithName.cs | 10 ++ .../OpenGenericAComponent.cs | 9 ++ .../OpenGenericScanningRegistrationTests.cs | 92 +++++++++++++++++++ 7 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 test/Autofac.Test.Scenarios.ScannedAssembly/MetadataAttributeScanningScenario/OpenGenericScannedComponentWithName.cs create mode 100644 test/Autofac.Test.Scenarios.ScannedAssembly/OpenGenericAComponent.cs diff --git a/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs b/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs index 1cd9c2122..ef68ba6de 100644 --- a/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs +++ b/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Reflection; using Autofac.Builder; @@ -245,5 +246,64 @@ public static IRegistrationBuilder candidateType.IsOpenGenericTypeOf(openGenericServiceType)) .As(candidateType => (Service)new KeyedService(serviceKeyMapping(candidateType), candidateType)); } + + /// + /// Specify how an open generic type from a scanned assembly provides metadata. + /// + /// Registration limit type. + /// Registration style. + /// Registration to set metadata on. + /// A function mapping the type to a list of metadata items. + /// Registration builder allowing the registration to be configured. + public static IRegistrationBuilder + WithMetadata( + this IRegistrationBuilder registration, + Func>> metadataMapping) + { + if (registration == null) + { + throw new ArgumentNullException(nameof(registration)); + } + + registration.ActivatorData.ConfigurationActions.Add((t, rb) => rb.WithMetadata(metadataMapping(t))); + return registration; + } + + /// + /// Use the properties of an attribute (or interface implemented by an attribute) on the scanned type + /// to provide metadata values. + /// + /// Inherited attributes are supported; however, there must be at most one matching attribute + /// in the inheritance chain. + /// The attribute applied to the scanned type. + /// Registration to set metadata on. + /// Registration builder allowing the registration to be configured. + public static IRegistrationBuilder + WithMetadataFrom( + this IRegistrationBuilder registration) + { + var attrType = typeof(TAttribute); + var metadataProperties = attrType + .GetRuntimeProperties() + .Where(pi => pi.CanRead); + + return registration.WithMetadata(t => + { + var attrs = t.GetCustomAttributes(true).OfType().ToList(); + + if (attrs.Count == 0) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, RegistrationExtensionsResources.MetadataAttributeNotFound, typeof(TAttribute), t)); + } + + if (attrs.Count != 1) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, RegistrationExtensionsResources.MultipleMetadataAttributesSameType, typeof(TAttribute), t)); + } + + var attr = attrs[0]; + return metadataProperties.Select(p => new KeyValuePair(p.Name, p.GetValue(attr, null))); + }); + } } } diff --git a/src/Autofac/RegistrationExtensions.AssemblyScanning.cs b/src/Autofac/RegistrationExtensions.AssemblyScanning.cs index 2e4112ffc..f7efe15e5 100644 --- a/src/Autofac/RegistrationExtensions.AssemblyScanning.cs +++ b/src/Autofac/RegistrationExtensions.AssemblyScanning.cs @@ -245,6 +245,14 @@ private static Type[] GetImplementedInterfaces(Type type) return type.IsInterface ? interfaces.AppendItem(type).ToArray() : interfaces.ToArray(); } + private static Type[] GetOpenGenericImplementedInterfaces(this Type @this) + { + return @this.GetInterfaces() + .Where(it => it.IsGenericType) + .Select(it => it.GetGenericTypeDefinition()) + .ToArray(); + } + /// /// Specifies that the components being registered should only be made the default for services /// that have not already been registered. diff --git a/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs b/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs index 9ef3b4473..a9ca1e1b2 100644 --- a/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs +++ b/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs @@ -252,5 +252,69 @@ public static IRegistrationBuilder + /// Filters the scanned open generic types to include only those in the namespace of the provided type + /// or one of its sub-namespaces. + /// + /// Registration to filter types from. + /// A type in the target namespace. + /// Registration builder allowing the registration to be configured. + public static IRegistrationBuilder + InNamespaceOf(this IRegistrationBuilder registration) + { + if (registration == null) + { + throw new ArgumentNullException(nameof(registration)); + } + + // Namespace is always non-null for concrete type parameters. + return registration.InNamespace(typeof(T).Namespace!); + } + + /// + /// Filters the scanned types to include only those in the provided namespace + /// or one of its sub-namespaces. + /// + /// Registration limit type. + /// Registration style. + /// Registration to filter types from. + /// The namespace from which types will be selected. + /// Registration builder allowing the registration to be configured. + public static IRegistrationBuilder + InNamespace( + this IRegistrationBuilder registration, + string ns) + { + if (registration == null) + { + throw new ArgumentNullException(nameof(registration)); + } + + if (ns == null) + { + throw new ArgumentNullException(nameof(ns)); + } + + return registration.Where(t => t.IsInNamespace(ns)); + } + + /// + /// Specifies that an open generic type from a scanned assembly is registered as providing all of its + /// implemented interfaces. + /// + /// Registration limit type. + /// Registration to set service mapping on. + /// Registration builder allowing the registration to be configured. + public static IRegistrationBuilder + AsImplementedInterfaces(this IRegistrationBuilder registration) + { + if (registration == null) + { + throw new ArgumentNullException(nameof(registration)); + } + + return registration.As(t => t.GetOpenGenericImplementedInterfaces()); + } } } diff --git a/src/Autofac/Util/TypeExtensions.cs b/src/Autofac/Util/TypeExtensions.cs index ac2b9d3e6..795ddcced 100644 --- a/src/Autofac/Util/TypeExtensions.cs +++ b/src/Autofac/Util/TypeExtensions.cs @@ -223,7 +223,6 @@ private static bool CheckBaseTypeIsOpenGenericTypeOf(this Type @this, Type type) private static bool CheckInterfacesAreOpenGenericTypeOf(this Type @this, Type type) { - var interfaces = @this.GetInterfaces().ToList(); return @this.GetInterfaces() .Any(it => it.IsGenericType ? it.GetGenericTypeDefinition().IsOpenGenericTypeOf(type) diff --git a/test/Autofac.Test.Scenarios.ScannedAssembly/MetadataAttributeScanningScenario/OpenGenericScannedComponentWithName.cs b/test/Autofac.Test.Scenarios.ScannedAssembly/MetadataAttributeScanningScenario/OpenGenericScannedComponentWithName.cs new file mode 100644 index 000000000..db83d64f4 --- /dev/null +++ b/test/Autofac.Test.Scenarios.ScannedAssembly/MetadataAttributeScanningScenario/OpenGenericScannedComponentWithName.cs @@ -0,0 +1,10 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Autofac.Test.Scenarios.ScannedAssembly.MetadataAttributeScanningScenario +{ + [Name("My Name")] + public class OpenGenericScannedComponentWithName + { + } +} diff --git a/test/Autofac.Test.Scenarios.ScannedAssembly/OpenGenericAComponent.cs b/test/Autofac.Test.Scenarios.ScannedAssembly/OpenGenericAComponent.cs new file mode 100644 index 000000000..6f7b47fdc --- /dev/null +++ b/test/Autofac.Test.Scenarios.ScannedAssembly/OpenGenericAComponent.cs @@ -0,0 +1,9 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Autofac.Test.Scenarios.ScannedAssembly +{ + public class OpenGenericAComponent + { + } +} diff --git a/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs b/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs index 1a5a89b66..c243ffe77 100644 --- a/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs +++ b/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs @@ -8,8 +8,10 @@ using Autofac.Core; using Autofac.Core.Lifetime; using Autofac.Core.Registration; +using Autofac.Features.Metadata; using Autofac.Features.Scanning; using Autofac.Test.Scenarios.ScannedAssembly; +using Autofac.Test.Scenarios.ScannedAssembly.MetadataAttributeScanningScenario; using Xunit; namespace Autofac.Test.Features.Scanning @@ -151,5 +153,95 @@ public void AsOpenTypesOfWithServiceKeyMappingShouldAssignKeyResultToEachRegistr Assert.NotNull(c.ResolveKeyed>(typeof(RedoOpenGenericCommand<>))); } + + [Fact] + public void AsImplementedInterfacesRegistersImplementedInterfaces() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .AsImplementedInterfaces(); + var c = cb.Build(); + + Assert.NotNull(c.Resolve>()); + } + + [Fact] + public void WhenFilterAppliedDefaultSelfRegistrationOmitted() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .AsImplementedInterfaces(); + var c = cb.Build(); + + Assert.Throws(() => c.Resolve>()); + } + + [Fact] + public void AsSelfExposesConcreteTypeAsService() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .AsImplementedInterfaces() + .AsSelf(); + var c = cb.Build(); + + Assert.NotNull(c.Resolve>()); + } + + [Fact] + public void WhenMetadataMappingAppliedValuesCalculatedFromType() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .WithMetadata(t => t.GetMethods().ToDictionary(m => m.Name, m => (object)m.ReturnType)); + + var c = cb.Build(); + var s = c.Resolve>>(); + + Assert.True(s.Metadata.ContainsKey("Execute")); + } + + [Fact] + public void MetadataCanBeScannedFromAMatchingAttributeInterface() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .Where(t => t == typeof(OpenGenericScannedComponentWithName<>)) + .WithMetadataFrom(); + + var c = cb.Build(); + + c.ComponentRegistry.TryGetRegistration(new TypedService(typeof(OpenGenericScannedComponentWithName)), out IComponentRegistration r); + + r.Metadata.TryGetValue("Name", out object name); + + Assert.Equal("My Name", name); + } + + [Fact] + public void InNamespaceLimitsServicesToBeRegistered() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .InNamespace("Autofac.Test.Scenarios.ScannedAssembly.MetadataAttributeScanningScenario"); + + var c = cb.Build(); + + Assert.NotNull(c.Resolve>()); + Assert.Throws(() => c.Resolve>()); + } + + [Fact] + public void InNamespaceOfLimitsServicesToBeRegistered() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .InNamespaceOf(); + + var c = cb.Build(); + + Assert.NotNull(c.Resolve>()); + Assert.Throws(() => c.Resolve>()); + } } } From ec726238404f92fba622e4aafbda7fd7a2da41e0 Mon Sep 17 00:00:00 2001 From: raymondhuy177 Date: Sat, 30 Jan 2021 22:50:30 +0700 Subject: [PATCH 3/5] Change method name --- ...enGenericScanningRegistrationExtensions.cs | 6 ++--- ...nExtensions.OpenGenericAssemblyScanning.cs | 23 ++++++++----------- .../OpenGenericScanningRegistrationTests.cs | 22 +++++++++--------- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs b/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs index ef68ba6de..7f94ad3d4 100644 --- a/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs +++ b/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs @@ -172,15 +172,15 @@ private static void ConfigureFrom - /// Configures the scanning registration builder to register all open types of the specified open generic. + /// Filters the scanned types to include only those assignable to the provided. /// /// The limit type. /// The registration style. /// The registration builder. - /// The open generic to register open types of. + /// The type or interface which all classes must be assignable from. /// The registration builder. public static IRegistrationBuilder - AsOpenTypesOf( + AssignableTo( IRegistrationBuilder registration, Type openGenericServiceType) { diff --git a/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs b/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs index a9ca1e1b2..0ca357a7c 100644 --- a/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs +++ b/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs @@ -204,50 +204,47 @@ public static IRegistrationBuilder - /// Specifies that an open generic type from a scanned assembly is registered if it implements an interface - /// that opens the provided open generic interface type. + /// Filters the scanned types to include only those assignable to the provided. /// /// Registration limit type. /// Registration style. /// Registration to set service mapping on. - /// The open generic interface or base class type for which implementations will be found. + /// The type or interface which all classes must be assignable from. /// Registration builder allowing the registration to be configured. public static IRegistrationBuilder - AsOpenTypesOf( + AssignableTo( this IRegistrationBuilder registration, Type openGenericServiceType) { - return ScanningRegistrationExtensions.AsOpenTypesOf(registration, openGenericServiceType); + return ScanningRegistrationExtensions.AssignableTo(registration, openGenericServiceType); } /// - /// Specifies that an open generic type from a scanned assembly is registered if it implements an interface - /// that opens the provided open generic interface type. + /// Filters the scanned types to include only those assignable to the provided. /// /// Registration limit type. /// Registration style. /// Registration to set service mapping on. - /// The open generic interface or base class type for which implementations will be found. + /// The type or interface which all classes must be assignable from. /// Key of the service. /// Registration builder allowing the registration to be configured. public static IRegistrationBuilder - AsOpenTypesOf( + AssignableTo( this IRegistrationBuilder registration, Type openGenericServiceType, object serviceKey) { return ScanningRegistrationExtensions.AsOpenTypesOf(registration, openGenericServiceType, serviceKey); } /// - /// Specifies that an open generic type from a scanned assembly is registered if it implements an interface - /// that opens the provided open generic interface type. + /// Filters the scanned types to include only those assignable to the provided. /// /// Registration limit type. /// Registration style. /// Registration to set service mapping on. - /// The open generic interface or base class type for which implementations will be found. + /// The type or interface which all classes must be assignable from. /// Function mapping types to service keys. /// Registration builder allowing the registration to be configured. public static IRegistrationBuilder - AsOpenTypesOf( + AssignableTo( this IRegistrationBuilder registration, Type openGenericServiceType, Func serviceKeyMapping) { return ScanningRegistrationExtensions.AsOpenTypesOf(registration, openGenericServiceType, serviceKeyMapping); diff --git a/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs b/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs index c243ffe77..18b711697 100644 --- a/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs +++ b/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs @@ -102,41 +102,41 @@ public void WhenExceptionsProvideConfigurationComponentConfiguredAppropriately() } [Fact] - public void AsOpenTypesOfNullTypeProvidedThrowsException() + public void AssignableToNullTypeProvidedThrowsException() { var cb = new ContainerBuilder(); - Assert.Throws(() => cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly). - AsOpenTypesOf(null)); + Assert.Throws(() => cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .AssignableTo(null)); } [Fact] - public void AsOpenTypesOfOpenGenericInterfaceTypeProvidedOpenGenericTypesRegistered() + public void AssignableToOpenGenericInterfaceTypeProvidedOpenGenericTypesRegistered() { var cb = new ContainerBuilder(); cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) - .AsOpenTypesOf(typeof(ICommand<>)); + .AssignableTo(typeof(ICommand<>)); var c = cb.Build(); Assert.NotNull(c.Resolve>()); } [Fact] - public void AsOpenTypesOfOpenGenericAbstractClassTypeProvidedOpenGenericTypesRegistered() + public void AssignableToOpenGenericAbstractClassTypeProvidedOpenGenericTypesRegistered() { var cb = new ContainerBuilder(); cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) - .AsOpenTypesOf(typeof(CommandBase<>)); + .AssignableTo(typeof(CommandBase<>)); var c = cb.Build(); Assert.NotNull(c.Resolve>()); } [Fact] - public void AsOpenTypesOfWithServiceKeyShouldAssignKeyToAllRegistrations() + public void AssignableToWithServiceKeyShouldAssignKeyToAllRegistrations() { var cb = new ContainerBuilder(); cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) - .AsOpenTypesOf(typeof(ICommand<>), "command"); + .AssignableTo(typeof(ICommand<>), "command"); var c = cb.Build(); Assert.Throws(() => c.Resolve>()); @@ -144,11 +144,11 @@ public void AsOpenTypesOfWithServiceKeyShouldAssignKeyToAllRegistrations() } [Fact] - public void AsOpenTypesOfWithServiceKeyMappingShouldAssignKeyResultToEachRegistration() + public void AssignableToWithServiceKeyMappingShouldAssignKeyResultToEachRegistration() { var cb = new ContainerBuilder(); cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) - .AsOpenTypesOf(typeof(ICommand<>), t => t); + .AssignableTo(typeof(ICommand<>), t => t); var c = cb.Build(); Assert.NotNull(c.ResolveKeyed>(typeof(RedoOpenGenericCommand<>))); From 67e08776f26ee4b1037704b4310f4c0df8d25529 Mon Sep 17 00:00:00 2001 From: raymondhuy177 Date: Sun, 31 Jan 2021 00:36:48 +0700 Subject: [PATCH 4/5] add missing testcase --- ...enGenericScanningRegistrationExtensions.cs | 19 +++---- ...nExtensions.OpenGenericAssemblyScanning.cs | 21 ++------ .../DuplicatedNameAttribute.cs | 17 +++++++ ...enericScannedComponentWithMultipleNames.cs | 11 ++++ .../OpenGenericScanningRegistrationTests.cs | 50 +++++++++++++++++++ 5 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 test/Autofac.Test.Scenarios.ScannedAssembly/MetadataAttributeScanningScenario/DuplicatedNameAttribute.cs create mode 100644 test/Autofac.Test.Scenarios.ScannedAssembly/MetadataAttributeScanningScenario/OpenGenericScannedComponentWithMultipleNames.cs diff --git a/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs b/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs index 7f94ad3d4..ea68d35e1 100644 --- a/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs +++ b/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs @@ -195,16 +195,16 @@ public static IRegistrationBuilder - /// Configures the scanning registration builder to register all open types of the specified open generic as a keyed service. + /// Filters the scanned types to include only those assignable to the provided. /// /// The limit type. /// The registration style. /// The registration builder. - /// The open generic to register open types of. + /// The type or interface which all classes must be assignable from. /// The service key. /// The registration builder. public static IRegistrationBuilder - AsOpenTypesOf( + AssignableTo( IRegistrationBuilder registration, Type openGenericServiceType, object serviceKey) @@ -219,20 +219,20 @@ public static IRegistrationBuilder serviceKey); + return AssignableTo(registration, openGenericServiceType, t => serviceKey); } /// - /// Configures the scanning registration builder to register all open types of the specified open generic as a keyed service. + /// Filters the scanned types to include only those assignable to the provided. /// /// The limit type. /// The registration style. /// The registration builder. - /// The open generic to register open types of. + /// The type or interface which all classes must be assignable from. /// A function to determine the service key for a given type. /// The registration builder. public static IRegistrationBuilder - AsOpenTypesOf( + AssignableTo( IRegistrationBuilder registration, Type openGenericServiceType, Func serviceKeyMapping) @@ -260,11 +260,6 @@ public static IRegistrationBuilder registration, Func>> metadataMapping) { - if (registration == null) - { - throw new ArgumentNullException(nameof(registration)); - } - registration.ActivatorData.ConfigurationActions.Add((t, rb) => rb.WithMetadata(metadataMapping(t))); return registration; } diff --git a/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs b/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs index 0ca357a7c..947f3ff51 100644 --- a/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs +++ b/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs @@ -231,7 +231,7 @@ public static IRegistrationBuilder( this IRegistrationBuilder registration, Type openGenericServiceType, object serviceKey) { - return ScanningRegistrationExtensions.AsOpenTypesOf(registration, openGenericServiceType, serviceKey); + return ScanningRegistrationExtensions.AssignableTo(registration, openGenericServiceType, serviceKey); } /// @@ -247,7 +247,7 @@ public static IRegistrationBuilder( this IRegistrationBuilder registration, Type openGenericServiceType, Func serviceKeyMapping) { - return ScanningRegistrationExtensions.AsOpenTypesOf(registration, openGenericServiceType, serviceKeyMapping); + return ScanningRegistrationExtensions.AssignableTo(registration, openGenericServiceType, serviceKeyMapping); } /// @@ -260,11 +260,6 @@ public static IRegistrationBuilder InNamespaceOf(this IRegistrationBuilder registration) { - if (registration == null) - { - throw new ArgumentNullException(nameof(registration)); - } - // Namespace is always non-null for concrete type parameters. return registration.InNamespace(typeof(T).Namespace!); } @@ -283,12 +278,7 @@ public static IRegistrationBuilder registration, string ns) { - if (registration == null) - { - throw new ArgumentNullException(nameof(registration)); - } - - if (ns == null) + if (string.IsNullOrEmpty(ns)) { throw new ArgumentNullException(nameof(ns)); } @@ -306,11 +296,6 @@ public static IRegistrationBuilder AsImplementedInterfaces(this IRegistrationBuilder registration) { - if (registration == null) - { - throw new ArgumentNullException(nameof(registration)); - } - return registration.As(t => t.GetOpenGenericImplementedInterfaces()); } } diff --git a/test/Autofac.Test.Scenarios.ScannedAssembly/MetadataAttributeScanningScenario/DuplicatedNameAttribute.cs b/test/Autofac.Test.Scenarios.ScannedAssembly/MetadataAttributeScanningScenario/DuplicatedNameAttribute.cs new file mode 100644 index 000000000..2c6c9cf98 --- /dev/null +++ b/test/Autofac.Test.Scenarios.ScannedAssembly/MetadataAttributeScanningScenario/DuplicatedNameAttribute.cs @@ -0,0 +1,17 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; + +namespace Autofac.Test.Scenarios.ScannedAssembly.MetadataAttributeScanningScenario +{ + public class DuplicatedNameAttribute : Attribute, IHaveName + { + public DuplicatedNameAttribute(string name) + { + Name = name ?? throw new ArgumentNullException("name"); + } + + public string Name { get; } + } +} diff --git a/test/Autofac.Test.Scenarios.ScannedAssembly/MetadataAttributeScanningScenario/OpenGenericScannedComponentWithMultipleNames.cs b/test/Autofac.Test.Scenarios.ScannedAssembly/MetadataAttributeScanningScenario/OpenGenericScannedComponentWithMultipleNames.cs new file mode 100644 index 000000000..47f50a06b --- /dev/null +++ b/test/Autofac.Test.Scenarios.ScannedAssembly/MetadataAttributeScanningScenario/OpenGenericScannedComponentWithMultipleNames.cs @@ -0,0 +1,11 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Autofac.Test.Scenarios.ScannedAssembly.MetadataAttributeScanningScenario +{ + [Name("My Name")] + [DuplicatedName("My Name 2")] + public class OpenGenericScannedComponentWithMultipleNames + { + } +} diff --git a/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs b/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs index 18b711697..785b61c2b 100644 --- a/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs +++ b/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Reflection; using Autofac.Core; @@ -107,6 +108,15 @@ public void AssignableToNullTypeProvidedThrowsException() var cb = new ContainerBuilder(); Assert.Throws(() => cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) .AssignableTo(null)); + + Assert.Throws(() => cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .AssignableTo(typeof(RedoOpenGenericCommand<>), (object)null)); + + Assert.Throws(() => cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .AssignableTo(null, "serviceKey")); + + Assert.Throws(() => cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .AssignableTo(null, t => t)); } [Fact] @@ -201,6 +211,36 @@ public void WhenMetadataMappingAppliedValuesCalculatedFromType() Assert.True(s.Metadata.ContainsKey("Execute")); } + [Fact] + public void WhenMetadataNotFoundThrowException() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .Where(t => t == typeof(OpenGenericScannedComponentWithName<>)) + .WithMetadataFrom(); + + var ex = Assert.Throws(() => cb.Build()); + + Assert.Equal( + string.Format(CultureInfo.CurrentCulture, RegistrationExtensionsResources.MetadataAttributeNotFound, typeof(ICloseCommand), typeof(OpenGenericScannedComponentWithName<>)), + ex.Message); + } + + [Fact] + public void WhenMultipleMetadataAttributesSameTypeThrowException() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .Where(t => t == typeof(OpenGenericScannedComponentWithMultipleNames<>)) + .WithMetadataFrom(); + + var ex = Assert.Throws(() => cb.Build()); + + Assert.Equal( + string.Format(CultureInfo.CurrentCulture, RegistrationExtensionsResources.MultipleMetadataAttributesSameType, typeof(IHaveName), typeof(OpenGenericScannedComponentWithMultipleNames<>)), + ex.Message); + } + [Fact] public void MetadataCanBeScannedFromAMatchingAttributeInterface() { @@ -218,6 +258,16 @@ public void MetadataCanBeScannedFromAMatchingAttributeInterface() Assert.Equal("My Name", name); } + [Fact] + public void InNamespaceNullProvidedThrowException() + { + var cb = new ContainerBuilder(); + var ex = Assert.Throws(() => + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly).InNamespace(ns: "")); + + Assert.Equal("ns", ex.ParamName); + } + [Fact] public void InNamespaceLimitsServicesToBeRegistered() { From 3282ad8a2b7902efde24ccc5c52ff891fc70a303 Mon Sep 17 00:00:00 2001 From: raymondhuy177 Date: Wed, 10 Feb 2021 17:31:46 +0700 Subject: [PATCH 5/5] add test case --- test/Autofac.Test/Assertions.cs | 22 ++++++++++++++++ .../OpenGenericScanningRegistrationTests.cs | 25 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/test/Autofac.Test/Assertions.cs b/test/Autofac.Test/Assertions.cs index 6c3d69b7a..2104c0aa5 100644 --- a/test/Autofac.Test/Assertions.cs +++ b/test/Autofac.Test/Assertions.cs @@ -4,7 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; +using Autofac.Builder; using Autofac.Core; +using Autofac.Features.Indexed; +using Autofac.Features.OpenGenerics; using Xunit; namespace Autofac.Test @@ -92,6 +96,24 @@ public static void AssertComponentRegistrationOrder + { + if (source is OpenGenericRegistrationSource) + { + var activatorData = typeof(OpenGenericRegistrationSource) + .GetField("_activatorData", BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(source) as ReflectionActivatorData; + return activatorData.ImplementationType != typeof(KeyedServiceIndex<,>); + } + else + { + return false; + } + }); + } + private static IEnumerable LookForComponents(this IEnumerable registrations, IEnumerable types) { return registrations diff --git a/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs b/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs index 785b61c2b..aad90c9cc 100644 --- a/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs +++ b/test/Autofac.Test/Features/Scanning/OpenGenericScanningRegistrationTests.cs @@ -10,6 +10,7 @@ using Autofac.Core.Lifetime; using Autofac.Core.Registration; using Autofac.Features.Metadata; +using Autofac.Features.OpenGenerics; using Autofac.Features.Scanning; using Autofac.Test.Scenarios.ScannedAssembly; using Autofac.Test.Scenarios.ScannedAssembly.MetadataAttributeScanningScenario; @@ -119,6 +120,30 @@ public void AssignableToNullTypeProvidedThrowsException() .AssignableTo(null, t => t)); } + [Theory] + [InlineData(typeof(ICloseCommand))] + [InlineData(typeof(CloseCommand))] + public void AssignableToClosedTypeProvidedNoneOpenGenericSourceRegistered(Type closedType) + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .AssignableTo(closedType); + var c = cb.Build(); + + Assert.False(c.RegisteredAnyOpenGenericTypeFromScanningAssembly()); + } + + [Fact] + public void ServiceIsNotAssignableToIsNotRegistered() + { + var cb = new ContainerBuilder(); + cb.RegisterAssemblyOpenGenericTypes(typeof(ICommand<>).GetTypeInfo().Assembly) + .AssignableTo(typeof(RedoOpenGenericCommand<>)); + var c = cb.Build(); + + Assert.Throws(() => c.Resolve>()); + } + [Fact] public void AssignableToOpenGenericInterfaceTypeProvidedOpenGenericTypesRegistered() {