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 Lostinet.ContextBoundModel;
using Lostinet.ContextBoundModel.Configuration;
public class MyDataAccess : AspectObject , IAspectServerMessageHandler
{
private string theconnection;
IMethodReturnMessage IMessageHandlerBase.ProcessMessage(
IMethodCallMessage mcm
, AspectObjectProxy proxy, MessageHandlerQueue queue)
{
if(theconnection!=null)
return queue.InvokeNext(mcm,proxy);
Console.WriteLine(" open connection ");
theconnection="newconnection";
IMethodReturnMessage retmsg=queue.InvokeNext(mcm,proxy);
theconnection=null;
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;
}
}
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"));
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 !");
IMethodReturnMessage retmsg=queue.InvokeNext(mcm,proxy);
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;
Console.WriteLine("Hi,Admin , account created : "
+userid+","+username+":"+password);
}
return retmsg;
}
}
public class TestMyBizObject
{
static public void Run()
{
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 :
public class TheAttributeThatAttachYourMessageHandlerAttribute
: Attribute , IMessageHandlerAttribute
{
}
public class YouCanAttachToMethodToottribute
: Attribute , IMessageHandlerAttribute
{
}
[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";
}
}
public IMessageHandler CreateMessageHandler(bool bserver)
{
return this;
}
public IMethodReturnMessage ProcessMessage(IMethodCallMessage mcm
, AspectObjectProxy proxy, MessageHandlerQueue queue)
{
object[] inargs=mcm.InArgs;
for(int i=0;i < inargs.Length;i++)
{
if(inargs[i]==null)
throw(new ArgumentNullException(mcm.GetInArgName(i)));
}
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 :