I'd like to be able to mix and match decorators with Autofac.
For example, let's say I have an IRepository interface, implemented by the Repository class.
I could have the following decorators: RepositoryLocalCache, RepositoryDistributedCache, RepositorySecurity, RepositoryLogging..., you get the ideea.
Based on config settings, I'd like to decorate the basic implementation with the needed decorators. That can be none, one or multiple decorators.
I am familiar with the syntax of registering one decorator, or a chain of them in a fixed order, but how can I make this dynamic?
As Steven points out above, the RegisterDecorator
methods in Autofac aren't really designed for this scenario, and are pretty clunky to use. They were built for some situations that were hard to implement using regular Autofac registrations - the "native" way of doing this is much cleaner.
For the example, IFoo
is the service and Impl
is the concrete (e.g. repository) implementation.
interface IFoo { }
class Impl : IFoo { }
class DecoratorA : IFoo
{
public DecoratorA(IFoo decorated) { }
}
class DecoratorB : IFoo
{
public DecoratorB(IFoo decorated) { }
}
First register all components using their concrete types:
var builder = new ContainerBuilder();
builder.RegisterType<Impl>();
builder.RegisterType<DecoratorA>();
builder.RegisterType<DecoratorB>();
Lambda registrations are fine too, just make sure they don't use As<IFoo>()
.
Now a wrapper that chains them up to provide the fully-configured service:
bool useA = true, useB = false;
builder.Register(c =>
{
IFoo result = c.Resolve<Impl>();
if (useA)
result = c.Resolve<DecoratorA>(TypedParameter.From(result));
if (useB)
result = c.Resolve<DecoratorB>(TypedParameter.From(result));
return result;
}).As<IFoo>();
useA
and useB
are your dynamically provided values from configuration.
Now resolving (or taking a dependency on) IFoo
will get you a dynamically-constructed decorator chain.
using (var container = builder.Build())
{
var foo = container.Resolve<IFoo>();
If you're using generics things are trickier, since you don't mention them I'll not go into it but if you are then please post another question.
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.
I've just stumbled upon this thread and would like to share how I do this:
A while ago, I wrote a couple of extension methods to simplify this problem. The methods are similar to @Steven's answer in the sense that they create names for the implementations on the fly. However, they don't use RegisterDecorator
, which means that there is no need for the "pass through" implementation.
The methods can be used like this:
builder.RegisterDecorated<EnquiryService, IEnquiryService>();
builder.RegisterDecorator<ServiceBusEnquiryServiceDecorator, IEnquiryService>();
builder.RegisterDecorator<EmailNotificationDecorator, IEnquiryService>();
There are several advantages to this implementation:
RegisterDecorated
has been called, you are free to call RegisterDecorator
from anywhere you are building up your registrations. This means that you can register decorators from within an entirely separate Autofac module that lives in a different assembly or project from the original implementation to decorate.The extension methods look like this:
public static class ContainerBuilderExtensions
{
private static readonly IDictionary<Type, string> _implementationNames = new ConcurrentDictionary<Type, string>();
public static void RegisterDecorated<T, TImplements>(this ContainerBuilder builder) where T : TImplements
{
builder.RegisterType<T>()
.As<TImplements>()
.Named<TImplements>(GetNameOf<TImplements>());
}
public static void RegisterDecorator<T, TImplements>(this ContainerBuilder builder) where T : TImplements
{
var nameOfServiceToDecorate = GetOutermostNameOf<TImplements>();
builder.RegisterType<T>();
builder.Register(c =>
{
var impl = c.ResolveNamed<TImplements>(nameOfServiceToDecorate);
impl = c.Resolve<T>(TypedParameter.From(impl));
return impl;
})
.As<TImplements>()
.Named<TImplements>(GetNameOf<TImplements>());
}
private static string GetNameOf<T>()
{
var type = typeof(T);
var name = type.FullName + Guid.NewGuid();
_implementationNames[type] = name;
return name;
}
private static string GetOutermostNameOf<T>()
{
var type = typeof(T);
if (!_implementationNames.ContainsKey(type))
{
throw new Exception("Cannot call RegisterDecorator for an implementation that is not decorated. Ensure that you have called RegisterDecorated for this type before calling RegisterDecorator.");
}
return _implementationNames[typeof(T)];
}
}
The snippet above is about as simple as it can be, but it has served me well. Of course, it'd be possible to make changes if you have more complex requirements.
This concept formed the basis of my contribution to Orchard CMS that added decorator capabilities: https://github.com/OrchardCMS/Orchard/pull/6233.
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