【翻译】PostSharp使用原则:第一天-OnExceptionAspect

 
欢迎来到学习PostSharp的第一天,这个系列将带你浏览PostSharp的特性。为了更好的理解这些特性,每个特性将会以实际的项目例子进行讲解。这周我们使用PostSharp来改造一个现有的简单的联系人管理程序,介绍这个这个项目的同时向你介绍一些PostSharp提供给我们的切面类。
 
开始

你需要下载并安装PostSharp以及它的例子程序,这个系列中我们使用PostSharp 2.0.9.3.
 
关于演示程序

演示程序是一个简单的windows forms项目,它将数据全部存储在内存当中。这个程序包含查找,添加,编辑和删除的功能。你可以先运行一下它,试试它的功能。
 
程序的问题

在修改程序代码之前,你可以试着查找一个列表里面没有的名字。这个时候你将会获得一个错误信息对话框。这个异常很隐蔽而且这个异常暴露了数据层的一些敏感的信息。
 
对于调试来说,异常是个不错的东西。因为他们包含了许多有用的信息,这些信息有利于我们追踪错误的代码。然而,异常是一把双刃剑。如果你发布了一个web service或者web应用程序,并且允许异常信息出现在用户的面前。那此时的异常信息就有可能被用来做些恶意的事情了。从另一方面来说,最终用户根本没有必要知道非常详细的异常信息,只要知道这个出错了就行了。解决上面说到的这些问题的最好方法就是捕获真正的异常,对他做一些操作(例如记录日志)然后抛出一个新的一般性的异常出来。
 
修复

上面的异常有两个地方我们可以进行处理,分别是数据层和UI层。但我们更想想在数据层处理它。因为当能不把具体异常抛给其它层的时候我们就尽量不要抛出这个具体的异常。这一点可能在我们的演示程序中看不出来,不过对于真实的开发而言,你在数据层可能还有许多其他的使用者。所以你需要考虑到底哪些数据应该被暴露出来,而哪些不应该。
 
我们通过在GetByName的方法中加上try/catch语句来解决上面提到的搜索异常。

 1 publicIQueryableGetByName(string value)
2 {
3 try
4 {
5 var res = _contactStore.Where(c => c.FirstName.Contains(value)
6 || c.LastName.Contains(value));
7
8 if(res.Count()<1)
9 {
10 ThrowNoResultsException();
11 }
12
13 Thread.Sleep(3000);
14 return res.AsQueryable();
15 }
16 catch(Exception ex)
17 {
18 //Log exception here
19 thrownewException("There was a problem.");
20 }
21 }
22
这样,当我们再次搜索不存在的名字的时候,程序就会弹出一个友好的、不带任何敏感数据的对话框。
 
一个更好的方法

虽然我们解决了我们的问题,不过我们使用了至少七行代码,这些代码看上去使得整个方法变得凌乱而且这些代码不可复用。如果我们需要修复其他方法中同样的问题,我们又要去添加这样的try/catch语句。
既然这是个切面问题,那我们就可以使用Aspect Oriented Programming了. 这个时候PostSharp就可以派上用场了。

OnExceptionAspect

PostSharp中有一个切面类名叫OnExceptionAspect,它是专门设计用来实现异常处理的类。我们新增一个DatabaseExceptionWrapper.cs的文件并在其中添加下列代码:
 1 [Serializable]
2 publicclassDatabaseExceptionWrapper:OnExceptionAspect
3 {
4 publicoverridevoidOnException(MethodExecutionArgs args)
5 {
6 string msg =string.Format("{0} had an error @ {1}: {2}\n{3}",
7 args.Method.Name,DateTime.Now,
8 args.Exception.Message, args.Exception.StackTrace);
9
10 Trace.WriteLine(msg);
11
12 thrownewException("There was a problem");
13 }
14 }
使用OnExceptionAspect,我们只要简单的继承它就行了。OnExceptionAspect暴露了一些虚方法给我们,我们可以使用这些虚方法来获得进入切点的时机。在这个例子中,我们想在异常发生时处理这些异常,那么我们重写OnException方法。当目标方法中一个未处理的异常发生时,OnException方法就会被调用。实际上PostSharp是将这些指定的目标方法包在try / catch中来捕获异常的。
在OnException内部,我们建立了msg对象来保存我们需要记录的异常信息。OnException给我们提供了一个MethodExecutionArgs参数,在这个参数中包含了当前方法和抛出的异常的相关信息。 args.Exception 保存了目标方法抛出的异常,我们可以从这里获得具体的异常信息。在后续的介绍中,我们会介绍更多的关于MethodExecutionArgs 的信息。
应用切面

现在我们有了这个切面,下面我们就来使用它。在如何选择切入点上Postsharp做的很灵活。今天我们介绍最直接的方式,即在目标方法上使用attribute的方式在声明这个切入点。打开InMemoryDataStore.cs文件并找到GetByName方法,移除我们先前加入的try / catch代码,将DatabaseExceptionWrapper标签加到方法上。
 1 [DatabaseExceptionWrapper]
2 publicIQueryableGetByName(string value)
3 {
4 var res = _contactStore.Where(c => c.FirstName.Contains(value)
5 || c.LastName.Contains(value));
6
7 if(res.Count()<1)
8 {
9 ThrowNoResultsException();
10 }
11
12 Thread.Sleep(3000);
13 return res.AsQueryable();
14 }
再次运行程序,你会发现和先前加了try/catch一样的效果。唯一的区别在于现在的代码可以复用而且看起来更简洁了。
PostSharp是一个编译期的处理程序,这意味着PostSharp所做的工作都在msbuild.exe将你的代码变成MSIL之后进行,postSharp通过寻找特定的attribute来知道哪些需要被织入。在本例中,Postsharp寻找到GetByName方法上面的DatabaseExceptionWrapper 属性然后织入代码到其中。

GetExceptionType

OnExceptionAspect类暴露给我们的另一个方法是GetExceptionType。OnExceptionAspect通过在方法中包裹try/catch的方式来捕获异常,但是有时候你可能需要处理特定的异常信息。一个好的例子就是在尝试连接web service的时候发生“连接超时”异常,你希望发生这个异常之后可以等待一段时间然后重试连接。在这里,你仅仅想处理“连接超时”异常,而不是所有的异常。这就是Get ExceptionType使用的地方。
实现GetExceptionType除了返回你希望处理的异常外其他没有什么东西了。这个方法会在PostSharp编译的时候决定try/catch建立的方式。在我们的切面类中加入如下代码:
1 publicoverrideTypeGetExceptionType(System.Reflection.MethodBase targetMethod)
2 {
3 returntypeof(InvalidOperationException);
4 }
我们告诉PostSharp在编译的时候只要捕获InvalidOperationException 异常就行了,如果其他异常发生了,那么我们的切面就管不了了。为了增加灵活性,你可以在切门类声明一个ExceptionType 的属性,这样就可以在切入点的时候指定你想要捕获的异常了。而GetExceptionType方法只要简单的返回这个属性就行了。
总结

以前创建一个日志代码会花费你多少时间?现在给一个方法增加一个日志和异常处理现在只有整洁的一行代码。而且不同的切面可以处理不同的情景,这些切面可以通过任何的组合方式并随意的添加到任何方法上,不需要任何额外的开发,这就是PostSharp带给我们的。

 

原文:http://www.sharpcrafters.com/blog/post/Day-1-e28093-OnExceptionAspect.aspx

译者注:有些地方可能并没有完全按照原文翻译,因为那样实在太拗口了。

posted @ 2011-11-28 22:49  qianlifeng  阅读(712)  评论(0编辑  收藏  举报