Cómo resolver la interfaz basada en el servicio donde se pasa

Tengo una interfaz.

public interface ISomeInterface {...} 

y dos implementaciones (SomeImpl1 y SomeImpl2):

 public class SomeImpl1 : ISomeInterface {...} public class SomeImpl2 : ISomeInterface {...} 

También tengo dos servicios donde inyecto ISomeInterface (a través de contructor):

 public class Service1 : IService1 { public Service1(ISomeInterface someInterface) { } ... } 

y

 public class Service2 : IService2 { public Service2(ISomeInterface someInterface) { } ... } 

Estoy usando Autofac como mi herramienta IoC. La pregunta. ¿Cómo puedo configurar los registros de Autofac para que SomeImpl1 se inyecte automáticamente en Service1, y SomeImpl2 se inyecte automáticamente en Service2?

¡Gracias!

Autofac soporta identificación de servicios por nombre . Usando esto, puede registrar sus implementaciones con un nombre (usando el método de extensión Named ). Luego puede resolverlos por nombre en los delegates de registro de IServiceX, usando el método de extensión ResolveNamed . El siguiente código demuestra esto.

 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 

Alternativa usando RegisterType (a diferencia de Register )

Puede obtener el mismo resultado utilizando el método de extensión RegisterType en combinación con WithParameter y ResolvedParameter . Esto es útil si el constructor que toma un parámetro con nombre también toma otros parámetros sin nombre que no le importa especificar en un delegado de registro:

 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 

Si puede cambiar de inyección de constructor a inyección de propiedad, y permitir que ambos servicios se deriven de la misma clase base (o implementar la misma interfaz), puede hacer lo siguiente:

 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; }); 

Para que esto funcione, debe definir la propiedad en el tipo base (o interfaz). Esta solución es muy flexible e incluso le permitiría hacer cosas complejas, como inyectar un tipo genérico, basado en el tipo principal, como se puede ver a continuación:

 builder.RegisterType().OnActivating(e => { var type = typeof(GenericImpl<>).MakeGenericType(e.Instance.GetType()); e.Instance.SomeInterface = (ISomeInterface)e.Context.Resolve(type); }); 

Este enfoque tiene algunas desventajas:

  1. Necesitamos inyección de propiedad.
  2. Necesitamos un tipo base o interfaz que contenga esa propiedad.
  3. Necesitamos reflexión para generar el tipo, lo que podría tener un impacto en el rendimiento (en lugar de que el contenedor cree un delegado eficiente) (aunque podríamos acelerar el proceso almacenando en caché el tipo).

Por el lado positivo, este diseño es simple y funciona para casi cualquier contenedor.

Se describen cuatro variantes para hacer esto en la documentación de autofac :

Opción 1: rediseñar sus interfaces

Cuando te encuentras en una situación en la que tienes un montón de componentes que implementan servicios idénticos pero no se pueden tratar de forma idéntica, esto generalmente es un problema de diseño de interfaz.

Desde una perspectiva de desarrollo orientado a objetos, querría que sus objetos se adhieran al principio de sustitución de Liskov y este tipo de rupturas.

Al hacer un rediseño de la interfaz, no tiene que “elegir una dependencia por contexto”, usa los tipos para diferenciar y dejar que la magia de conexión automática se produzca durante la resolución.

Si tiene la capacidad de afectar el cambio en su solución, esta es la opción recomendada.

Opción 2: cambiar las inscripciones

Puede asociar manualmente el tipo apropiado con el componente consumidor de esa manera:

 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(); 

Opción 3: usar servicios con clave

 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"))); 

Opción 4: utilizar metadatos

 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"))));