AOP with StructureMap Container

Source code for this post can be found in my git.

The first thing I want to say is what is a AOP? So we need to know about the definition of it, yes starting now.

Aspect Oriented Programming (AOP) was originally developed in the late 1980’s at the Xerox Palo Alto Research Center (PARC). All software that is developed has concerns. A concern is a goal, concept or area of interest. In most software there are two types of concerns, core concerns and system level concerns. Previous development methodologies such as Object Oriented Programming (OOP) have done a good job of encapsulating core concerns into modules or classes. The issue with Object Oriented Programming however occurs when concerns need to span more than one module or class. These system level concerns are also known as cross cutting concerns. Common examples of cross cutting concerns are logging, security and tracing. Using Object Oriented Programming these concerns would be coded and added to each class and module where required, making the resulting code harder to read, difficult to maintain, understand and reuse.

AOP however handles these concerns without having to modify each class or module affected. AOP complements Object Oriented Programming by facilitating modularity and pulling the implementation for cross cutting concerns into a single unit.

An implementation of AOP will require the following:

  • Joinpoints - well-defined points within a program, for example method calls, method entry, method exit and exception handling blocks.
  • Pointcuts - collections of one or more joinpoints that will be acted upon.
  • Advice or Introductions – Advice is the code that is executed based on a set of specific joinpoints and a specific context. Introductions provide the ability to affect the static code of a program with the introduction of additional attributes or methods.
  • Weaver – responsible for taking the information specified for joinpoints, pointcuts, advice / introductions and the system code, to create a functional system by incorporating the AOP code into the standard code.

We usually have 4 levels for AOP in enterprise system, include: Authentication, Audit, Logging, Error Handling. And in this post I just work on 3 levels are Audit, Logging and Error Handling.

I really love StructureMap and some of case, I usually find some solution for implemented interceptor on StructureMap (really it is a AOP), but in internet have a little post about it, and some post is not clear for me. So I decide to write this post today for make clear some thing in StructureMap interceptor. In the post about interceptor for StructureMap at here and here, Jeremy Miller explained very detail about make some intercept on object creation and enrich this object with other behavior. These was very good, but I want to make a intercept on the dynamic proxy of Castle project. I think it is a best way to custom the method call. It is really a magic on code. I like it. Really I am a newbie on StructureMap (just finding and working on it for 1.5 years). So I think many things I must learn to improve my code on StructureMap.

So what do you need on IoC container? It will manage object life cycle, intercept and permit you to make something miracle, and also make your code ease to testing. Just thinking that seen to be have a invisible person to help you to manage objects in your application (just joking). I used to apply many IoC containers in my working, such as Spring.NET, Casle Windsor, Unity, StructureMap, AutoFac, MEF, LinFu, NInject, but maybe I only like StructureMap (I don't know the reason, but I like it). And now I like the interceptor on IoC Container. The magic thing is you can register your object to the IoC Container and let him to make the proxy for that object. So we must be using the Dynamic Proxy from Castle project. It is really good for this task. Why do you need a proxy for you object? You can google this purpose, I only talk that we don't want to show real object before we make sure that everything is valid.

I talked many things about it, but no-one can see my work, but no problems, really need to explain about it before you see a code.

The first thing we need to download the StructureMap and Dynamic Proxy, after that, we have already enough to work on it.

Because Convention over Configuration (CoC) is very popular nowaday, maybe got some ideas from Ruby on Rails (RoR). In this post I will make some CoC for config the code for scan all attributes that need to intercepting.

We need a engine that will scan all concerns about audit, logging, exception management. So we shall have a contract for it as below:

    public interface IAttributeScanEngine
    {
        void Run(IInvocation invocation, Type type);

        IEnumerable<Type> AutoFindAttribute(string path);
    }

The idea is I will find all class that implemented by IAttributeScanTask interface and run it. So if you write some concern that implemented IAttributeScanTask, it also will run, but don't need to open the class and add the config for your concern (CoC). And the contract for IAttributeScanTask is:

    public interface IAttributeScanTask
    {
        void Run(IInvocation invocation, bool isFirstCall);

        bool AttributeRecognize(ICustomAttributeProvider methodInfo);
    }

 

Run method will be calling the concrete method, and make some intercept on this method. An AttributeRecognize method is using for find the attribute that we shall decorate on the method that we want to intercepting. That's fine. Now we have the implemented for it at below:

    public class AutoAttributeScanEngine : IAttributeScanEngine
    {
        private readonly IDirectoryTask _directoryTask;
        private readonly ILog _logger;
        private bool _firstcall = true;

        public AutoAttributeScanEngine()
        {
        }

        public AutoAttributeScanEngine(IDirectoryTask directoryTask, ILog logger)
        {
            _directoryTask = directoryTask;
            _logger = logger;
        }

        public void Run(IInvocation invocation, Type type)
        {
            var concreteObject = (IAttributeScanTask)Activator.CreateInstance(type, _logger);
            concreteObject.Run(invocation, _firstcall);
            _firstcall = false;
        }

        public virtual IEnumerable<Type> AutoFindAttribute(string path = "")
        {
            IList<Type> result = new List<Type>();

            if (string.IsNullOrEmpty(path))
            {
                path = _directoryTask.GetCurrentDirectory();
            }

            var assemblies = _directoryTask.GetFiles(path, "*.dll");

            if (assemblies.Length > 0)
            {
                AddRange(result, FindAllAssemblies(assemblies));
            }

            return result;
        }

        private static IEnumerable<Type> FindAllAssemblies(IEnumerable<string> paths)
        {
            IList<Type> result = new List<Type>();

            foreach (var assembly in paths)
            {
                var realAssembly = Assembly.LoadFrom(assembly);

                if (realAssembly == null)
                    throw new NullReferenceException();

                var allTypes = realAssembly.GetTypes();

                if (allTypes.Length > 0)
                {
                    AddRange(result, FindAllTypes(allTypes));
                }
            }

            return result;
        }

        private static IEnumerable<Type> FindAllTypes(IEnumerable<Type> types)
        {
            IList<Type> result = new List<Type>();

            foreach (var type in types)
            {
                //http://www.hanselman.com/blog/DoesATypeImplementAnInterface.aspx
                if (typeof(IAttributeScanTask).IsAssignableFrom(type)
                    && type.FullName != typeof(IAttributeScanTask).FullName)
                    result.Add(type);
            }

            return result;
        }

        private static void AddRange(ICollection<Type> src, IEnumerable<Type> des)
        {
            foreach (var type in des)
            {
                src.Add(type);
            }
        }
    }

Now we need to have some attribute for decorating on the top of method for Audit, Logging, CatchException. And details of it as below:

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public class AuditMethodAttribute : Attribute
    {
    }
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public class CatchMethodExceptionAttribute : Attribute
    {
    }
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public class LockObjectAttribute : Attribute
    {
    }
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public class LoggingAttribute : Attribute
    {
    }

And for each attributes we shall have the scan task for it as:

    public class AuditMethodScanTask : IAttributeScanTask
    {
        private readonly ILog _logger;

        public AuditMethodScanTask(ILog logger)
        {
            _logger = logger;
        }

        public void Run(IInvocation invocation, bool isFirstCall)
        {
            if (AttributeRecognize(invocation.Method) && isFirstCall)
            {
                CatchExceptionHelper.TryCatchAction(
                    () => AuditMethod(invocation.Proceed, invocation.Method.Name, isFirstCall),
                    _logger);
            }
        }

        public bool AttributeRecognize(ICustomAttributeProvider methodInfo)
        {
            var attribute = methodInfo.GetCustomAttributes(typeof(AuditMethodAttribute), true);

            if (attribute == null)
                return false;

            return attribute is AuditMethodAttribute[];
        }

        private static void AuditMethod(Action method, string methodName, bool isFirstCall)
        {
            //connect to database for audit this method call
            if (isFirstCall)
                method();
        }
    }
    public class LoggingScanTask : IAttributeScanTask
    {
        private readonly ILog _logger;

        public LoggingScanTask()
        {
        }

        public LoggingScanTask(ILog logger)
        {
            _logger = logger;
        }

        public void Run(IInvocation invocation, bool isFirstCall)
        {
            if (AttributeRecognize(invocation.Method))
            {
                CatchExceptionHelper.TryCatchAction(
                    () => LogInformation(invocation.Proceed, invocation.Method.Name, isFirstCall),
                    _logger);
            }
        }

        public bool AttributeRecognize(ICustomAttributeProvider methodInfo)
        {
            var attribute = methodInfo.GetCustomAttributes(typeof(LoggingAttribute), true);

            if (attribute == null)
                return false;

            return attribute is LoggingAttribute[];
        }

        private void LogInformation(Action method, string methodName, bool isFirstCall)
        {
            var startTime = DateTime.Now;

            if (isFirstCall)
                method();

            var endTime = DateTime.Now;
            var duration = endTime.Subtract(startTime).TotalSeconds.ToString("N3");

            var msg = string.Format("Duration of raw {0}(): {1}s",
                                    methodName, duration);

            _logger.Info(msg);
        }
    }
    public class LockObjectScanTask : IAttributeScanTask
    {
        private readonly ILog _logger;
        private static readonly object Locker = new object();

        public LockObjectScanTask()
        {
        }

        public LockObjectScanTask(ILog logger)
        {
            _logger = logger;
        }

        public void Run(IInvocation invocation, bool isFirstCall)
        {
            if (AttributeRecognize(invocation.Method) && isFirstCall)
            {
                CatchExceptionHelper.TryCatchAction(
                    () => LockMethod(invocation.Proceed, invocation.Method.Name, isFirstCall),
                    _logger);
            }
        }

        public bool AttributeRecognize(ICustomAttributeProvider methodInfo)
        {
            var attribute = methodInfo.GetCustomAttributes(typeof(LockObjectAttribute), true);

            if (attribute == null)
                return false;

            return attribute is LockObjectAttribute[];
        }

        private static void LockMethod(Action method, string methodName, bool isFirstCall)
        {
            lock (Locker)
            {
                if (isFirstCall)
                    method();
            }
        }
    }

Next step we introduce the customize interceptor for StructureMap that extend from Dynamic Proxy

    public class MyExInterceptor : IInterceptor
    {
        private readonly ILog _logger;
        private readonly IAttributeScanEngine _attributeEngine;
        private static string _attrNamespace;

        public MyExInterceptor(string attrNamespace)
            : this(MyObjectFactory.GetInstance<ILog>(),
                   MyObjectFactory.GetInstance<IAttributeScanEngine>(), attrNamespace)
        {
        }

        public MyExInterceptor(ILog logger, IAttributeScanEngine attributeEngine, string attrNamespace)
        {
            _logger = logger;
            _attributeEngine = attributeEngine;
            _attrNamespace = attrNamespace;
        }

        public void Intercept(IInvocation invocation)
        {
            var attributes = _attributeEngine.AutoFindAttribute("");

            foreach (var attribute in attributes)
            {
                var nameTemp = GetAttributeName(GetScanTaskName(attribute.FullName));

                var type = Type.GetType(nameTemp);

                if (type != null)
                {
                    var existedMethod = Attribute.GetCustomAttribute(invocation.GetConcreteMethodInvocationTarget(), type, false);

                    if (existedMethod != null)
                    {
                        var localAttribute = attribute;
                        CatchExceptionHelper.TryCatchAction(() => _attributeEngine.Run(invocation, localAttribute), _logger);
                    }
                }
            }
        }

        private static string GetScanTaskName(string fullName)
        {
            var result = fullName.Substring(fullName.LastIndexOf(".") + 1, fullName.Length - fullName.LastIndexOf(".") - 1);
            result = result.Substring(0, result.IndexOf("ScanTask"));

            return result;
        }

        private static string GetAttributeName(string scanTaskName)
        {
            return _attrNamespace + "." + scanTaskName + "Attribute";
        }
    }

and also have a Dynamic Proxy helper for registering this custom interceptor with Dynamic Castle:

    public class DynamicProxyHelper
    {
        public static object CreateInterfaceProxyWithTargetInterface(Type interfaceType, object concreteObject)
        {
            var dynamicProxy = new ProxyGenerator();

            var result = dynamicProxy.CreateInterfaceProxyWithTargetInterface(
                interfaceType,
                concreteObject,
                new[] { new MyExInterceptor("ConfORMSample.Core.Interceptor") });

            return result;
        }
    }

As you see we will make a object proxy based on the interface, so if you don't have a object that implemented from one interface, you will not be able to creating a proxy for it.

In this post I make a hard code for namespace ConfORMSample.Core,Interceptor. But I will improve in the future by pass the list of namespaces and scan all ScanTasks inside this namespaces.

We must to register it to StructureMap, in all class extend the Registry class. We have one here:

    public class TestingRegistry : Registry
    {
        public TestingRegistry()
        {
            For<ITenantContext>().Use<SimpleTenantContext>();

            For<INewsRepository>().Use<NewsRepository>()
                .EnrichWith(ex => DynamicProxyHelper.CreateInterfaceProxyWithTargetInterface(typeof(INewsRepository), ex));
        }
    }

As here I use the EnrichWith method fron StructureMap and registering this intercept with my MyExInterceptor. And I also have some unit testing for this

    [TestClass]
    public class AutoAttributeScanEngineTesting
    {
        private IAttributeScanEngine _engine;
        private IDirectoryTask _directoryTask;
        private ILog _logger;

        [TestInitialize]
        public void InitTestCase()
        {
            _directoryTask = new DirectoryWrapper();
            _logger = MockRepository.GenerateMock<ILog>();
            _engine = new AutoAttributeScanEngine(_directoryTask, _logger);
        }

        [TestMethod]
        public void Can_Init_Scan_Engine()
        {
            Assert.IsNotNull(_engine);
        }

        [TestMethod]
        public void Can_Scan_All_Instance()
        {
            var allTypes = _engine.AutoFindAttribute("");
            Assert.IsNotNull(allTypes);
            Assert.IsTrue(allTypes.Count() >= 0);
        }

        public void Can_Scan_And_Run_All_Instance()
        {
        }

        [TestCleanup]
        public void CleanUpTestCase()
        {
        }
    }
    [TestClass]
    public class NewsRepositoryIntereptorTesting
    {
        private IList<string> _assemblyNames;
        private IConfigurator _configurator;
        private INewsRepository _repository;

        [TestInitialize]
        public void InitTestCase()
        {
            _assemblyNames = new List<string>
                              {
                                  "ConfORMSample.Core",
                                  "ConfORMSample.NewsMgt.Entities",
                                  "ConfORMSample.Repository",
                                  "ConfORMSample.EventHandlers",
                                  "ConfORMTesting"
                              };

            _configurator = new Configurator();
            new MyBootStrapper(_assemblyNames, _configurator);

            _repository = MyObjectFactory.GetInstance<INewsRepository>();
        }

        [TestMethod]
        public void Can_Interceptor_Method()
        {
            _repository.TestInterceptor();
        }

        [TestCleanup]
        public void CleanUpTestCase()
        {
            GC.SuppressFinalize(_configurator);
            GC.SuppressFinalize(_repository);
        }
    }

Finally we shall decorate is as below:

    public class NewsRepository : RepositoryWithTypedId<NewsGuid>, INewsRepository
    {
        private readonly ITenantContext _tenantContext;

        public NewsRepository()
            : this(MyObjectFactory.GetInstance<ITenantContext>())
        {
        }

        public NewsRepository(ITenantContext tenantContext)
        {
            _tenantContext = tenantContext;
        }

        [Logging]
        public override IDbContext GetDbContext(string factoryKey = "")
        {
            Contract.Requires(_tenantContext != null"Tenant Context is null");

            return !string.IsNullOrEmpty(factoryKey)
                ? base.GetDbContext(factoryKey)
                : base.GetDbContext(_tenantContext.GetFactory("NewsMgtKey"));
        }

        [Logging]
        protected override ISession GetSession(string factoryKey = "")
        {
            Contract.Requires(_tenantContext != null"Tenant Context is null");

            return !string.IsNullOrEmpty(factoryKey)
                ? base.GetSession(factoryKey)
                : base.GetSession(_tenantContext.GetFactory("NewsMgtKey"));
        }

        [AuditMethod]
        [LockObject]
        [CatchMethodException]
        [Logging]
        public virtual void TestInterceptor()
        {
        }
    }

If you set a break point on GetDbContext, GetSession, and TestInterceptor methods and debug this class, you will see a dynamic proxy will be created for this class and all the converns will run first after that it will call your concrete method, and it seen to be the proxy will control your process. Carefull with this approach, it can be cause some security hole in your code.

Conclusion, AOP is make your concern can expand on many modules in your application. It's really good because you don't need to modify code when you make change something in your app. so you will not violate the Open-Closed Principle and one concern is only have one responsibility (follow Single Responsibility Principle), all your class must be implemented the interface (follow Interface-Segregation Principle). If you have some concerns about this post, leaving comment here, and I will try to make you happy. Thanks for your read.

Shout it kick it on DotNetKicks.com

6 Comments

Comments have been disabled for this content.