Tip

Aspect-oriented programming (AOP) and the .NET Framework

There has been a lot of talk about Aspect Oriented Programming (AOP) and how this new programming paradigm is going to revolutionize the way we develop software much like how Object Oriented Programming (OOP) did about 15 years ago. The AOP model allows a developer to implement individual concerns of a system in a loosely coupled fashion and combine them into a whole. This enables one to easily modify the behavior of a system dynamically at runtime and thus better enables a system design to meet growing requirements.

In this article, we will look in-depth at incorporating AOP semantics into components developed using the .NET Framework. There is a plethora of articles written about concepts and elements of AOP, and how AOP frameworks implement these concepts. Hence, this article will not be discussing AOP concepts per se in detail but focus exclusively on how existing capabilities offered by the .NET Framework enable us to develop components that are aspect aware.

The gist of AOP model is to identify concerns such as logging, exception handling, etc. that cross-cut several layers in a system, modularize them into orthogonal units called aspects and inject these aspects at selected execution points in a system dynamically at runtime. One could leverage the interception mechanisms built into the Common Language Runtime (CLR) in conjunction with metadata to accomplish this task without having to resort to third-party AOP frameworks and tools. We will employ a trivial example that uses the most banal and ubiquitous of all cross-cutting concerns, namely, logging, and delve into the details of various .NET programming artifacts that need to be developed in order to inject this aspect at runtime. All examples and code listings are shown in C#.

Working with metadata

When a C# compiler compiles the source code, it creates a PE (portable executable) file which has four main parts, namely, PE header, CLR header, metadata and IL. Metadata is a block of binary data that consists of three categories of tables, namely, definition tables, reference tables and manifest tables. As a compiler compiles the source code, entries are created in the definition tables corresponding to each artifact such as types, methods, fields, properties events defined in the source code.

Anybody who has done any non-trivial development with the .NET Framework is well aware of the usage of attributes. Attributes allow a developer to define information that is emitted by the C# compiler into the metadata. Attributes can be used to annotate any programming artifact for which information resides in a metadata definition table. This information can be queried by the CLR at runtime so as to dynamically alter the manner in which the code executes.

In addition to the CLR-defined attributes such as Serializable, WebMethod, PrincipalPermission etc., developers may create their own custom attributes. A custom attribute is merely an instance of a type which must be derived directly from System.Attribute. We can also specify what target (assembly, class, interface, method, field or property) the attribute applies to. Following is an example of defining a custom attribute and using it to annotate a method.

Our goal is to develop an aspect oriented programming model wherein, by merely annotating a method, field or property in a class with a custom attribute, we force the runtime to trigger an event whenever that method, field or property is called or accessed. The event thus triggered executes the logic that we have isolated as an aspect.

Merely defining an attribute type and using it as illustrated above is useless by itself. All that we accomplish with a custom attribute such as MyAttribute is to make the C# compiler emit corresponding metadata when it compiles the source code for SomeClass.someMethod. It does not have any meaning beyond that. At runtime, the CLR merely ignores this metadata information. In order to affect runtime behavior, we have to force the CLR to check for the presence of MyAttribute and subsequently execute code that injects the desired behavior if the attribute is present. The key to accomplishing this goal is to piggy back on the existing interception mechanisms in the CLR.

More on Aspect-Oriented Programming in the .NET Framework
Aspect-Oriented Software Development -- tutorials, events and a community wiki

Aspect-Oriented Programming Enables Better Code Encapsulation and Reuse -- MSDN article

Decouple Components by Injecting Custom Services into Your Object's Interception Chain -- MSDN article

Context-bound object activation

The central concept in the interception mechanism employed by the CLR is the notion of a context associated with a target object. A context is a set of properties or usage rules that define an environment. One or more target objects may share the same context. The rules defined in a context are enforced by the CLR at runtime when the objects are entering or leaving the context. A context is an object of type System.Runtime.Remoting.Contexts.Context. Objects that reside in a context and are bound to the context rules are called context-bound objects. Objects that are not context-bound are called agile objects. The context associated with the current thread of execution is obtained using the property System.Threading.Thread.CurrentContext. While executing within a method or accessing a field or property of a context-bound object, the CLR will ensure that this thread property always returns a reference to the same context object that the context-bound object was originally bound to thus guaranteeing that the latter executes in the same environment every time. In order to make an object context-bound, its type must be derived from System.ContextBoundObject which in turn derives from System.MarshalByRefObject. An important distinction between a context-bound and an agile object is that a client can never obtain a direct reference to an instance of a context-bound object.

When the new operator is used to instantiate a context-bound object, the CLR intervenes and executes a series of steps which together constitute the object activation phase. During this phase, the CLR generates two proxies, namely, an instance of TransparentProxy and an instance of RealProxy. Both these types are defined in the System.Runtime.Remoting.Proxies namespace.

The TransparentProxy is merely a stand-in for the target object and it is this proxy that a client always gets a reference to whenever it tries to communicate with a context-bound object. The injection of these proxies thus enables the CLR to always intercept any type of access to the context-bound object. In addition to these proxies, the CLR also provides an extensible mechanism for inserting message sinks between the proxies and the target object. A message sink is an object that implements the type System.Runtime.Remoting.Messaging.IMessageSink.

After the object activation phase is completed, the objects that appear in the invocation chain between the client and the target object may be visualized as illustrated in Figure 1 below. The method invocation by the client is delegated by the TransparentProxy to the RealProxy which in turn sends it through a chain of IMessageSink instances before it reaches the target object. These message sinks provide us an entry point in order to inject the desired aspect at runtime.

Where do custom attributes fit in?

During the object activation phase of a context-bound object, the CLR inspects if the target object type has been annotated with a special category of custom attributes which implement the interface System.Runtime.Remoting.Contexts.IContextAttribute.

Custom attribute types that implement the IContextAttribute interface are called context-attributes. The CLR provides an opportunity for each such context-attribute to add zero or more context-properties into the context that the target object will be associated with. It does so by first creating an instance of the context-attribute and invoking the IsContextOK method, passing in the Context associated with the caller.

Each context-attribute votes on whether the calling context is acceptable to the target object. This step determines if the Context associated with the caller is shared or a new Context is created for the target object. Subsequently, the CLR invokes the GetPropertiesForNewContext method on each context-attribute. The IConstructionCallMessage argument represents the construction call request for the target object. It is in this method that the context-properties are added to the context. A context-property is an object that implements the interface System.Runtime.Remoting.Context.IContextProperty.

After the Context associated with the target object has been properly initialized with context-properties, the CLR provides each context-property an opportunity to inject a message sink between the RealProxy and the target object as illustrated in Figure 1 above. Subsequently, when a method is called on the context-bound object, the CLR routes the invocation through the SyncProcessMessage method of the message sink. An object of type System.Runtime.Remoting.Messaging.IMessage is passed into this method which represents the invocation request that originated from the client. Inside this method, one could add code that implements the runtime behavior associated with the corresponding context-attribute. Custom attributes thus facilitate a mechanism with which we can inject aspects at runtime.

Interception with custom sinks

Message sinks are categorized into four logical groups, namely, envoy, client, server and object sinks. A context-property can indicate to the CLR the type(s) of message sink(s) that it wishes to inject into the invocation chain by implementing one or more of the four interfaces, namely, IContributeEnvoySink, IContributeClientContextSink, IContributeServerContextSink, and IContributeObjectSink. These interfaces are defined in the System.Runtime.Remoting.Contexts namespace. The implementation of the GetXXXSink method in these interfaces typically involves creating a message sink that implements the desired aspect and adding to it a reference to the next downstream sink so that they all chained to together.

It was mentioned earlier that ,while executing a method or accessing a field or property of a context-bound object, the thread property System.Threading.Thread.CurrentContext always points to the context object that the context-bound object was originally bound to. In order to make this happen, whenever a client calls a context-bound object, the CLR has to switch the thread context somewhere in the invocation chain between the client and the target. It is important to note that this switch occurs after the envoy sinks and client sinks have intercepted the call and before the server sinks and object sinks get a chance to perform their interception. Also, note that when the CLR is assembling these sinks in the invocation chain, it obtains the envoy, server and object sinks from the context associated with the target object. The client sinks, on the other hand, are obtained from the caller's context. These concepts are illustrated below.

Knowledge of these concepts is essential to understand the execution environment under which an aspect is injected using any or all of these message sinks. For most scenarios, it would suffice to inject server sinks into the invocation chain. A server sink is associated with the type of the target object. Thus, it corresponds to a  advice which affects the runtime behavior of all target objects of the same type. An object sink, on the other hand, is associated with a specific instance of the target object and hence corresponds to a per-instance advice.

Putting it all together

We have covered quite a bit of ground about the innards of the interception mechanism. So, let’s recapitulate the key points before we move on to an example.

  • Instances of ContextBoundObject are always associated with a Context.
  • A Context can have zero or more properties which are instances of IContextProperty.
  • The properties are created and added to the Context by custom attributes that annotate the ContextBoundObject. These custom attributes must be instances of IContextAttribute.
  • A context-property can also perform the role of a message sink factory by implementing one or more of the four interfaces, namely, IContributeEnvoySink, IContributeClientContextSink, IContributeServerContextSink, and IContributeObjectSink. A message sink is an instance of IMessageSink
  • Access to a ContextBoundObject is always intercepted by the CLR using proxies and message sinks. A message sink is inserted into the invocation chain by the CLR during object activation and can inject the desired aspect at runtime.

The relationships between these entities are illustrated with the two UML diagrams below.

And now, a practical example

We will use the same example that has already been bludgeoned to death by numerous articles on AOP, namely, using aspect-oriented programming to log diagnostics of a method call. First, we define our custom attribute type LogAttribute, shown in Listing 1.

This context-attribute ensures that the target object is always instantiated in its own context by returning false from the IsContextOK method. The GetPropertiesForNewContext method of the context-attribute creates and adds a context-property LogProperty to the context via the construction call request passed in to the method. Implementation of the context-property is shown in Listing 2.

The context-property should return true from the IsNewContextOK method. The CLR invokes this method on each context-property after they have all been added to the target context during the object activation phase. It gives each property a chance to inspect whether the context is in a properly initialized state or not. If this method returns false, CLR will raise a System.Runtime.Remoting.RemotingException exception. LogProperty inserts a message sink into the invocation chain by implementing the IContributeServerContextSink interface. To keep things simple in this example, we will not insert any envoy, client or object sinks. The GetServerContextSink method creates an instance of LoggingServerSink which implements the desired behavior. Implementation of the message sink is shown in Listing 3.

Note that a server sink such as LoggingServerSink gets a chance to intercept the construction as well as any method invocations on a target object.

In this example, we are only interested in intercepting a method invocation. The SyncProcessMessage method is where the messages sink injects the desired aspect, namely, that of logging information about the invocation parameters and time taken to execute the target method. The context-bound object Aspects.Target as well as a simple calling client is shown in Listing 4.

Note that the target object type is annotated with the context-attribute LogAttribute. Usage of this attribute triggers the interception of method invocations on the type Target by the server sink LoggingServerSink and would hence generate the logging information. Voila! We have made our .NET component Aspects.Target aspect aware.

We aren't done yet!

There is still a little more ground to cover. In the above example, we applied the LogAttribute custom attribute to the Target class declaration. Because the declaration scope of this attribute was defined as AttributeTargets.Class, this causes the aspect injection to be applied to all the methods in the Aspects.Target class. So, if we wish to alter the runtime behavior of only the add method with an aspect, what do we do? One would guess that the declaration scope of the LogAttribute would merely need to be changed to AttributeTargets.Method and the attribute moved to the top of the add method declaration as shown in the pseudo-code below.

Unfortunately, it is not as straightforward as that. You see, when the CLR inspects the type of a context-bound object during the object activation phase, it checks only for context-attributes that are defined at the class scope. Attributes declared at any other scope are ignored. Thus, if we move the LogAttribute custom attribute to method scope, we would go back to square one where all that we accomplish is to make the C# compiler emit some metadata which is blissfully ignored by the CLR at runtime. The work around for this problem is as follows:

  • We retain the context-attribute at the class scope as this provides us an entry point into the object activation phase and allows us to insert our custom message sink in the invocation chain.
  • The custom message sink, however, does not inject the desired aspect as the latter is specific to the method, field or property that has been invoked or accessed. Hence, it delegates this task to another .NET component. This component could be the custom attribute itself that annotates the method, field or property in question or could be just any plain .NET component.

The modified implementation of the server sink ModifiedLoggingServerSink is shown in Listing 5.

As before, this sink intercepts only method invocations. Using APIs in System.Reflection namespace, it checks whether the method in question has been annotated with a custom attribute type LogMethodAttribute before injecting the aspect. The Log method in the declaring class of this attribute injects the logging aspect that originally resided in the message sink. Note that LogMethodAttribute, shown in Listing 6, has method scope and it is not a context-attribute which is fine as the CLR allows only context-attributes at the class scope to participate in object activation.

And, that's it! To inject the logging aspect into any method invocation, all that we have to do is annotate the method in question with the custom attribute LogMethodAttribute, annotate the method's class with the context-attribute LogAttribute. The modified version of the advised target object, namely, Aspects.Target is shown in Listing 7.

Design Improvements

Before talking about improvements to the AOP model discussed in this article, let's quickly run down some of the key terms in AOP parlance.

  • Join-point is a specific point of execution in the code such as the call of a method, access of a field or property, throwing of an exception etc.
  • Advice is a particular action taken when a join-point is executed. An advice encapsulates the behavior that we want to inject into the code at runtime.
  • Aspect is a concern that cuts across multiple layers in an application. In an AOP framework, it is typically a class that encapsulates one or more advices.
  • Point-cut is an expression language used by AOP frameworks much akin to regular expressions. A point-cut is used to represent a set of join-points specifying when an advice is to be executed.

The sample code could be generalized and improved in several ways. In the example, the message sink ModifiedLoggingServerSink is hard-coded to inject only one advice, namely, the Log method and it considers only one join-point, namely, methods that have been annotated with the LogMethodAttribute custom attribute. In order to have the maximum flexibility, we would like to be able to turn the advice on/off, control which advice is triggered as well as specify the join-points where the injection occurs using one or more of the following:

  • Name of the method, field or property being invoked or accessed
  • Types of parameters passed into a method, and/or return value of a method
  • Type of custom attribute, if any, decorating the method, field or property
  • Declaring type of the method, field or property.

And, we would, of course, want to do all of this without having to recompile the code. This requirement is addressed by loading such information from an external configuration file and making that information available to the message sink so that it can determine which aspect/advice to inject at a given join-point. The details of such an implementation are not presented here. For example, using the configurations syntax in JBoss AOP framework as a reference, this could be done as follows:

The above configuration specifies two separate bindings. The first one causes the advice Log to be injected whenever the execution is at the join-point of calling the method Aspects.Target.add. The aspect that provides this advice is the class Aspects.Attributes.LogMethodAttribute. The second binding triggers the advice SomeMethod to be injected when the execution is at the join-point of accessing any field that has been annotated with the attribute LogMethodAttribute. Here, the aspect is implemented by the class Aspects.SomeClass. The AOP model outlined in this article is not capable of injecting aspects at joint-points of a plain .NET component. We must have a context-attribute annotating the declaring class of the component whose access we want to advise.

Concluding remarks

The article explored the details of utilizing the intrinsic features of the .NET Framework to implement an aspect-oriented programming model. Custom attributes were used in conjunction with the extensible interception mechanism provided by the CLR to implement this model. Even though the capabilities of this AOP model are a far cry from that of full-fledged AOP frameworks, it is nonetheless adequate enough to implement simple aspect injection semantics without having to resort to any third-party tools.

Download the source code for this article

About the author

Dr. Viji Sarathy has worked for several years in the design of object-oriented, distributed software systems. More recently, he is specializing in the design and implementation of software architectures with both .NET Framework and J2EE technologies. He is a MCAD for .NET and Sun Certified Enterprise Architect for J2EE. He is currently a Senior Software Architect at ComFrame Software Corp. Founded in 1997 in Birmingham, Ala., ComFrame delivers a wide range of custom software solutions to its clients. ComFrame has offices in Birmingham and Huntsville, Ala. and Nashville, Tenn.


This was first published in January 2008

There are Comments. Add yours.

 
TIP: Want to include a code block in your comment? Use <pre> or <code> tags around the desired text. Ex: <code>insert code</code>

REGISTER or login:

Forgot Password?
By submitting you agree to receive email from TechTarget and its partners. If you reside outside of the United States, you consent to having your personal data transferred to and processed in the United States. Privacy
Sort by: OldestNewest

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

Disclaimer: Our Tips Exchange is a forum for you to share technical advice and expertise with your peers and to learn from other enterprise IT professionals. TechTarget provides the infrastructure to facilitate this sharing of information. However, we cannot guarantee the accuracy or validity of the material submitted. You agree that your use of the Ask The Expert services and your reliance on any questions, answers, information or other materials received through this Web site is at your own risk.