Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open generic scanning extensions #1246

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Autofac.Builder;
Expand Down Expand Up @@ -169,5 +170,135 @@ private static void ConfigureFrom<TActivatorData, TScanStyle, TRegistrationBuild
action(type, scanned);
}
}

/// <summary>
/// Filters the scanned types to include only those assignable to the provided.
/// </summary>
/// <typeparam name="TLimit">The limit type.</typeparam>
/// <typeparam name="TRegistrationStyle">The registration style.</typeparam>
/// <param name="registration">The registration builder.</param>
/// <param name="openGenericServiceType">The type or interface which all classes must be assignable from.</param>
/// <returns>The registration builder.</returns>
public static IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle>
AssignableTo<TLimit, TRegistrationStyle>(
IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle> registration,
Type openGenericServiceType)
{
if (openGenericServiceType == null)
{
throw new ArgumentNullException(nameof(openGenericServiceType));
}

return registration
.Where(candidateType => candidateType.IsOpenGenericTypeOf(openGenericServiceType))
.As(candidateType => (Service)new TypedService(candidateType));
}

/// <summary>
/// Filters the scanned types to include only those assignable to the provided.
/// </summary>
/// <typeparam name="TLimit">The limit type.</typeparam>
/// <typeparam name="TRegistrationStyle">The registration style.</typeparam>
/// <param name="registration">The registration builder.</param>
/// <param name="openGenericServiceType">The type or interface which all classes must be assignable from.</param>
/// <param name="serviceKey">The service key.</param>
/// <returns>The registration builder.</returns>
public static IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle>
AssignableTo<TLimit, TRegistrationStyle>(
IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle> registration,
Type openGenericServiceType,
object serviceKey)
{
if (openGenericServiceType == null)
{
throw new ArgumentNullException(nameof(openGenericServiceType));
}

if (serviceKey == null)
{
throw new ArgumentNullException(nameof(serviceKey));
}

return AssignableTo(registration, openGenericServiceType, t => serviceKey);
}

/// <summary>
/// Filters the scanned types to include only those assignable to the provided.
/// </summary>
/// <typeparam name="TLimit">The limit type.</typeparam>
/// <typeparam name="TRegistrationStyle">The registration style.</typeparam>
/// <param name="registration">The registration builder.</param>
/// <param name="openGenericServiceType">The type or interface which all classes must be assignable from.</param>
/// <param name="serviceKeyMapping">A function to determine the service key for a given type.</param>
/// <returns>The registration builder.</returns>
public static IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle>
AssignableTo<TLimit, TRegistrationStyle>(
IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle> registration,
Type openGenericServiceType,
Func<Type, object> serviceKeyMapping)
{
if (openGenericServiceType == null)
{
throw new ArgumentNullException(nameof(openGenericServiceType));
}

return registration
.Where(candidateType => candidateType.IsOpenGenericTypeOf(openGenericServiceType))
.As(candidateType => (Service)new KeyedService(serviceKeyMapping(candidateType), candidateType));
}

/// <summary>
/// Specify how an open generic type from a scanned assembly provides metadata.
/// </summary>
/// <typeparam name="TLimit">Registration limit type.</typeparam>
/// <typeparam name="TRegistrationStyle">Registration style.</typeparam>
/// <param name="registration">Registration to set metadata on.</param>
/// <param name="metadataMapping">A function mapping the type to a list of metadata items.</param>
/// <returns>Registration builder allowing the registration to be configured.</returns>
public static IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle>
WithMetadata<TLimit, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle> registration,
Func<Type, IEnumerable<KeyValuePair<string, object?>>> metadataMapping)
{
registration.ActivatorData.ConfigurationActions.Add((t, rb) => rb.WithMetadata(metadataMapping(t)));
return registration;
}

/// <summary>
/// Use the properties of an attribute (or interface implemented by an attribute) on the scanned type
/// to provide metadata values.
/// </summary>
/// <remarks>Inherited attributes are supported; however, there must be at most one matching attribute
/// in the inheritance chain.</remarks>
/// <typeparam name="TAttribute">The attribute applied to the scanned type.</typeparam>
/// <param name="registration">Registration to set metadata on.</param>
/// <returns>Registration builder allowing the registration to be configured.</returns>
public static IRegistrationBuilder<object, OpenGenericScanningActivatorData, DynamicRegistrationStyle>
WithMetadataFrom<TAttribute>(
this IRegistrationBuilder<object, OpenGenericScanningActivatorData, DynamicRegistrationStyle> registration)
{
var attrType = typeof(TAttribute);
var metadataProperties = attrType
.GetRuntimeProperties()
.Where(pi => pi.CanRead);

return registration.WithMetadata(t =>
{
var attrs = t.GetCustomAttributes(true).OfType<TAttribute>().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<string, object?>(p.Name, p.GetValue(attr, null)));
});
}
}
}
8 changes: 8 additions & 0 deletions src/Autofac/RegistrationExtensions.AssemblyScanning.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

/// <summary>
/// Specifies that the components being registered should only be made the default for services
/// that have not already been registered.
Expand Down
96 changes: 96 additions & 0 deletions src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,5 +202,101 @@ public static IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRe
registration.ActivatorData.Filters.Add(predicate);
return registration;
}

/// <summary>
/// Filters the scanned types to include only those assignable to the provided.
/// </summary>
/// <typeparam name="TLimit">Registration limit type.</typeparam>
/// <typeparam name="TRegistrationStyle">Registration style.</typeparam>
/// <param name="registration">Registration to set service mapping on.</param>
/// <param name="openGenericServiceType">The type or interface which all classes must be assignable from.</param>
/// <returns>Registration builder allowing the registration to be configured.</returns>
public static IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle>
AssignableTo<TLimit, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle> registration, Type openGenericServiceType)
{
return ScanningRegistrationExtensions.AssignableTo(registration, openGenericServiceType);
}

/// <summary>
/// Filters the scanned types to include only those assignable to the provided.
/// </summary>
/// <typeparam name="TLimit">Registration limit type.</typeparam>
/// <typeparam name="TRegistrationStyle">Registration style.</typeparam>
/// <param name="registration">Registration to set service mapping on.</param>
/// <param name="openGenericServiceType">The type or interface which all classes must be assignable from.</param>
/// <param name="serviceKey">Key of the service.</param>
/// <returns>Registration builder allowing the registration to be configured.</returns>
public static IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle>
AssignableTo<TLimit, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle> registration, Type openGenericServiceType, object serviceKey)
{
return ScanningRegistrationExtensions.AssignableTo(registration, openGenericServiceType, serviceKey);
}

/// <summary>
/// Filters the scanned types to include only those assignable to the provided.
/// </summary>
/// <typeparam name="TLimit">Registration limit type.</typeparam>
/// <typeparam name="TRegistrationStyle">Registration style.</typeparam>
/// <param name="registration">Registration to set service mapping on.</param>
/// <param name="openGenericServiceType">The type or interface which all classes must be assignable from.</param>
/// <param name="serviceKeyMapping">Function mapping types to service keys.</param>
/// <returns>Registration builder allowing the registration to be configured.</returns>
public static IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle>
AssignableTo<TLimit, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle> registration, Type openGenericServiceType, Func<Type, object> serviceKeyMapping)
{
return ScanningRegistrationExtensions.AssignableTo(registration, openGenericServiceType, serviceKeyMapping);
}

/// <summary>
/// Filters the scanned open generic types to include only those in the namespace of the provided type
/// or one of its sub-namespaces.
/// </summary>
/// <param name="registration">Registration to filter types from.</param>
/// <typeparam name="T">A type in the target namespace.</typeparam>
/// <returns>Registration builder allowing the registration to be configured.</returns>
public static IRegistrationBuilder<object, OpenGenericScanningActivatorData, DynamicRegistrationStyle>
InNamespaceOf<T>(this IRegistrationBuilder<object, OpenGenericScanningActivatorData, DynamicRegistrationStyle> registration)
{
// Namespace is always non-null for concrete type parameters.
return registration.InNamespace(typeof(T).Namespace!);
}

/// <summary>
/// Filters the scanned types to include only those in the provided namespace
/// or one of its sub-namespaces.
/// </summary>
/// <typeparam name="TLimit">Registration limit type.</typeparam>
/// <typeparam name="TRegistrationStyle">Registration style.</typeparam>
/// <param name="registration">Registration to filter types from.</param>
/// <param name="ns">The namespace from which types will be selected.</param>
/// <returns>Registration builder allowing the registration to be configured.</returns>
public static IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle>
InNamespace<TLimit, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, TRegistrationStyle> registration,
string ns)
{
if (string.IsNullOrEmpty(ns))
{
throw new ArgumentNullException(nameof(ns));
}

return registration.Where(t => t.IsInNamespace(ns));
}

/// <summary>
/// Specifies that an open generic type from a scanned assembly is registered as providing all of its
/// implemented interfaces.
/// </summary>
/// <typeparam name="TLimit">Registration limit type.</typeparam>
/// <param name="registration">Registration to set service mapping on.</param>
/// <returns>Registration builder allowing the registration to be configured.</returns>
public static IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, DynamicRegistrationStyle>
AsImplementedInterfaces<TLimit>(this IRegistrationBuilder<TLimit, OpenGenericScanningActivatorData, DynamicRegistrationStyle> registration)
{
return registration.As(t => t.GetOpenGenericImplementedInterfaces());
}
}
}
1 change: 0 additions & 1 deletion src/Autofac/Util/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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; }
}
}
Original file line number Diff line number Diff line change
@@ -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<T>
{
}
}
Original file line number Diff line number Diff line change
@@ -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<T>
{
}
}
Original file line number Diff line number Diff line change
@@ -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<T>
{
}
}
22 changes: 22 additions & 0 deletions test/Autofac.Test/Assertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -92,6 +96,24 @@ public static void AssertComponentRegistrationOrder<TService, TFirstComponent, T
Assert.True(foundLast);
}

public static bool RegisteredAnyOpenGenericTypeFromScanningAssembly(this IComponentContext context)
{
return context.ComponentRegistry.Sources.Any(source =>
{
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<Type> LookForComponents(this IEnumerable<IComponentRegistration> registrations, IEnumerable<Type> types)
{
return registrations
Expand Down
Loading