65.9K
CodeProject is changing. Read more.
Home

Build an AOP.NET Extensible Business Component using ContextBoundModel

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.14/5 (7 votes)

Jun 8, 2004

4 min read

viewsIcon

77703

downloadIcon

962

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 :