统一.返回注入工厂中的泛型类型

本文关键字:泛型类型 工厂 返回 注入 统一 | 更新日期: 2023-09-27 18:36:24

在WebAPI项目中,我有两个不同实现的同一个通用接口:

public interface Interface<T> {}
public class Class1<T> : Interface<T> {} where T : class
public class Class2<T> : Interface<T> {} where T : class

我需要根据请求中的某些参数来决定运行时,使用哪个实现。所以我像这样配置了 Unity 容器:

....
container.RegisterType(typeof(Interface<>),
            new InjectionFactory(
                c =>
                    ToggleInjectionFactory.Get(container,     
                                               typeof(Class1<>),
                                               typeof(Class2<>),
                                               HttpContext.Current.Request)
            ));
...
// Factory
public class ToggleInjectionFactory
{
...
    public static object Get(IUnityContainer container, Type typeTo1, Type typeTo2, HttpRequest request)
    {
        if (Toggle.IsOn(request))
        {
            return container.Resolve(typeTo1);
        }
        return container.Resolve(typeTo2);
    }
}

但是我遇到了错误,因为typeTo1typeTo2包含通用参数。错误信息:

Resolution of the dependency failed, type = "Class1`1[T]", name = "(none)".
Exception occurred while: while resolving.
Exception is: ArgumentException - Type Class1`1[T] contains generic parameters

有没有办法实现container.Resolve(typeTo1)以便我可以返回一个封闭的类型实例?我不想为我拥有的每个可能类型重写<T>的寄存器语句。

统一.返回注入工厂中的泛型类型

几种方法可以用来获得你想要的结果,但更简单的方法会受到不能很好地扩展的问题,因为逻辑变得更加复杂,类的数量也在增加。 我在这里想到的是按惯例注册、有条件注册和命名注册。

Unity 允许您注册打开的泛型,并将它们自动解析为已关闭的泛型。 但是,使用 InjectionFactory 的已发布方法不起作用,因为InjectionFactory没有 Unity 用于创建封闭泛型类型的解析BuildContext

实现所需目标的一种解决方案是使用 Unity 容器扩展,因为容器扩展将具有可用的构建上下文,并允许您确定要构建的正确类型。 在这种情况下,可以在对象解析的 TypeMapping 阶段完成简单的类型映射。

public class ToggleExtension : UnityContainerExtension
{
    private Toggle toggle;
    public ToggleExtension(Toggle toggle)
    {
        this.toggle = toggle;
    }
    protected override void Initialize()
    {
        Context.Strategies.Add(new ToggleBuildUpStrategy(this.toggle),
            UnityBuildStage.TypeMapping);
    }
}
public class ToggleBuildUpStrategy : BuilderStrategy
{
    private Toggle toggle;
    public ToggleBuildUpStrategy(Toggle toggle)
    {
        this.toggle = toggle;
    }
    public override void PreBuildUp(IBuilderContext context)
    {
        // If we have an Interface<> then do some type mapping to the applicable concrete class
        // Note that I'm using Toggle here because something similar was used in the question
        if (context.BuildKey.Type.IsGenericType && 
            context.BuildKey.Type.GetGenericTypeDefinition() == typeof(Interface<>))
        {
            Type target = null;
            // Luckily HttpContext.Current.Request request context is available here
            // For other non-static contexts might have to work out how to inject into the extension
            if (this.toggle.IsOn(HttpContext.Current.Request))
            {
                target = typeof(Class1<>);
            }
            else
            {
                target = typeof(Class2<>);
            }
            // Get generic args
            Type[] argTypes = context.BuildKey.Type.GetGenericArguments();
            // Replace build key type Interface<> => Class1<> or Class2<>
            // So that the correct type is resolved
            context.BuildKey = new NamedTypeBuildKey(target.MakeGenericType(argTypes), 
                context.BuildKey.Name);
        }
    }
}

这是我使用的 Toggle 实现 - 它与问题中的不太一样,但非静态使测试更容易:

public class Toggle
{
    private bool isToggleOn;
    public void SetToggleOn()
    {
        isToggleOn = true;
    }
    public void SetToggleOff()
    {
        isToggleOn = false;
    }
    public bool IsOn(HttpRequest request)
    {
        // Implement more complicated toggle logic
        return isToggleOn;
    }
}

最后进行一些测试,以确保解析正确的类型:

IUnityContainer container = new UnityContainer();
Toggle toggle = new Toggle();
toggle.SetToggleOn();
container.AddExtension(new ToggleExtension(toggle));
Interface<X> x = container.Resolve<Interface<X>>();
Debug.Assert(x.GetType() == typeof(Class1<X>));
toggle.SetToggleOff();
x = container.Resolve<Interface<X>>();
Debug.Assert(x.GetType() == typeof(Class2<X>));

似乎您使解决方案过于复杂。相反,更简单的方法是不将其推送到 IoC 容器中。原因是 DI 是维护独立于依赖项及其逻辑的逻辑。

在这种情况下,您有一个确定依赖项堆栈的开关,然后只需将其推送到解析中,从而在容器和实际逻辑之间创建依赖项。这可能会导致问题,因为它只是在推动您的解决方案问题,而不是实际解决它。在这种情况下,确定开关的逻辑可能非常适合具有对象分辨率(这是该逻辑最有可能执行的操作...什么是"切换")似乎如果您在逻辑段中有开关,它应该是确定解决方案第一步的所有者。当然,您仍将使用一些 IoC 作为最终接口以具体解析类,但不要将交换机逻辑推送到 IoC。

换句话说,是否可以有另一层接口,IoC 解析,然后调用解析器使用类型开关作为基本接口?

IBaseInterface resolvedObject = Toggle.IsOn(request) ? di.Get<IToggledInterface>() : di.Get<INotToggledInterface>();

注意:正在解析的接口使用 IBaseInterface 进行最终曝光。它们都暗示了接口,但是除了允许 IoC 解析的"类型"定义之外,它们本身可能不会向符号添加任何内容。

我从 Randy 的回答中汲取了大量灵感,但我不同意您必须使用 Unity 扩展跳过箍。如果你小心一点,InjectionFactory工作正常。

下面的代码只是从任何开放类型I<>解析为相应的类型C<> .
它不做切换部分,但这只是一个if声明。

对于不耐烦的人:可运行的小提琴

用法:

interface I<T> { }
class C<T> : I<T> { }
...
var ioc = new UnityContainer();
ioc.RegisterType(
    typeof(I<>), //can't use ioc.RegisterType<I<>>(...), C# doesn't support that
    // generic type mapping defined here
    // the first type parameter must match what's above, in this case typeof(I<>)
    new OpenGenericTypeFactory(typeof(I<>), typeof(C<>)) 
);
var c = ioc.Resolve<I<double>>();
Console.WriteLine(c.GetType());

这将打印:

C`1[System.Double]

工厂的执行:

class OpenGenericTypeFactory : Microsoft.Practices.Unity.InjectionFactory {
    public OpenGenericTypeFactory(Type from, Type to)
        : base((container, type, _) => Create(container, from, to, type))
    {
    }
    
    private static object Create(IUnityContainer container, Type fromType, Type toType, Type requestedType)
    {
        if (!requestedType.IsGenericType || requestedType.GetGenericTypeDefinition() != fromType)
        {
            throw new Exception($"Injection factory for {fromType}: got type {requestedType} instead.");
        }
        Type[] argTypes = requestedType.GetGenericArguments();
        var closedGenericTarget = toType.MakeGenericType(argTypes);
        try
        {
            return container.Resolve(closedGenericTarget);
        }
        catch
        {
            throw new Exception($"Failed to resolve type {closedGenericTarget} for requested type {requestedType}");
        }
    }
}