Applying decorators conditionally is actually quite cumbersome in Autofac. Let's do this in two steps. First let's write the code that would apply those decorators unconditionally:
var builder = new ContainerBuilder();
builder.RegisterType<Repository>().Named<IRepository>("implementor");
builder.RegisterDecorator<IRepository>(
(c, inner) => new RepositoryLocalCache(inner),
fromKey: "implementor",
toKey: "decorator1");
builder.RegisterDecorator<IRepository>(
(c, inner) => new RepositoryDistributedCache(inner),
fromKey: "decorator1",
toKey: "decorator2");
builder.RegisterDecorator<IRepository>(
(c, inner) => new RepositorySecurity(inner),
fromKey: "decorator2",
toKey: "decorator3");
builder.RegisterDecorator<IRepository>(
(c, inner) => new RepositoryLogging(inner),
fromKey: "decorator3",
toKey: null);
Applying decorators in Autofac is done by registering multiple components with the same service type (IRepository
in your case) using keyed registrations (the toKey
) and point those registrations at one another using the fromKey
). The outermost decorator should be keyless, since by default Autofac will always resolve the keyless registration for you.
These keyed registrations are Autofac's biggest weakness in this respect, since the decorators are hard-wired to the next, because of those keys. If you simply wrap the RepositoryDistributedCache
in an if
-block, the configuration will break, since the RepositorySecurity
will now point at a registration that doesn't exist.
The solution to this problem is to generate the keys dynamically and add an extra 'dummy' keyless decorator that is not applied conditionally:
int counter = 0;
Func<object> getCurrentKey => () => counter;
Func<object> getNextKey => () => ++counter;
var builder = new ContainerBuilder();
builder.RegisterType<Repository>().Named<IRepository>(getCurrentKey());
if (config.UseRepositoryLocalCache) {
builder.RegisterDecorator<IRepository>(
(c, inner) => new RepositoryLocalCache(inner),
fromKey: getCurrentKey(), toKey: getNextKey());
}
if (config.UseRepositoryDistributedCache) {
builder.RegisterDecorator<IRepository>(
(c, inner) => new RepositoryDistributedCache(inner),
fromKey: getCurrentKey(), toKey: getNextKey());
}
if (config.UseRepositorySecurity) {
builder.RegisterDecorator<IRepository>(
(c, inner) => new RepositorySecurity(inner),
fromKey: getCurrentKey(), toKey: getNextKey());
}
if (config.UseRepositoryLogging) {
builder.RegisterDecorator<IRepository>(
(c, inner) => new RepositoryLogging(inner),
fromKey: getCurrentKey(), toKey: getNextKey());
}
// The keyless decorator that just passes the call through.
builder.RegisterDecorator<IRepository>(
(c, inner) => new RepositoryPassThrough(inner),
fromKey: getCurrentKey(), toKey: null);
Here we make use of a counter
variable and create a getNextKey
and getCurrentKey
lambdas that make it easier to do the configuration. Again note the last RepositoryPassThrough
decorator. This decorator should simply call its decoratee and do nothing else. Having this extra decorator makes it much easier to complete the configuration; it would otherwise be much harder to decide what the last decorator would be.
One of the things that makes this much harder with Autofac is the lack of auto-wiring support for non-generic decorators. As far as I know, this is the only part in the Autofac API where auto-wiring (i.e. letting the container figure out which constructor arguments to inject) is not supported. It would be much easier if the registrations could be done using a type instead of a delegate, since we could in that case build an initial list of decorators to apply and than just iterate the list. We still have to deal with those keyed registrations though.