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

3

背景

我正在编写一些集成测试,测试特定接口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()调用中。根据文档中的说明:
返回当前注册的类型数组。该列表包含所有明确注册的类型和所有隐式注册的实例。隐式注册是指所有已请求的具体未注册类型、使用未注册类型解析(使用ResolveUnregisteredType事件)解决的所有类型以及请求的未注册集合。请注意,由于这些隐式注册,此方法的结果可能会随时间而变化。
我真正想要的是让Simple Injector告诉我关于在所有注册组件中请求IQueryMapping<,>的每个发生的情况。
例如,如果我将IQueryMapping<Foo,Bar>作为开放泛型DefaultAutoMapping<,>保留,并且一个已注册的组件具有构造函数签名
public MyComponent(IQueryMapping<Foo, Bar> mapping)
{
   ...
}

我希望容器能够告诉我有关这个专用的IQueryMapping<,>的信息,以便我可以请求一个实例并将其运行通过我的测试。


GetCurrentRegistrations() 方法还会返回所有已关闭的泛型类型注册版本(例如 IQueryMapping<,>),但前提是容器知道它们的存在,这基本上意味着您应该已经解析了一个消费者。这实际上意味着在调用 Verify() 后,所有已关闭的版本都可以从 GetCurrentRegistrations() 中获取。长话短说:请调用 Verify() - Steven
@Steven - 哇,我简直不敢相信我错过了那个(并且在错误的地方花了这么多时间)!如果你能把它写成答案,我会接受它。 - Alex
2个回答

2
调用RegisterOpenGeneric会在后台将一个委托钩子挂接到ResolveUnregisteredType事件上。这基本意味着容器本身完全不知道注册情况,只有在请求已注册抽象的封闭泛型版本时才会添加注册信息;可以直接使用GetInstance()进行调用,也可以间接地解析依赖于该抽象的类型。
关键在于在调用GetCurrentRegistrations()之前先调用Verify()。调用Verify()将导致容器构建所有表达式树、编译所有委托,并创建容器已知的所有注册实例。这将强制容器添加每个找到的开放泛型抽象的封闭泛型版本的注册信息。
长话短说:先调用Verify()

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

Simple Injector Pipeline documentation中看来,这些信息在注册时不容易获得——它只在解析时计算(在“Build ctor 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;

然而,这只能下降到注册类型的第一层 - 在我的项目中有许多开放式泛型注册。

第二步

幸运的是,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;
    }
}

这会递归遍历所有已注册的类型,查找它们的构造函数以找到其他要搜索的类型。但是,这仍然不完美,因为我们无法保证特定组件应该通过其构造函数解析(而不是例如工厂方法)。
第三步 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;
    }
}

我想知道我是否做得正确,或者无意中让自己陷入了困境,所以如果您在这个领域有知识,请给我反馈!


1
这是一份非常全面的分析。很高兴看到你对库进行了深入的研究。为此点赞。幸运的是,解决方案比你在这里建议的要简单得多 :-) - Steven
2
@Steven 这是一个很棒的库!感谢你的编写。 - Alex
非常欢迎您。请继续关注v3版本,它将更加出色 :D - Steven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接