c# - inyeccion - beneficios de la inyección de dependencias
Dependencia Inyección composición raíz y patrón decorador. (3)
Estoy obteniendo StackoverflowException
en mi implementación del patrón decorador cuando uso la inyección de dependencia. Creo que es porque me estoy "perdiendo" algo de mi comprensión de DI / IoC.
Por ejemplo, actualmente tengo CustomerService
y CustomerServiceLoggingDecorator
. Ambas clases implementan ICustomerService
, y lo único que hace la clase de decorador es usar un ICustomerService
inyectado, pero agrega un registro simple de NLog para que pueda usar el registro sin afectar el código en CustomerService
sin romper el principio de responsabilidad única.
Sin embargo, el problema aquí es que debido a que CustomerServiceLoggingDecorator
implementa ICustomerService
, y también necesita una aplicación de ICustomerService
inyectada para que funcione, Unity continuará intentando resolverlo por sí mismo, lo que causa un bucle infinito hasta que se desborda la pila.
Estos son mis servicios:
public interface ICustomerService
{
IEnumerable<Customer> GetAllCustomers();
}
public class CustomerService : ICustomerService
{
private readonly IGenericRepository<Customer> _customerRepository;
public CustomerService(IGenericRepository<Customer> customerRepository)
{
if (customerRepository == null)
{
throw new ArgumentNullException(nameof(customerRepository));
}
_customerRepository = customerRepository;
}
public IEnumerable<Customer> GetAllCustomers()
{
return _customerRepository.SelectAll();
}
}
public class CustomerServiceLoggingDecorator : ICustomerService
{
private readonly ICustomerService _customerService;
private readonly ILogger _log = LogManager.GetCurrentClassLogger();
public CustomerServiceLoggingDecorator(ICustomerService customerService)
{
_customerService = customerService;
}
public IEnumerable<Customer> GetAllCustomers()
{
var stopwatch = Stopwatch.StartNew();
var result = _customerService.GetAllCustomers();
stopwatch.Stop();
_log.Trace("Querying for all customers took: {0}ms", stopwatch.Elapsed.TotalMilliseconds);
return result;
}
}
Actualmente tengo la configuración de registros como esta (este método de código auxiliar fue creado por Unity.Mvc
):
public static void RegisterTypes(IUnityContainer container)
{
// NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
// container.LoadConfiguration();
// TODO: Register your types here
// container.RegisterType<IProductRepository, ProductRepository>();
// Register the database context
container.RegisterType<DbContext, CustomerDbContext>();
// Register the repositories
container.RegisterType<IGenericRepository<Customer>, GenericRepository<Customer>>();
// Register the services
// Register logging decorators
// This way "works"*
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(
new CustomerService(
new GenericRepository<Customer>(
new CustomerDbContext()))));
// This way seems more natural for DI but overflows the stack
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();
}
Así que ahora no estoy seguro de la forma "correcta" de crear un decorador con inyección de dependencia. Basé a mi decorador en la respuesta de Mark Seemann here . En su ejemplo, está actualizando varios objetos que pasan a la clase. Así es como mi "funciona" * funciona el fragmento. Sin embargo, creo que he perdido un paso fundamental.
¿Por qué crear manualmente nuevos objetos como este? ¿Esto no niega el punto de que el contenedor haga la resolución por mí? ¿O debería en cambio contain.Resolve()
(localizador de servicio) dentro de este método, para obtener todas las dependencias aún inyectadas?
Estoy un poco familiarizado con el concepto de "raíz de composición", que es donde se supone que debe conectar estas dependencias en un solo lugar y luego en cascada hasta los niveles más bajos de la aplicación. Entonces, ¿los Unity.Mvc
RegisterTypes()
generados por Unity.Mvc
RegisterTypes()
la raíz de la composición de una aplicación MVC de ASP.NET? Si es así, ¿es realmente correcto estar actualizando directamente los objetos aquí?
Tenía la impresión de que generalmente con Unity necesita crear la raíz de la composición, sin embargo, Unity.Mvc
es una excepción a esto, ya que crea su propia raíz de composición porque parece poder inyectar dependencias en los controladores que tienen una Interfaz como ICustomerService
en el constructor sin que yo escriba el código para hacerlo.
Pregunta : Creo que me falta una pieza clave de información, lo que me lleva a StackoverflowExceptions
debido a las dependencias circulares. ¿Cómo implemento correctamente mi clase de decorador mientras sigo la inyección / inversión de dependencia de los principios y convenciones de control?
Segunda pregunta : ¿y si decidiera que solo quería aplicar el decorador de registro en ciertas circunstancias? Entonces, si tenía MyController1
que deseaba tener una dependencia CustomerServiceLoggingDecorator
, pero MyController2
solo necesita un CustomerService
normal, ¿cómo puedo crear dos registros separados? Porque si lo hago:
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();
container.RegisterType<ICustomerService, CustomerService>();
Luego se sobrescribirá uno, lo que significa que ambos controladores tendrán un decorador inyectado o un servicio normal inyectado. ¿Cómo permito ambos?
Edición: Esta no es una pregunta duplicada porque tengo problemas con las dependencias circulares y no entiendo el enfoque correcto de DI para esto. Mi pregunta se aplica a todo un concepto, no solo al patrón decorador como la pregunta enlazada.
Pregunta: Creo que me falta una pieza clave de información, lo que me lleva a Exceptions debido a las dependencias circulares. ¿Cómo implemento correctamente mi clase de decorador mientras sigo la inyección / inversión de dependencia de los principios y convenciones de control?
Como ya se señaló, la mejor manera de hacerlo es con la siguiente construcción.
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(new ResolvedParameter<CustomerService>()));
Esto le permite especificar cómo se resuelven los parámetros por tipo. También puede hacerlo por nombre, pero por tipo es una implementación más limpia y permite una mejor comprobación durante el tiempo de compilación, ya que no se detectará un cambio o error en una cadena. Tenga en cuenta que la única diferencia mínima entre esta parte del código y el código ofrecido por Mark Seemann es una corrección en la ortografía de InjectionConstructor
. No daré más detalles sobre esta parte, ya que no hay nada más que agregar que Mark Seemann no haya explicado.
Segunda pregunta: ¿y si decidiera que solo quería aplicar el decorador de registro en ciertas circunstancias? Entonces, si tenía MyController1 que deseaba tener una dependencia CustomerServiceLoggingDecorator, pero MyController2 solo necesita un CustomerService normal, ¿cómo puedo crear dos registros separados?
Puede hacerlo de la manera especificada anteriormente utilizando la notación Fluent O utilizando la dependencia con nombre con una anulación de dependencia.
Fluido
Esto registra el controlador con el contenedor y especifica una sobrecarga para ese tipo en el constructor. Prefiero este enfoque en lugar del segundo, pero solo depende de dónde desea especificar el tipo.
container.RegisterType<MyController2>(
new InjectionConstructor(new ResolvedParameter<CustomerService>()));
Dependencia nombrada
Haces esto de la misma manera, registras a ambos como tal.
container.RegisterType<ICustomerService, CustomerService>("plainService");
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(new ResolvedParameter<CustomerService>()));
La diferencia aquí es que utiliza una dependencia con nombre en lugar de los otros tipos que se pueden resolver utilizando la misma interfaz. Esto se debe a que la interfaz debe resolverse exactamente a un tipo concreto cada vez que Unity realiza una resolución, por lo que no puede tener varios tipos registrados sin nombre registrados en la misma interfaz. Ahora puede especificar una anulación en el constructor de su controlador utilizando un atributo. Mi ejemplo es para un controlador llamado MyController2
y agregué el atributo de Dependency
con el nombre también especificado anteriormente en el registro. Por lo tanto, para este constructor, se inyectará un tipo CustomerService
lugar del tipo CustomerServiceLoggingDecorator
predeterminado. MyController1
seguirá utilizando el registro predeterminado sin nombre para ICustomerService
que es el tipo CustomerServiceLoggingDecorator
.
public MyController2([Dependency("plainService")]ICustomerService service)
public MyController1(ICustomerService service)
También hay formas de hacer esto cuando resuelve manualmente el tipo en el propio contenedor, consulte Resolver objetos utilizando sustituciones . El problema aquí es que necesita acceso al propio contenedor para hacer esto, lo cual no se recomienda. Como alternativa, puede crear una envoltura alrededor del contenedor que luego inyecta en el Controlador (u otro tipo) y luego recuperar un tipo de esa manera con anulaciones. De nuevo, esto se complica un poco y lo evitaría si fuera posible.
Preámbulo
Siempre que tenga problemas con un contenedor DI (Unidad o de otro tipo), pregúntese esto: ¿vale la pena usar un contenedor DI?
En la mayoría de los casos, la respuesta debería ser no . Utilice Pure DI en su lugar. Todas sus respuestas son triviales para responder con Pure DI.
Unidad
Si debes usar Unity, quizás lo siguiente te sea de ayuda. No he usado Unity desde 2011, así que las cosas pueden haber cambiado desde entonces, pero al buscar el problema en la sección 14.3.3 en mi libro , algo como esto podría hacer el truco:
container.RegisterType<ICustomerService, CustomerService>("custSvc");
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(
new ResolvedParameter<ICustomerService>("custSvc")));
Alternativamente, también puedes hacer esto:
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(
new ResolvedParameter<CustomerService>()));
Esta alternativa es más fácil de mantener porque no se basa en servicios con nombre, pero tiene la desventaja (potencial) de que no puede resolver CustomerService
través de la interfaz ICustomerService
. Probablemente no deberías estar haciendo eso de todos modos, por lo que no debería ser un problema, así que esta es probablemente una mejor alternativa.
Sobre la base de la segunda respuesta de Mark, buscaría registrar el CustomerService
al CustomerService
con un InjectionFactory
y solo registrarlo con el tipo de servicio sin su interfaz como:
containter.RegisterType<CustomerService>(new InjectionFactory(
container => new CustomerService(containter.Resolve<IGenericRepository<Customer>>())));
Esto permitiría, como en la respuesta de Mark, que usted registre el objeto de registro como:
containter.RegisterType<ICutomerService, CutomerServiceLoggingDecorator>(new InjectionConstructor(
new ResolvedParameter<CustomerService>()));
Esta es básicamente la misma técnica que utilizo cada vez que requiero que se cargue algo de manera perezosa, ya que no quiero que mis objetos dependan de Lazy<IService>
y al envolverlos en proxy me permite inyectar solo el servicio de IService
pero tengo que resolverlo con pereza el proxy.
Esto también le permitirá elegir dónde se inyecta el objeto de registro o el objeto normal en lugar de requerir cadenas magic
simplemente resolviendo un CustomerService
para su objeto en lugar del ICustomerService
.
Para un servicio al CustomerService
registro:
container.Resolve<ICustomerService>()
O para un CustomerService
no se registre:
container.Resolve<CustomerService>()