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

Application Development With Component-Based Object Extender

5.00/5 (1 vote)
14 Nov 2011CPOL6 min read 24.9K   205  
Discusses how to use Component-Based Object Extender to add functionality to an application.

Introduction

Component-Based Object Extender (CBO Extender) is an object extensibility framework. As indicated by the name, it is component-based, which means it works with interfaces, and it extends objects directly instead of extending their types. CBO Extender can be used to extend any interface method including interfaces from a third party, from the .NET Framework, or interfaces defined by you. It is a general-purpose tool for adding functionality to objects by attaching behaviors to interface methods of the objects. You can find an overview of it in the article Component-Based Object Extender.

Extending objects directly means that functionality is added to an application without involving design changes of components. This article discusses how an application is enhanced by using the CBO Extender. The goal is to add functionality to the application as needed when objects are consumed without changing their components.

Background

In the CBO Extender, behaviors are represented by some methods with specific signature (a.k.a. aspect methods). The "aspect" is borrowed from AOP (Aspect-Oriented Programming), which represents a cross-cutting concern. However, aspect methods are not limited to cross-cutting concerns. Although an aspect method may represent a cross-cutting concern like logging, security checking, auditing, etc., it can also represent a concern like sorting and grouping, which is not cross-cutting in nature. In the context of CBO Extender, aspects are methods that can implement any logic including, but not limited to, cross-cutting concerns.

CBO Extender consists of a Dynamic Decorator and an AOP Container. You can use the Dynamic Decorator directly in your code by calling its CreateProxy<T> method. If you use some kind of IoC Container, you may use AOP Container with your IoC Container. There are two ways to use AOP Container: by configuration or by Fluent registration code.

In this article, we start with developing a simple console application to meet minimum business requirements. Then, we try to extend it by adding system enhancements like logging and security check as well as functional enhancement like sorting. Three different approaches are presented to accomplish these enhancements: using the CreateProxy<T> method, using AOPContainer with Fluent registration code, and using AOPContainer with configuration.

Note: CBO Extender has been uploaded as a NuGet package. You can download and add it to your project from Visual Studio 2010. You can also download the source code, latest updates, and more examples here.

To install CBOExtender to your project from Visual Studio 2010, click Tools->Library Package Manager->Manage NuGet Packages... to open the Manage NuGet Packages dialog. Type in CBOExtender as shown.

Image 1

You probably need to install NuGet for Visual Studio 2010 before you can download the package.

Example: HR Console

The HR Console reads department data and employee data from a data source (an XML file) and populates a department collection and an employee collection. It then tries to get the department ID of the first department, updates the department ID of the first employee, and gets the employee's details. The application code is as follows.

C#
static void Main(string[] args)
{
    //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 from data source");
    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 = new RepositoryDepartment();
    rpDepartment.DataSource = dsDB;
    rpDepartment.GetAll();

    int? iDep = rpDepartment.RepList[0].DepartmentID;

    IRepository<IEmployee> rpEmployee = new RepositoryEmployee();
    rpEmployee.DataSource = dsDB;
    rpEmployee.GetAll();

    Console.WriteLine();

    //Print employees
    Console.WriteLine("Employees in collection");
    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();

    IEmployee oEm = rpEmployee.RepList[0];
    oEm.DepartmentID = 2;
    oEm.DetailsByLevel(3);
    Console.WriteLine();

    //Print employees after update
    Console.WriteLine("Employees after 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();

    Console.ReadLine();
}

When executing, the output of the above code looks as follows.

Image 2

It simply prints out the original employee list from the data source, the employee list in the collection populated from the original list, and the employee list in the collection after updating.

Enhancements and Requirements

To make the application a bit more interesting, let's enhance it to have logging and security checking capabilities. We also want to be able to sort the employee list by last name or date of birth.

As a guideline to make these changes, we should keep the existing business components (Employee, Department, RepositoryEmployee, and RepositoryDepartment) from change whenever possible. After all, these enhancements are application specific and are requested after these business components have been designed.

There are three different approaches that can be used to achieve the enhancements and requirements. They are: using the CreateProxy<T> method, using AOPContainer with Fluent registration code, and using AOPContainer with configuration. Each of the approaches is discussed separately in the following sections.

Use CreateProxy

Let's analyze the enhancements a bit. Logging is classical cross-cutting concerns and may be used by multiple modules or different applications. So, we put the aspect methods for entering/exiting logs in a shared class as follows.

C#
public class SharedConcerns
{
    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();
    }
}

Security checking is a cross-cutting concern and is application-specific (Windows security or custom security, etc.). So, we put the aspect method for security checking in a class inside the application as follows:

C#
class AppConcerns
{
    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);
    }
}

Sorting employee list is not a cross-cutting concern and is an extension to the RepositoryEmployee component. It is unlikely shared by other components. So, we use an anonymous method for this aspect as follows:

C#
(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;
    }
}

Note that all the above aspect methods have the same signature. They take an AspectContext as the first parameter, an object[] as the second parameter, and return void. The following code shows how these aspects are used to enhance the application:

C#
static void Main(string[] args)
{
    //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 = new RepositoryDepartment();
    rpDepartment = ObjectProxyFactory.CreateProxy<IRepository<IDepartment>>(
        rpDepartment,
        new string[] { "GetAll" },
        new Decoration(SharedLib.SharedConcerns.EnterLog, null),
        new Decoration(SharedLib.SharedConcerns.ExitLog, null));

    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 = ObjectProxyFactory.CreateProxy<IDepartment>(
        rpDepartment.RepList[0],
        new string[] { "get_DepartmentID" },
        new Decoration(SharedLib.SharedConcerns.EnterLog, null),
        null).DepartmentID;

    IRepository<IEmployee> rpEmployee = new RepositoryEmployee();
    rpEmployee = ObjectProxyFactory.CreateProxy<IRepository<IEmployee>>(
        rpEmployee,
        new string[] { "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));
    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 = ObjectProxyFactory.CreateProxy<IEmployee>(oEm,
            new string[] { "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 = ObjectProxyFactory.CreateProxy<IEmployee>(oEm,
            new string[] { "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();

    Console.ReadLine();
}

Note that CreateProxy<T> is called right before you consume an object by accessing its methods. When executing the above code, the following output is displayed.

Image 3

Your application now has logging, security checking, and sorting capabilities without changing/deriving from the existing business components or creating new business components. You can change application behaviors by just changing an aspect. For instance, if you want to change the employee sorting from by their last names to by their dates of birth, you just modify the anonymous method to do it.

Use AOP Container with Fluent Registration

AOPContainer provides a common application programming interface for IoC Containers to have AOP capabilities. The above application is rewritten using AOPContainer with Windsor Container and Unity Container. The Fluent interface is used to register aspects to components. The complete code is listed as follows:

C#
static void Main(string[] args)
{
    AOPContainer aopcontainer = null;
    switch (args.Length == 0 ? "" : args[0])
    {
        case "0": //Register types for Windsor Container and create AOPWindsorContainer
            {
                Console.WriteLine("Use Windsor Container");

                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
            {
                Console.WriteLine("Use Unity Container");

                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("Usage: ConsoleUtil i");
                Console.WriteLine("where i: 0 (Windsor Container)");
                Console.WriteLine("         1 (Unity Container)");
                Console.ReadLine();
            }
            return;
    }

    Console.WriteLine();

    //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 Fluent interface registration method RegisterAspect<T, V> is convenient to register aspects to a component. By registering an aspect to a component, all instances resolved by the AOPContainer will have the aspect attached. However, there are situations that you don't want all instances to be attached to an aspect but want to add the aspect to a specific instance at a specific spot. ChainAspect<T, V> provides this flexibility. As seen in the above code, ChainAspect<T, V> is used to add security checking to a specific employee instance right before updating its property.

When executing, it generates the following output.

Image 4

Use AOP Container with Configuration

Instead of using the Fluent interface to register aspects, you can also configure aspects in the application configuration file. For aspect methods to be used in the configuration file, they need to be wrapped in a class that implements a marker interface IConcerns and provides a static constructor to keep a mapping between the method names and decoration objects. The following code presents these changes for the aspect methods defined in the previous section.

C#
public class SharedConcerns : IConcerns
{
    static SharedConcerns()
    {
        ConcernsContainer.runtimeAspects.Add("SharedLib.SharedConcerns.EnterLog", 
                          new Decoration(SharedConcerns.EnterLog, null));
        ConcernsContainer.runtimeAspects.Add("SharedLib.SharedConcerns.ExitLog", 
                          new Decoration(SharedConcerns.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();
    }
}

class AppConcerns : IConcerns
{
    class EmployeeLastnameComparer : IComparer<IEmployee>
    {
        public int Compare(IEmployee e1, IEmployee e2)
        {
            return String.Compare(e1.LastName, e2.LastName);
        }
    }

    static AppConcerns()
    {
        ConcernsContainer.runtimeAspects.Add("ConsoleUtil.AppConcerns.SecurityCheck", 
                          new Decoration(AppConcerns.SecurityCheck, null));
        ConcernsContainer.runtimeAspects.Add(
                "ConsoleUtil.AppConcerns.SortEmployeeByLastname", 
                new Decoration(AppConcerns.SortEmployeeByLastname, null));
    }

    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);
    }

    public static void SortEmployeeByLastname(AspectContext ctx, object[] 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;
        }
    }
}

With the changes with the aspect methods, we are ready to configure the aspects for the application. The configuration settings are listed as follows.

XML
<configSections>
    <section name="DynamicDecoratorAspect" 
          type="CBOExtender.Configuration.DynamicDecoratorSection,CBObjectExtender"/>
</configSections>

<DynamicDecoratorAspect>
    <objectTemplates>
        <add name="1"
                type="ConsoleUtil.RepositoryDepartment"
                interface="ConsoleUtil.IRepository`1[ThirdPartyHR.IDepartment]"
                methods="GetAll"
                predecoration="SharedLib.SharedConcerns.EnterLog,SharedLib"
                postdecoration="SharedLib.SharedConcerns.ExitLog,SharedLib" />
        <add name="2"
                type="ConsoleUtil.RepositoryEmployee"
                interface="ConsoleUtil.IRepository`1[ThirdPartyHR.IEmployee]"
                methods="GetAll"
                predecoration=""
                postdecoration="ConsoleUtil.AppConcerns.SortEmployeeByLastname"/>
        <add name="3"
                type="ThirdPartyHR.Department"
                interface="ThirdPartyHR.IDepartment"
                methods="get_DepartmentID"
                predecoration="SharedLib.SharedConcerns.EnterLog,SharedLib"
                postdecoration=""/>
    </objectTemplates>
</DynamicDecoratorAspect>

Three IoC containers are used in the application: Windsor, Unity, and MEF. The application code is listed as follows.

C#
static void Main(string[] args)
{
    AOPContainer aopcontainer = null;

    switch (args.Length == 0 ? "" : args[0])
    {
        case "0": //Register types for Windsor Container and create AOPWindsorContainer
            {
                Console.WriteLine("Using AOPWindsorContainer.");

                IWindsorContainer container = new WindsorContainer();
                container.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)
                );

                aopcontainer = new AOPWindsorContainer((IWindsorContainer)container);
            }
            break;

        case "1": //Register types for Unity Container and create AOPUnityContainer
            {
                Console.WriteLine("Using AOPUnityContainer.");

                IUnityContainer container = new UnityContainer();
                container.RegisterType<IEmployee, Employee>(new InjectionConstructor()
                ).RegisterType<IDepartment, Department>(new InjectionConstructor()
                ).RegisterType<IRepository<IDepartment>, RepositoryDepartment>(
                new InjectionConstructor()).RegisterType<IRepository<IEmployee>, 
                RepositoryEmployee>(new InjectionConstructor());

                aopcontainer = new AOPUnityContainer((IUnityContainer)container);
            }
            break;

        case "2": //Register catalogs for MEF and create AOPMefContainer
            {
                Console.WriteLine("Using AOPMefContainer.");

                AggregateCatalog catalog = new AggregateCatalog();
                catalog.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFrom("Employee.dll")));
                catalog.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFrom("Department.dll")));
                catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetEntryAssembly()));

                CompositionContainer container = new CompositionContainer(catalog);

                aopcontainer = new AOPMefContainer(container);
            }
            break;

        default:
            {
                Console.WriteLine("Usage: ConsoleUtil i");
                Console.WriteLine("where i: 0 (Windsor Container)");
                Console.WriteLine("         1 (Unity Container)");
                Console.WriteLine("         2 (MEF)");
                Console.ReadLine();
            }
            return;
    }

    Console.WriteLine();

    //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();
}

As you can see, after registering types with a particular IoC Container, there is no need to register aspects to types explicitly. The AOP Container will automatically parse the configuration file and register aspects for components based on the configuration settings. When executing, it generates the following output:

Image 5

Points of Interest

  • Application enhancement is made easy by extending objects when they are consumed.
  • Objects can be extended by using CreateProxy, AOP Container with Fluent registration, or AOP Container with configuration.
  • Software modifications are confined to the consuming side of components, which results in stable components and agile applications.
  • Aspect methods are simple, lightweight, and can be powerful by accessing target, call context, and passing parameters.

License

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