面向服务的RIA应用系统开发中的异常处理

概述:本文将介绍的话题与XML Web Service,WCF,SharePoint,Silverlight开发有关。具体来说,就是在SharePoint平台上,结合Silverlight(客户端技术)和XML Web Service 或者WCF(服务端技术)开发应用系统的一些常见问题,典型问题就是异常处理,以及大数据传输问题。总结起来,算是我个人的一点经验之谈,抛砖引玉,供大家参考参考

 

这一篇,主要谈谈异常处理

 

异常处理

异常处理是重要的,而且相当重要。这是一个基础性设计问题, 我先讲一些通用的概念吧

 

我自己做过,也看过很多系统,在这方面其实都做得不怎么理想,有两个典型表现:

1. 没有做异常处理

2. 没有做合适的异常处理

 

没有做异常处理的情况,一个典型例子如下:

image

这是会吓到用户的,不是吗?看到这一堆红的,很专业的错误消息,用户会做何感想呢?

好吧,你可能不在乎用户的感受,那么看看下面这个吧

image

不错,连接字符串(包括密码等)都一五一十地告诉用户了,我不知道你的老板看到这样的情况,会做如何感想?

 

没有做合适的异常处理的情况,一个典型的例子如下

image

毫无疑问,你确实做了异常处理,你用了try…catch…不是吗?这是一个进步了

但是其实你什么都没有做

image

这个画面自然没有之前那个那么可怕,但是对用户而言,却没有本质上的帮助。

 

那么,一个较为合适的异常处理策略是怎么样的呢?简单而言,我觉得主要就是:

1. 尽量精确地捕获异常,

2. 根据异常类型,提供不同的响应。


像上面这样的例子,如果我们能做类似如下的处理,可能会好不少

image

image

这至少有了很大进步:你处理了异常,而且对异常有区别的对待,然后还提供了针对不同异常不同的响应。这是异常处理中的最基本的原则。

但还有其他的原则。从架构上而言,我们不太推荐直接在客户端里面访问后台的数据库等资源的。上面的例子,页面(aspx)其实是客户端的概念,在极其简单的应用程序中,你确实可以像上面这样访问数据库,并且进行异常处理。

但是,你不觉得这样做其实很复杂吗?每个页面都可以访问数据库,你每个页面里面都需要写很多重复的代码,这些代码很难维护的。

在ASP.NET应用系统中,我们可以利用Global.asax中统一处理Error事件

image

 

并且同时使用CustomError的重定向功能,转到错误页面

image

关于这两个技术的使用,有兴趣的朋友,可以参考 这里 的介绍

 

所以,接下来个原则就是:

3. 尽量不要在客户端中直接地处理异常

4. 你得有一个异常处理的策略,而不是仅仅知道对异常进行处理,而且真的那么辛苦一个一个去处理

 

那么,在多层架构中,到底在哪一层处理异常比较好,并且如何把握处理异常的度呢?说实在话,不是很好掌握,这得靠你琢磨,体会,我是很难告诉你的

是不是鸭梨很大呢?Nerd smile

 

好吧,我给大家推荐一个资源,也是异常处理的一个比较好的框架:微软的企业库(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

 

好了,关于异常处理,我们该做的铺垫,都做了。如果你已经开始意识到异常处理不是那么简单和容易的事情,那么我的目的已经基本达到了。Hot smile

下面要来谈一谈你还不了解的秘密:

在Silverlight中访问WCF或者XML Web Service时的异常处理

我们先来看一下XML Web Service吧,我知道在一些比较有历史的应用系统中,大量使用了XML Web Service。它也确实很容易使用,至少写个Helloworld是这样。我们就来看一个这样的例子吧

image

【备注】这可不是一个好惹的“Helloworld”,它随时可以出错Broken heart。这没有什么好奇怪的,世事难料,谁能知道你后台的服务会不会出错呢?

 

接下来,很多同学已经利用刚才学到的知识,在编写客户端代码的时候,注意了异常处理。

在Silverlight中,你不会用TRY…CATCH, 因为Silverlight都是异步调用服务的,它提供了一个异步模型,如下面这样

image

wow,看起来不错,有这个xxxxxCompleted事件(这是在添加服务引用时自动生成的),一切看起来都是比较舒服的。

代码逻辑很清晰,如果出错,则显示错误消息,否则显示正常结果。但是实际运行起来,情况却有点不同。

我们发现,如果出错的话,会提供这样一个错误消息。NotFound。这是怎么回事?

image

谢天谢地,我们还有IE自带的一个开发工具,可以了解一下到底后台发生了什么事情

image

这里倒确实可以看到服务端返回的错误消息了

image

但是,你不可能要求用户自己去这样看错误消息吧?这未免难度太高了点儿。

而且,问题是为什么服务器明明返回了错误消息,但客户端(准确地说,是Silverlight客户端)却收不到呢?

这里原因就在于Silverlight的特有安全模型,我们都知道,Silverlight是运行在浏览器内部的,它的安全模型有时候也成为沙箱(sandbox),简单地说,它只有一定的权限。他只能做浏览器允许它做的事情。

本例而言,既然服务器返回了500这个状态码,而大家要知道,500这个状态码的意思是Internal Server Error。浏览器会认为说,这个错误与应用程序无关,而不会将详细的信息传递给Silverlight运行时。

那么,有没有办法改变这个行为,毕竟我们最关心的是如何解决这个问题。

说起来也不是很难,我们可以在Silverlight应用程序启动的时候,通知宿主的浏览器,我们需要处理这一类的错误. 通过WebRequest.RegisterPrefix来实现这样的需求。

WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp)

 

再次运行的话,我们看到的错误消息就是下面这样的

image

这个至少能从一定程度上解决我们的问题:我们拿到了真正的异常消息,而不是一个谁都看不懂的NotFound错误

 

准确地说,这是跟Silverlight有关,而与服务端无关,服务端确实返回了消息,只不过客户端解析的机制导致了之前的问题。

这个问题在WCF很类似,但也有些不同的特点。下面是一个例子

image

 

如果不注册WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp)

则异常同样是NotFound

image

但如果注册了,则会遇到下面的情况

image

这是什么意思呢?因为默认情况下,WCF是不会向客户端发送异常的详细消息的。这个可以通过在服务端进行必要的配置来启用

image

启用了这个开关后,再运行之后,就可以看到真正的错误消息了

image

通过如上的讲解,大家应该能清楚地看到在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自带了这样一个模板,你可以很容易生成那个类型

image

理解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中异常处理,以及一些扩展机制

posted @ 2011-10-22 10:09  陈希章  阅读(2065)  评论(2编辑  收藏  举报