Aspect Oriented Programming

Introduction

Microsoft introduced Declarative Programming Style with release of MTS and COM+. MTS and COM+ provided many configurable services. E.g. “Transactional” attributes, Synchronization attributes, etc. The best thing about all these services is that it requires minimum efforts on developer side. All the services are configurable using the attributes. But we could never really add to set of attributes that MTS or COM+ provided. To some extent it helped us to visualize the capabilities of the future application frameworks. 

In this article we are going to look at some features of .Net framework, which enable us to build highly configurable applications. We can build application frameworks, which help the developer to concentrate on developing the core business related functionality. Developers can invoke the services just by configuring/declaring few attributes. Developer can be ignorant of underlying framework plumbing.

But before we go to these features we need to understand concept called as Aspect Oriented Programming. 

What is AOP?

Reusability is not new to us and we all make conscious efforts to achieve high degree of reusability. 

Lets look at the way we use the reusable components. Lets take a scenario where we want to create a trace of an application execution. We want to log the name of each and every method that is executed and some other statistics such as how much time it took to execute the method.

So we write a simple logger class. We may provide features to log the details in a file or database table. We provide some methods to log details such as “BeginLog”, “EndLog”. Our program calls these methods. Each method that we want to execute, calls a method such as “BeginLog”. To take a step further, we may use the magic of reflection to know which method has called the component method. At the end of the method we call the “EndLog” method to end the trace for the same method.

So even after having reusable component, developer needs to do lot of coding. But how about marking the method with just an attribute/aspect such as “LogEnable” and rest of the things are taken care by the underlying components? Well if you like this concept, then Welcome to Aspect Oriented Programming.

Aspect-oriented Programming (AOP) was invented at Xerox PARC in the 1990s.

In Aspect oriented programming, we provide the Attribute/Aspects to the class at various levels such as class/method/ property, etc. We can treat this aspect as metadata about the class. The run time can discover the metadata and provide the services required by the component.

How AOP works?

Looking at COM+ objects, we can think of attributes, such as Transaction, Security, Synchronization attributes, as aspects. The COM+ runtime provides stacks of objects, called as interceptors. These interceptors get in between the client and server and provide services such as Security, Transaction, Synchronization, etc. 

The AOP framework works in the similar way. To design AOP framework we need the ability to 

Create Runtime that binds object and client together 
Describe the Metadata of the object. This metadata will provide the Aspect for the object. 
AOP framework works solely depends on the interception and delegation. AOP framework performs interception and delegation at two levels – Object activation and Method Invocation.

At the time of object activation, run time uses the metadata (Aspects/Attributes) of the object to compose the stack of interceptors. The metadata helps to identify the services requested by the object. The interceptors are responsible to provide requested service. The runtime returns the reference to the interceptor instead of reference of the object, to the client. When client calls methods on the object, the interceptors intercept the call to do some pre processing. All the interceptors are chained to each other. The last interceptor in the chain directs the call to object. When the call is finished, all the interceptors get chance to post-process the method call.

The set of aspects together form the context for the class. The context helps to decide the right environment for the method execution. We will see in more details about contexts later.

AOP allows for better separation of tasks, which are commonly used. 

The AOP approach has a number of benefits. First, it improves performance because the operations are more succinct. Second, it allows programmers to spend less time rewriting the same code. Overall, AOP enables better encapsulation of distinct procedures and promotes future interoperation.

AOP and .Net

One of the reasons why we can build the AOP oriented components in .Net is that it allows us to extend the .Net framework. .Net framework allows us provide the metadata for each component. To provide the custom attribute we need to develop custom attributes.

As you can see, the attribute class itself has some attributes. The AttributeTarget attribute will control the application of the attribute to class/method/property and so on.

      [AttributeUsage(AttributeTargets.Class)]
     
public class ContextAtrbAttribute :Attribute
     
{
           
private string _fileName;
           
public ContextAtrbAttribute(string fileName)
           
{
                 
this._fileName = fileName;
           
}

     
}

Now we can specify the metadata for the component using these attributes.

      [ContextAtrb(@"c:\ContextClass.txt")]
     
public class ContextClass: ContextBoundObject
     
{

} 

Contexts in .Net

Now we have to look at the concept called as Contexts in .Net.

Context is a logical grouping of the objects that have same aspect values. With help of contexts we establish interceptors to intercept the calls on the objects. 

Two objects with different contexts communicate to each other using .Net context architecture. Though we do not want to go into the details of context architecture we need to understand its basics.

Basics of Context architecture

Two objects with different contexts communicate to each other using the messages. These messages are passed thro’ several message sinks.

These message sinks can be custom built. These sinks are chained to each other and these sinks pass the message to next sink till they reach the end of the chain. We can build the custom sinks and introduce them in the chain. This gives us the opportunity to intercept the message.

There are two processes that take place.

Firstly when we create a context bound object, the run time sets up the chain of message sinks. This chain is used when two objects from different contexts communicate to each other. 

Secondly, when two objects in different contexts communicate to each other, the runtime forms proxy object between the caller and callee. The proxy is of two types – Transparent proxy and Real Proxy. The runtime forwards the message to the transparent proxy. This proxy serializes the stack in to message and passes it to the real proxy. The real proxy takes the message and sends it to first message sink in the channel. The first message sink intercepts the call. Message sink can preprocess the message. This message sink then passes the message to the next message sink. This process continues till the last sink i.e. Stack builder sink is reached. This sink de-serializes the message into the stack and calls on the method of the object. After the method call, the same sink again serializes the parameter values and return value into the stack and returns it to the previous sink. Each sink in the chain gets its turn to post process the message. 

The sequence of flow is illustrated in Figure 1.

The messages are of 3 types – Construction messages, Method call message, and Response message.

From the above discussion its very clear that to intercept the calls on the object we need to build our custom sinks and link in the chain of sink at proper position.

Prerequisites

With all this we need to know what all classes we need to create to build our custom interceptors.

We need to use following namespaces – 

using System.Runtime.Remoting.Contexts;
using
System.Runtime.Remoting.Activation;
using
System.Runtime.Remoting.Messaging

First of all to make our class aware of “context” our class should inherit from “ContextBoundObject” .Net runtime will automatically create a separate context for it to live in whenever we create object of that class. .Net runtime establishes the proxies to participate in the calls across the contexts. 

Secondly we need to build the ASPECTS. We need the attributes that we can apply to the class. So we have to create a class that implements IContextAttribute interface. It should also inherit from Attribute framework class. This class will define the attributes or the aspects that we want to apply to the class. 

Next we want to define Context Properties. Based on the context properties, runtime will differentiate between two contexts. To create context properties, we need to implement “IContextProperty” interface and “IContributeObjectSink" interface.

We are only half done with all these classes. We will have classes required to define the contexts.

After this we need to define our own Message Sink. To do that we need to create a class that implements the “IMessageSink” interface. 

Lets take look at all these steps in detail.

Implementation Detail

First we need to derive our class from ContextBoundObjects.

      [ContextAtrb(@"c:\ContextClass.txt")]
     
public class ContextClass: ContextBoundObject
     
{

    }

In the above example, the “ContextAtrb” attribute is the aspect provided for the class. This is called custom attribute. We have seen how to develop this custom attribute.

IContextAttribute interface

For this CustomAttribute to participate in this process of interception it should implement IContextAttribute interface. This interface has two methods 

bool IsContextOK(Context ctx,IConstructionCallMessage msg)

void GetPropertiesForNewContext( IConstructionCallMessage msg);

These two methods are called by the interceptors/proxies for each attribute of the class.

The method IsContextOk provides us with opportunity to decide if the object being created should have the same context or not. The parameter ctx is the context of the caller. If we return the “true” value the object being created will posses the same context as that of the caller. The other parameter “msg” will be the message. This message can be Constructor message only as this method is called only at the time of the construction of the class.

GetPropertiesForNewContext

This method is called only when the IsContextOk method returns false. This method gives us opportunity to create the property for the context. 

These properties can be implemented in a separate class. This class should be derived from two interfaces - IContextProperty, IContributeObjectSink. The context property allows us to link our own message sink in the chain of existing message sink. 

As we discussed earlier, the interception mechanism works in two steps. The first step is to setup the required message sinks. The second step involves the actual interception.

This property class that implement IContributeObjectSink interface helps us to setup the required message sinks.

IContextProperty interface

IContextProperty has three methods.

string Name {get;}

public bool IsNewContextOK(Context ctx)

void Freeze (Context ctx)

Name 

This is a read only property and returns the name of the property. The context class provided by .Net will add this property in its collection using this name. We can access this property using the method GetProperty of the context class.

IsNewContextOK 

If this method returns true, then only the activation of the object will continue. If it returns false, the exception will be raised. This method is called after the context properties are created. Hence it gives the opportunity to check if the properties created are right for new context.

IContributeObjectSink interface

IcontributeObjectSink interface is most important interface. This interface will allow us to hook our own message sinks in the chain of the message sinks. We need to implement one method i.e.

IMessageSink GetObjectSink(MarshalByRefObject o, IMessageSink m_Next)

GetObjectSink

This method creates the message sink and returns it.

IMessageSink interface

Now the only part left is to create the message sink. To do so we need to write a class deriving from interface IMessageSink

This interface has methods 

IMessageSink NextSink

IMessage SyncProcessMessage(IMessage imCall)

IMessageCtrl AsyncProcessMessage(IMessage im, IMessageSink ims)

In addition to all these methods we also have to implement the constructor.

Constructor

The constructor of the class should have a parameter of type IMessageSink. This parameter is the next message sink in the chain. The framework passes the next message sink in the chain to the class. This message sink should be returned in the method NextSink. The class has the freedom to change the message sink and return some other message sink as well. This next message sink will be linked as the next message sink in the chain.

SyncProcess

This method is called for each method call on the object. im parameter represents the messages of type Method call.

The sink gets created when the GetObjectSink method for context property is called. And the methods of this interface are called when the methods on the object are executed.

Implementation for all these interfaces is available in Listing 1.

Applications of AOP

This style of programming has found many applications. To start with the example of logging that we have seen in the beginning can be over simplified with this approach. We have to develop classes such as LogginAttribute, LoggingFileProperty and LogginSink. The LoggingSink class will intercept the call and do the logging. We can mark each class and its methods with the LogginAttribute. At the time of interception, the SyncProcessMessage method will find out the current method being called. It will check if the method has attribute “LoggingAttribute” and accordingly log the method details such as parameters and its values etc. Thus developers will just have to mark the methods with attribute and rest will be taken care by the framework.

The same applies for role-based security, declarative transactions and so on. The list is never ending.

Author – Raviraj Bhalerao

Raviraj is working in IT fields for last 6 years. He has worked on Microsoft platforms and specialized in VB6, ASP, SQL Server. Currently he is working in .Net.

Appendix

Figure 1


Listing 1

using System;
using
System.Runtime.Remoting.Contexts;
using
System.Runtime.Remoting.Activation;
using
System.Runtime.Remoting.Messaging;
using
System.IO;
using
System.Threading;
using
System.Reflection; 

namespace ContextBoundObjects
{
      
/// <summary>
      
/// We want to provide tracing functionality  to all methods in this class.
      
/// </summary>
      
[ContextAtrb(@"c:\ContextClass.txt")] //Mark the class with Aspect.
      
public class ContextClass: ContextBoundObject //Inherit from ContextBoundObject.
      
{
             
private int Id = 100; 
             
public void DoWork()
             
{
                    
try
                    
{
                          
Utils.printInfo(MethodBase.GetCurrentMethod());
                          
Console.WriteLine("Writing context - {0}", MyContextUtil.currentContextProp.Name);
                    
}
                    
catch (Exception exc)
                    
{
                          
Console.WriteLine(exc.ToString());
                    
}
                    
finally
                    
{
                    
}
             
}
             
//To study the interception in case of property
             
public int myId
             
{
                    
get
                    
{
                          
Utils.printInfo(MethodBase.GetCurrentMethod());
                     
       return Id;
                    
}
             
}
      

       /// <summary>
      
/// Define the ASPECT class. This is the custom atrribute.
      
///
</summary>

       [AttributeUsage(AttributeTargets.Class)]
      
public class ContextAtrbAttribute :Attribute,IContextAttribute
      
{
             
private string _fileName;
             
public ContextAtrbAttribute(string fileName)
             
{
                    
Utils.printInfo(MethodBase.GetCurrentMethod());
                    
this._fileName = fileName;
             
}
             
// This method is called at the time of object activation.
             
public bool IsContextOK(Context ctx, IConstructionCallMessage msg)
             {
                    
Utils.printInfo(MethodBase.GetCurrentMethod());
                    
//ctx is the context of the caller. If the context "ContextProp" is already defined
                    
//we do not want to create the context. Else we will create the new context.
                    
if(ctx.GetProperty("ContextProp") == null)
                          
return(false);
                    
return(true);
             
}
             
//This method is called only if IsContextProp returns true. This method is called
             
//at the time of object activation.
             
public void GetPropertiesForNewContext(IConstructionCallMessage msg)
             
{
                    
Utils.printInfo(MethodBase.GetCurrentMethod());
                    
//msg parameter is of type Constructor.
                    
//We create the properties of the context.
                    
msg.ContextProperties.Add(new ContextProp(_fileName));
             
}
      
}
      

       /// <summary>
      
/// This class defines the message sink. Sink is created at the time 
      
/// of object activation.
      
/// </summary>
      
public class mySink:IMessageSink
      
{
             
private IMessageSink              m_Next; 

              /// <summary>
             
/// Runtime passes the ims as the next sink.
             
/// </summary>
             
internal mySink(IMessageSink ims)
             
{
                    
Utils.printInfo(MethodBase.GetCurrentMethod());
                    
m_Next                            = ims;
             

             
/// <summary>
             
/// Returns the next sink in the chain.
             
/// </summary>
             
public IMessageSink NextSink 
             
{
                    
get 
                    

                          
Utils.printInfo(MethodBase.GetCurrentMethod());
                          
//Here you can add new message sinks to the chain.
                          
return m_Next; 
                    
}
             
}
             
/// <summary>
             
/// Important method for interception. Every method/ property that is called
             
/// on the object of "ContextClass" class, sink intercepts the method.The call
             
/// is passed as parameter in the form of imCall.
             
/// </summary>
             
public IMessage SyncProcessMessage(IMessage imCall)
             
{
                    
Utils.printInfo(MethodBase.GetCurrentMethod());
                    
IMessage retMsg; 

                     // Perform pre processing on the message                    

                     //Trace the calls on the object
                    
string[] keyArray = new string[imCall.Properties.Count];
                    
string[] valueArray = new string[imCall.Properties.Count];
                     
                    
ContextProp objContextProp;
                    
objContextProp = MyContextUtil.currentContextProp;                     objContextProp.LogMessage(imCall.Properties["__MethodName"].ToString()); 

                     retMsg = m_Next.SyncProcessMessage(imCall); //Calls actual method - DoWork. imCall contains info about the method.
                     // Perform post processing on the message
                    
return retMsg;
             

              public IMessageCtrl AsyncProcessMessage(IMessage im, IMessageSink ims)
             
{
                    
Utils.printInfo(MethodBase.GetCurrentMethod());
                    
return m_Next.AsyncProcessMessage(im, ims);
             

      
}

       public class ContextProp:IContextProperty, IContributeObjectSink
      
{
             
private string _logFileName; 
             
internal static string PropName
             
{
                    
get
                    
{
                          
Utils.printInfo(MethodBase.GetCurrentMethod());
                          
return("ContextProp");
                    
}
             
}
             
public ContextProp(string logFileName)
             
{
                    
Utils.printInfo(MethodBase.GetCurrentMethod());
                    
_logFileName = logFileName;
             
}
             
/// <summary>
             
/// This property is used to get the context property name.
             
///
</summary>

              public string Name
             
{
                    
get
                    
{
                          
Utils.printInfo(MethodBase.GetCurrentMethod());
                          
return(PropName);
                    
}
             
}
             
public void Freeze (Context ctx)
             
{
                    
Utils.printInfo(MethodBase.GetCurrentMethod());
             
}

              /// <summary>
             
/// This method gives us opportunity to check if the new context being created
             
/// if right one.
             
/// </summary>
             
public bool IsNewContextOK(Context ctx)
             
{
                    
Utils.printInfo(MethodBase.GetCurrentMethod());
                    
//If you return False, then runtime will raise the System.Runtime.Remoting.RemotingException to the client.
                    
return(true);
             
}
             
//Custom interface of the property.
             
public void LogMessage(string msg)
             
{
                    
Utils.printInfo(MethodBase.GetCurrentMethod());
                    
StreamWriter logFile;
                    
logFile = File.AppendText(_logFileName);
                    
logFile.WriteLine(msg);
                    
logFile.Flush();
                    
logFile.Close();
             
}

              /// <summary>
             
/// This method allows to create the message sinnk relevant to the context properties.
             
/// Different context properties can return the different message sinks. This way we 
             
/// can build the stack of the relevant message sinks.
             
///
</summary>
 

              public IMessageSink GetObjectSink(MarshalByRefObject o, IMessageSink m_Next)
             
{
                    
Utils.printInfo(MethodBase.GetCurrentMethod());
                    
//Here we create the message sink and return.
                    
return new mySink(m_Next);
             
}
      

/// <summary>
///
This class is used to get the current context of the thread.
///
</summary>

       public class MyContextUtil
      
{
             
public static ContextProp currentContextProp
             
{
                    
get
                    
{
                          
return Thread.CurrentContext.GetProperty(ContextProp.PropName) as ContextProp;
                    
}
             
}
      
}

       /// <summary>
      
/// This class provides the static method. This method is used to trace the sequence
      
/// of events in the interception process.
      
///
</summary>

       public class Utils
      
{
             
public static void printInfo(MethodBase mBase)
             
{

                     Console.WriteLine("Class - {2} Method - {0} type - {1}",mBase.Name,mBase.MemberType.ToString(),mBase.ReflectedType.ToString());

              }
      
}
}
 

原文地址: http://www.c-sharpcorner.com/Code/2002/Nov/aop.asp
posted @ 2004-04-04 22:51  dudu  阅读(5102)  评论(1编辑  收藏  举报