如何根据传递到的服务来解析接口
我有一个界面。
public interface ISomeInterface {...}
和两个实现(SomeImpl1和SomeImpl2):
public class SomeImpl1 : ISomeInterface {...} public class SomeImpl2 : ISomeInterface {...}
我也有两个服务,我注入ISomeInterface(通过contructor):
public class Service1 : IService1 { public Service1(ISomeInterface someInterface) { } ... }
和
public class Service2 : IService2 { public Service2(ISomeInterface someInterface) { } ... }
我正在使用Autofac作为我的IoC工具。 这个问题。 如何配置Autofac注册,以便SomeImpl1自动注入Service1,SomeImpl2将自动注入Service2。
谢谢!
Autofac支持按名称识别服务 。 使用此方法,您可以使用名称注册实现(使用Named
扩展方法)。 然后,您可以使用ResolveNamed
扩展方法在IServiceX注册委托中按名称解析它们。 以下代码演示了这一点。
var cb = new ContainerBuilder(); cb.Register(c => new SomeImpl1()).Named("impl1"); cb.Register(c => new SomeImpl2()).Named ("impl2"); cb.Register(c => new Service1(c.ResolveNamed ("impl1"))).As(); cb.Register(c => new Service2(c.ResolveNamed("impl2"))).As(); var container = cb.Build(); var s1 = container.Resolve();//Contains impl1 var s2 = container.Resolve();//Contains impl2
使用RegisterType
替代方案(与Register
相对)
使用RegisterType
扩展方法结合WithParameter
和ResolvedParameter
WithParameter
相同的结果。 如果采用命名参数的构造函数还采用您不注意在注册委托中指定的其他非命名参数,这将非常有用:
var cb = new ContainerBuilder(); cb.RegisterType().Named("impl1"); cb.RegisterType().Named("impl2"); cb.RegisterType().As().WithParameter(ResolvedParameter.ForNamed("impl1")); cb.RegisterType().As().WithParameter(ResolvedParameter.ForNamed("impl2")); var container = cb.Build(); var s1 = container.Resolve();//Contains impl1 var s2 = container.Resolve();//Contains impl2
如果可以从构造函数注入切换到属性注入,并且让两个服务都派生自相同的基类(或实现相同的接口),则可以执行以下操作:
builder.RegisterType().OnActivating(e => { var type = e.Instance.GetType(); // get ISomeInterface based on instance type, ie: ISomeInterface dependency = e.Context.ResolveNamed(type.Name); e.Instance.SomeInterface = dependency; });
为此,您需要在基类型(或接口)上定义属性。 此解决方案非常灵活,甚至可以让您根据父类型注入复杂的事物,例如注入generics类型,如下所示:
builder.RegisterType().OnActivating(e => { var type = typeof(GenericImpl<>).MakeGenericType(e.Instance.GetType()); e.Instance.SomeInterface = (ISomeInterface)e.Context.Resolve(type); });
这种方法有一些缺点:
- 我们需要注入房产。
- 我们需要一个包含该属性的基类型或接口。
- 我们需要reflection来构建类型,这可能会影响性能(而不是容器构建一个高效的委托)(尽管我们可以通过缓存类型来加快速度)。
从好的方面来说,这种设计很简单,适用于几乎所有容器。
autofac文档中描述了执行此操作的四种变体:
选项1:重新设计您的接口
当您遇到一堆组件实现相同的服务但它们不能被相同处理的情况时,这通常是一个接口设计问题。
从面向对象的开发角度来看,您希望您的对象遵循Liskov替换原则和这种中断。
通过重新设计界面,您不必“按上下文选择依赖” – 您可以使用类型进行区分,并在解析期间发生自动连线魔术。
如果您能够影响解决方案的更改,则建议您使用此选项。
选项2:更改注册
您可以通过以下方式手动将相应类型与使用组件关联:
var builder = new ContainerBuilder(); builder.Register(ctx => new ShippingProcessor(new PostalServiceSender())); builder.Register(ctx => new CustomerNotifier(new EmailNotifier())); var container = builder.Build(); // Lambda registrations resolve based on the specific type, not the // ISender interface. builder.Register(ctx => new ShippingProcessor(ctx.Resolve())); builder.Register(ctx => new CustomerNotifier(ctx.Resolve())); var container = builder.Build();
选项3:使用密钥服务
builder.RegisterType() .As() .Keyed ("order"); builder.RegisterType() .As() .Keyed ("notification"); builder.RegisterType() .WithParameter( new ResolvedParameter( (pi, ctx) => pi.ParameterType == typeof(ISender), (pi, ctx) => ctx.ResolveKeyed("order"))); builder.RegisterType(); .WithParameter( new ResolvedParameter( (pi, ctx) => pi.ParameterType == typeof(ISender), (pi, ctx) => ctx.ResolveKeyed("notification")));
选项4:使用元数据
builder.RegisterType() .As() .WithMetadata("SendAllowed", "order"); builder.RegisterType() .As() .WithMetadata("SendAllowed", "notification"); builder.RegisterType() .WithParameter( new ResolvedParameter( (pi, ctx) => pi.ParameterType == typeof(ISender), (pi, ctx) => ctx.Resolve>>() .First(a => a.Metadata["SendAllowed"].Equals("order")))); builder.RegisterType(); .WithParameter( new ResolvedParameter( (pi, ctx) => pi.ParameterType == typeof(ISender), (pi, ctx) => ctx.Resolve>>() .First(a => a.Metadata["SendAllowed"].Equals("notification"))));