Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / Windows Forms

Bringing AOP to MEF

Rate me:
Please Sign up or sign in to vote.
4.86/5 (36 votes)
11 Jul 2011CPOL14 min read 74.6K   1.3K   87   23
An experiment into combining AOP and MEF.

Table of Contents

Introduction

A while back, I wrote an article which talked about different aspect orientated programming (AOP) approaches; this article is available here, and if you have no idea about AOP, that would not be a bad article to read before you dive into this one.

Anyway, in that article, I did a lot of the ground work that I needed for this article; in fact, this article could almost be thought of as Part II of that article.

So what's different in this article?

Well, in this article, what I wanted to do was to see how easy it would be to come up with a generic framework that would allow aspects to be added to types that could then be used with MEF. OK, aspects can quite easily be added to any type, but there is a certain amount of plumbing that one must do in order to provide aspects for a given type; that is just a fact, no matter what AOP framework you go for, it is tedious. So in essense, this article is all about an idea I had to dumb down this process, and hopefully it will make your life easier, and enable you to work with both aspects and MEF with a minimum of effort.

MEF

Managed Extensibility Framework (MEF) is essentially an IOC container that allows dependencies to be resolved from the MEF container (CompositionContainer) via the use of a set of MEF attributes. Say you have a ClassA that needs to be constructed with an instance of ClassB, you may have something like this (note the use of the [Export] and [ImportingConstructor] and [PartCreationPolicy] MEF attributes):

C#
using System.ComponentModel.Composition;
using System;

namespace MefDemo
{
    [PartCreationPolicy(CreationPolicy.NonShared)]
    [Export(typeof(DemoClass)]
    public class ClassB
    {
    }
}

using System.ComponentModel.Composition;
using System;

namespace MefDemo
{
    [PartCreationPolicy(CreationPolicy.NonShared)]
    [Export]
    public class ClassA
    {
        [ImportingConstructor]
        public Program(ClassB demoClass)
        {
        }
    }
}

This diagram may help you understand how MEF works on a high level:

Image 1

To truly get an understanding of how MEF works would take me many, many articles, which is outside the scope of this article. If you do not know how MEF works, I would ask that you read this MEF programming guide.

Aspects

When I first started this article, I was already aware of a few freely available AOP frameworks like:

These all offer different flavours of AOP.

To understand the difference between the different approaches, let's consider the following two subsections.

Proxy AOP

With the AOP frameworks that use proxy based technologies, the way that aspects are enabled is by a proxy. The proxy typically inherits from the type you wish to add the aspects to, and the proxy is usually a dynamic object that is written out using Reflection.Emit APIs. These proxy technology AOP frameworks rely on any property/method that aspects are to be added to be marked as virtual.

They also rely on extra classes that implement certain vendor specific AOP interfaces. These extra classes typically get added to the proxied object, such that whenever a method/property is called on the proxy object, it will first examine a list of AOP classes and run those prior to calling the original method.

The problem with this approach is that methods/properties must be marked as virtual.

This diagram illustrates how a proxy AOP framework may work:

Image 2

IL Weaving

IL weaving is a completely different story and the story goes something like this:

AssemblyA is a type but as well as AssemblyA, there are also a bunch of vendor specific AOP classes written. At compile time, AssemblyA has any type that is a candidate for AOP literally rewritten to produce new IL for that type in AssemblyA.

This is really a better option, and would be my preferred way of doing things, as your methods/properties do not have to be virtual at all, you just write your code and let the AOP IL rewriting process do the rest.

Problem with this approach is that it is hard, and I know of only two frameworks that do this: LinFu.AOP and PostSharp (which costs loads).

I had originally written most of this article using LinFu.AOP. When I got to the final hurdle, I introduced a ICommand (based on delegates) and LinFu.AOP died horribly and actually created an invalid assembly. That is not to say LinFu.AOP is bad, I consider the author of LinFu.AOP a freekin genius, it just did not like ICommand (based on delegates) objects, so if that is OK with you, use LinFu.AOP; in fact, Castle Dynamic Proxy does not allow AOP to be added to ICommand (based on delegates) objects either, but it did not die as a result. Now, there is a new version of LinFu.AOP out which may address this issue, however I played with that too, and had other issues. Given some time, I am confident the LinFu.AOP author will sort these issues out, he is the bomb.

I have included in the downloads a complete working sample based on LinFu.AOP; the only caveat is that I found it did not work with ICommand (based on delegates) objects. Which sadly was enough to sway me to use Castle Dynamic Proxy.

If you want to mess around with the solution, you will need to first unload the project "AspectableTypes" and edit the path to the PostWeaveTaskLocation within the project file, and then reload the project. You will be looking for a line like this:

<PostWeaveTaskLocation>C:\Users\WIN7LAP001\Desktop\In Progress\AspectableMef\AspectableMef\Lib\LinFu.Aop.Tasks.dll</PostWeaveTaskLocation>

Anyway, if you want to look at an IL AOP version of this article, it is included in the downloads at the top of this article.

Here is how an IL weaving AOP framework works:

Image 3

Note: The rest of this article will be using a proxy based solution, namely Castle Dynamic Proxy, which I have integrated in order to create the code that goes with this article.

Aspectable MEF

In this section, we will look at how the AspectableMef framework works. To be honest, it is not that complicated, it just abstracts a certain amount of otherwise tedious code from you. It really isn't rocket science, it's more of a time saving thing really.

How the MEF Part Works

I think the following set of bullet points outlines the process, we will go through each of these later.

  1. For the class where you want to use aspects, adorn the class with the AspectsStatusExportAttribute I have created. This tells MEF that this ExportAttribute will be wanted to be aspected, as well as make it available as a standard MEF export.
  2. Develop your aspect code (typically, this consists of three things: the actual aspect, an attribute that the aspect will look for, and also the actual aspect interception handling code).
  3. Further adorn your type with your specific aspect attributes.
  4. Create your composition container, and allow the small ApectableMef framework to do the rest.

So in laymen's terms, that is how it works. Let's see some code now, shall we?

Let's start with how MEF knows how to create these aspect enabled classes. This is down to two things.

AspectsStatusExportAttribute

This is a special MEF ExportAttribute that I have created that allows you to not only allow your object to be exported to MEF but also to store extra metadata that will be examined later. Here is the full code for AspectsStatusExportAttribute:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;

namespace AspectableMef.Castle
{
    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class AspectsStatusExportAttribute : ExportAttribute
    {
        public AspectsStatusExportAttribute(Type contractType) : base(contractType) { }
        public bool AreAspectsEnabled { get; set; }
    }
}

AOPExportProvider

Now if you do not know MEF, this may be a bit of a struggle, but those that know MEF may know that you can add custom ExportProvider(s) which sit between the CompositionContainer and the consumer of the Export.

Knowing that it is not that hard to create a custom ExportProvider that examines the part (object) that is being exported for a certain attribute, and if that certain attribute is found, do not return the original object but some other more powerful object.

This is exactly what the attached code does, it provides a specialised ExportProvider which looks for a certain attribute (namely the AspectsStatusExportAttribute) and if that is found and the metadata states that the object should be AOP'd, a more powerful proxied (AOP enabled) object is returned; if that attribute suggests that no AOP should be performed, the original non-proxied object is returned as if no AOP was done at all.

I think this is pretty neat; anyway, here is the entire code for the custom AOPExportProvider:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;

namespace AspectableMef.Castle
{
    public class AOPExportProvider : ExportProvider, IDisposable
    {
        private CatalogExportProvider _exportProvider;

        public AOPExportProvider(Func<ComposablePartCatalog> catalogResolver)
        {
            _exportProvider = new CatalogExportProvider(catalogResolver());
            
            //support recomposition
            _exportProvider.ExportsChanged += (s, e) => OnExportsChanged(e);
            _exportProvider.ExportsChanging += (s, e) => OnExportsChanging(e);
        }

        public ExportProvider SourceProvider
        {
            get
            {
                return _exportProvider.SourceProvider;
            }
            set
            {
                _exportProvider.SourceProvider = value;
            }
        }

        protected override IEnumerable<Export> GetExportsCore(
            ImportDefinition definition, AtomicComposition atomicComposition)
        {
            IEnumerable<Export> exports = 
              _exportProvider.GetExports(definition, atomicComposition);
            return exports.Select(export => 
               new Export(export.Definition, () => GetValue(export)));
        }

        private object GetValue(Export innerExport)
        {
            var value = innerExport.Value;
            if (innerExport.Metadata.Any(x => x.Key == "AreAspectsEnabled"))
            {
                KeyValuePair<String, Object> specificMetadata = 
                    innerExport.Metadata.Where(x => x.Key == 
                    "AreAspectsEnabled").Single();
                if ((Boolean)specificMetadata.Value == true)
                {
                    return AspectProxy.Factory(value);

                }
                else
                {
                    return value;
                }

            }
            else
            {
                return value;
            }
        }

        public void Dispose()
        {
            _exportProvider.Dispose();
        }
    }
}

It can be seen that this class also makes use of a AspectProxy (this is based on using Castle's Dynamic Proxy technology) which is shown below:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Castle.DynamicProxy;

namespace AspectableMef.Castle
{
    public class AspectProxy
    {
        public static Object Factory(object obj)
        {
            ProxyGenerator generator = new ProxyGenerator();
            object[] attribs = 
              obj.GetType().GetCustomAttributes(typeof(IAspectFactory), true);

            IInterceptor[] interceptors = 
                    (from x in attribs select 
                    ((IAspectFactory)x).GetAroundInvokeApectInvoker)
                    .Cast<IInterceptor>().ToArray();
            object proxy = generator.CreateClassProxy(obj.GetType(), interceptors);
            return proxy;
        }
    }
}

The important bit is that we generate a proxy which enables what Castle calls interception, which they enable by the use of custom classes that inherit from a IInterceptor interface (more on this in just a minute).

Castle DynamicProxy Aspect Design

As stated above, this article focuses on using Castle to supply the AOP aspects for the code associated for this article. As we just saw above, we use Castle dynamic proxy generator (see above where we use the ProxyGenerator). So how about the AOP stuff? How does that work? Well, we did see a glimpse of it above where we saw how we added a IInterceptor[] when we used the ProxyGenerator to create the actual proxy. So, what do these IInterceptor things do for us?

Well, I think the best way to learn about how Castle AOP and its IInterceptor interfaces work is to use an example. I will use one of the aspects that comes with this demo code to illustrate how it all works. I will use an INPCAspect from the demo code to describe how it all works.

IAspectFactory

This is an interface that I created to allow the AspectableMef to create the correct type of IInterceptor. Here is this interface:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Castle.DynamicProxy;

namespace AspectableMef.Castle
{
    interface IAspectFactory
    {
        IInterceptor GetAroundInvokeApectInvoker { get; }
    }
}

INPCAspect

The next part of the puzzle is to create an actual aspect, which is done by implementing the IAspectFactory interface, and inheriting from Attribute, as shown below:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Castle.DynamicProxy;

namespace AspectableMef.Castle
{
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class INPCAspect : Attribute, IAspectFactory
    {
        #region IAspectFactory Members
        public IInterceptor GetAroundInvokeApectInvoker
        {
            get
            {
                return new INPCInterceptor();
            }
        }
        #endregion
    }
}

INPCAttribute

The next part of the puzzle is to create an a Method/Property marker attribute, that can be examined for later, to see if a certain method/property wishes to be aspectable or not.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AspectableMef.Castle
{
    /// <summary>
    /// This attribute is used by the Castle <c>NotifyPropertyChangedInterceptor</c> where
    /// it examines the target types property being set for this attribute, and if it has
    /// this attribute it will fire the NotifyChanged() method
    /// on the target object when the property with the <c>INPCAttribute</c> is set.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property)]
    public class INPCAttribute : Attribute
    {
    }
}

INPCInterceptor

The last (but most important) part of the puzzle is to create a class that inherits from Castle's IInterceptor. It is this code that will be the actual code run when a method is called (properties are just methods really, Get_xxx/Set_xxx). So what do we do? Well, we simply need to implement the IInterceptor interface, which provides a single method:

C#
void Intercept(IInvocation invocation)

which we can use to carry out an aspect code in.

The important thing that we must never forget to do is to call invocation.Proceed() without which the original method (the one that was called to get us into the IInterceptor code) would not complete.

Anyway, here is an example INPCInterceptor:

C#
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Castle.DynamicProxy;


namespace AspectableMef.Castle
{
    public class INPCInterceptor : IInterceptor
    {
        #region IInterceptor members
        public void Intercept(IInvocation invocation)
        {
            // let the original call go 1st
            invocation.Proceed();

            if (invocation.Method.Name.StartsWith("set_"))
            {
                string propertyName = invocation.Method.Name.Substring(4);
                var pi = invocation.TargetType.GetProperty(propertyName);

                // check for the special attribute
                if (!pi.HasAttribute<INPCAttribute>())
                    return;

                FieldInfo info = invocation.TargetType.GetFields(
                        BindingFlags.Instance | BindingFlags.NonPublic)
                            .Where(f => f.FieldType == typeof(PropertyChangedEventHandler))
                            .FirstOrDefault();

                if (info != null)
                {
                    //get the INPC field, and invoke it we managed to get it ok
                    PropertyChangedEventHandler evHandler =
                        info.GetValue(invocation.InvocationTarget) 
                        as PropertyChangedEventHandler;
                    if (evHandler != null)
                        evHandler.Invoke(invocation.InvocationTarget,
                            new PropertyChangedEventArgs(propertyName));
                }
            }
        }
        #endregion
    }
}

Special Notes and Demo of Usage

In order to use the INPCAspect, the class you are using it on must already implement the INotifyPropertyChanged interface. Anyway, here is an example of its usage:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.Composition;
using AspectableMef.Castle;
using System.Diagnostics;
using System.Windows;

namespace ConsoleApplication
{
    [PartCreationPolicy(CreationPolicy.NonShared)]
    [AspectsStatusExport(typeof(DemoClass), AreAspectsEnabled = true)]
    [INPCAspect]
    public class DemoClass : INotifyPropertyChanged
    {

        [INPCAttribute]
        public virtual string Name { get; set; }

        #region INPC Implementation

        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }
}

Example Aspects I Have Created

I have created several demo Aspects which I will explain below. Now, these are for demonstration purposes only; if you do not like these or would do things differently, fair enough, that is fine, go for it. These Aspects are here just to show you how to write your own aspects.

INPC

We just discussed that, my you have a short memory, don't you? Yes, it's as we just discussed.

Logging

LogAspect is pretty simple and simply logs the entry of any method that is marked up with LogAttribute. The LogAspect classes I have provided with this demo code use log4Net, which I think is an awesome logging framework for .NET, but if you do not like this, just write your own aspects. The idea is the important thing, not the implementation. (Although that's cool too right? Well, I like it, but then again, I would, I wrote it.)

The code is as shown below:

LogAspect

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using log4net;
using Castle.DynamicProxy;

namespace AspectableMef.Castle
{
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class LogAspect : Attribute, IAspectFactory
    {

        private static ILog logger;
        public static void SetLogger(ILog newLogger)
        {
            logger = newLogger;
        }

        public IInterceptor GetAroundInvokeApectInvoker
        {
            get
            {
                return new LogInterceptor(logger);
            }
        }
    }
}

LogAttribute

This can be used to adorn a property/method which enables the LogInterceptor to know if a particular method/property should log.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AspectableMef.Castle
{

    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
    public class LogAttribute : Attribute
    {
    }
}

LogInterceptor

This code is only run if the class that is being MEFed has a LogAspectAttribute and the method/property has the LogAttribute used.

Here is the code for LogInterceptor:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel.Composition;

using Castle.DynamicProxy;
using log4net;

namespace AspectableMef.Castle
{
    public class LogInterceptor : IInterceptor
    {
        private static ILog logger;
        
        public LogInterceptor(ILog logger)
        {
            if (LogInterceptor.logger == null)
                LogInterceptor.logger = logger;
        }
        
        public void Intercept(IInvocation invocation)
        {
            DoLogging(invocation);
        }

        private void DoLogging(IInvocation invocation)
        {
            // let the original call go 1st
            invocation.Proceed();

            if (invocation.Method.HasAttribute<LogAttribute>())
            {
                try
                {
                    StringBuilder sb = null;
                    sb = new StringBuilder(invocation.TargetType.FullName)
                        .Append(".")
                        .Append(invocation.Method)
                        .Append("(");

                    for (int i = 0; i < invocation.Arguments.Length; i++)
                    {
                        if (i > 0)
                            sb.Append(", ");
                        sb.Append(invocation.Arguments[i]);
                    }

                    sb.Append(")");
                    logger.Debug(sb.ToString());
                    invocation.Proceed();
                    logger.Debug("Result of " + sb + " is: " + 
                                 invocation.ReturnValue);
                }

                catch (Exception e)
                {
                    logger.Error(e.Message);
                }
            }
        }
    }
}

Special Notes and Demo of Usage

In order to use the LogAspect which for the demo code relies on log4Net, there are several things that need to be done, which are shown below (these are all included in the demo code though, do not worry).

log4Net Logger Code

You need to create a log4Net logger class:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using System.Reflection;

namespace ConsoleApplication
{
    public static class LogManager
    {
        private static ILog log = null;

        public static ILog Log
        {
            get { return log ?? (log = log4net.LogManager.GetLogger(
                MethodBase.GetCurrentMethod().DeclaringType)); }
        }
    }
}

log4Net Config

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" 
       type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
  </configSections>

  <log4net>
    <appender name="ConsoleAppender" 
           type="log4net.Appender.ConsoleAppender" >
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" 
                value="%d [%t] %-5p [%x] - %m%n" />
      </layout>
    </appender>

    <appender name="RollingFileAppender" 
        type="log4net.Appender.RollingFileAppender" >
      <file type="log4net.Util.PatternString"
            value="c:\temp\AspectableMef.log"/>
      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
      <rollingStyle value="Composite"/>
      <datePattern value="yyyyMMdd"/>
      <maxSizeRollBackups value="100"/>
      <maximumFileSize value="15MB"/>
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date %-5level %logger: %message%newline" />
      </layout>
    </appender>

    <root>
      <level value="ALL" />
      <appender-ref ref="RollingFileAppender" />
      <appender-ref ref="ConsoleAppender" />
    </root>
  </log4net>
</configuration>

Telling log4Net to Use the Config

You must tell log4Net to use the config file:

C#
//Could have configured Log4Net like this as well, in AssemblyInfo.cs
//[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]

FileInfo assFile = new FileInfo(Assembly.GetEntryAssembly().Location);
XmlConfigurator.Configure(new FileInfo(string.Format("{0}{1}", 
                assFile.Directory, @"\log4net.config")));

Tell LogAspect About log4Net

C#
LogAspect.SetLogger(LogManager.Log);

With all the above done, all you now need to do is use it in a class, which is done as follows:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.Composition;
using AspectableMef.Castle;
using System.Diagnostics;
using System.Windows;

namespace ConsoleApplication
{
    [PartCreationPolicy(CreationPolicy.NonShared)]
    [AspectsStatusExport(typeof(DemoClass), AreAspectsEnabled = true)]
    [LogAspect]
    public class DemoClass : INotifyPropertyChanged
    {
        private  DateTime timeLast = DateTime.Now;

        [Log]
        public virtual void LoggedMethod(DateTime dt)
        {
            timeLast = dt;
        }
    }
}

Security

The SecurityAspect is a little more complicated as I tied it in with the standard WindowsPrincipal security that can be applied to the thread. As such, there are a few more moving parts, such as the ones shown below.

ISecurityContextProvider

This is a simple interface that would be implemented by a class that could actually provide a SecurityContext for a particular user (you will see a mock/fake/dummy version of this in just a minute). This interface would typically be implemented by your own code:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AspectableMef.Castle
{
    public interface ISecurityContextProvider
    {
        SecurityContext GetSecurityContextByLogon(String logon);
    }
}

And here is an example of a class that implements this ISecurityContextProvider. This class is not part of the AspectableMef, and should be in your code base. This is obviously a dummy example just to show how the security aspect that is provided with AspectableMef works; you should create a more meaningful class, that I guess would hit ActiveDirectory or at least a database to fetch user specific roles/privileges.

C#
using System.Threading;
using System.Collections.Generic;
using System;

using AspectableMef.Castle;

namespace ConsoleApplication
{
    /// <summary>
    /// This class is for demonstration purposes
    /// you would obviously not do this in a real, app
    /// your implementation should provide real data read
    /// from some persistant store like a database etc etc
    /// </summary>
    public class FakeContextProvider : ISecurityContextProvider
    {
        /// <summary>
        /// This method is returning mocked data,
        /// you would need to make this return meaningful data
        /// for you real implementation
        /// </summary>
        public SecurityContext GetSecurityContextByLogon(String logon)
        {
            List<String> roles = new List<String>();
            List<String> privileges = new List<String>();


            for (int i = 0; i < 10; i++)
            {
                roles.Add(string.Format("role{0}", i.ToString()));
            }

            for (int i = 0; i < 10; i++)
            {
                privileges.Add(string.Format("privilege{0}", i.ToString()));
            }


            return new SecurityContext(Thread.CurrentPrincipal.Identity.Name, 
                                       roles, privileges);
        }
    }
}

This fake ISecurityContextProvider is used to return a SecurityContext which looks like this:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AspectableMef.Castle
{
    public class SecurityContext
    {
        public SecurityContext(string logon, List<String> roles, 
            List<String> privileges)
        {
            this.Logon = logon;
            this.Roles = roles;
            this.Privileges = privileges;
        }

        public String Logon { get; private set; }
        public List<String> Roles { get; private set; }
        public List<String> Privileges { get; private set; }
    }
}

SecureWindowsPrincipal

Now that we have seen how ISecurityContextProvider works, let's see how we can use it. Like I say, the provided code uses WindowsPrincipal based security; as such, we need a specialised WindowsPrincipal object. The code contains a specialised WindowsPrincipal inheriting object called SecureWindowsPrincipal, which is as shown below. This code basically make use of the ISecurityContextProvider implementation class you just saw above, to work out whether a user has certain roles/privileges. This is done by using the ISecurityContextProvider implementation class' SecrurityContext object, which we also saw above.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Principal;
using System.Threading;

namespace AspectableMef.Castle
{
    public class SecureWindowsPrincipal : WindowsPrincipal
    {
        #region Ctor
        public SecureWindowsPrincipal(ISecurityContextProvider provider) 
            : base(WindowsIdentity.GetCurrent())
        {
            SecurityContext context = 
                provider.GetSecurityContextByLogon(Thread.CurrentPrincipal.Identity.Name);

            if (context != null)
            {
                this.CurrentSecurityContext = context;
            }
        }

        public SecureWindowsPrincipal(ISecurityContextProvider provider, String userName) 
            : base(WindowsIdentity.GetCurrent())
        {
            SecurityContext context = provider.GetSecurityContextByLogon(userName);

            if (context != null)
            {
                this.CurrentSecurityContext = context;
            }
        }
        #endregion

        #region Public Properties

        public SecurityContext CurrentSecurityContext { get; private set; }

        #endregion

        #region Public Methods

        public override Boolean IsInRole(String role)
        {

            if ((this.CurrentSecurityContext != null) && 
                (this.CurrentSecurityContext.Roles != null))
                return this.CurrentSecurityContext.Roles.Contains(role);

            return false;
        }

        public Boolean HasPrivilege(String privilege)
        {

            if ((this.CurrentSecurityContext != null) && 
                (this.CurrentSecurityContext.Privileges != null))
                return this.CurrentSecurityContext.Privileges.Contains(privilege);

            return false;
        }

        #endregion        
    }
}

OK, so that's the extra parts. Now on to the normal aspect code, which is as shown below.

SecurityAspect

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Castle.DynamicProxy;

namespace AspectableMef.Castle
{
    [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
    public class SecurityAspect : Attribute, IAspectFactory
    {
        #region Ctor
        public SecurityAspect()
        {
        }
        #endregion

        #region IAspectFactory Members

        public IInterceptor GetAroundInvokeApectInvoker
        {
            get
            {
                return new SecurityInterceptor();
            }
        }
        #endregion
    }
}

SecurityAttribute

This can be used to adorn a property/method which enables the SecurityInterceptor to know if a particular method/property should be checked for security.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AspectableMef.Castle
{
    [AttributeUsage(AttributeTargets.Property | 
       AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public class SecurityAttribute : Attribute
    {
        public String Role { get; set; }
        public String Privilege { get; set; }
    }
}

SecurityInterceptor

This code is only run if the class that is being MEFed has a SecurityAspectAttribute and the method/property has the SecurityAttribute used.

Here is the code for SecurityInterceptor:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using System.Threading;
using Castle.DynamicProxy;

namespace AspectableMef.Castle
{
    public class SecurityInterceptor : IInterceptor
    {
        #region IInterceptor memers
        public void Intercept(IInvocation invocation)
        {
            bool shouldCallOriginalMethod = false;

            //check for the special attribute
            if (!invocation.Method.HasAttribute<SecurityAttribute>())
                shouldCallOriginalMethod = true;

            if (Thread.CurrentPrincipal == null)
                shouldCallOriginalMethod = true;

            SecureWindowsPrincipal principal = null;

            try
            {
                principal = (SecureWindowsPrincipal)Thread.CurrentPrincipal;
            }
            catch
            {
                shouldCallOriginalMethod = true;
            }


            if (principal != null)
            {
                foreach (SecurityAttribute securityAttrib in 
                    invocation.Method.GetAttributes<SecurityAttribute>())
                {
                    if (!principal.IsInRole(securityAttrib.Role) && 
                            !principal.HasPrivilege(securityAttrib.Privilege))
                    {
                        String error = String.Format("The user with Logon : {0}, " + 
                           "does not have the Role : {1}, or Privilege {2}",
                           Thread.CurrentPrincipal.Identity.Name, 
                           securityAttrib.Role, securityAttrib.Privilege);

                        throw new SecureWindowsPrincipalException(error);
                    }
                }

                shouldCallOriginalMethod = true;
            }
            else
            {
                shouldCallOriginalMethod = true;
            }

            if (shouldCallOriginalMethod)
                invocation.Proceed();
        }
        #endregion
    }
}

Special Notes and Demo of Usage

In order to get WindowsPrincipal used properly, you must tell the thread to use WindowsPrincipal, which is done as follows:

C#
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
Thread.CurrentPrincipal = new SecureWindowsPrincipal(new FakeContextProvider());

With all the above done, all you now need to do is use it in a class, which is done as follows:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.Composition;
using AspectableMef.Castle;
using System.Diagnostics;
using System.Windows;

namespace ConsoleApplication
{
    [PartCreationPolicy(CreationPolicy.NonShared)]
    [AspectsStatusExport(typeof(DemoClass), AreAspectsEnabled = true)]
    [SecurityAspect]
    public class DemoClass : INotifyPropertyChanged
    {
        [SecurityAttribute(Role = "dealer1", Privilege = "somePriv1")]
        [SecurityAttribute(Role = "authoriser", Privilege = "somePriv2")]
        public virtual void BadSecurityMethod()
        {
            //this should cause an Exception to be raised,
            //and should not show MessageBox
            MessageBox.Show("BadSecurityMethodCommand");
        }

        [SecurityAttribute(Role = "role0", Privilege = "privilege3")]
        public virtual void GoodSecurityMethod()
        {
            MessageBox.Show("GoodSecurityMethodCommand");
        }
    }
}

A Demo Class

Here is a complete demo class that is both aspectable (via Castle and also Exportable via MEF). Note how you can use the normal MEF PartCreationPolicyAttribute with this class. In essence, it's just a class, and as such is MEFable (if such a word were to exist), but thanks to the specialised AspectsStatusExportAttribute and the AOPExportProvider, it is also AOPable (not sure that word exists either).

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.Composition;
using AspectableMef.Castle;
using System.Diagnostics;
using System.Windows;

namespace ConsoleApplication
{
    [PartCreationPolicy(CreationPolicy.NonShared)]
    [AspectsStatusExport(typeof(DemoClass), AreAspectsEnabled = true)]
    [INPCAspect]
    [LogAspect]
    [SecurityAspect]
    public class DemoClass : INotifyPropertyChanged
    {
        private  DateTime timeLast = DateTime.Now;

        public DemoClass()
        {
            Guid = Guid.NewGuid();
        }

        [INPCAttribute]
        public virtual string Name { get; set; }

        public Guid Guid { get; private set; }

        [Log]
        public virtual void LoggedMethod(DateTime dt)
        {
            timeLast = dt;
        }

        [SecurityAttribute(Role = "dealer1", Privilege = "somePriv1")]
        [SecurityAttribute(Role = "authoriser", Privilege = "somePriv2")]
        public virtual void BadSecurityMethod()
        {
            //this should cause an Exception to be raised,
            //and should not show MessageBox
            MessageBox.Show("BadSecurityMethodCommand");
        }

        [SecurityAttribute(Role = "role0", Privilege = "privilege3")]
        public virtual void GoodSecurityMethod()
        {
            MessageBox.Show("GoodSecurityMethodCommand");
        }

        #region INPC Implementation

        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
    }
}

Writing Your Own Aspects

So how do you write you own aspects? Well, that is pretty easy actually, just follow these three steps:

  1. Create your own XXXAspect class, which inherits from Attribute and also implements IAspectFactory (use INPCAspect as a base for your own code).
  2. Create your own marker XXXAttribute that can be used to mark methods/properties (use INPCAttribute as a base for your own code).
  3. Create your own XXXInterceptor code which is Castle's IInterceptor based class (use INPCInterceptor as a base for your own code).
  4. Don't forget to mark up your apsectable class with your aspect attributes and also the AspectableMef AspectsStatusExportAttribute.

Words of Warning

Now, one thing of note is that there are some cases where all of the AOP frameworks I tried fail miserably, which is with things like the code shown below. The problem is with proxy style AOP frameworks (Castle), you are never going through the proxy generated type to call the Action<T> or Func<T,TR> delegates, so no interception code can be made for those two methods.

C#
using System.Windows.Input;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Cinch
{
    /// <summary>
    /// Simple delegating command, based largely on DelegateCommand from PRISM/CAL
    /// </summary>
    /// <typeparam name="T">The type for the </typeparam>
    public class SimpleCommand<T1, T2> : ICommand, ICompletionAwareCommand
    {
        private Func<T1, bool> canExecuteMethod;
        private Action<T2> executeMethod;

        public SimpleCommand(Func<T1, bool> canExecuteMethod, 
        Action<T2> executeMethod)
        {
            this.executeMethod = executeMethod;
            this.canExecuteMethod = canExecuteMethod;
        }

        public SimpleCommand(Action<T2> executeMethod)
        {
            this.executeMethod = executeMethod;
            this.canExecuteMethod = (x) => { return true; };
        }

        public bool CanExecute(T1 parameter)
        {
            if (canExecuteMethod == null) return true;
            return canExecuteMethod(parameter);
        }

        public void Execute(T2 parameter)
        {
            if (executeMethod != null)
            {
                executeMethod(parameter);
            }
        }

        public bool CanExecute(object parameter)
        {
            return CanExecute((T1)parameter);
        }

        public void Execute(object parameter)
        {
            Execute((T2)parameter);
        }

#if SILVERLIGHT
        /// <summary>
        /// Occurs when changes occur that affect whether the command should execute.
        /// </summary>
        public event EventHandler CanExecuteChanged;
#else
        /// <summary>
        /// Occurs when changes occur that affect whether the command should execute.
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (canExecuteMethod != null)
                {
                    CommandManager.RequerySuggested += value;
                }
            }

            remove
            {
                if (canExecuteMethod != null)
                {
                    CommandManager.RequerySuggested -= value;
                }
            }
        }
#endif

        /// <summary>
        /// Raises the <see cref="CanExecuteChanged" /> event.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic",
            Justification = "The this keyword is used in the Silverlight version")]
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
            Justification = "This cannot be an event")]
        public void RaiseCanExecuteChanged()
        {
#if SILVERLIGHT
            var handler = CanExecuteChanged;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
#else
            CommandManager.InvalidateRequerySuggested();
#endif
        }
    }
}

Special Thanks

Special thanks go out to:

That's It

That's it for now. I hope this article and the downloads inspire you enough to have a go at creating your own aspects.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
BugImport attribute does not work when class is marked with AspectsStatusExport attribute Pin
VladEr9-Jan-15 5:55
VladEr9-Jan-15 5:55 
GeneralMy vote of 5 Pin
jim lahey15-Mar-13 5:01
jim lahey15-Mar-13 5:01 
GeneralRe: My vote of 5 Pin
Sacha Barber15-Mar-13 5:46
Sacha Barber15-Mar-13 5:46 
GeneralMy vote of 5 Pin
Rhuros9-Aug-11 22:43
professionalRhuros9-Aug-11 22:43 
GeneralRe: My vote of 5 Pin
Sacha Barber11-Aug-11 19:56
Sacha Barber11-Aug-11 19:56 
Questionnice one Pin
Pranay Rana15-Jul-11 2:34
professionalPranay Rana15-Jul-11 2:34 
AnswerRe: nice one Pin
Sacha Barber18-Jul-11 2:19
Sacha Barber18-Jul-11 2:19 
QuestionNice job! Pin
Glenn Block10-Jul-11 3:39
Glenn Block10-Jul-11 3:39 
AnswerRe: Nice job! Pin
Sacha Barber10-Jul-11 23:45
Sacha Barber10-Jul-11 23:45 
GeneralRe: Nice job! Pin
Glenn Block10-Jul-11 23:59
Glenn Block10-Jul-11 23:59 
GeneralRe: Nice job! Pin
Sacha Barber11-Jul-11 2:04
Sacha Barber11-Jul-11 2:04 
AnswerRe: Nice job! Pin
Sacha Barber11-Jul-11 11:01
Sacha Barber11-Jul-11 11:01 
GeneralMy vote of 5 Pin
Michał Zalewski9-Jul-11 14:00
Michał Zalewski9-Jul-11 14:00 
GeneralRe: My vote of 5 Pin
Sacha Barber9-Jul-11 20:35
Sacha Barber9-Jul-11 20:35 
Thanks
Sacha Barber
  • Microsoft Visual C# MVP 2008-2011
  • Codeproject MVP 2008-2011
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralMy vote of 5 Pin
Wendelius9-Jul-11 12:34
mentorWendelius9-Jul-11 12:34 
GeneralRe: My vote of 5 Pin
Sacha Barber9-Jul-11 20:35
Sacha Barber9-Jul-11 20:35 
Generalmy vote of 5 [modified] Pin
Marcelo Ricardo de Oliveira9-Jul-11 2:51
mvaMarcelo Ricardo de Oliveira9-Jul-11 2:51 
GeneralRe: my vote of 5 Pin
Sacha Barber9-Jul-11 10:14
Sacha Barber9-Jul-11 10:14 
GeneralRe: my vote of 5 Pin
Marcelo Ricardo de Oliveira9-Jul-11 11:35
mvaMarcelo Ricardo de Oliveira9-Jul-11 11:35 
GeneralRe: my vote of 5 Pin
Sacha Barber9-Jul-11 20:36
Sacha Barber9-Jul-11 20:36 
Questionsimply outstanding Pin
Pete O'Hanlon9-Jul-11 1:24
subeditorPete O'Hanlon9-Jul-11 1:24 
AnswerRe: simply outstanding Pin
Sacha Barber9-Jul-11 10:10
Sacha Barber9-Jul-11 10:10 
GeneralMy vote of 5 Pin
Sacha Barber9-Jul-11 0:58
Sacha Barber9-Jul-11 0:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.