Click here to Skip to main content
Click here to Skip to main content

Configurable Aspects for MEF

, 6 Sep 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
Add AOP capabilities to MEF by configuration using Dynamic Decorator.

Introduction

A little while back, in a discussion with Sacha Barber about Dynamic Decorator and Castle DynamicProxy (see messages of Dynamic Decorator and Castle DynamicProxy Comparison), he mentioned his work on bringing AOP to MEF. It didn't catch much of my attention until recently I got a chance to read his article Bringing AOP to MEF.

The advancement of object technologies hasn't made AOP any easier. Out of the box, none of the IoC containers support AOP as far as I know. Sacha Barber's pioneer work made me aware of this gap and inspired me to come up with ideas for this article.

In this article, I introduce an AOP model for MEF based on Dynamic Decorator. Using this model, you configure aspects for objects and the MEF brings the objects in life with the aspects attached to them.

Background

The Dynamic Decorator is a tool for extending the functionality of objects by attaching behaviors to them instead of by modifying their classes or creating new classes. It is attractive because it saves you from design or redesign of your classes. Please read the articles Dynamic Decorator Pattern and Add Aspects to Object Using Dynamic Decorator to understand more about the Dynamic Decorator, what problems it tries to solve, how it solves the problems, and its limits.

There are several other articles that discuss how the Dynamic Decorator is used for application development and compare it with other similar technologies. Please refer to the References section of this article for them.

Once you go over some of the articles, you will see that the Dynamic Decorator is simple to use and powerful. In this article, I describe a configurable model of the Dynamic Decorator, which makes its use even easier and still as powerful. This model is then integrated with the MEF so that an object coming out from MEF has aspects attached already.

Using this model, you configure aspects for your objects in application configuration files. Then, a customized MEF container brings your objects in life and wires them with the aspects based on the configurations. By the time you use the objects, the functionality defined by aspects has already attached to the objects.

Make Component MEF Ready

Managed Extensibility Framework (MEF) is essentially an IoC container that allows dependencies to be resolved via the use of a set of MEF attributes. Say, you have a simple component Employee that implements two interfaces IEmployee and INotifyPropertyChanged. In the following code snippet, the attributes [Export] and [PartCreationPolicy(CreationPolicy.NonShared)] of Employee are used by MEF to specify that an instance of the Employee is the exported object, and a new non-shared instance will be created for every requestor.

public interface IEmployee
{
    System.Int32? EmployeeID { get; set; }
    System.String FirstName { get; set; }
    System.String LastName { get; set; }
    System.DateTime DateOfBirth { get; set; }
    System.Int32? DepartmentID { get; set; }
    System.String DetailsByLevel(int iLevel);
    //1-full name; 2-full name plus birth day;
    //3-full name plus birth day and department id.
}

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

[PartCreationPolicy(CreationPolicy.NonShared)]
[Export]
public class Employee : IEmployee, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    #region Properties

    public System.Int32? EmployeeID { get; set; }
    public System.String FirstName { get; set; }
    public System.String LastName { get; set; }
    public System.DateTime DateOfBirth { get; set; }
    public System.Int32? DepartmentID { get; set; }

    #endregion

    public Employee(
        System.Int32? employeeid
        , System.String firstname
        , System.String lastname
        , System.DateTime bDay
        , System.Int32? departmentID
    )
    {
        this.EmployeeID = employeeid;
        this.FirstName = firstname;
        this.LastName = lastname;
        this.DateOfBirth = bDay;
        this.DepartmentID = departmentID;
    }

    public Employee() { }

    public System.String DetailsByLevel(int iLevel)
    {
        System.String s = "";
            
        switch (iLevel)
        {
            case 1:
                s = "Name:" + FirstName + " " + LastName + ".";
                break;

            case 2:
                s = "Name: " + FirstName + " " + LastName + 
                    ", Birth Day: " + DateOfBirth.ToShortDateString() + ".";
                break;

            case 3:
                s = "Name: " + FirstName + " " + LastName + 
                    ", Birth Day: " + DateOfBirth.ToShortDateString() + 
                    ", Department:" + DepartmentID.ToString() + ".";
                break;

            default:
                break;
                    
        }
        return s;
    }
}

The above code is normal C# code for interface and class definitions in addition to two attributes [PartCreationPolicy(CreationPolicy.NonShared)] and [Export]. With the two attributes, the component Employee is ready for MEF.

Define Aspects

Aspects are cross-cutting concerns. For Dynamic Decorator, an aspect is a method, which takes an AspectContext type object as its first parameter and an object[] type object as its second parameter and returns void.

You may design your aspects in a more general way to use them in various situations. You may also design your aspects for some particular situations. For example, you can define your entering/exiting log aspects in a general way and put them in the class SysConcerns as follows.

public class SysConcerns
{
    static SysConcerns()
    {
        ConcernsContainer.runtimeAspects.Add(
          "DynamicDecoratorAOP.SysConcerns.EnterLog", 
          new Decoration(SysConcerns.EnterLog, null));
        ConcernsContainer.runtimeAspects.Add(
          "DynamicDecoratorAOP.SysConcerns.ExitLog", 
          new Decoration(SysConcerns.ExitLog, null));
    }

    public static void EnterLog(AspectContext ctx, object[] parameters)
    {
        StackTrace st = new StackTrace(new StackFrame(4, true));
        Console.Write(st.ToString());
            
        IMethodCallMessage method = ctx.CallCtx;
        string str = "Entering " + ctx.Target.GetType().ToString() + 
                     "." + method.MethodName + "(";
        int i = 0;
        foreach (object o in method.Args)
        {
            if (i > 0)
                str = str + ", ";
            str = str + o.ToString();
        }
        str = str + ")";

        Console.WriteLine(str);
        Console.Out.Flush();

    }

    public static void ExitLog(AspectContext ctx, object[] parameters)
    {
        IMethodCallMessage method = ctx.CallCtx;
        string str = "Exiting " + ctx.Target.GetType().ToString() + 
                     "." + method.MethodName +
            "(";
        int i = 0;
        foreach (object o in method.Args)
        {
            if (i > 0)
                str = str + ", ";
            str = str + o.ToString();
        }
        str = str + ")";

        Console.WriteLine(str);
        Console.Out.Flush();
    }
}

As you can see, these methods access Target and CallCtx in a general way and can be shared by various types of objects to write entering/exiting logs.

On the other hand, some aspects may need to access more specific information. For example, you want to attach a change notification functionality only to an Employee object when its property is set. The following code defines some specific aspects.

class LocalConcerns
{
    static LocalConcerns()
    {
        ConcernsContainer.runtimeAspects.Add(
          "ConsoleUtil.LocalConcerns.NotifyChange", 
          new Decoration(LocalConcerns.NotifyChange, null));
        ConcernsContainer.runtimeAspects.Add(
          "ConsoleUtil.LocalConcerns.SecurityCheck", 
          new Decoration(LocalConcerns.SecurityCheck, null));
    }

    public static void NotifyChange(AspectContext ctx, object[] parameters)
    {
        ((Employee)ctx.Target).NotifyPropertyChanged(ctx.CallCtx.MethodName);
    }

    public static void SecurityCheck(AspectContext ctx, object[] parameters)
    {
        Exception exInner = null;

        try
        {
            if (parameters != null && parameters[0] is WindowsPrincipal && 
               ((WindowsPrincipal)parameters[0]).IsInRole("BUILTIN\\" + 
                 "Administrators"))
            {
                return;
            }
        }
        catch ( Exception ex)
        {
            exInner = ex;
        }

        throw new Exception("No right to call!", exInner);
    }
}

In the above code, the NotifyChange method can only be used by a target of Employee while the SecurityCheck requires a WindowsPrincipal object as a parameter.

Note: You may already have noticed that there is a static constructor in each of the classes SysConcerns and LocalConcerns. Inside the static classes, each of the aspect methods defined in the classes is used to create an instance of Decoration which is then added to a dictionary ConcernsContainer.runtimeAspects. The definition of ConcernsContainer is as follows.

public class ConcernsContainer
{
    static public Dictionary<string, Decoration> runtimeAspects = 
                  new Dictionary<string, Decoration>();
}

The purpose of this Dictionary and the static constructors is to keep an inventory for all Decoration objects based on the aspect methods defined in the application and make them accessible by the corresponding method names. It makes it possible to configure aspects in the application configuration file by specifying the corresponding method names.

Configure Aspects

In the configuration file, you specify how aspects are associated with objects. Here are some examples to demonstrate how the aspects are configured:

<configuration>
    <configSections>
        <section name="DynamicDecoratorAspect" 
          type="DynamicDecoratorAOP.Configuration.DynamicDecoratorSection,
                DynamicDecoratorAOP.Configuration" />
    </configSections>

    <DynamicDecoratorAspect>
        <objectTemplates>
            <add name="1" 
                 type="ThirdPartyHR.Employee" 
                 interface="ThirdPartyHR.IEmployee" 
                 methods="DetailsByLevel,get_EmployeeID" 
                 predecoration="SharedLib.SysConcerns.EnterLog,SharedLib.SysConcerns" 
                 postdecoration=""/>
            <add name="2" 
                 type="ThirdPartyHR.Employee" 
                 interface="ThirdPartyHR.IEmployee" 
                 methods="set_EmployeeID" 
                 predecoration="ConsoleUtil.LocalConcerns.SecurityCheck" 
                 postdecoration="ConsoleUtil.LocalConcerns.NotifyChange"/>
        </objectTemplates>
    </DynamicDecoratorAspect>
</configuration>

First of all, you need to add a section <DynamicDecoratorAspect> in your configuration file. Then, inside <objectTemplates> of <DynamicDecoratorAspect>, you add individual elements. For each element inside <objectTemplates>, the following attributes need to be specified:

  • type - target type
  • interface - interface returned
  • methods - names of target methods which will be attached to the aspects specified by predecoration and postdecoration
  • predecoration - preprocessing aspect
  • postdecoration - postprocessing aspect

Notes:

  1. The names in the value of the methods attribute are comma separated. For example, "DetailsByLevel,get_EmployeeID".
  2. The value of the predecoration attribute has two parts and is separated by a comma. The first part specifies the aspect name while the second part specifies the assembly name in which the aspect is defined, for example, "SharedLib.SysConcerns.EnterLog,SharedLib.SysConcerns". If the second part is not specified, it is assumed that the aspect is defined in the entry assembly, for example, "ConsoleUtil.LocalConcerns.SecurityCheck".
  3. The value of the postdecoration attribute has two parts and is separated by a comma. The first part specifies the aspect name while the second part specifies the assembly name in which the aspect is defined. If the second part is not specified, it is assumed that the aspect is defined in the entry assembly.

Use AOPCompositeContainer

The AOPCompositeContainer class is responsible for reading in aspect configurations, constructing target objects, and wiring aspects to target objects. The following code demonstrates how it is used.

class Program
{
    static void Main(string[] args)
    {
        AggregateCatalog catalog = new AggregateCatalog();
        catalog.Catalogs.Add(
          new AssemblyCatalog(Assembly.LoadFrom("Employee.dll")));

        Employee target = null;
        IEmployee emp = null;
        var container = new AOPCompositeContainer(catalog);

        emp = container.GetExportProxy<Employee, IEmployee>();
        emp.EmployeeID = 1;
        emp.FirstName = "John";
        emp.LastName = "Smith";
        emp.DateOfBirth = new DateTime(1990, 4, 1);
        emp.DepartmentID = 1;
        emp.DetailsByLevel(2);

        IEmployee emp1 = container.GetExportProxy<Employee, 
                             IEmployee>("set_EmployeeID");

        try
        {
            //Commenting out this line will throw out an exception
            Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
            Decoration dec = 
              ConcernsContainer.runtimeAspects[
              "ConsoleUtil.LocalConcerns.SecurityCheck"];
            dec.Parameters = new object[] { Thread.CurrentPrincipal };

            target = container.GetTarget<Employee>();
            target.PropertyChanged += 
              new PropertyChangedEventHandler(PropertyChanged_Listener);
            emp1.EmployeeID = 2;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }

        container.Dispose(); 
        Console.ReadLine();
    }

    static void PropertyChanged_Listener(object sender, 
                PropertyChangedEventArgs e)
    {
        Console.WriteLine(e.PropertyName.ToString() + " has changed.");
    }
}

When creating an AOPCompositeContainer object, you pass in an object of ComposablePartCatalog, which is the base class of AggregateCatalog. Then, you can use GetExportProxy<T, V>() to get a proxy of the export by specifying the target export type T and the returned interface type V. For example, in the above code, you specify Employee as the target export type and IEmployee as the returned interface type. It returns a proxy of IEmployee type. That's it. Now, when using emp, it starts writing the entering log.

GetExportProxy<T, V>() finds the first matched element in the configuration file by looking for the type attribute as T and interface attribute as V, and then, uses the element settings to create a proxy. For GetExportProxy<Employee, IEmployee>(), it tries to find the type attribute as "ThirdPartyHR.Employee" and interface attribute as "ThirdPartyHR.IEmployee" in the configuration file. The first element in the configuration is matched. Therefore, the value "DetailsByLevel,get_EmployeeID" of the methods attribute, the value "SharedLib.SysConcerns.EnterLog,SharedLib.SysConcerns" of the predecoration attribute, and the value "" of the postdecoration attribute are used to create a proxy. When using emp, only the methods DetailsByLevel and get_EmployeeID will write the entering log.

GetExportProxy<T, V>(string methods) finds the first matched element in the configuration file by looking for the type attribute as T, interface attribute as V, and methods attribute as methods. For example, the code GetExportProxy<Employee, IEmployee>("set_EmployeeID") tries to match the type attribute as "ThirdPartyHR.Employee", interface attribute as "ThirdPartyHR.IEmployee", and methods attribute as "set_EmployeeID", and the second element in the configuaration is matched. Therefore, the value "ConsoleUtil.LocalConcerns.SecurityCheck" of the predecoration attribute and the value "ConsoleUtil.LocalConcerns.NotifyChange" of the postdecoration attribute are used to create a proxy. When using emp1, only the method set_EmployeeID will check the security before its invocation and raise a notification after its invocation.

There are a few more points worth noting. Before emp1 invokes the aspect LocalConcerns.SecurityCheck, its paramters argument needs to be updated to a WindowsPrincipal object. This can be achieved by getting the Decoration object associated with the aspect using the Dictionary of the ConcernsContainer and then setting its Parameters property. The first three lines of the above code in the try block does this.

In order to capture the event raised from the aspect LocalConcerns.NotifyChange after the method set_EmployeeID sets the property, you need to register a listener to the export object of Employee before you call the method set_EmployeeID. To achieve this, you use the GetTarget<T>() method of AOPCompositeContainer to get the export object, then register the listener to the export object. The last three lines of code in the try block does this.

When executing the program, you see the following output.

If you comment out the line Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal), you see the following output:

Summary

Configurable aspects combined with MEF make AOP very simple. You define your aspect methods and then add elements in the application configuration file to associate your business objects with the aspects. Most of the time, that is all you need to do. In some special cases, you can still update the parameters argument of an aspect and get the target object using code.

References

The following articles will help you understand Dynamic Decorator and its features:

  1. Dynamic Decorator Pattern
  2. Add Aspects to Object Using Dynamic Decorator

The following articles discuss how the Dynamic Decorator can help you improve your application development:

  1. Components, Aspects, and Dynamic Decorator
  2. Components, Aspects and Dynamic Decorator for ASP.NET Application
  3. Components, Aspects, and Dynamic Decorator for ASP.NET MVC Application
  4. Components, Aspects, and Dynamic Decorator for Silverlight / WCF Service Application
  5. Components, Aspects, and Dynamic Decorator for an MVC/AJAX/REST Application

The following articles compare the Dynamic Decorator with some other similar tools:

  1. Dynamic Decorator and Castle DynamicProxy Comparison
  2. Dynamic Decorator, Unity and Castle DynamicProxy Comparison

The following articles discuss some miscellaneous topics regarding Dynamic Decorator and AOP:

  1. Performance of Dynamic Decorator
  2. Generic Dynamic Decorator
  3. Aspects to Object vs. Aspects to Class

License

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

Share

About the Author

Gary H Guo

United States United States
Object-oriented (OO) is about "classes" not "objects". But I truly believe that "objects" deserve more our attentions. If you agree, read more on... Dynamic Object Programming (DOP), Component-Based Object Extender (CBO Extender), AOP Container and Dynamic Decorator Pattern.
 
Mobile development is not just another type of front end. The real challenge is actually in the back end: How to present meaningful information in time to mobile users with exponentially increased data flooding around? Here is my first mobile solution: SmartBars - Barcode Reader, Price Comparison and Coupons.
 
Gary lives in southeast Michigan. My first programming language is FORTRAN. For the last a few years, I have primarily focused on .NET technologies with Mobile Development as my newest interest.

Comments and Discussions

 
QuestionUsing Attributes instead of config file PinmemberMember 397996331-May-12 1:25 
AnswerRe: Using Attributes instead of config file PinmemberGary H Guo31-May-12 4:15 
GeneralMy vote of 5 PinmemberDean Oliver30-Jan-12 3:04 
GeneralRe: My vote of 5 PinmemberGary H Guo30-Jan-12 15:53 
Thank you, sir!
QuestionNice article PinmemberSteve Solomon12-Sep-11 23:20 
AnswerRe: Nice article PinmemberGary H Guo13-Sep-11 4:48 
QuestionDouble edges review PinmvpSacha Barber5-Sep-11 22:10 
AnswerRe: Double edges review PinmemberGary H Guo6-Sep-11 5:33 
GeneralRe: Double edges review PinmvpSacha Barber6-Sep-11 5:36 
GeneralRe: Double edges review PinmemberGary H Guo6-Sep-11 5:44 
GeneralRe: Double edges review PinmvpSacha Barber6-Sep-11 5:52 
GeneralRe: Double edges review PinmemberSteve Solomon12-Sep-11 23:19 
GeneralRe: Double edges review PinmvpSacha Barber12-Sep-11 23:46 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.141015.1 | Last Updated 6 Sep 2011
Article Copyright 2011 by Gary H Guo
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid