I have recently begun using autofac, migrating away from structuremap and lamar. 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 bothDog
andWolf
, but notCat
- An
ObjectFeeder
can feedDog
,Wolf
,Cat
, but notIAnimal
- An
AnimalFeeder
can feedDog
,Wolf
,Cat
, andIAnimal
- etc.
To prove this out I've written an xuint 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.