统一。 Return InjectionFactory 中的通用类型
Unity. Return generic type in a InjectionFactory
在一个 WebAPI 项目中,我有两个相同通用接口的不同实现:
public interface Interface<T> {}
public class Class1<T> : Interface<T> {} where T : class
public class Class2<T> : Interface<T> {} where T : class
我需要根据请求中的一些参数来决定运行时,使用哪个实现。所以我像这样配置了统一容器:
....
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);
}
}
但是我遇到了错误,因为 typeTo1
和 typeTo2
包含通用参数。错误信息:
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)
这样我就可以 return 一个封闭类型的实例吗?我不想为每个可能的类型 <T>
重写 register 语句。
您似乎把解决方案复杂化了。相反,更简单的方法是不将其推送到 IoC 容器中。原因是DI是为了维护独立于依赖项及其逻辑的逻辑。
在这种情况下,您有一个开关来确定您的依赖项堆栈,然后您只需将其推送到解决方案中,在您的容器和实际逻辑之间创建依赖项。这可能会导致问题,因为它只是在推动解决问题,而不是真正解决问题。在这种情况下,确定开关的逻辑可能非常适合对象解析(这是该逻辑最有可能做的事情...... "Toggle" 是什么)
看来,如果你在一个逻辑片中有开关,它应该是确定分辨率第一步的所有者。诚然,您仍然会使用一些 IoC 作为具体 class 解决方案的最终接口,但不要将开关逻辑推送到 IoC。
换句话说,您能否拥有 IoC 解析的另一层接口,然后调用解析器使用基本接口的类型切换?
IBaseInterface resolvedObject = Toggle.IsOn(request) ? di.Get<IToggledInterface>() : di.Get<INotToggledInterface>();
注意:正在解析的接口最终暴露使用IBaseInterface。他们都实现了接口,但是除了 "Type" 允许 IoC 解析的定义之外,他们自己可能不会向签名添加任何内容。
有几种方法可用于获得您想要的结果,但随着逻辑变得更加复杂并且 类 的数量增加,更简单的方法会无法很好地扩展。我在这里想到的是按约定注册、有条件注册和命名注册。
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>));
我从 Randy 的回答中获得了很大的启发,但我不同意您必须通过 Unity 扩展来克服困难。 InjectionFactory
工作正常,如果你有点小心。
下面的代码只是从任何开放类型 I<>
解析为对应的类型 C<>
。
它不执行切换部分,但这只是一个 if
语句。
对于不耐烦的人:runnable fiddle
用法:
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}");
}
}
}
在一个 WebAPI 项目中,我有两个相同通用接口的不同实现:
public interface Interface<T> {}
public class Class1<T> : Interface<T> {} where T : class
public class Class2<T> : Interface<T> {} where T : class
我需要根据请求中的一些参数来决定运行时,使用哪个实现。所以我像这样配置了统一容器:
....
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);
}
}
但是我遇到了错误,因为 typeTo1
和 typeTo2
包含通用参数。错误信息:
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)
这样我就可以 return 一个封闭类型的实例吗?我不想为每个可能的类型 <T>
重写 register 语句。
您似乎把解决方案复杂化了。相反,更简单的方法是不将其推送到 IoC 容器中。原因是DI是为了维护独立于依赖项及其逻辑的逻辑。
在这种情况下,您有一个开关来确定您的依赖项堆栈,然后您只需将其推送到解决方案中,在您的容器和实际逻辑之间创建依赖项。这可能会导致问题,因为它只是在推动解决问题,而不是真正解决问题。在这种情况下,确定开关的逻辑可能非常适合对象解析(这是该逻辑最有可能做的事情...... "Toggle" 是什么) 看来,如果你在一个逻辑片中有开关,它应该是确定分辨率第一步的所有者。诚然,您仍然会使用一些 IoC 作为具体 class 解决方案的最终接口,但不要将开关逻辑推送到 IoC。
换句话说,您能否拥有 IoC 解析的另一层接口,然后调用解析器使用基本接口的类型切换?
IBaseInterface resolvedObject = Toggle.IsOn(request) ? di.Get<IToggledInterface>() : di.Get<INotToggledInterface>();
注意:正在解析的接口最终暴露使用IBaseInterface。他们都实现了接口,但是除了 "Type" 允许 IoC 解析的定义之外,他们自己可能不会向签名添加任何内容。
有几种方法可用于获得您想要的结果,但随着逻辑变得更加复杂并且 类 的数量增加,更简单的方法会无法很好地扩展。我在这里想到的是按约定注册、有条件注册和命名注册。
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>));
我从 Randy 的回答中获得了很大的启发,但我不同意您必须通过 Unity 扩展来克服困难。 InjectionFactory
工作正常,如果你有点小心。
下面的代码只是从任何开放类型 I<>
解析为对应的类型 C<>
。
它不执行切换部分,但这只是一个 if
语句。
对于不耐烦的人:runnable fiddle
用法:
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}");
}
}
}