Reputation: 2089
So I'm trying to implement the concept of interception, while using autofac. I'm not doing anything fancy, like implementing dynamic interception, every class I have concrete code around.
My Code (not my real code, I found this online but it demonstrates my problem
public class DefaultProductService : IProductService
{
public Product GetProduct(int productId)
{
return new Product();
}
}
public class CachedProductService : IProductService
{
private readonly IProductService _innerProductService;
private readonly ICacheStorage _cacheStorage;
public CachedProductService(IProductService innerProductService, ICacheStorage cacheStorage)
{
if (innerProductService == null) throw new ArgumentNullException("ProductService");
if (cacheStorage == null) throw new ArgumentNullException("CacheStorage");
_cacheStorage = cacheStorage;
_innerProductService = innerProductService;
}
public Product GetProduct(int productId)
{
string key = "Product|" + productId;
Product p = _cacheStorage.Retrieve<Product>(key);
if (p == null)
{
p = _innerProductService.GetProduct(productId);
_cacheStorage.Store(key, p);
}
return p;
}
}
public class ProductManager : IProductManager
{
private readonly IProductService _productService;
public ProductManager(IProductService productService)
{
_productService = productService;
}
}
My problem is, I want my ProductManager to receive a "CachedProductService" for IProductService, and I want my CachedProductService to receive a "DefaultProductService" for IProductService.
I know of a few solutions, but none of them seem exactly correct. What is the right way of doing this?
Thanks! Michael
Upvotes: 0
Views: 306
Reputation: 16192
What you are trying to do seems not to be an interception concept but rather a decorator concept.
In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class.
from Decorator pattern (Wikipedia)
Autofac has built-in support for decorator using named registration.
First, you have to declare your base component and decorators as named registration :
builder.RegisterType<DefaultProductService>().Named<IProductService>("base");
builder.RegisterType<CacheProductServiceAdapter>().Named<IProductService>("cache");
Then you can register your decorator
builder.RegisterDecorator((c, inner) => c.ResolveNamed<IProductService>("cache", TypedParameter.From(inner)), fromKey : "base")
.As<IProductService>();
You can also have more than one adapter :
builder.RegisterType<DefaultProductService>().Named<IProductService>("base");
builder.RegisterType<CacheProductServiceAdapter>().Named<IProductService>("cache");
builder.RegisterType<LoggingProductServiceAdapter>().Named<IProductService>("logging");
builder.RegisterDecorator((c, inner) => c.ResolveNamed<IProductService>("cache", TypedParameter.From(inner)), fromKey : "base", toKey:"cached");
builder.RegisterDecorator((c, inner) => c.ResolveNamed<IProductService>("logging", TypedParameter.From(inner)), fromKey : "cached")
.As<IProductService>();
See adapters and decorators from Autofac documentation for more information.
Upvotes: 1
Reputation: 2089
Based on Scott's suggestion, I think I figured out a way without having to introduce a dependency on the container in my ProductManager class (I really hated to have to do that!). I can simply instantiate the CachedProductService and tell it which ProductService to use
builder.RegisterType(typeof(ProductService)).Named<IProductService>("DefaultProductService");
builder.Register(c => new CachedProductService(c.ResolveKeyed<IProductService>("DefaultProductService"))).As<IProductService>();
Upvotes: 0
Reputation: 29207
You can do this using named dependencies.
Instead of saying that the implementation of IProductService
is DefaultProductService
, you'll have two implementations distinguished by their names, like this:
builder.Register<DefaultProductService>().Named<IProductService>("DefaultProductService");
builder.Register<CachedProductService>().Named<IProductService>("CachedProductService");
There are a few different ways described in the documentation to indicate which implementation you want to inject into a given class. This looks like the simplest and most explicit:
public class ProductManager : IProductManager
{
private readonly IProductService _productService;
public ProductManager([WithKey("CachedProductService")]IProductService productService)
{
_productService = productService;
}
}
To be honest, I don't like it because it causes the class to have a dependency on the container. It doesn't allow you to configure this completely separate from the classes themselves. With Windsor you would do this when registering dependencies with the container, telling it which named dependencies to use.
But this should work.
Upvotes: 1