65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 ContextBoundModel 构建 AOP.NET 可扩展业务组件

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.14/5 (7投票s)

2004年6月8日

4分钟阅读

viewsIcon

78052

downloadIcon

962

面向方面编程和 DotNet 实现 - ContextBoundModel

引言

面向方面编程 是一种拦截技术,它可以在方法调用之前/之后运行某些代码,或者直接替换方法调用。基于这种有用的能力,您可以做很多事情来帮助方法更好地运行,或者您只想像事件一样监听方法。当然,您可以为方法创建一个插件,它将扩展目标类的功能!ContextBoundModel (CBM) 是 AOP 的 DotNet 实现。本文展示的代码将让您了解 CBM 的简单和清晰。

基础知识

现在,您可能知道,可以拦截 AOP 对象的某个方法调用,但代码的语法是什么?

ContextBoundModel 定义了一个接口 IMessageHandlerBase,如果您想拦截一个方法,应该实现这个接口。

using System.Runtime.Remoting.Messaging;

public interface IMessageHandlerBase
{
 IMethodReturnMessage ProcessMessage(IMethodCallMessage mcm
  ,AspectObjectProxy proxy
  ,MessageHandlerQueue queue);
}
接口 IMessageHandlerBase 只定义了一个方法 - ProcessMessage。参数 IMethodCallMessage 包含有关方法调用的信息:方法名称和参数。参数 AspectObjectProxy 是一个管理目标 AOP 对象的RealProxy。您可以使用 proxy.Items 来存储目标 AOP 对象的自定义数据。参数 MessageHandlerQueue 包含下一个 IMessageHandlerBase。您可以使用 queue.InvokeNext(mcm,proxy) 来调用实际方法并获取一个 IMethodReturnMessage,其中包含有关返回值或异常的信息。

第一部分 - AspectObject 和自拦截

实现 AOP 对象的基础是继承 AspectObject,它定义在 Lostinet.ContextBoundModel 命名空间中。CBM 框架将为每个方法构建一个 MessageHandlerQueue。所以 CBM 中的 AOP 对象应该是

using Lostinet.ContextBoundModel;

public class MyDataAccess : AspectObject

AOP 类可以通过实现 IAspectServerMessageHandler 来成为自拦截类。

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

这意味着,当该类的其他方法在“外部”被调用时,将调用 ProcessMessage。MyDataAccess 的示例代码是

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
 }
} 

MyDataAccess 类提供了一个名为 'InsertAccount' 的方法。通常,此方法会打开连接,执行 SQL 查询,然后关闭连接。但现在,将调用 ProcessMessage 而不是 InsertAccount 方法。ProcessMessage 打开一个连接,使用 'queue.InvokeNext(mcm,proxy);' 调用 InsertAccount 的实际代码并获取 IMethodReturnMessage,然后关闭连接并返回结果。

这里我展示了调用 InsertAccount 的代码。

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

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

测试代码的调用图是

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)

第二部分 - IMessageHandler

AOP 对象可以被其他 IMessageHandler 拦截。所以,如果您设计了一个实现 IMessageHandler 的类,如何让该类拦截 AOP 对象?CBM 提供了 4 种方法来实现这一点。

在第二个示例类中,代码使用运行时配置方法为 AOP 类型注册一个 IMessageHandler 类型。

这是一个 AOP 类 - MyBizObject,以及一个 IMessageHandler 实现 - CreateAccountSendMailHandler。在 TestMyBizObject.Run 中,代码使用 AspectConfigurationSettings.MessageHandlers.AddMyBizObject 注册 CreateAccountSendMailHandler

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");
 }
}

测试代码的调用图是

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

神奇之处在于,MyBizObject 并不知道 CreateAccountSendMailHandler,但当调用 mybizobj.CreateAccount("lostinet","password") 时,CreateAccountSendMailHandler.ProcessMessage 将被调用!!!

第三部分 - IMessageHandlerAttribute

Attribute 是一种元数据。您可以将其视为附加到类或方法的配置。如果您不知道什么是 attribute,请参阅:MSDN - 使用 Attributes 扩展元数据

ContextBoundModel 使用 Attribute 为类或方法配置 IMessageHandler这是重用 IMessageHandler 最快的方式!使用 attribute 重用 IMessageHandler 的代码如下:

//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(){}
}

这是第三个简单的类。

[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);
  }
 }
}

请看 AccountService.CreateAccount 方法,它被标记了 [CheckArgumentsNotNull] attribute。当调用 AccountService.CreateAccount 时,将调用 CheckArgumentsNotNullAttribute.CreateMessageHandler 来返回一个 IMessageHandler 来处理消息,然后调用 CheckArgumentsNotNullAttribute.ProcessMessage!!

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)

第四部分 - 在控制台应用程序中测试代码

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

此应用程序输出

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

更多想法?

您可以构建自己的 IMessageHandler/IMessageHandlerAttribute 对,并像这样重用它们:

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

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

您可以通过本文前面的链接下载示例代码。readme.txt 文件中包含一个提供 20 多个示例代码的链接!

资源

文章

.NET 实现

Java 实现

© . All rights reserved.