Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

AOP Container 2

5.00/5 (1 vote)
24 Oct 2011CPOL5 min read 22.1K   212  
Discusses the Fluent interface to register aspects to a component and a method to chain aspects to an object for AOP Container.

Introduction

The article AOP Container introduced an AOP model that enables any IoC container to have AOP capabilities by configuration. As examples, the model was applied to MEF, Unity, and Windsor Containers.

Since its publishing, the AOP Container has been enhanced with new features such as registering aspects using code and adding aspects to an object after its creation. These features are discussed in this article.

Background

The reason for me to create an AOP Container is to make it easy to add AOP capabilities to IoC Containers. Therefore, it makes sense for the AOP Container to provide methods to register aspects using code just like most IoC Containers provide methods to register types using code.

There are also situations you may not want all objects of a component to have an aspect, but would like to add or chain aspects to a specific object as needed after its creation. The AOP Container offers this flexibility by providing a method for you to use after the creation of an object.

In the following sections, I discuss how to register aspects to a component using code and how to chain aspects to an object after its creation. The Windsor Container and Unity Container are used as examples of IoC Containers.

Using the Code

The following code demonstrates the use of the AOP Container together with the Windsor Container and Unity Container. Basically, the AOP Container provides a common application programming interface for aspect registration and object resolving.

C#
static void Main(string[] args)
{
    AOPContainer aopcontainer = null;
    switch (args[0])
    {
        case "0":
        //Register types for Windsor Container and create AOPWindsorContainer
            {
                IWindsorContainer windsorContainer = new WindsorContainer();
                windsorContainer.Register(AllTypes
                    .FromAssembly(Assembly.LoadFrom("Employee.dll"))
                    .Where(t => t.Name.Equals("Employee"))
                    .Configure(c => c.LifeStyle.Transient)
                ).Register(AllTypes
                    .FromAssembly(Assembly.LoadFrom("Department.dll"))
                    .Where(t => t.Name.Equals("Department"))
                    .Configure(c => c.LifeStyle.Transient)
                ).Register(AllTypes
                    .FromAssembly(Assembly.GetExecutingAssembly())
                    .Where(t => (t.Name.Equals("RepositoryEmployee") || 
                           t.Name.Equals("RepositoryDepartment")))
                    .Configure(c => c.LifeStyle.Transient)
                ).Register(Castle.MicroKernel.Registration.Component.For(typeof(List<IEmployee>))
                ).Register(Castle.MicroKernel.Registration.Component.For(typeof(List<IDepartment>))
                );

                aopcontainer = new AOPWindsorContainer(windsorContainer);
            }
            break;

        case "1": //Register types for Unity Container and create AOPUnityContainer
            {
                IUnityContainer unityContainer = new UnityContainer();
                unityContainer.RegisterType<IEmployee, Employee>(new InjectionConstructor()
                ).RegisterType<IDepartment, Department>(new InjectionConstructor()
                ).RegisterType<IRepository<IDepartment>, RepositoryDepartment>(new InjectionConstructor()
                ).RegisterType<IRepository<IEmployee>, RepositoryEmployee>(new InjectionConstructor()
                ).RegisterType<IList<IEmployee>, List<IEmployee>>(new InjectionConstructor()
                ).RegisterType<IList<IDepartment>, List<IDepartment>>(new InjectionConstructor()
                );

                aopcontainer = new AOPUnityContainer(unityContainer);
            }
            break;

        default:
            {
                Console.WriteLine("Invalid number for an AOP container.");
                Console.ReadLine();
            }
            return;
    }

    //Register Aspects
    aopcontainer.RegisterAspect<RepositoryDepartment, 
                  IRepository<IDepartment>>("GetAll",
        new Decoration(SharedLib.SharedConcerns.EnterLog, null),
        new Decoration(SharedLib.SharedConcerns.ExitLog, null)
    ).RegisterAspect<RepositoryEmployee, IRepository<IEmployee>>("GetAll",
        null,
        new Decoration((ctx, parameters) =>
        {
            object target = ctx.Target;
            if (target.GetType().ToString() == "ConsoleUtil.RepositoryEmployee")
            {
                List<IEmployee> emps = ((IRepository<IEmployee>)target).RepList;
                IEnumerable<IEmployee> query = emps.OrderByDescending(emp => emp,
                    new EmployeeLastnameComparer()).ToList<IEmployee>();
                ((IRepository<IEmployee>)target).RepList = (List<IEmployee>)query;
            }
        }, null)
    ).RegisterAspect<Department, IDepartment>("get_DepartmentID",
        new Decoration(SharedLib.SharedConcerns.EnterLog, null),
        null);

    //Set security policy.
    //Commenting out this line, the security check aspect will throw out an exception 
    Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

    //Load employees and departments into a data set from an xml file.
    DataSet dsDB = new DataSet();
    dsDB.ReadXml("employees.xml");

    //Print original employees
    Console.WriteLine("Employees");
    string str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (DataRow r in dsDB.Tables["Employee"].Rows)
    {
        str = r["EmployeeID"].ToString() + "\t" + 
              r["FirstName"].ToString() + "\t" + 
              r["LastName"].ToString() + "\t" + 
              r["DateOfBirth"].ToString() + "\t" + 
              r["DepartmentID"].ToString();
        Console.WriteLine(str); 
    }
    Console.WriteLine();

    IRepository<IDepartment> rpDepartment = 
      aopcontainer.Resolve<RepositoryDepartment, 
          IRepository<IDepartment>>("GetAll");
    rpDepartment.AopContainer = aopcontainer;
    rpDepartment.DataSource = dsDB;
    //The following line populates a department list and writes enter/exit logs.
    rpDepartment.GetAll();

    //The following line gets a department id and writes enter log.
    int? iDep = rpDepartment.RepList[0].DepartmentID;

    IRepository<IEmployee> rpEmployee = 
      aopcontainer.Resolve<RepositoryEmployee, 
            IRepository<IEmployee>>("GetAll");
    rpEmployee.AopContainer = aopcontainer;
    rpEmployee.DataSource = dsDB;
    //The following line populates an employee list and sorts the list.
    rpEmployee.GetAll();

    Console.WriteLine();

    //Print employees sorted by employee's last name 
    Console.WriteLine("Employees sorted by employee's last name");
    str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (IEmployee em in rpEmployee.RepList)
    {
        str = em.EmployeeID.ToString() + "\t" + em.FirstName + 
              "\t" + em.LastName + "\t" + 
              em.DateOfBirth.ToShortDateString() + "\t" + 
              em.DepartmentID.ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    try
    {
        IEmployee oEm = rpEmployee.RepList[0];
        //Add a security check aspect to an employee before changing its property
        oEm = AOPContainer.ChainAspect<IEmployee, IEmployee>(oEm,
            "set_EmployeeID,set_FirstName,set_LastName,set_DateOfBirth,set_DepartmentID",
            new Decoration(AppConcerns.SecurityCheck, 
            new object[] { System.Threading.Thread.CurrentPrincipal }),
            null);
        //The following line check security before setting the property
        oEm.DepartmentID = 2;

        //Chain enetr/exit log aspects to the employee's DetailsByLevel method
        oEm = AOPContainer.ChainAspect<IEmployee, IEmployee>(oEm,
            "DetailsByLevel",
            new Decoration(SharedLib.SharedConcerns.EnterLog, null),
            new Decoration(SharedLib.SharedConcerns.ExitLog, null));
        //The following line write entering log before and exiting log
        //after execution of the target's DetailsByLevel(3)
        oEm.DetailsByLevel(3);
        Console.WriteLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Console.WriteLine();
    }

    //Print employees after sort and update
    Console.WriteLine("Employees after sort and update");
    str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
    Console.WriteLine(str);
    Console.WriteLine("====================================================");
    foreach (IEmployee em in rpEmployee.RepList)
    {
        str = em.EmployeeID.ToString() + "\t" + em.FirstName + "\t" + 
              em.LastName + "\t" + em.DateOfBirth.ToShortDateString() + 
              "\t" + em.DepartmentID.ToString();
        Console.WriteLine(str);
    }
    Console.WriteLine();

    aopcontainer.IocContainer.Dispose();

    Console.ReadLine();
}

The first part of the code (code between the switch ... case clause) is specific to IoC Containers. That is, depending on which IoC Container is used, you register your components to the IoC Container and create the corresponding AOP Container. The code for case "0" is specific to Windsor Container while the code for case "1" is specific to Unity Container. The code basically registers types for the IoC Containers so that later you can use the IoC Containers to resolve an object of the registered type. Depending on which IoC Container is used, you also create a corresponding AOP Container. For Windsor Container, an AOPWindsorContainer is created by passing the instance of the Windsor Container. For Unity Container, an AOPUnityContainer is created by passing the instance of the Unity Container.

The rest of the code is independent of IoC Containers. Before we continue, let me explain a little more detail of the RegisterAspect method of the AOPContainer. The declaration of RegisterAspect is as follows.

C#
public AOPContainer RegisterAspect<T, V>(string methods, 
                    Decoration preDeco, Decoration postDeco) where T : V
  • T - target type (target is the original object to which aspects are attached)
  • V - interface implemented by the target type and returned when using the AOPContainer to resolve an object of T
  • methods - names (separated by comma if more than one) of target's methods which will be attached the aspects specified by preDeco and postDeco
  • preDeco - preprocessing Decoration
  • postDeco - postprocessing Decoration

The Decoration is an aggregation of DecorationDelegate and object[]. Its constructor is as follows:

C#
public Decoration(DecorationDelegate aspectHandler, object[] parameters)
  • aspectHandler - a delegate with AspectContext as first parameter, object[] as second parameter, and void as return
  • parameters - array of objects passed in the aspectHandler as its second parameter when invoking

DecorationDelegate is defined as follows:

C#
public delegate void DecorationDelegate(AspectContext ctx, object[] parameters)

Essentially, an aspect of AOP Container is a method that takes the signature of DecorationDelegate.

Hope that gives you enough background to understand the following aspect registration code.

The code:

C#
aopcontainer.RegisterAspect<RepositoryDepartment, IRepository<IDepartment>>("GetAll",
    new Decoration(SharedLib.SharedConcerns.EnterLog, null),
    new Decoration(SharedLib.SharedConcerns.ExitLog, null)
)

registers the preprocessing aspect SharedLib.SharedConcerns.EnterLog and the postprocessing aspect SharedLib.SharedConcerns.ExitLog to the GetAll method of RepositoryDepartment which implements IRepository<IDepartment>. SharedLib.SharedConcerns.EnterLog and SharedLib.SharedConcerns.ExitLog are aspect methods to write entering/exiting logs, respectively.

The code:

C#
RegisterAspect<RepositoryEmployee, IRepository<IEmployee>>("GetAll",
    null,
    new Decoration((ctx, parameters) =>
    {
        object target = ctx.Target;
        if (target.GetType().ToString() == "ConsoleUtil.RepositoryEmployee")
        {
            List<IEmployee> emps = ((IRepository<IEmployee>)target).RepList;
            IEnumerable<IEmployee> query = emps.OrderByDescending(emp => emp,
                new EmployeeLastnameComparer()).ToList<IEmployee>();
            ((IRepository<IEmployee>)target).RepList = (List<IEmployee>)query;
        }
    }, null)
)

registers a null preprocessing aspect and an anonymous method as postprocessing aspect to the GetAll method of RepositoryEmployee which implements IRepository<IEmployee>. Note that the anonymous method provides sorting logic to the RepositoryEmployee.

The code:

C#
RegisterAspect<Department, IDepartment>("get_DepartmentID",
    new Decoration(SharedLib.SharedConcerns.EnterLog, null),
    null)

registers the preprocessing aspect SharedLib.SharedConcerns.EnterLog and the null postprocessing aspect to the get_DepartmentID method of Department which implements IDepartment.

We're done with the type registration and aspect registration. Before we use the AOP Container to do something useful, we set the security policy to WindowsPrincipal, load the departments and employees into a dataset, and print out the employees.

Now, we are ready to use the AOP Container to do something.

First, we use the aopcontainer to resolve a RepositoryDepartment object. It returns an interface of IRepository<IDepartment> to the variable rpDepartment. When using rpDepartment to call the GetAll method, you will see the entering log before and exiting log after the method's execution of the target.

Next, rpDepartment.RepList[0].DepartmentID will write the entering log before returning the department ID. The reason it works like this is that the aopcontainer is used to resolve individual departments inside the GetAll of the RepositoryDepartment.

Similarly, the aopcontainer is used to resolve a RepositoryEmployee object which returns an interface of IRepository<IEmployee> to the variable rpEmployee. When using rpEmployee to call the GetAll method, it will sort the employee list and save it in the target.

Registering aspects to a component using the RegisterAspect<T, V> method means all objects of the component resolved by Resolve<T, V> will have the aspects attached. However, there are situations in which you don't want all objects of a component to have an aspect attached, but would like to add an aspect to an object after its creation. There are also situations where you would like to chain more aspects to an object after its creation. The ChainAspect<T, V> method of AOPContainer provides a solution for these situations and can be used to add or chain aspects to an object as needed.

In the try block of the above code, ChainAspect<IEmployee, IEmployee> adds the security check aspect to the first employee so that only the built-in administrators can modify the employee's properties. If the application is executed by a non-administrator, an exception will throw and the property is not modified.

Run the application, and the output looks as follows:

Image 1

Points of Interest

  • The AOP Container provides a mechanism and a common application programming interface for adding aspects to IoC Containers.
  • The AOP Container provides a Fluent interface to register aspects to components. Objects resolved by the AOP Container have aspects attached.
  • The AOP Container provides a method to add/chain aspects to an object as needed after its creation.
  • Although this article discusses the AOP Container with Windsor and Unity IoC Containers, you should be able to easily combine the AOP Container with other IoC Containers.

License

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