This helpful article from David Haydn (EDIT: scam link removed, it could have been this article) shows how you can use the InjectionConstructor
class to help you set up a chain using the decorator pattern with Unity. However, if the items in your decorator chain have other parameters in their constructor, the InjectionConstructor
must explicitly declare each one of them (or Unity will complain that it can't find the right constructor). This means that you can't simply add new constructor parameters to items in the decorator chain without also updating your Unity configuration code.
Here's some example code to explain what I mean. The ProductRepository
class is wrapped first by CachingProductRepository
and then by LoggingProductRepostiory
. Both CachingProductRepository and LoggingProductRepository, in addition to taking a IProductRepository in their constructor, also need other interfaces from the container.
public class Product { public int Id; public string Name; } public interface IDatabaseConnection { } public interface ICacheProvider { object GetFromCache(string key); void AddToCache(string key, object value); } public interface ILogger { void Log(string message, params object[] args); } public interface IProductRepository { Product GetById(int id); } class ProductRepository : IProductRepository { public ProductRepository(IDatabaseConnection db) { } public Product GetById(int id) { return new Product() { Id = id, Name = "Foo " + id.ToString() }; } } class CachingProductRepository : IProductRepository { IProductRepository repository; ICacheProvider cacheProvider; public CachingProductRepository(IProductRepository repository, ICacheProvider cp) { this.repository = repository; this.cacheProvider = cp; } public Product GetById(int id) { string key = "Product " + id.ToString(); Product p = (Product)cacheProvider.GetFromCache(key); if (p == null) { p = repository.GetById(id); cacheProvider.AddToCache(key, p); } return p; } } class LoggingProductRepository : IProductRepository { private IProductRepository repository; private ILogger logger; public LoggingProductRepository(IProductRepository repository, ILogger logger) { this.repository = repository; this.logger = logger; } public Product GetById(int id) { logger.Log("Requesting product {0}", id); return repository.GetById(id); } }
Here's a (passing) unit test. See the comments for the bits of surplus configuration I want to remove the need for:
[Test] public void ResolveWithDecorators() { UnityContainer c = new UnityContainer(); c.RegisterInstance<IDatabaseConnection>(new Mock<IDatabaseConnection>().Object); c.RegisterInstance<ILogger>(new Mock<ILogger>().Object); c.RegisterInstance<ICacheProvider>(new Mock<ICacheProvider>().Object); c.RegisterType<IProductRepository, ProductRepository>("ProductRepository"); // don't want to have to update this line every time the CachingProductRepository constructor gets another parameter var dependOnProductRepository = new InjectionConstructor(new ResolvedParameter<IProductRepository>("ProductRepository"), new ResolvedParameter<ICacheProvider>()); c.RegisterType<IProductRepository, CachingProductRepository>("CachingProductRepository", dependOnProductRepository); // don't want to have to update this line every time the LoggingProductRepository constructor changes var dependOnCachingProductRepository = new InjectionConstructor(new ResolvedParameter<IProductRepository>("CachingProductRepository"), new ResolvedParameter<ILogger>()); c.RegisterType<IProductRepository, LoggingProductRepository>(dependOnCachingProductRepository); Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>()); }
Another approach, thanks to a suggestion from @DarkSquirrel42, is to use an InjectionFactory
. The downside is that the code still needs updating every time a new constructor parameter is added to something in the chain. The advantages are much easier to understand code, and only a single registration into the container.
Func<IUnityContainer,object> createChain = container => new LoggingProductRepository( new CachingProductRepository( container.Resolve<ProductRepository>(), container.Resolve<ICacheProvider>()), container.Resolve<ILogger>()); c.RegisterType<IProductRepository>(new InjectionFactory(createChain)); Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With