How to resolve all service types for the full hierarchy of a closed generic's closure type

363 Views Asked by At

I have recently begun using , migrating away from and . With the previous dependency injection tools it was seemingly rather trivial to resolve all services that closed on the full hierarchy (all interfaces, and base classes) of a given type.

For example structuremap / lamar simply required scanning

// ... registry stuff...
Scan(scan =>
        {
            scan.TheCallingAssembly();
            scan.WithDefaultConventions();
            scan.ConnectImplementationsToTypesClosing(typeof(IAnimalFeeder<>));
        });

and resolution was just a matter of calling GetAllInstances on the context:

var openFeederType = typeof(IAnimalFeeder<>);
var animalType = typeof(animal);
var closedType = openFeederType.MakeGenericType(animalType);

var feeders = context.GetAllInstances(closedType)   // resolve all feeders for given animal hierarchy

However, this does not appear to be the case with autofac. Unless I'm missing some documentation and missuderstanding methods it appears that I need to jump though a number of reflection calls in order to manually determine what the type hierarchy of my service type is, close a generic on each discovered type, close that generic on an IEnumerable and finally make a Resolve call on each closed IEnumerable.

What follows is my approach, and I want to be sure it is valid and not reimplementing existing functionalty

Given our favorite polymorphic animal examples

public interface IAnimal { }
public class Dog : AbstractAnimal { }
public class Wolf : Dog { }
public class Cat : AbstractAnimal { }

I want to discover (Resolve) all feeders, defined below, that can feed an animal

public interface IAnimalFeeder<in TAnimal> { }
public abstract class AbstractAnimal : IAnimal { }
public class AnimalFeeder : IAnimalFeeder<IAnimal> { }

public class ObjectFeeder : IAnimalFeeder<object> { }
public class DogSnackFeeder : IAnimalFeeder<Dog> { }
public class DogFeeder : IAnimalFeeder<Dog> { }
public class WolfFeeder : IAnimalFeeder<Wolf> { }

public class CatFeeder : IAnimalFeeder<Cat> { }

eg:

  • A DogFeeder can feed both Dog and Wolf, but not Cat
  • An ObjectFeeder can feed Dog, Wolf, Cat, but not IAnimal
  • An AnimalFeeder can feed Dog, Wolf, Cat, and IAnimal
  • etc.

To prove this out I've written an theory with a custom reflection based Resolve in the static ResolutionHelper class (ResolveManyClosedGenericsOfTypeHierarchy) to do my resolution

public class GenericResolveTests
{
    [Theory]
    [InlineData(new[] {typeof(ObjectFeeder), typeof(AnimalFeeder), typeof(DogFeeder), typeof(DogSnackFeeder)}, typeof(Dog))]
    [InlineData(new[] {typeof(ObjectFeeder), typeof(AnimalFeeder), typeof(DogFeeder), typeof(DogSnackFeeder), typeof(WolfFeeder)}, typeof(Wolf))]
    [InlineData(new[] {typeof(ObjectFeeder), typeof(AnimalFeeder), typeof(CatFeeder)}, typeof(Cat))]
    [InlineData(new[] {typeof(ObjectFeeder)}, typeof(object))]
    [InlineData(new[] {typeof(AnimalFeeder)}, typeof(IAnimal))]
    public void Generic_Resolve_Test(Type[] expectedTypes,
                                     Type closureType)
    {
        // Arrange
        var assembly = Assembly.GetExecutingAssembly();
        var builder = new ContainerBuilder();
        // Registration
        builder.RegisterAssemblyTypes(assembly)          // Register sourcing from the given assembly
               .PublicOnly()                             // Only register public classes
               .AsClosedTypesOf(typeof(IAnimalFeeder<>)) // Register types that are assignable to a closed instance of the open generic type IAnimalFeeder<>
               .AsSelf()                                 // Register types as themselves (explicit concrete types are made available)
               .AsImplementedInterfaces();               // Register the type as providing all of its public interfaces as services
        var container = builder.Build();
        // Act
        // Resolution with non-standard autofac Resolve functionality
        var result = container.ResolveManyClosedGenericsOfTypeHierarchy(closureType, typeof(IAnimalFeeder<>));
        // Assert
        Assert.NotNull(result);
        var results = result.ToList();
        Assert.Equal(expectedTypes.Length, results.Count);
        var resultTypes = results.Select(r => r.GetType())
                                 .ToList();
        Assert.All(expectedTypes,
                   expectedType => Assert.Contains(expectedType, resultTypes));
    }
}
public static class ResolutionHelper
{
    /// <summary>
    ///     Resolve closed generics of <paramref name="openGenericType" /> based on the type hierarchy of
    ///     <typeparamref name="TServiceClosure" />
    /// </summary>
    /// <typeparam name="TServiceClosure">the service closure type</typeparam>
    /// <param name="componentContext">the component context used for resolution</param>
    /// <param name="openGenericType">the open generic type to close</param>
    /// <returns>
    ///     a collection of closed <see cref="openGenericType" /> on the type hierarchy of
    ///     <typeparamref name="TServiceClosure" />
    /// </returns>
    public static IEnumerable<object> ResolveManyClosedGenericsOfTypeHierarchy<TServiceClosure>(this IComponentContext componentContext,
                                                                                                Type openGenericType)
    {
        return ResolveManyClosedGenericsOfTypeHierarchy(componentContext.Resolve, typeof(TServiceClosure), openGenericType);
    }
    /// <summary>
    ///     Resolve closed generics of <paramref name="openGenericType" /> based on the type hierarchy of
    ///     <typeparamref name="TServiceClosure" />
    /// </summary>
    /// <typeparam name="TServiceClosure">the service closure type</typeparam>
    /// <param name="container">the container used for resolution</param>
    /// <param name="openGenericType">the open generic type to close</param>
    /// <returns>
    ///     a collection of closed <see cref="openGenericType" /> on the type hierarchy of
    ///     <typeparamref name="TServiceClosure" />
    /// </returns>
    public static IEnumerable<object> ResolveManyClosedGenericsOfTypeHierarchy<TServiceClosure>(this IContainer container,
                                                                                                Type openGenericType)
    {
        return ResolveManyClosedGenericsOfTypeHierarchy(container.Resolve, typeof(TServiceClosure), openGenericType);
    }
    /// <summary>
    ///     Resolve closed generics of <paramref name="openGenericType" /> based on the type hierarchy of
    ///     <paramref name="serviceClosureType" />
    /// </summary>
    /// <param name="componentContext">the component context used for resolution</param>
    /// <param name="serviceClosureType">the service closure type</param>
    /// <param name="openGenericType">the open generic type to close</param>
    /// <returns>
    ///     a collection of closed <see cref="openGenericType" /> on the type hierarchy of
    ///     <paramref name="serviceClosureType" />
    /// </returns>
    public static IEnumerable<object> ResolveManyClosedGenericsOfTypeHierarchy(this IComponentContext componentContext,
                                                                               Type serviceClosureType,
                                                                               Type openGenericType)
    {
        return ResolveManyClosedGenericsOfTypeHierarchy(componentContext.Resolve, serviceClosureType, openGenericType);
    }
    /// <summary>
    ///     Resolve closed generics of <paramref name="openGenericType" /> based on the type hierarchy of
    ///     <paramref name="serviceClosureType" />
    /// </summary>
    /// <param name="container">the container used for resolution</param>
    /// <param name="serviceClosureType">the service closure type</param>
    /// <param name="openGenericType">the open generic type to close</param>
    /// <returns>
    ///     a collection of closed <see cref="openGenericType" /> on the type hierarchy of
    ///     <paramref name="serviceClosureType" />
    /// </returns>
    public static IEnumerable<object> ResolveManyClosedGenericsOfTypeHierarchy(this IContainer container,
                                                                               Type serviceClosureType,
                                                                               Type openGenericType)
    {
        return ResolveManyClosedGenericsOfTypeHierarchy(container.Resolve, serviceClosureType, openGenericType);
    }
    /// <summary>
    ///     Resolve closed generics of <paramref name="openGenericType" /> based on the type hierarchy of
    ///     <paramref name="serviceClosureType" />
    /// </summary>
    /// <param name="resolver">
    ///     a resolution <see cref="Func{TInput, TResult}" /> that resolves <see cref="Type" /> input into
    ///     an <see cref="object" />
    /// </param>
    /// <param name="serviceClosureType">the service closure type</param>
    /// <param name="openGenericType">the open generic type to close</param>
    /// <returns>
    ///     a collection of closed <see cref="openGenericType" /> on the type hierarchy of
    ///     <paramref name="serviceClosureType" />
    /// </returns>
    public static IEnumerable<object> ResolveManyClosedGenericsOfTypeHierarchy(Func<Type, object> resolver,
                                                                               Type serviceClosureType,
                                                                               Type openGenericType)
    {
        if (resolver == null)
        {
            throw new ArgumentNullException(nameof(resolver));
        }
        if (serviceClosureType == null)
        {
            throw new ArgumentNullException(nameof(serviceClosureType));
        }
        if (openGenericType == null)
        {
            throw new ArgumentNullException(nameof(openGenericType));
        }
        if (!openGenericType.IsGenericTypeDefinition)
        {
            throw new ArgumentException("type must be a generic type definition", nameof(openGenericType));
        }
        var typesToResolve = GetAllClosedGenericOfTypeHierarchy(serviceClosureType, openGenericType);
        return _()
            .SelectMany(o => o);
        IEnumerable<IEnumerable<object>> _()
        {
            foreach (var type in typesToResolve)
            {
                yield return (IEnumerable<object>) resolver(type);
            }
        }
    }
    /// <summary>
    ///     Create a closed generic type of <see cref="openGenericType" /> for each <see cref="type" /> and its implementing
    ///     <see langwod="interface" /> and base class
    /// </summary>
    /// <param name="type">the type to find linage of</param>
    /// <param name="openGenericType">the open generic to make closed types of</param>
    /// <returns>a collection of closed generic types</returns>
    private static IEnumerable<Type> GetAllClosedGenericOfTypeHierarchy(Type type,
                                                                        Type openGenericType)
    {
        return _();
        IEnumerable<Type> _()
        {
            foreach (var selfAndSuper in GetSelfAndSupers(type))
            {
                var closedFeederType = openGenericType.MakeGenericType(selfAndSuper);
                yield return typeof(IEnumerable<>).MakeGenericType(closedFeederType);
            }
        }
    }
    /// <summary>
    ///     Given a <see cref="Type" /> <paramref name="inputType" /> return a collection of <paramref name="inputType" />, all
    ///     of <paramref name="inputType" />'s interfaces, and all of its base classes
    /// </summary>
    /// <param name="inputType">the type to determine linage of</param>
    /// <returns>
    ///     a collection of type including  <paramref name="inputType" />, all of its interfaces, and all of its base
    ///     classes
    /// </returns>
    private static IEnumerable<Type> GetSelfAndSupers(Type inputType)
    {
        return _()
            .Distinct();
        IEnumerable<Type> _()
        {
            // return self
            yield return inputType;
            // return interfaces
            foreach (var t in inputType.GetInterfaces())
            {
                yield return t;
            }
            // return base types
            var baseType = inputType.BaseType;
            while (baseType != null)
            {
                yield return baseType;
                baseType = baseType.BaseType;
            }
        }
    }
}

Am I jumping though hoops I shouldn't be to make this happen?

Just for consistency sake, this is the approach I'm taking for DTO validation, where animals are various types of DTOs and the "feeders" are explicit units of DTO validation.

0

There are 0 best solutions below