面向服务的RIA应用系统开发中的异常处理
概述:本文将介绍的话题与XML Web Service,WCF,SharePoint,Silverlight开发有关。具体来说,就是在SharePoint平台上,结合Silverlight(客户端技术)和XML Web Service 或者WCF(服务端技术)开发应用系统的一些常见问题,典型问题就是异常处理,以及大数据传输问题。总结起来,算是我个人的一点经验之谈,抛砖引玉,供大家参考参考
这一篇,主要谈谈异常处理
异常处理
异常处理是重要的,而且相当重要。这是一个基础性设计问题, 我先讲一些通用的概念吧
我自己做过,也看过很多系统,在这方面其实都做得不怎么理想,有两个典型表现:
1. 没有做异常处理
2. 没有做合适的异常处理
没有做异常处理的情况,一个典型例子如下:
这是会吓到用户的,不是吗?看到这一堆红的,很专业的错误消息,用户会做何感想呢?
好吧,你可能不在乎用户的感受,那么看看下面这个吧
不错,连接字符串(包括密码等)都一五一十地告诉用户了,我不知道你的老板看到这样的情况,会做如何感想?
没有做合适的异常处理的情况,一个典型的例子如下
毫无疑问,你确实做了异常处理,你用了try…catch…不是吗?这是一个进步了
但是其实你什么都没有做
这个画面自然没有之前那个那么可怕,但是对用户而言,却没有本质上的帮助。
那么,一个较为合适的异常处理策略是怎么样的呢?简单而言,我觉得主要就是:
1. 尽量精确地捕获异常,
2. 根据异常类型,提供不同的响应。
像上面这样的例子,如果我们能做类似如下的处理,可能会好不少
这至少有了很大进步:你处理了异常,而且对异常有区别的对待,然后还提供了针对不同异常不同的响应。这是异常处理中的最基本的原则。
但还有其他的原则。从架构上而言,我们不太推荐直接在客户端里面访问后台的数据库等资源的。上面的例子,页面(aspx)其实是客户端的概念,在极其简单的应用程序中,你确实可以像上面这样访问数据库,并且进行异常处理。
但是,你不觉得这样做其实很复杂吗?每个页面都可以访问数据库,你每个页面里面都需要写很多重复的代码,这些代码很难维护的。
在ASP.NET应用系统中,我们可以利用Global.asax中统一处理Error事件
并且同时使用CustomError的重定向功能,转到错误页面
关于这两个技术的使用,有兴趣的朋友,可以参考 这里 的介绍
所以,接下来个原则就是:
3. 尽量不要在客户端中直接地处理异常
4. 你得有一个异常处理的策略,而不是仅仅知道对异常进行处理,而且真的那么辛苦一个一个去处理
那么,在多层架构中,到底在哪一层处理异常比较好,并且如何把握处理异常的度呢?说实在话,不是很好掌握,这得靠你琢磨,体会,我是很难告诉你的
是不是鸭梨很大呢?
好吧,我给大家推荐一个资源,也是异常处理的一个比较好的框架:微软的企业库(Enterprise Library),里面有一个专门做异常处理的模块,相当不错
你可以通过 http://entlib.codeplex.com/ 下载到所有你想要找的资源,例如源代码,范例,动手实验等等
博客园的Terry以前写过这方面的文章可以参考一下,虽然他当时写的时候,版本还比较低
http://www.cnblogs.com/Terrylee/archive/2005/11/14/275659.html
http://www.cnblogs.com/Terrylee/archive/2005/11/16/277557.html
好了,关于异常处理,我们该做的铺垫,都做了。如果你已经开始意识到异常处理不是那么简单和容易的事情,那么我的目的已经基本达到了。
下面要来谈一谈你还不了解的秘密:
在Silverlight中访问WCF或者XML Web Service时的异常处理
我们先来看一下XML Web Service吧,我知道在一些比较有历史的应用系统中,大量使用了XML Web Service。它也确实很容易使用,至少写个Helloworld是这样。我们就来看一个这样的例子吧
【备注】这可不是一个好惹的“Helloworld”,它随时可以出错。这没有什么好奇怪的,世事难料,谁能知道你后台的服务会不会出错呢?
接下来,很多同学已经利用刚才学到的知识,在编写客户端代码的时候,注意了异常处理。
在Silverlight中,你不会用TRY…CATCH, 因为Silverlight都是异步调用服务的,它提供了一个异步模型,如下面这样
wow,看起来不错,有这个xxxxxCompleted事件(这是在添加服务引用时自动生成的),一切看起来都是比较舒服的。
代码逻辑很清晰,如果出错,则显示错误消息,否则显示正常结果。但是实际运行起来,情况却有点不同。
我们发现,如果出错的话,会提供这样一个错误消息。NotFound。这是怎么回事?
谢天谢地,我们还有IE自带的一个开发工具,可以了解一下到底后台发生了什么事情
这里倒确实可以看到服务端返回的错误消息了
但是,你不可能要求用户自己去这样看错误消息吧?这未免难度太高了点儿。
而且,问题是为什么服务器明明返回了错误消息,但客户端(准确地说,是Silverlight客户端)却收不到呢?
这里原因就在于Silverlight的特有安全模型,我们都知道,Silverlight是运行在浏览器内部的,它的安全模型有时候也成为沙箱(sandbox),简单地说,它只有一定的权限。他只能做浏览器允许它做的事情。
本例而言,既然服务器返回了500这个状态码,而大家要知道,500这个状态码的意思是Internal Server Error。浏览器会认为说,这个错误与应用程序无关,而不会将详细的信息传递给Silverlight运行时。
那么,有没有办法改变这个行为,毕竟我们最关心的是如何解决这个问题。
说起来也不是很难,我们可以在Silverlight应用程序启动的时候,通知宿主的浏览器,我们需要处理这一类的错误. 通过WebRequest.RegisterPrefix来实现这样的需求。
WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp)
再次运行的话,我们看到的错误消息就是下面这样的
这个至少能从一定程度上解决我们的问题:我们拿到了真正的异常消息,而不是一个谁都看不懂的NotFound错误
准确地说,这是跟Silverlight有关,而与服务端无关,服务端确实返回了消息,只不过客户端解析的机制导致了之前的问题。
这个问题在WCF很类似,但也有些不同的特点。下面是一个例子
如果不注册WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp)
则异常同样是NotFound
但如果注册了,则会遇到下面的情况
这是什么意思呢?因为默认情况下,WCF是不会向客户端发送异常的详细消息的。这个可以通过在服务端进行必要的配置来启用
启用了这个开关后,再运行之后,就可以看到真正的错误消息了
通过如上的讲解,大家应该能清楚地看到在Silverlight中访问服务时,异常处理的一些特殊性,而且我们也提供了解决方案。
高级部分
实际上,除了在Silverlight中修改http处理的策略(默认是浏览器处理,我们可以修改为应用程序自己处理)之外,我们可能更倾向于在服务端做一些改进。关于这一点,在XML Web Service这个层面上,很难再做改善。所以,我们现在一般在选择架构的时候,都优先考虑WCF作为服务架构,因为它具有更好的扩展性。
我们来想想服务器改善是什么样的一个思路:既然客户端无法显示那个真正的错误消息,是由于服务器返回了500这个状态码的回复,那么能不能修改这个状态码,强制修改为200呢?
在WCF中,你可以做到这一点,通过EndPointBehavior来扩展。下面就是一个例子
// This is an auto-generated file to enable WCF faults to reach Silverlight clients. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Runtime.Serialization; using System.Net; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher; namespace SilverlightApplicationSample.Web { public class SilverlightFaultBehavior : Attribute, IServiceBehavior { private class SilverlightFaultEndpointBehavior : IEndpointBehavior { public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new SilverlightFaultMessageInspector()); } public void Validate(ServiceEndpoint endpoint) { } private class SilverlightFaultMessageInspector : IDispatchMessageInspector { public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { return null; } public void BeforeSendReply(ref Message reply, object correlationState) { if((reply != null) && reply.IsFault) { HttpResponseMessageProperty property = new HttpResponseMessageProperty(); property.StatusCode = HttpStatusCode.OK; reply.Properties[HttpResponseMessageProperty.Name] = property; } } } } public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { foreach(ServiceEndpoint endpoint in serviceDescription.Endpoints) { endpoint.Behaviors.Add(new SilverlightFaultEndpointBehavior()); } } public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } } }
本例中,我们实现了一个特殊的EndPointBehavior,还有一个特殊的ServiceBehavior。
如何使用他们呢?很简单,像下面这样就可以了
using System; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Activation; namespace SilverlightApplicationSample.Web { [ServiceContract(Namespace = "")] [SilverlightFaultBehavior] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] public class HelloSilverlighWCFService { [OperationContract] public void DoWork() { // Add your operation implementation here return; } // Add more operations here and mark them with [OperationContract] } }
这样的话,就能让客户端受到异常消息了。
呵呵,有的朋友已经发现了吧,这个类型不是我编写的,其实Visual Studio自带了这样一个模板,你可以很容易生成那个类型
理解WCF的扩展机制,对于大家用好WCF将极其重要,有兴趣的朋友可以参考
http://msdn.microsoft.com/en-us/library/gg132853.aspx
关于在SharePoint中使用WCF,有很多特殊性,也会遇到一些问题,请参考下面的文章
http://msdn.microsoft.com/en-us/library/ff521581.aspx
总结
本文主要讲解了XML Web Service和WCF中异常处理,以及一些扩展机制