.NET 4 实践 - 使用dynamic 和MEF实现轻量级的 AOP 组件 (1)
AOP魔法
今天你AOP了吗?谈到AOP,总有一种神秘的感觉,人类对于未知的东西一般都会有这种感觉,就像魔术,一旦揭开谜底,顿时豁然开朗。如果你愿意的话,那么就和我一起踏上AOP的揭秘之旅吧!
几年来一直为.NET框架不支持AOP特性耿耿于怀,尽管有许多第三方组件和工具在.NET平台下实现了AOP,而且其中不乏珍品,但或多或少存在着这样那样的限制。直到.NET4的发放,终于让我们有了机会来自己做一个AOP。
熬了几个夜,总算“有心不负功夫人”——DynamicAspect出世了!虽然还是在测试版阶段,但功能一点都不弱,如果你想体验一把的话,可以到这里去下一个玩玩。
不过在玩之前,你最好对AOP有那么一点点的概念,如果你全然不知AOP为何物的话,可以Google或者百度一番后,再回到这里听我摆龙套不迟。
怎么样?拿到DynamicAspect了吗?那么请随我继续前行吧。在下载的源代码包中含有一个Sample, 我们就从这个Sample开始!(强烈建议你打开VS2010,按部就班的输入下面的每一行代码。)
Sample程序实现一个极其简单的ATM功能,也就是模拟一次存款和取款的过程。
首先,在VS2010中创建一个控制台应用程序(Console Application)项目,忘记说了,是C#哦!VB的朋友别跑:),将其命名为BankSample。
在项目中,添加一个新的Bank类,添加代码如下:(不想动手的话,就复制/粘贴吧 ^_^)
class Bank
{
decimal _account;
public void Withdraw(decimal amount)
{
_account -= amount;
}
public void Deposit(decimal amount)
{
_account += amount;
}
public void ShowAccount()
{
Console.WriteLine("Your are account amount is {0: 0.00##}", _account);
}
public decimal Account
{
get { return _account; }
}
}
代码相当的简单,Withdraw从银行取款,Deposit向银行存款,_account字段保存当前银行账户数额。ShowAccount方法显示当前账户信息,Account属性返回当前账户金额。
接下来,在Program的Main方法中编写如下代码:
static void Main(string[] args)
{
Bank bank = new Bank();
Console.WriteLine("========Desposit money from bank account=============");
Console.WriteLine("Please enter deposit amount : ");
decimal amount = 0;
while (true){string s = Console.ReadLine();
if (decimal.TryParse(s, out amount)){bank.Deposit(amount);bank.ShowAccount();break;
}else
{Console.WriteLine("The amount is incorrect, please input again: ");
}}
Console.WriteLine("========Withdraw money from bank account=============");
Console.WriteLine("Please enter withdraw amount : ");
while (true){string s = Console.ReadLine();
if (decimal.TryParse(s, out amount)){bank.Withdraw(amount);bank.ShowAccount();break;
}else
{Console.WriteLine("The amount is incorrect, please input again: ");
}}Console.ReadKey();}
代码主要分为两个部分,上半部分是存款操作,下半部分是取款操作。编译确保代码没有错误,然后运行。
让我们观察一下上面的过程,貌似少了点什么,对,需要做安全检测!也就是说至少在调用bank.Desposit()和Bank.Withdraw()方法之前,我们要求用户输入用户名和密码。如果验证通过则继续,否则抛出安全异常。要实现这一步并不难,创建一个验证类来负责处理用户的安全验证。验证方法可以插入在Main方法的代码中,也可以插入在Bank类中需要验证的方法的代码中。考虑这样的一种情况,如果需要验证的不仅仅是Bank类的方法,在一个复杂的应用中可能有许多方法也需要做验证,那么你需要花费时间在这些代码中去插入调用验证方法的代码。更恐怖的是,如果验证的规则发生变化,比如说某些类可能需要不同的验证方式(通过调用不同的验证代码),那么你需要一一的找到这些地方,然后进行修改。故事终于出现冲突了,那就让我们来解决这个冲突吧。首先添加对DynamicAspect组件的引用,同时也添加对System.ComponentModel.Composition.dll的引用,然后像平常一样创建一个新的类:AuthenticationAspect,让这个类从AspectBase派生,在类中重载OnBeforeMethodCall方法,编写代码如下所示:
public override void OnBeforeMethodCall(WeavingContext context){if (context.InvokeMemberBinder.Name != "ShowAccount"){Console.WriteLine("Please enter user ID: ");
var userid = Console.ReadLine();Console.WriteLine("Please enter password: ");
var password = Console.ReadLine();if (CheckAccount(userid, password) == false)throw new Exception("Invalid user account!");}}
上面代码是自解释的,所以就不再赘言了,至于WeavingContext的参数类型,只当做不存在吧(我们以后再来讨论它)。
下面是CheckAccount的方法和实现:
private bool CheckAccount(string userid, string password)
{
return userid.ToLower() == "user" && password == "p@ssw0rd";
}
出于演示的目的,我们使用硬编码,在实际的应用中可能需要连接到数据库或者从某个地方获取用户的安全信息。
为了让我们的故事能够流畅的进行,注意到在OnBeforeMethodCall方法的代码中当用户不能通过验证时,有一个异常被抛出,所以我们需要新的类来处理异常事件。在项目中新添一个类:ExceptionAspect同样让它从AspectBase派生,不过这次重载的是OnExceptionMethodCall方法,实现方法的代码如下:
public override bool OnExceptionMethodCall(WeavingContext context, Exception ex)
{
Console.WriteLine("{0}: {1}", ex.Message, ex.InnerException.Message);
return true;
}
编译程序,确保代码没有错误,然后运行程序。什么?什么都没有发生!被忽悠了哈。且慢,引用刘谦的一句话,见证奇迹的时候到了。要做的就是对代码施加一种魔法,打开Program.cs文件,对Main方法中的的开始处作如下变化:
static void Main(string[] args)
{
// Bank bank = new Bank();
dynamic bank = new Bank().AsDynamic<Bank>();
如果出现波浪线,请添加相应的using语句。再次运行程序,相信我不说你都看到了。
没有悬念了,如果你还意犹未尽的话,我们在给这个故事一个比较完美的结局,想像一下这样的场合:如果用户输入的金额为负,会发生什么?另外如果取款的时候透支了(假如该银行不支持透支)又将如何?那就是我们需要对输入的金额(也就是方法的参数进行校验)。在项目中在添加一个新的类:ValidatorAspect, 一样从AspectBase派生。重载并实现OnBeforeMethodCall方法如下所示:
public override void OnBeforeMethodCall(WeavingContext context)
{
var methodName = context.InvokeMemberBinder.Name;
if (methodName == "Withdraw" || methodName == "Deposit")
{
var amount = (decimal)context.ArgumentValues[0];
if (amount <= 0)
throw new Exception("Amount should be greater than 0");
if (methodName == "Withdraw" && context.Target is Bank)
{
var bank = (Bank)context.Target;
if (amount > bank.Account)
throw new Exception("Amount is greater than current account.");
}
}
}
由于我们已经对代码施加过魔法了,那就编译并运行来验证不同的参数的情况。
从下一篇开始,我们将详细讲述DynamicAspect的实现原理以及一些高级应用案例。
(未完待续)