Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mix decorators in autofac?

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?

like image 532
Attila Szasz Avatar asked May 01 '14 11:05

Attila Szasz


3 Answers

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.

like image 104
Nicholas Blumhardt Avatar answered Nov 15 '22 07:11

Nicholas Blumhardt


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.

like image 6
Steven Avatar answered Nov 15 '22 06:11

Steven


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:

  • The decorators can be conditionally turned on or off
  • There can be zero, one, or many decorators registered so long as there is at least one registration that is not a decorator
  • Once 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.
  • You can have control of the order that the decorators are nested by changing the order of the registrations. The outermost decorator will be the last decorator to be registered.

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.

like image 1
Chris Payne Avatar answered Nov 15 '22 06:11

Chris Payne