Build an AOP.NET Extensible Business Component using ContextBoundModel






3.14/5 (7 votes)
Jun 8, 2004
4 min read

77703

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
- CBM Home - http://www.contextboundmodel.net/
Articles
- CodeProject - Aspect Oriented Programming / Aspect Oriented Software Design
- MSDN - Aspect-Oriented Programming Enables Better Code Encapsulation and Reuse
- MSDN - Decouple Components by Injecting Custom Services into Your Object's Interception Chain
.NET Implementations
- LOOM.NET - http://www.rapier-loom.net/
- Spring.Net - http://www.springframework.net/
Java Implementations :
- AspectJ - http://www.aspectj.org/
- Spring - http://www.springframework.org/