繁体   English   中英

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

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

背景

我正在编写一些集成测试,我测试特定接口的实现, IQueryMapping<TSource, TDest> 此接口用于从IQueryable<TSource>映射到IQueryable<TDest> 我想确保在使用Entity Framework编译查询时不会抛出异常。

任务

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

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

不幸的是,open.Geetics似乎没有出现在Container.GetCurrentRegistrations()调用中。 来自文档

返回包含当前注册的数组。 此列表包含所有显式注册的类型以及所有隐式注册的实例。 隐式注册都是已请求的具体未注册类型,所有已使用未注册类型解析(使用ResolveUnregisteredType事件)解析的类型,以及请求的未注册集合。 请注意,由于这些隐式注册,此方法的结果可能会随时间而变化。

我真正喜欢的是Simple Injector 告诉我所有已注册组件中每次请求出现的IQueryMapping<,>

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

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

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

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

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

长话短说:首先调用Verify()

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

Simple Injector Pipeline文档中可以看出,这些信息在注册时并不容易获得 - 它仅在解析时(在“构建ctor参数”点)计算。

拿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() 此方法创建一个表达式,在执行时将创建给定的实例。 但是,因为它是一个表达式,所以也可以使用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;
    }
}

我想知道我是做对了还是无意中把自己挖进洞里,所以如果你在这方面知识渊博,请给我反馈!

暂无
暂无

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

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