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

时间:2015-06-02 15:43:21

标签: c# generics simple-injector

背景

我正在编写一些集成测试,我在其中测试特定接口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<,>));

不幸的是,开放式仿制药似乎没有出现在Container.GetCurrentRegistrations()电话中。来自docs

  

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

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

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

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

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

2 个答案:

答案 0 :(得分:2)

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

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

长话短说:首先致电Verify()

答案 1 :(得分:1)

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

Simple Injector Pipeline documentation看来,这些信息在注册时并不容易获得 - 只能在解决时间(在“构建参数”的位置)计算。

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

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