Skip to content

Commit

Permalink
Merge 3282ad8 into 5c04bf6
Browse files Browse the repository at this point in the history
  • Loading branch information
RaymondHuy authored Feb 10, 2021
2 parents 5c04bf6 + 3282ad8 commit 1687687
Show file tree
Hide file tree
Showing 10 changed files with 525 additions and 1 deletion.
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

0 comments on commit 1687687

Please sign in to comment.