Decorator pattern using attributes

Simplifying the use of decorator pattern using attributes

Tue, 16 Apr 2019

Banner

Decorator Pattern from Last Post

In the last post we looked at very basic implementation of Decorator Pattern using C# and .NET Core. We ended up with a piece of code that will works fine but we were still not happy with it as it lacked readability and that code will be really hard to maintain for a production application. If you have not looked at the last post i will highly recommend you to have quick read of the post before reading through this post.

Just a heads up this post will be mostly the Code snippets with comments because we discussed theory in the Last Post in this post we will improve on the Code that we wrote in last post. For the code related this post please go to Github repo link.

Here is what our setup looks like after first post. We have command handler ToggleItemCommandHandler to update the status of a TODO item. Then we had a decorator AuditLogginDecorator that will log every Command Handler execution. We decorated the ToggleItemCommandHandler with AuditLoggingDecorator then this is how registered them with out out of box Dependency Injection provided by ASP.NET Core.

//Startup.cs
//Don't worry if it looks like a mess. We will simplify it later in the post.
services.AddTransient<ICommandHandler<ToggleItemCommand>>(provider => 
                new AuditLoggingDecorator<ToggleItemCommand>(
                    new ToggleItemCommand.ToggleItemCommandHandler(
                        provider.GetService<IRepository>()
                        )
                    )
              );

If we look at the code above it still look very complex and lack readability. Assume you have 10 different command you have and you want to Decorate each them using above approach the thing will get complicated really soon.

We can use the help of Attributes to simplify the usage of Decorator Pattern. Let’s create an Attribute for our Decorator named ToggleItemCommandHandler.Custom Attribute is nothing special but just a class that inherits from Attribute class. In our case it’s an empty Attribute that does not have any Business Logic.

So we will create an attribute to Represent the AuditLoggingDecorator here is what the code for attribute looks like.

Code for this post can be found at this Github repo link. I will highly recommend to download and read through the code before moving forward.

This Attribute we have created does not have any business logic then how it can help us with our decorator pattern. Let’s have a look at it.

using System;

namespace Logic.Decorators
{
    [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
    public sealed class AuditLogAttribute : Attribute
    {
        public AuditLogAttribute()
        {
        }
    }
}

Next we will add this attribute to our command Handler for which we want to use the Audit Logging Decorator. Now we have an Attribute, Decorator and CommandHandler that we want to decorate using the Decorator.

        [AuditLog]
        internal sealed class ToggleItemCommandHandler: ICommandHandler<ToggleItemCommand>
        {
            //Removed code for brevity
        }

Using out of box .NET Core Dependency Injection

We can either use the Default .NET core dependency injection or we can rely on some 3rd party DI container which can make our life lot easier. So we will look at the both options using the Default DI or some 3rd part DI container. First lets have a look how we can do it using out of the Box dependency injection of .NET Core.

We will accomplish this by changing the way we register our Handlers for .NET Core dependency injection. We will use the help of reflection to achieve this. This is a long code Snippet. But I added comments to improve the readability of the code. We will extract the Handler registration process into an independent class.

//File: Api/Utils/HandlerRegistration.cs
//class to wrap the logic for handler registration
public static class HandlerRegistration
    {
        //Extension for Service collection provided .NET Core DI infrastructure
        public static void AddHandlers(this IServiceCollection services)
        {
            //Get all the Types With 
            List<Type> handlerTypes = typeof(ICommand).Assembly.GetTypes()
                .Where(x => x.GetInterfaces().Any(y => IsHandlerInterface(y)))
                .Where(x => x.Name.EndsWith("Handler"))
                .ToList();

            foreach (Type type in handlerTypes)
            {
                //Try to register each handler one by one
                AddHandler(services, type);
            }
        }

        private static void AddHandler(IServiceCollection services, Type type)
        {
            //Get all the custom attributes the Given Handler is decorated with.
            object[] attributes = type.GetCustomAttributes(false);
            //Convert those Attributes to the Decorator classes
            //using our one to one Attribute to Decorator Mapping
            List<Type> decorators = attributes
                .Select(x => ToDecorator(x))
                .Concat(new[] { type })
                .Reverse()
                .ToList();
            //Get the Type for the Handler Class that should be registered for Dependency Injection
            Type interfaceType = type.GetInterfaces().Single(y => IsHandlerInterface(y));
            //Create Factory to Inject the HandlerClass decorated with all Decorators defined using Attributes.
            Func<IServiceProvider, object> factory = BuildFactory(decorators, interfaceType);

            services.AddTransient(interfaceType, factory);
        }

        //Check if the given Type is a Handler Interface
        //It can be Command handler or Query Handler
        //This method will return true if it is either of them
        private static bool IsHandlerInterface(Type type)
        {
            if (!type.IsGenericType)
                return false;

            Type typeDefinition = type.GetGenericTypeDefinition();

            return typeDefinition == typeof(ICommandHandler<>) || typeDefinition == typeof(IQueryHandler<,>);
        }

        //Receive the Attribute as input
        //And decide which Decorator should be used to enhance the class
        //As in our example we created Attributed corresponding to each Decorator
        //So it is simply one to one mapping between Attribute and Decorator Class
        private static Type ToDecorator(object attribute)
        {
            Type type = attribute.GetType();

            if (type == typeof(DatabaseRetryAttribute))
                return typeof(DatabaseRetryDecorator<>);

            if (type == typeof(AuditLogAttribute))
                return typeof(AuditLoggingDecorator<>);

            // other attributes go here

            throw new ArgumentException(attribute.ToString());
        }


        //This method will be create dynamic factories
        //These dynamic factories will be configured into DI Container
        //List<Type> decorators: This will method recieve all the Decorators that should be applied to a Type
        //Type interfaceType: Type that should be Enhanced or decorated.
        private static Func<IServiceProvider, object> BuildFactory(List<Type> decorators, Type interfaceType)
        {
            //Get the constructor for each of the decorators
            List<ConstructorInfo> ctors = decorators
                .Select(x =>
                {
                    Type type = x.IsGenericType ? x.MakeGenericType(interfaceType.GenericTypeArguments) : x;
                    return type.GetConstructors().Single();
                })
                .ToList();

            //Factory that will returned as result
            //This factory will take DI Container or ServiceProvider as parameter.
            Func<IServiceProvider, object> func = provider =>
            {
                object current = null;
                //Iterate through the Constructor for Each Decorator
                foreach (ConstructorInfo ctor in ctors)
                {
                    //Get all the parameters for a given Decorator Constructor
                    List<ParameterInfo> parameterInfos = ctor.GetParameters().ToList();
                    //Fetch the Required parameters from the ServiceProvider or DI container
                    object[] parameters = GetParameters(parameterInfos, current, provider);
                    //Invoke the Constructor
                    current = ctor.Invoke(parameters);
                }

                return current;
            };

            return func;
        }


        //Get Parameters from Dependency Injection Container
        private static object[] GetParameters(List<ParameterInfo> parameterInfos, object current, IServiceProvider provider)
        {
            var result = new object[parameterInfos.Count];

            for (int i = 0; i < parameterInfos.Count; i++)
            {
                //Get the Object from DI Container for each ParameterInfo
                result[i] = GetParameter(parameterInfos[i], current, provider);
            }

            return result;
        }

        //Get Object or Parameter Value from DI Container
        private static object GetParameter(ParameterInfo parameterInfo, object current, IServiceProvider provider)
        {
            Type parameterType = parameterInfo.ParameterType;

            if (IsHandlerInterface(parameterType))
                return current;
            //Get the Parameter object from DI Container(ServicesProvider)
            object service = provider.GetService(parameterType);
            if (service != null)
                return service;

            throw new ArgumentException($"Type {parameterType} not found");
        }

    }

If i try to summarize the above code snippet. If looks like too complex and hard to understand it is hard and don’t worry we will look at easier simpler way to achieve the same later in the post.

  1. Create extension method HanlderRegistration for IServiceCollection
  2. Inside the HandlerRegistration
  3. —Get the all the Types from assembly that Implement ICommand Interface
  4. —Filter out all the Handlers from the List that we got in Step3.
  5. —Try to Register Each Handler we filtered out in step 4 by calling AddHanlder method.
  6. ----Inside AddHandler we receive the Handler type to Register and The IServiceCollection.
  7. ----We get the list of attributes that are added to Handler parameter from Step 6
  8. ----We map each Attribute Step7 to Decorator as we created the Attribute for each Decorator we have.
  9. ----Now we have List of Decorators the actual Handler and IServiceCollection next step will be to create Factory.
  10. ----We have method BuildFactory that return Factory to resolve the Handler decorated with all the Decorators.
  11. ------Inside BuildFactory get the constructor for all the Decorators in The List of Decorators from Step9.
  12. ------Then we crate a Lambda to Iterate through these constructors.
  13. --------Inside Lambda: Now for each constructor we Get List of Parameters required for the Constructor.
  14. ----------Inside Lambda:Now for each Parameter from the Step12 we resolve the Parameter using GetParameter method call.
  15. --------Inside Lambda:Invoke each Constructor with Parameters fetched in Step13 and Step14.
  16. ----Return the Above Lambda from BuildFactory
  17. —Register the Return of BuildFactory to the IServiceCollection in Step 6.
  18. services.AddTransient(interfaceType, factory);

Now we have a extension method for IServiceCollection that we can use from our startup.cs file to register all the CommandHandlers this extension method will use the Attribute on the top of command handlers to decide which Decorators should be on the top of each CommandHandler we register.

If you think the above code snippet is way too complex and it can be simplified further. Answer to that question is yes we can simplify. There are lot of 3rd party DI containers that provided very simple API to register Decorated services one of my favorites is Simple Injector

Next we will at a example how we can achieve something as above with Simple Injector. We start by installing the Nuget Package for Simple Injector.

Using Simple Injector as an alternative.

First thing first we need a Nuget package to use 3rd party Dependency Injector instead of out of box .net core DI setup. So our Startup.cs will change in order to use some 3rd Party Container for Dependency Injection instead of default DI setup.

Install-Package SimpleInjector.Integration.AspNetCore.Mvc.Core

Once we have the dependency installed we will start configuring the Dependencies using the Simple Injector container. So let’s start from the Startup.cs file under ‘Api’ project. THis is how our Startup.cs will change.

namespace Api
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        //Make Create only one instance of Simple Injector container for whole applications
        private Container container = new Container();
        
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            
            //USING THE OUT OF BOX DEPENDENCY INJECTION - KEPT THIS
            //SO YOU CAN COMPARE QUICKLY
            //Commented the old registrations for Dependency Injection
            //services.AddDbContext<AppDbContext>(options =>
            //    options.UseInMemoryDatabase(dbName));
            //services.AddTransient<IRepository, EfRepository>();
            //services.AddSingleton<Messages>();
            //services.AddHandlers();

            //USING SIMPLE INJECTOR - OUR CUSTOM EXTENSION METHOD
            //WE WILL LOOK AT DETAILS OF THIS EXTENSION METHOD SOON
            services.UseSimpleInjectorConfiguration(container);

        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseMiddleware<ExceptionHandler>();
            app.UseMvc();
            container.RegisterMvcControllers(app);
            //Verify all the Registrations with Simple Injector Container
            container.Verify();
            //Need this code to make migrations run for in-memory DB
            using (AsyncScopedLifestyle.BeginScope(container))
            {
                //Make sure the Migrations are run
                var dbContext = container.GetInstance<AppDbContext>();
                dbContext.Database.EnsureCreated();
            }
        }


    }
}

Now we are using extension method UseSimpleInjectorConfiguration that will accept the Simple Injector container as parameter. Let’s have a detailed look at the actual implementation of the method.

public static void UseSimpleInjectorConfiguration(this IServiceCollection services, Container container)
        {
            //Just some random name for our Dummy In memory DB
            string dbName = Guid.NewGuid().ToString();

            // Default lifestyle scoped + async
            // The recommendation is to use AsyncScopedLifestyle in for applications that solely consist of a Web API(or other asynchronous technologies such as ASP.NET Core)
            container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();

            //container Register our db context
            container.Register<AppDbContext>(() =>
            {
                var options = new DbContextOptionsBuilder<AppDbContext>().UseInMemoryDatabase(dbName).Options;
                return new AppDbContext(options);
            }, Lifestyle.Scoped);


            //Register all the services we are interested in
            container.Register<Messages>();
            container.Register<IRepository, EfRepository>(Lifestyle.Transient);


            //Register all command handlers using Generic Interfaces
            container.Register(typeof(ICommandHandler<>), typeof(ICommandHandler<>).Assembly);
            
            //Register the Decorators Conditionally
            //This code will Decorate a Handler with AuditLoggingDecorator
            //If It has custom attribute of Type AuditLogAttribute
            container.RegisterDecorator(typeof(ICommandHandler<>),
                typeof(AuditLoggingDecorator<>),
                context => context.ImplementationType.GetCustomAttributes(false).Any(a => a.GetType() == typeof(AuditLogAttribute)));

            //Register all Query handlers
            container.Register(typeof(IQueryHandler<,>), typeof(IQueryHandler<,>).Assembly);

            // Register controllers DI resolution Using Simple Injector
            services.AddSingleton<IControllerActivator>(new SimpleInjectorControllerActivator(container));

            // Wrap AspNet requests into Simple Injector's scoped lifestyle
            services.UseSimpleInjectorAspNetRequestScoping(container);

        }

As you can see if we use some 3rd party Container for Dependency Injection our code for registering all Query Handlers and Decorator Pattern is simplified a lot. We can use 3rd party DI container as they can do lot of heavy lifting of reflection to resolve dependencies for your application. But if you don’t want another dependency and want full control you can always do everything using Reflection as we demonstrated earlier in the post.

Our custom implementation using reflection is still available in the Code attached to this post.

Conclusion

This post was mostly about the code refactoring to improve the Decorator pattern that implement in the last post. So we looked at our custom implementation to register Decorators using the Help of Attributes and Reflection. Then we simplified that further using Simple Injector Container. I will leave the decision up to you whether you will prefer the custom Implementation or you want some 3rd party DI container to take care of the reflection for you. I will see you soon with a new post till good bye and take care. If you like my writing and want to hear more from me you can find me on Twitter.

Loading...
Ranjeet Singh

Ranjeet Singh Software Developer (.NET, ReactJS, Redux, Azure) You can find me on twitter @NotRanjeet or on LinkedIn at LinkedIn