获取通用服务类型的所有实现 - 包括开放式通用

Get all implementations of a generic service type - including open generics

背景

我正在编写一些集成测试,用于测试特定接口的实现,IQueryMapping<TSource, TDest>。此接口的存在是为了从 IQueryable<TSource> 映射到 IQueryable<TDest>。我想确保他们这样做 使用 Entity Framework.

编译查询时不抛出异常

任务

我很懒,我不想每次创建新映射时都必须更新我的测试!我想要做的就是确保我的应用程序使用的每个映射都将通过测试。我可以 bootstrap 我的容器,然后找到所有在其中注册的实现,如下所示:

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

到目前为止,一切顺利!

问题

除了我的具体实现,我还有一个默认的开放式通用映射器,它执行基本的自动 属性 映射(记住,我很懒,不想手动执行此操作!)

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

不幸的是,开放泛型似乎没有出现在 Container.GetCurrentRegistrations() 调用中。来自 docs:

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

我真正想要的是让 Simple Injector 告诉我所有已注册组件.

中每个请求出现的 IQueryMapping<,>

例如,如果我将 IQueryMapping<Foo, Bar> 保留为开放通用 DefaultAutoMapping<,> 并且已注册的组件具有构造函数签名

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

我希望容器 告诉我这个专门的 IQueryMapping<,>,这样我就可以请求它的一个实例并通过我的 运行 它测试。

据我所知,没有内置的方法可以做到这一点。然而,当我在写我的问题时(经常发生),我找到了一种方法来实现我想做的事情。它可能远非理想但是...

Simple Injector Pipeline documentation 看来,此信息在注册时并不容易获得 - 它仅在解析时计算(在 'Build ctor arguments' 点)。

拿1个

我想到的一个想法是遍历每个已注册的类型并检查其构造函数以获取可能的参数:

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;

但是,这只会下降到注册类型的第一级 - 在我的项目中有许多开放的通用注册。

拍摄 2

幸运的是,Simple Injector 提供了一种根据服务类型检索 InstanceProducer 的方法,这就是我们创建递归函数所需的全部内容:

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;
    }
}

这会递归所有已注册的类型,查看它们的构造函数以找到要搜索的其他类型。然而,这仍然不完美,因为我们不能保证特定组件 应该 通过其构造函数(而不是工厂方法)解析。

取 3

InstanceProducer 上一个有趣的方法是 BuildExpression()。此方法创建一个表达式,执行时将创建给定的实例。但是,因为它是一个 Expression,所以它也可以使用 ExpressionVisitor 遍历。我们可以创建一个 ExpressionVisitor 的实现来收集表达式中的所有类型:

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);
        }
    }
}

终于! ExpressionVisitor 收集的类型可以传递给 container.GetRegistration(t)。类型将是具体类型,因此我们需要对 Take 1 中的 LINQ 语句做一个小改动,使用一种方法来测试服务类型是否可分配给 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;
    }
}

我想知道我这样做是正确的还是无意中给自己挖了一个坑,所以如果您在这方面知识渊博,请给我反馈!

RegisterOpenGeneric 的调用将在后台将委托挂接到 ResolveUnregisteredType 事件。这基本上意味着容器本身完全不知道注册,只有在请求注册抽象的封闭通用版本时才会添加注册;直接调用 GetInstance() 或间接调用,因为依赖于该抽象的类型已解析。

这里的技巧是在调用 GetCurrentRegistrations() 之前调用 Verify()。调用 Verify() 将导致容器构建所有表达式树、编译所有委托并创建容器已知的所有注册的所有实例。这将强制容器添加该开放通用抽象的每个找到的封闭通用版本的注册。

长话短说:先打电话给 Verify()