簡體   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