Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Build an AOP.NET Extensible Business Component using ContextBoundModel

0.00/5 (No votes)
7 Jun 2004 1  
Aspect-Oriented Programming and the DotNet Implementation - ContextBoundModel

Introduction

Aspect Oriented Programming is an interception technology that enables some code to run before/after a method call or just instead of it . Based on this useful ability , you can do many things that help the method run better , or you just want to listen to the method as an Event does . Off course , you can make a plug-in for the method , that will extend the target class's function ! ContextBoundModel (CBM) is a DotNet implementation for AOP . This Article shows the codes that let you know how easy and clear CBM is.

The Basics

Now , maybe you know , you can intercept a method call of a AOP object , but what's the syntax of the code ?

ContextBoundModel define an interface IMessageHandlerBase , if you want to intercept a method , you should implement this interface :

using System.Runtime.Remoting.Messaging;

public interface IMessageHandlerBase
{
 IMethodReturnMessage ProcessMessage(IMethodCallMessage mcm
  ,AspectObjectProxy proxy
  ,MessageHandlerQueue queue);
}
The interface IMessageHandlerBase define only one method - ProcessMessage . The parameter IMethodCallMessage contains the information about the method-call : method name and arguments . The parameter AspectObjectProxy is a RealProxy that manage the target AOP object . You can use proxy.Items to store your custom data for the target AOP object . The parameter MessageHandlerQueue contains the next IMessageHandlerBase . You could use queue.InvokeNext(mcm,proxy) to call the real method and get an IMethodReturnMessage that contains the information about the return value , or the Exception .

Part 1 - AspetObject and self interception

The basic of implement an AOP object is to inherits AspectObject , which is defined in namespace Lostinet.ContextBoundModel . The CBM framework will build a MessageHandlerQueue for each method . So an AOP object in CBM should be :

using Lostinet.ContextBoundModel;

public class MyDataAccess : AspectObject

An AOP class can be a self interception class while it implement IAspectServerMessageHandler :

public class MyDataAccess : AspectObject , IAspectServerMessageHandler
{
 IMethodReturnMessage IMessageHandlerBase.ProcessMessage(
  IMethodCallMessage mcm
  ,AspectObjectProxy proxy
  ,MessageHandlerQueue queue)
 {
  ....
 }
}

This means , the ProcessMessage will be called when other method of the class being invoke at 'Outside' . The sample code of MyDataAccess is :

using System;
using System.Runtime.Remoting.Messaging;

//using namespaces in Lostinet.ContextBoundModel.dll

using Lostinet.ContextBoundModel;
using Lostinet.ContextBoundModel.Configuration;

public class MyDataAccess : AspectObject , IAspectServerMessageHandler
{
 private string theconnection;

 //implementation for IAspectServerMessageHandler

 //this method will invoke before other member invoked!

 IMethodReturnMessage IMessageHandlerBase.ProcessMessage(
  IMethodCallMessage mcm
  , AspectObjectProxy proxy, MessageHandlerQueue queue)
 {
  if(theconnection!=null)
   return queue.InvokeNext(mcm,proxy);
  Console.WriteLine(" open connection ");
  theconnection="newconnection";//open a database connection :-)

  //do the default action - Invoke the method .

  IMethodReturnMessage retmsg=queue.InvokeNext(mcm,proxy);
  theconnection=null;//release the connection

  Console.WriteLine(" close connection ");
  return retmsg;
 }

 public int InsertAccount(string username,string password)
 {
  Console.WriteLine("the connection is :"+theconnection);
  Console.WriteLine("insert account:"+username+","+password);

  return 101;//return new identity

 }
} 

The MyDataAccess class provide a method named 'InsertAccount' . Usually this method open a connection , do the SQL query , and close the connection . But now , the ProcessMessage will be called instead of the method InsertAccount . The ProcessMessage opens a connection , use 'queue.InvokeNext(mcm,proxy);' to invoke the real code of InsertAccount and get an IMethodReturnMessage and then close the connection and return the result .

Here I show the code that calls the InsertAccount :

public class TestMyDataAccess
{
 static public void Run()
 {
  MyDataAccess da=new MyDataAccess();

  da.InsertAccount("myusername","mypassword");
 }
}

The call graph of the test code is :

TestMyDataClass.Run
  -->Access.InsertAccount (intercepted call)
    -->[The CBM interception helper code]
      -->MyDataAccess.IMessageHandlerBase.ProcessMessage
        -->MessageHandlerQueue.InvokeNext
          -->[The CBM interception helper code]
            -->MyDataAccess.InsertAccount (real call)

Part 2 - IMessageHandler

An AOP object can be intercepted for other IMessageHandler . so If you have designed a class that implement IMessageHandler , how to make the class intercept the AOP object ? CBM provides 4 ways to do this thing .

In the second sample class , the code use the runtime Configuration method to register an IMessageHandler type for an AOP type .

Here is an AOP class - MyBizObject , and an IMessageHandler implementation - CreateAccountSendMailHandler . In the TestMyBizObject.Run , the code uses AspectConfigurationSettings.MessageHandlers.Add to register CreateAccountSendMailHandler for MyBizObject .

public class MyBizObject : AspectObject
{
 MyDataAccess myda=new MyDataAccess();

 public int CreateAccount(string username,string password)
 {
  if(username==null)throw(new ArgumentNullException("username"));
  if(password==null)throw(new ArgumentNullException("password"));

  if(string.Compare("admin",username,true)==0)
   throw(new Exception("failed to create account"));

  //INSERT ACCOUNT TO DATABASE HERE

  Console.WriteLine("Creating Account :"+username);

  return myda.InsertAccount(username,password);
 }
}

public class CreateAccountSendMailHandler : IMessageHandler
{
 public string HandlerName
 {
  get
  {
   return "SendMailAfterCreateAccount";
  }
 }
 public IMethodReturnMessage ProcessMessage(IMethodCallMessage mcm, 
  AspectObjectProxy proxy
  , MessageHandlerQueue queue)
 {
  Console.WriteLine("Amazing!! I know "+mcm.MethodName+" is calling !");

  //do the default action - Invoke the method .

  IMethodReturnMessage retmsg=queue.InvokeNext(mcm,proxy);

  //if the method failed

  if(retmsg.Exception!=null)
   return retmsg;

  if(mcm.MethodName=="CreateAccount")
  {
   string username=mcm.Args[0].ToString();
   string password=mcm.Args[1].ToString();
   int userid=(int)retmsg.ReturnValue;

   //Send an email to the admin : userid/username/password

   Console.WriteLine("Hi,Admin , account created : "
    +userid+","+username+":"+password);
  }

  return retmsg;
 }
}

public class TestMyBizObject
{
 static public void Run()
 {
  //register CreateAccountSendMailHandler for MyBizObject

  //this could be configed in app/web.config file

  //or use AspectConfigurationSettings.Config to use a xml file

  AspectConfigurationSettings.MessageHandlers.Add(
   "SendMailAfterCreateAccount"
   ,typeof(MyBizObject)
   ,typeof(CreateAccountSendMailHandler)
   ,true,true,true
   );

  MyBizObject mybizobj=new MyBizObject();

  mybizobj.CreateAccount("lostinet","password");
 }
}

The call graph of the test code is :

TestMyBizObject.Run
  --  -->MyBizObject.CreateAccount (intercepted call)
    -->[The CBM interception helper code]
      -->CreateAccountSendMailHandler.ProcessModel
        -->MessageHandlerQueue.InvokeNext
          -->[The CBM interception helper code]
            -->MyBizObject.CreateAccount (real call)

The magic thing is that , the MyBizObject does not know CreateAccountSendMailHandler , but the CreateAccountSendMailHandler.ProcessMessage will be invoked while mybizobj.CreateAccount("lostinet","password") is called!!!

Part 3 - IMessageHandlerAttribute

The Attribute is a kind of Metadata . You can think it as a configuration that attach on a class or method . If you do not know what an attribute is , please see : MSDN-Extending Metadata Using Attributes.

ContextBoundModel uses Attribute to configure IMessageHandler for a class or method . It is the fastest way to reuse an IMessageHandler ! The code use attribute to reuse an IMessageHandler is just like this :

//define an attribute class that implement IMessageHandlerAttribute

public class TheAttributeThatAttachYourMessageHandlerAttribute
 : Attribute , IMessageHandlerAttribute
{
}
public class YouCanAttachToMethodToottribute
 : Attribute , IMessageHandlerAttribute
{
}

//and then , you can reuse it as :


[TheAttributeThatAttachYourMessageHandler]
public class AnAopObject : AspectObject
{
 [YouCanAttachToMethodToo]
 public void MethodThatCanBeIntercepted(){}
}

Here is the third simple class :

[AttributeUsage(AttributeTargets.Method|AttributeTargets.Parameter)]
public class CheckArgumentsNotNullAttribute : Attribute
 ,IMessageHandlerAttribute
 ,IMessageHandler
{
 public string HandlerName
 {
  get
  {
   return "CheckArgumentsNotNull";
  }
 }

 //implement IMessageHandlerAttribute

 public IMessageHandler CreateMessageHandler(bool bserver)
 {
  //return this as IMessageHandler

  //of cause you can create an new Handler and return it.

  return this;
 }

 //implement IMessageHandler

 public IMethodReturnMessage ProcessMessage(IMethodCallMessage mcm
  , AspectObjectProxy proxy, MessageHandlerQueue queue)
 {
  //check the arguments !

  object[] inargs=mcm.InArgs;
  for(int i=0;i < inargs.Length;i++)
  {
   //if the argument is null , throw an exception !

   if(inargs[i]==null)
    throw(new ArgumentNullException(mcm.GetInArgName(i)));
  }

  //do the default action - invoke target method.

  return queue.InvokeNext(mcm,proxy);
 }
}

public class AccountService : AspectObject
{
 MyBizObject mybo=new MyBizObject();

 [CheckArgumentsNotNull]
 public void CreateAccount(string username,string password)
 {
  mybo.CreateAccount(username,password);
 }
}

public class TestAccountService
{
 static public void Run()
 {
  AccountService service=new AccountService();
   
  try
  {
   string password=null;
   service.CreateAccount("yourname",password);
  }
  catch(Exception x)
  {
   Console.WriteLine(x.GetType().FullName);
  }
 }
}

Please look the method AccountService.CreateAccount , it's marked the attribute [CheckArgumentsNotNull] . When the AccountService.CreateAccount be called , CheckArgumentsNotNullAttribute.CreateMessageHandler will be invoked to return an IMessageHandler to process the message , the CheckArgumentsNotNullAttribute.ProcessMessage will be invoked !!

TestAccountService.Run
  -->AccountService.CreateAccount (intercepted call)
    -->[The CBM interception helper code]    
         //here  the CreateMessageHandler  being invoked
      -->CreateAccountSendMailHandler.ProcessModel
        -->CheckArgumentsNotNullAttribute.InvokeNext
          -->[The CBM interception helper code]
            -->AccountService.CreateAccount (real call)

Part 4 - Test the code at Console Application

class EntryPoint
{
 [STAThread] static void Main()
 {
  TestMyDataAccess.Run();
  Console.WriteLine(" -------- ");
  TestMyBizObject.Run();
  Console.WriteLine(" -------- ");
  TestAccountService.Run();
 }
}

This Application Output:

open connection
the connection is :newconnection
insert account:myusername,mypassword
close connection
--------
Amazing!! I know .ctor is calling !
Amazing!! I know CreateAccount is calling !
Creating Account :lostinet
open connection
the connection is :newconnection
insert account:lostinet,password
close connection
Hi,Admin , account created : 101,lostinet:password
--------
Amazing!! I know .ctor is calling !
System.ArgumentNullException
Press any key to continue

More Ideas?

You can build your own IMessageHandler/IMessageHandlerAttribute pair and reuse them just like this :

[AutoStartSqlConnection]
[AutoStartSqlTransactionAndCommitIfNoException]
[LogIntoDataBase]
[WriteWindowsEventLogWhileException]
[EmailAdministratorsWhileException]
public void Login([NotNull] string username,[NotNull] string password)

{
   ........
   thow(new Exception("wrong password"));
}

You can download the sample code via the link in front of this article . The readme.txt shows a link that provides 20 more samples codes !

Resources

Articles

.NET Implementations

Java Implementations :

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here