简体   繁体   English

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

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

Background 背景

I am writing some integration tests where I test implementations of a particular interface, IQueryMapping<TSource, TDest> . 我正在编写一些集成测试,我测试特定接口的实现, IQueryMapping<TSource, TDest> This interface exists to map from an IQueryable<TSource> to an IQueryable<TDest> . 此接口用于从IQueryable<TSource>映射到IQueryable<TDest> I want to make sure that they do not throw exceptions when compiling a query using Entity Framework. 我想确保在使用Entity Framework编译查询时不会抛出异常。

The Task 任务

I am lazy and I don't want to have to update my tests every time I create a new mapping! 我很懒,每次创建新映射时都不想更新我的测试! All I want to do is ensure that every mapping which is used by my application will pass the test. 我想要做的就是确保我的应用程序使用的每个映射都将通过测试。 I can bootstrap my container and then find all implementations that are 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! 到现在为止还挺好!

The Problem 问题

Alongside my concrete implementations, I have a default open generic mapper which performs basic automatic property mapping (remember, I am lazy and don't want to do this manually!) 除了我的具体实现,我有一个默认的开放式通用映射器,它执行基本的自动属性映射(记住,我很懒,不想手动执行此操作!)

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

Unfortunately, open generics don't seem to appear in Container.GetCurrentRegistrations() call. 不幸的是,open.Geetics似乎没有出现在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 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. 隐式注册都是已请求的具体未注册类型,所有已使用未注册类型解析(使用ResolveUnregisteredType事件)解析的类型,以及请求的未注册集合。 Note that the result of this method may change over time, because of these implicit registrations. 请注意,由于这些隐式注册,此方法的结果可能会随时间而变化。

What I'd really like is for Simple Injector to tell me about every requested occurrence of IQueryMapping<,> in all registered components . 我真正喜欢的是Simple Injector 告诉我所有已注册组件中每次请求出现的IQueryMapping<,>

For example, if I have left IQueryMapping<Foo, Bar> as an open generic DefaultAutoMapping<,> and a registered component has the constructor signature 例如,如果我将IQueryMapping<Foo, Bar>保留为开放式通用DefaultAutoMapping<,>并且已注册的组件具有构造函数签名

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

I would like the container to tell me about this specialised IQueryMapping<,> , so that I can request an instance of it and run it through my test. 我希望容器告诉我这个专门的IQueryMapping<,> ,以便我可以请求它的实例并通过我的测试运行它。

A call to RegisterOpenGeneric will in the background hook a delegate onto the ResolveUnregisteredType event. RegisterOpenGeneric的调用将在后台将委托挂钩到ResolveUnregisteredType事件。 This basically means that the container itself is completely unaware of the registration, and the registration will only get added when a closed-generic version of the registered abstraction is requested; 这基本上意味着容器本身完全不知道注册,只有在请求注册抽象的封闭通用版本时才会添加注册; either directly using a call to GetInstance() or indirectly because a type that depends on that abstraction is resolved. 直接使用对GetInstance()的调用或间接调用因为依赖于该抽象的类型。

The trick here is to call Verify() before calling GetCurrentRegistrations() . 这里的技巧是在调用GetCurrentRegistrations()之前调用Verify() GetCurrentRegistrations() A call to Verify() will cause the container to build all expression trees, compile all delegates, and create all instances of all registrations that are known to the container. Verify()调用将使容器构建所有表达式树,编译所有委托,并创建容器已知的所有注册的所有实例。 This will force the container to add the registrations of each found closed-generic version of that open-generic abstraction. 这将强制容器添加每个找到的开放式通用抽象的封闭通用版本的注册。

Long story short: call Verify() first. 长话短说:首先调用Verify()

As far as I am aware there is no built-in way to do this. 据我所知,没有内置的方法来做到这一点。 However, while I was writing my question (as so often happens) I found a way to achieve what I want to do. 然而,当我在写我的问题时(经常发生)我找到了一种方法来实现我想要做的事情。 It's probably far from ideal but... 它可能远非理想但......

From the Simple Injector Pipeline documentation it looks like this information isn't readily available in registration - it is only calculated at resolve time (at the point of 'Build ctor arguments'). Simple Injector Pipeline文档中可以看出,这些信息在注册时并不容易获得 - 它仅在解析时(在“构建ctor参数”点)计算。

Take 1 拿1

One thought that occurred to me is to iterate through every registered type and inspect 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 down to the first level of registered types - in my project there are many open generic registrations. 但是,这只会归结为注册类型的第一级 - 在我的项目中有许多开放的通用注册。

Take 2 拿2

Fortunately, Simple Injector provides a way to retrieve an InstanceProducer based on a service type, which is all we need to create a recursive function: 幸运的是,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;
    }
}

This recurses through all registered types, looking through their constructors to find other types to search through. 这将通过所有已注册的类型进行递归,查看其构造函数以查找要搜索的其他类型。 However, this still isn't perfect as we can't guarantee that a particular component should be resolved through its constructor (as opposed to, say, a factory method). 但是,这仍然不完美,因为我们不能保证应该通过其构造函数来解析特定组件(而不是工厂方法)。

Take 3 拿3

One interesting method on InstanceProducer is BuildExpression() . InstanceProducer上一个有趣的方法是BuildExpression() This method creates an expression which, when executed will create the given instance. 此方法创建一个表达式,在执行时将创建给定的实例。 However, because it is an Expression it can also be traversed using an ExpressionVisitor. 但是,因为它是一个表达式,所以也可以使用ExpressionVisitor遍历它。 We can create an implementation of ExpressionVisitor that gathers up all of the types in the expression: 我们可以创建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);
        }
    }
}

Finally! 最后! The types gathered by the ExpressionVisitor can be passed to container.GetRegistration(t) . ExpressionVisitor收集的类型可以传递给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 test if the service type is assignable to any generic version of IQueryMapping<,> : 类型将是具体类型,因此我们需要对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;
    }
}

I'd like to know if I'm doing this right or inadvertently digging myself into a hole, so please give me feedback if you are knowledgable in this area! 我想知道我是做对了还是无意中把自己挖进洞里,所以如果你在这方面知识渊博,请给我反馈!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM