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

Add Aspects to Object Using Dynamic Decorator

5.00/5 (9 votes)
29 Sep 2010CPOL15 min read 56K   576  
Discuss how to add aspects to object at runtime and enhance them using Dynamic Decorator

Introduction

In the article Dynamic Decorator Pattern, I introduced a Dynamic Decorator, which can be used to add extra functionality to an object at runtime without modifying its class or writing decoration code during design time. It provides a convenient way to add aspects (crosscutting concerns) to objects. In this article, I discuss some important features of Dynamic Decorator and how to use them in aspects to object.

Background

As you see in the article Dynamic Decorator Pattern, adding an aspect to object is as simple as writing a method. Superior designs can be achieved by separating crosscutting concerns into individual methods (aspects) and then using them to decorate existing objects. Although it is architecturally sound, the usefulness of this approach depends on what you can do with/inside aspects. That is the purpose of this article. In this article, I discuss some important features of the Dynamic Decorator and how these features can be used to enhance your aspects. The features discussed are:

  1. Add preprocessing and postprocessing aspects
  2. Chain multiple aspects together
  3. Pass parameters to aspect
  4. Handle exception thrown from aspect
  5. Suppress exception thrown from aspect
  6. Access target object within aspect
  7. Access method call context within aspect
  8. Add aspects to .NET Framework object

Code examples for these features are given to demonstrate how they can be used in aspects. A limit of the Dynamic Decorator is also discussed.

After reading this article, you should be able to do serious programming using the Dynamic Decorator for writing aspects to your objects.

Using the Code

*Note: The code in this article has been upgraded. Some examples here may not work with the code in the Dynamic Decorator Pattern. The latest code can be downloaded from this article.

In the following discussion, the Employee is used in most of the examples. The code of Employee class is listed as follows:

C#
public interface IEmployee
{
    System.Int32? EmployeeID { get; set; }
    System.String FirstName { get; set; }
    System.String LastName { get; set; }
    System.DateTime DateOfBirth { get; set; }
    System.String FullName(bool bCommaSeparate);
    System.Single Salary();
}
C#
public class Employee : IEmployee
{
    #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; }

    #endregion

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

    public Employee() { }

    public System.String FullName(bool bCommaSeparate)
    {
        if (bCommaSeparate)
        {
            System.String s = LastName + ", " + FirstName;
            Console.WriteLine("Full Name: " + s);
            return s;
        }
        else
        {
            System.String s = FirstName + " " + LastName;
            Console.WriteLine("Full Name: " + s);
            return s;
        }
    }

    public System.Single Salary()
    {
        System.Single i = 10000.12f;
        Console.WriteLine("Salary: " + i);
        return i;
    }
}

The Employee, which is also used in the Dynamic Decorator Pattern, implements an IEmployee interface. It implements the bare minimum business logic.

For most of today's applications, it is inadequate to implement only business functionality. For an application working properly, system requirements like security, logging and so on also need to be satisfied. These system requirements are not business specific and tend to cut cross multiple abstractions in the application.

Say you want to do security checking before calling Employee's Salary method, and also you want to do some logging before and after calling Employee's FullName and Salary methods. These requirements are excellent examples of crosscutting concerns. And it is ideal to put them into some aspects and use them to decorate your objects.

In the following code examples, I use anonymous methods for aspects to make the examples concise and individually contained. However, I could also use named methods in place of the anonymous methods just like what I did in the examples of Dynamic Decorator Pattern. An advantage of named methods is that they can be used in multiple places. If designed properly, an aspect defined by a named method can be used to decorate different objects, either of same type or of different types.

1. Add Preprocessing and Postprocessing Aspects

Preprocessing aspect is executed before the execution of target method while postprocessing aspect is executed after the execution of target method. Using Dynamic Decorator, you can provide an aspect for preprocessing and a second aspect for postprocessing.

The following code adds a preprocessing aspect wrapped in an anonymous method ((x, y) => { Console.WriteLine("Do enter log here"); }) and a postprocessing aspect wrapped in a second anonymous method ((x, y) => { Console.WriteLine("Do exit log here"); }) to em's Salary and FullName methods, respectively.

C#
IEmployee em = new Employee(1, "John", "Smith", new DateTime(1990, 4, 1));
IEmployee tpem = (IEmployee)ObjectProxyFactory.CreateProxy(
    em,
    new String[] { "Salary", "FullName" },
    new Decoration((x, y) => { Console.WriteLine("Do enter log here"); }, null),
    new Decoration((x, y) => { Console.WriteLine("Do exit log here"); }, null));

tpem.FullName(false);
Console.WriteLine("");
tpem.Salary();

After execution of the above code, you will see the following results.

Do enter log here
Full Name: John Smith
Do exit log here

Do enter log here
Salary: 10000.12
Do exit log here

As you can see, the preprocessing aspect is executed before calling Salary and FullName while the postprocessing aspect is executed after calling Salary and FullName.

2. Chain Multiple Aspects Together

If you have more than one preprocessing aspects or postprocessing aspects, you can create a chain of proxies to hook up all preprocessing aspects or all postprocessing aspects together by calling ObjectProxyFactory.CreateProxy multiple times with the previous proxy as the target of next proxy. A powerful preprocessing or postprocessing chain can be constructed this way. For instance, you want to add both entering logging and security checking before calling a method of an object. Instead of putting entering logging and security checking logic in a single aspect, you can have an aspect for entering logging and a separate aspect for security checking and chain them one after the other. This way, you are able to keep separate concerns in separate aspects.

The following code adds a security checking preprocessing aspect to em's Salary method, and then, adds an entering logging preprocessing aspect and an exiting logging postprocessing aspect to tpCheckRight's Salary and FullName methods. Note that tpCheckRight is a proxy of em and tpLogCheckRight is a proxy of tpCheckRight. When tpLogCheckRight is used to access methods of IEmployee, the execution path is tpLogCheckRight -> tpCheckRight -> em.

C#
IEmployee em = new Employee(1, "John", "Smith", new DateTime(1990, 4, 1));
IEmployee tpCheckRight = (IEmployee)ObjectProxyFactory.CreateProxy(
    em,
    new String[] { "Salary" },
    new Decoration((x, y) => { Console.WriteLine("Do security check here"); }, null),
    null);

IEmployee tpLogCheckRight = (IEmployee)ObjectProxyFactory.CreateProxy(
    tpCheckRight,
    new String[] { "Salary", "FullName" },
    new Decoration((x, y) => { Console.WriteLine("Do enter log here"); }, null),
    new Decoration((x, y) => { Console.WriteLine("Do exit log here"); }, null));

tpLogCheckRight.FullName(false);
Console.WriteLine("");
tpLogCheckRight.Salary();

After execution of the above code, you will see the following results:

Do enter log here
Full Name: John Smith
Do exit log here

Do enter log here
Do security check here
Salary: 10000.12
Do exit log here

When tpLogCheckRight.FullName(false) executes, it looks for preprocessing aspect of tpCheckRight and finds one (the anonymous method (x, y) => { Console.WriteLine("Do enter log here"); }), and therefore executes the preprocessing aspect. Then, it invokes FullName of tpCheckRight (note tpCheckRight is the target of tpLogCheckRight for this case). When execution of FullName of tpCheckRight, it looks for preprocessing aspect of em and does not find one. So, it continues to invoke FullName of em. Then, it looks for postprocessing aspect of em and does not find one. So, it continues to look for postprocessing aspect of tpCheckRight and finds one (the anonymous method (x, y) => { Console.WriteLine("Do exit log here"); }), and therefore executes the postprocessing aspect. The following diagram presents the execution sequence of tpLogCheckRight.FullName(false).

Image 1

When tpLogCheckRight.Salary() executes, it looks for preprocessing aspect of tpCheckRight and finds one (the anonymous method (x, y) => { Console.WriteLine("Do enter log here"); }), and therefore executes the preprocessing aspect. Then, it invokes Salary of tpCheckRight (note tpCheckRight is the target of tpLogCheckRight for this case). When execution of Salary of tpCheckRight, it looks for preprocessing aspect of em and finds one (the anonymous method (x, y) => { Console.WriteLine("Do security check here"); }), and therefore executes the preprocessing aspect. Then, it invokes Salary of em. Then, it looks for postprocessing aspect of em and does not find one. So, it continues to look for postprocessing aspect of tpCheckRight and finds one (the anonymous method (x, y) => { Console.WriteLine("Do exit log here"); }), and therefore executes the postprocessing aspect. The execution of tpLogCheckRight.Salary() follows the same sequence as tpLogCheckRight.FullName(false) shown in the above diagram.

Note that over the course of execution of the proxy chain, the last added preprocessing aspect executes first, then, the second last added preprocessing aspect, etc., finally the first added preprocessing aspect; after that, the original object method executes; after that, the first added postprocessing aspect executes, then, the second added postprocessing aspect, etc., finally the last added postprocessing aspect.

3. Pass Parameters to Aspect

You can pass an array of objects into an aspect. You achieve this by passing the array of objects as the second parameter of Decoration constructor when calling ObjectProxyFactory.CreateProxy. This array then is passed into the corresponding aspect as the second parameter when the aspect is invoked. Inside the aspect, you can get the runtime type of each object in the array and use it.

In the following example, an array with a single object implementing IPrincipal interface is passed as the second parameter of the Decoration constructor for the preprocessing aspect. This array is then passed into the aspect (the first parameter of Decoration constructor) as the second parameter when the aspect invokes. For this case, y presents the array when the aspect executes. Note that the array is a type of object[]. Before you use it, you cast the first element of the array to a WindowsPrincipal. Then, you are able to access the methods of the specific type.

C#
Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

IEmployee em = new Employee(1, "John", "Smith", new DateTime(1990, 4, 1));
IEmployee tpem = (IEmployee)ObjectProxyFactory.CreateProxy(
    em,
    new String[] { "Salary" },
    new Decoration((x, y) =>
    {
        if (y != null && ((WindowsPrincipal)y[0]).IsInRole
			("BUILTIN\\" + "Administrators"))
        {
            Console.WriteLine("Has right to call");
            return;
        }

        throw new Exception("No right to call!");
    }
        , new object[] { Thread.CurrentPrincipal }),
    null);

tpem.Salary();

Assuming that you logged in as a Windows Administrator, after execution of the above code, you will see the following results:

Has right to call
Salary: 10000.12

If you didn't log in as a Windows Administrator, when executing the above code, you will receive an exception and the Salary method will not be executed.

In this example, I use an anonymous method to do security checking. Of course, you can move the code of the anonymous method into a named method and use it in place of the anonymous method when calling ObjectProxyFactory.CreateProxy. (See examples in the Dynamic Decorator Pattern.) Moreover, you can use this method as an aspect to another object, either of the same type or of a different type. You just need to pass this object and the named method to another ObjectProxyFactory.CreateProxy call. Now, the returned proxy object will do security checking for you.

4. Handle Exception Thrown from Aspect

When an exception occurs inside an aspect, normal execution of code is interrupted. Since the aspect is executed within a proxy invocation, the actual exception is lost in the context. The Dynamic Decorator addresses this issue so that the application will see the actual exception instead of a general invocation exception.

In the following code, the preprocessing aspect throws an exception. The method call tpem.Salary() is wrapped in a try...catch clause.

C#
IEmployee em = new Employee(1, "John", "Smith", new DateTime(1990, 4, 1));
IEmployee tpem = (IEmployee)ObjectProxyFactory.CreateProxy(
    em,
    new String[] { "Salary" },
    new Decoration((x, y) => { throw new Exception
		("An exception has occurred!"); }, null),
    null);

try
{
    tpem.Salary();
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

When the above code executes, you will see the following results:

An exception has occurred!

As you can see, the way to handle an exception thrown from an aspect has no difference from any other .NET exceptions.

5. Suppress Exception Thrown from Aspect

Most of the time when an exception occurs, the normal execution of code is interrupted. The exception is then handled at a higher level of call stack. However, there are times that you may want to suppress the exception thrown from an aspect and continue to execute the code after the aspect. For example, you may want to continue to call the target method even if the entering logging aspect throws an exception due to a remote web service unavailable. The Decoration has an overloaded constructor to take a third parameter to enable suppressing exception thrown from an aspect. When this parameter is set to true and an exception occurs inside the aspect, the exception will be suppressed and the execution continues like the exception hadn't occurred.

In the following example, the third parameter of Decoration is set to true, which means that if an exception occurs in the preprocessing aspect, it will be handled inside the proxy invocation and execution of program continues just like the exception hadn't occurred.

C#
IEmployee em = new Employee(1, "John", "Smith", new DateTime(1990, 4, 1));
IEmployee tpem = (IEmployee)ObjectProxyFactory.CreateProxy(
    em,
    new String[] { "Salary" },
    new Decoration((x, y) => { throw new Exception
		("An exception has occurred!"); }, null, true),
    null);

try
{
    tpem.Salary();
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

When the above code executes, you will see the following results:

Salary: 10000.12

As you can see, even if an exception is thrown inside the preprocessing aspect, the execution of program continues and the Salary method is called like no exception had ever occurred. If the third parameter is set to false or not set (by using the Decoration's constructor with two parameters), you will see the exception and the Salary method will not be executed.

6. Access Target Object Within Aspect

You can access target object within aspects. The target object is passed in as the first parameter of ObjectProxyFactory.CreateProxy. It is then passed into the preprocessing and postprocessing aspects as the first parameter when the aspects are invoked. Here is the interesting point. On the one hand, you enhance the target object by adding extra capabilities to it using aspects. On the other hand, inside the aspects you are able to access the target and use it to enhance your aspects.

In the following code, the target object em is passed as the first parameter of ObjectProxyFactory.CreateProxy. When executing tpem.Salary(), the preprocessing aspect is invoked with the target object passed in as the first parameter, x. Inside the aspect, the runtime type of the target is used.

C#
IEmployee em = new Employee(1, "John", "Smith", new DateTime(1990, 4, 1));
IEmployee tpem = (IEmployee)ObjectProxyFactory.CreateProxy(
    em,
    new String[] { "Salary" },
    new Decoration((x, y) =>
    {
        Console.WriteLine("Entering " + x.GetType().ToString());
    }
        , null),
    null);

tpem.Salary();

When the above code executes, you will see the following results:

Entering ThirdPartyHR.Employee
Salary: 10000.12

As you can see, the preprocessing aspect is able to access the target object and use its runtime type ThirdPartyHR.Employee. If you use the same anonymous method as a preprocessing aspect of another object of a different type, you will get a different runtime type rather than ThirdPartyHR.Employee.

If you move the code of the anonymous method into a named method, you can use it as aspects not only for em but also for other objects, either of the same type or different types, in your application. With the capability of accessing target object from within an aspect, an extremely powerful scenario can be created: you write a name method, use it as aspect for various objects in your application, the aspect then automatically presents runtime types of the target objects.

7. Access Method Call Context Within Aspect

You can access method call context from inside an aspect. The method call context includes method runtime information like method name, argument types and values, etc. The method call context together with the target object gives a complete runtime execution context of an object.

The following code demonstrates how to access the method call context inside a preprocessing aspect by calling CallContext.GetData("Method Context"), which returns an IMethodCallMessage interface. From there, you may retrieve various runtime information of the method including method name, method argument types and values, etc.

C#
IEmployee em = new Employee(1, "John", "Smith", new DateTime(1990, 4, 1));
IEmployee tpem = (IEmployee)ObjectProxyFactory.CreateProxy(
    em,
    new String[] { "Salary", "FullName" },
    new Decoration((x, y) =>
    {
        IMethodCallMessage method = 
	(IMethodCallMessage)CallContext.GetData("Method Context");
        string str = "Entering " + x.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);
    }
        , null),
    null);

tpem.FullName(false);
Console.WriteLine("");
tpem.Salary();

When the above code executes, you will see the following results:

Entering ThirdPartyHR.Employee.FullName(False)
Full Name: John Smith

Entering ThirdPartyHR.Employee.Salary()
Salary: 10000.12

As you can see, the preprocessing aspect is able to access not only the target object, but also which method of the target is being called and what are the values of its arguments. When tpem.FullName(false) executes, the preprocessing aspect gets the target type, ThirdPartyHR.Employee, the name of method, FullName, and the value of method argument, False. When tpem.Salary() executes, the preprocessing aspect gets the target type, ThirdPartyHR.Employee, the name of method, Salary, and the value of method argument, empty for this case. Note that the preprocessing aspect decorates both FullName and Salary methods of the target object. Depending on which method is called, the aspect accesses the actual method call context and outputs them accordingly.

If you move the code for the preprocessing aspect into a named method, you can use it as an aspect of other objects, either of the same type or of different types, in your application. The interesting point here is: The aspect defined by the named method automatically retrieves runtime types and method call contexts of target objects. With capability of accessing both runtime types and method call contexts of target objects, the aspect can do almost anything you can do by modifying source code in terms of adding extra functionality to objects.

8. Add Aspects to .NET Framework Object

The final example demonstrates how to add an aspect to a .NET Framework object. This is an easy and powerful way to enhance the .NET class library, or other third party libraries.

Say, I want to write some log after a transaction is finished, either successfully or unsuccessfully. What I need is to add some logging logic after the transaction's Commit and Rollback methods. In the following code, I pass in an object of ADO.NET's SqlTransaction class to ObjectProxyFactory.CreateProxy, specify the methods Commit and Rollback, and provide a postprocessing aspect. Now, the transaction object has logging capability. The InsertOrder includes two insert SQL statements that are put into one transaction.

C#
string connStr = ConfigurationManager.ConnectionStrings["ConnStr"].ConnectionString;
IDbConnection conn = new SqlConnection(connStr);
IDbTransaction transaction = null;

conn.Open();
transaction = conn.BeginTransaction();

transaction = (IDbTransaction)ObjectProxyFactory.CreateProxy(
    transaction,
    new String[] { "Commit", "Rollback" },
    null,
    new Decoration((x, y) =>
    {
        IMethodCallMessage method = (IMethodCallMessage)CallContext.GetData
		("Method Context");
        Console.WriteLine(x.GetType().ToString() + "." + method.MethodName + " exited.");
    }
        , null));

try
{
    InsertOrder(conn, transaction);

    //Both insert commands succeeded. Commit the transaction.
    transaction.Commit();
}
catch (Exception e)
{
    //Exception occurred during the execution of insert commands. 
    //Rollback the transaction.
    if (transaction != null)
        transaction.Rollback();

    Console.WriteLine(e.Message);
}

When the above code executes, if the transaction finishes successfully, you will see the following results:

System.Data.SqlClient.SqlTransaction.Commit exited.

If the transaction finishes unsuccessfully, you will see the following results:

System.Data.SqlClient.SqlTransaction.Rollback exited.

As you can see, adding aspect to an object of a .NET Framework class is the same as adding aspect to your own objects. Moreover, you can use the same aspect for both .NET Framework class's objects or your own objects. For example, if you move the postprocessing aspect code to a named method, you can use it as an aspect for both the SqlTransaction object and your Employee object.

Ideally, you can design several aspects (crossscutting concerns, e.g., entering logging, exiting logging and security checking, etc.), and share them across objects in your application no matter whether they are objects of your own types, objects of .NET Framework classes, or objects of third party types.

Limit of Dynamic Decorator

One limit of Dynamic Decorator is that it can only be cast to an interface (e.g., IEmployee or IDbTransaction), not a class (e.g., Employee or SqlTransaction). The reason is that the ObjectProxyFactory.CreateProxy returns an instance of transparent proxy class that implements the interface to be cast. It is an error to try to cast an instance of transparent proxy class to another class type like Employee or SqlTransaction.

That means that only interface methods implemented by a class can be decorated with preprocessing aspects or postprocessing aspects. Giving the popularity of interface programming and its advantages over inheritance of base class, this limit is insignificant. After all, there is no appealing reason that a method has to be not an interface method but a class method.

Points of Interest

Using the Dynamic Decorator, you are able to add aspects to your application by providing a method for each of aspects. Existing objects, either of the same type or of different types, then can be decorated at runtime by these aspects. When writing your aspects, keep the following points in mind.

  • Aspects can be enhanced by passing parameters, accessing target object and method call context.
  • An object can have multiple preprocessing aspects and/or multiple postprocessing aspects.
  • Exception handling for aspect is transparent.
  • Adding aspects to objects of the .NET Framework classes or third party classes is the same as adding aspects to objects of your own classes.

History

  • 30th September, 2010: Initial post

License

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