Get all generic service type implementations - including open source generics

Background

I'm writing some integration tests where I test the implementation of a particular interface IQueryMapping<TSource, TDest>

. This interface exists to display from IQueryable<TSource>

to IQueryable<TDest>

. I want to make sure they do this without throwing exceptions when compiling a query using Entity Framework.

A task

I'm lazy and I don't want to update my tests every time I create a new mapping! All I want to do is make sure every mapping that my application uses passes the test. I can load my container and then find all implementations registered with it like so:

from r in Container.GetCurrentRegistrations()
let t = r.ServiceType
where t.IsGenericType && t.GetGenericTypeDefinition() == typeof (IQueryMapping<,>)
select r.GetInstance()

      

So far so good!

Problem

Along with my concrete implementations, I have a default open source generic mapper that does basic automatic property mapping (remember, I'm lazy and don't want to do it manually!)

container.RegisterOpenGeneric(typeof(IQueryMapping<,>), typeof(DefaultAutoMapping<,>));

      

Unfortunately, open source generics are not shown in the call Container.GetCurrentRegistrations()

. From the docs :

Returns an array with the current registrations. This list contains all explicitly registered types and all implicitly registered instances. Implicit registrations are all specific unregistered types that have been requested, all types that have been resolved using the unregistered type permission (using the ResolveUnregisteredType event), and have requested unregistered collections. Note that the result of this method may change over time due to these implicit registrations.

I would love to have Simple Injector talk about every entry requested IQueryMapping<,>

in all registered components
.

For example, if I leave IQueryMapping<Foo, Bar>

as public public DefaultAutoMapping<,>

and the registered component has a constructor signature

public MyComponent(IQueryMapping<Foo, Bar> mapping)
{
   ...
}

      

I would like the container to tell me about this specialized oneIQueryMapping<,>

, so I can request an instance of it and run it through my test.

+3


source to share


2 answers


The call RegisterOpenGeneric

invokes the on-focus delegate to the event ResolveUnregisteredType

. This basically means that the container itself is completely unaware of the registration, and the registration will only be added when a private shared version of the registered abstraction is requested; either directly, using a call GetInstance()

, or indirectly, because the type that depends on this abstraction is allowed.

The trick here is calling Verify()

before calling GetCurrentRegistrations()

. The call Verify()

will cause the container to build all expression trees, compile all delegates, and create all instances of all registrations that the container knows. This will cause the container to add a registration to every closed generic version of this open generic abstraction it finds.



In short: call first Verify()

.

+2


source


As far as I know, there is no built-in way to do this. However, when I was writing my question (as is often the case) I found a way to achieve what I want to do. This is probably far from ideal, but ...

From the Simple Injector Pipeline documentation it looks like this information is not available at registration - it is calculated only at the time of the solution (with the "Build ctor arguments" item).

Take 1

One thought that came to my mind was to iterate over each registered type and check its constructor for possible arguments:

from r in container.GetCurrentRegistrations()
from ctor in r.Registration.ImplementationType.GetConstructors()
from param in ctor.GetParameters()
let t = param.ParameterType
where t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IQueryMapping<,>)
select t;

      

However, this will only go up to the first level of registered types - there are many open generic registrations in my project.

Take 2

Fortunately, Simple Injector provides a InstanceProducer

service type-based getter that is all you need to create a recursive function:



public static class ContainerExtensions
{
    public static IEnumerable<InstanceProducer> GetInstanceProducers(this Container container)
    {
        return container.GetCurrentRegistrations()
            .SelectMany(x => GetInstanceProducers(container, x));
    }

    private static IEnumerable<InstanceProducer> GetInstanceProducers(Container container, InstanceProducer instanceProducer)
    {
        yield return instanceProducer;
        var producers = from ctor in instanceProducer.Registration
                            .ImplementationType.GetConstructors()
                        from param in ctor.GetParameters()
                        from producer in GetInstanceProducers(
                            container,
                            container.GetRegistration(param.ParameterType))
                        select producer;

        foreach (var producer in producers)
            yield return producer;
    }
}

      

This iterates through all registered types, traversing their constructors to find other types to look for. However, this is still not ideal as we cannot guarantee that a particular component should be resolved through its constructor (as opposed to, say, a factory method).

Take 3

One interesting method on InstanceProducer

- BuildExpression()

. This method creates an expression that, when executed, will create this instance. However, since it is an expression, it can also be moved using the ExpressionVisitor. We can create an ExpressionVisitor implementation that collects all types in an expression:

public static class ContainerExtensions
{
    public static IEnumerable<InstanceProducer> GetInstanceProducers(this Container container)
    {
        return container.GetCurrentRegistrations()
            .SelectMany(GetExpressionTypes)
            .Distinct()
            .Select(container.GetRegistration);
    }

    private static IEnumerable<Type> GetExpressionTypes(InstanceProducer instanceProducer)
    {
        var expression = instanceProducer.BuildExpression();
        var visitor = new TypeExpressionVisitor();
        visitor.Visit(expression);
        return visitor.Types;
    }

    private class TypeExpressionVisitor : ExpressionVisitor
    {
        private readonly List<Type> _types;

        public IEnumerable<Type> Types
        {
            get { return _types; }
        }

        public TypeExpressionVisitor()
        {
            _types = new List<Type>();
        }

        protected override Expression VisitNew(NewExpression node)
        {
            _types.Add(node.Type);
            return base.VisitNew(node);
        }

        protected override Expression VisitInvocation(InvocationExpression node)
        {
            _types.Add(node.Type);
            return base.VisitInvocation(node);
        }
    }
}

      

At last! Types collected by ExpressionVisitor can be passed to container.GetRegistration(t)

. The types will be concrete types, so we need to make a small change to the LINQ statement in Take 1, using a method to check if a service type is assigned to any generic version IQueryMapping<,>

:

public static IEnumerable<object[]> GetMappingObjects
{
    get
    {
        return
            from r in Container.GetInstanceProducers()
            where IsAssignableToGenericType(r.ServiceType, typeof(IQueryMapping<,>))
            select new[] {r.GetInstance()};
    }
}

public static bool IsAssignableToGenericType(Type givenType, Type genericType)
{
    while (true)
    {
        var interfaceTypes = givenType.GetInterfaces();

        if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
            return true;

        if (interfaceTypes.Any(it => it.IsGenericType && it.GetGenericTypeDefinition() == genericType))
            return true;

        var baseType = givenType.BaseType;
        if (baseType == null)
            return false;

        givenType = baseType;
    }
}

      

I would like to know if I will be doing it right or inadvertently digging myself into a hole, so please give me feedback if you know in this area!

+1


source







All Articles