Implementing the Decorator design pattern in Orchard Modules

Topics: Core, Writing modules
Mar 5, 2015 at 1:07 PM
We have a fairly large and complex project built on top of Orchard.

One of the things we have found ourselves doing more and more often is suppressing default implementations of core services.

Take for example IContentManager. We may have 3 implementations of this at any one time:
  • CachedContentManager
  • GlimpseContentManager
  • DefaultContentManager
Currently, we use inheritence and the OrchardSuppressDependency attribute to effectively stack these implementations on top of each other eg:
    [OrchardSuppressDependency("Glimpse.Orchard.AlternateImplementations.GlimpseContentManager")]
    public class CachedContentManager : GlimpseContentManager, IContentManager
    {
        ...
    }
This means that we are left with a chain of dependant features. For example- in the above snippet, the feature that contains CachedContentManager is dependant on the feature that contains GlimpseContentManager. This is bad news if we want to disable GlimpseContentManager but not CachedContentManager.

I propose adding the ability to use the Decorator pattern with your IDependencys.

One way to do this would be to provide a Decorator attribute that implies that your new implementation is a decorator and requires another implementation of the same interface to be passed to the constructor.

For example:
    [OrchardSuppressDependency("Glimpse.Orchard.AlternateImplementations.GlimpseContentManager")]
    [Decorator("2")]
    public class CachedContentManager : IContentManager
    {
        ...
    }

    [OrchardSuppressDependency("Orchard.ContentManagement.DefaultContentManager")]
    [Decorator("1.0.0")]
    public class GlimpseContentManager : IContentManager
    {
        ...
    }
    
    public class DefaultContentManager : IContentManager
    {
        ...
    }
Provided all of these features are enabled, this would result in CachedContentManager decorating GlimpseContentManager which in turn is decorating DefaultContentManager. When you inject IContentManager, this will be resolved to CachedContentManager.

Disabling the feature that contains GlimpseContentManager will result in CachedContentManager decorating DefaultContentManager.

Note the use of a FlatPositionComparer value in the Decorator attribute to denote the priority of the implementation.

I also think this would be a non-breaking change.


We'd be quite keen to provide an implementation of this (or any other method that would produce the same results), so we're interested in knowing:
  • If this feature would be a valued contribution?
  • If people have thoughts on the proposed implementation?
  • Any other general feedback?
Developer
Mar 5, 2015 at 10:06 PM
You can do this from an Autofac module:
    public class DecoratorsModule : Module
    {
        protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
        {
            if (registration.Activator.LimitType.IsAssignableTo<IContentManager>())
            {
                registration.Activating += (sender, e) =>
                    {
                        var decorator = new MyContentManager((IContentManager)e.Instance);
                        e.Instance = decorator;
                    };
            }
        }
    }
MyContentManager will decorate IContentManager.
Developer
Apr 1, 2015 at 11:19 AM
This is now better available through a feature in Helpful Libraries.
Apr 1, 2015 at 11:26 AM
Edited Apr 1, 2015 at 11:26 AM
I have actually created a prototype for this similar to the proposal above:

In my prototype, you can mark a class with OrchardDecorator attribute, and the shell container factory takes care of the registration for you.

I've been meaning to follow up this post, but the Codeplex comments feature has been down for maintenance for a while.


As soon as I get some time, I'll post my branch along with a demo module that I have created. I'd also like to present the concept at a tuesday meeting for discussion at some time soon.