WCF RESTful 4.0 Service级别的异常处理(WCF RESTful 4.0 Service level Exception Handling)

众所周知, WCF RESTful 4.0目前没有全局的异常处理方法. 也就是说, 你不能捕获所有的异常.

在ASP.NET里, 你可以在Global.asax.cs的Application_Error事件中捕获并处理所有之前没有被捕获的异常:

http://stackoverflow.com/questions/747011/how-do-i-create-a-global-exception-handler-for-a-wcf-services

   1: public class Global : HttpApplication
   2: {
   3:     protected void Application_Error(object sender, EventArgs e)
   4:     {
   5:         Exception unhandledException = Server.GetLastError();
   6:  
   7:         //处理异常
   8:         ...
   9:     }
  10: }

但在WCF RESTful 4.0中, 这种方法实际上只能捕获一些很特殊的异常, 比如你在Application_BeginRequest添加了自定义代码, 这些代码中出现了异常.

而其他绝大多数情况下, 当你的WCF服务中出现错误时, 这个事件是不会被触发的.

 

要想捕获所有"有用的异常信息", 一个折衷的办法是在每个Service Method中, 用一个try…catch包裹所有整个方法体, 像这样:

   1: [WebGet(UriTemplate = "me/{profileIdOrName}")]
   2: public string GetMyProfile(string profileIdOrName)
   3: {
   4:     try
   5:     {
   6:         //以下是GetMyProfile的方法体
   7:         RequireLogin();
   8:         return GetUserProfile(CurrentUser.Id, profileIdOrName);
   9:     }
  10:     catch (Exception ex)
  11:     {
  12:         //ExceptionHandling.Handle 是异常处理模块
  13:         if(ExceptionHandling.Handle(ex))
  14:             throw;
  15:  
  16:         return null;
  17:     }
  18: }

这样做虽然不能保证捕获所有的异常, 但是至少能保证不会遗漏任何业务逻辑层面的异常. 其他不能捕获的, 大部分是属于配置错误.

如果你能接受这个解决方法, 那你没有必要再接着读下去了.

这篇文章的其余部分, 介绍了一个不用在每个方法中加try…catch, 却能达到相同效果的解决方案.

 

为了达到在每个方法中加try…catch相同的效果(捕获所有除配置等错误之外的异常), 我们可以利用IErrorHandler接口.

首先, 实现一个IErrorHandler:

   1: using System;
   2: using System.ServiceModel.Channels;
   3: using System.ServiceModel.Dispatcher;
   4: using System.ServiceModel.Web;
   5:  
   6: namespace Test
   7: {
   8:     public class GeneralErrorHandler : IErrorHandler
   9:     {
  10:         /// <summary>
  11:         /// 处理异常的方法代理. 
  12:         /// <para>通常情况下这个方法代理应该返回false, 以使其他处理异常的方法代理能继续处理这个异常.</para>
  13:         /// </summary>
  14:         public readonly Func<Exception, bool> FnHandleError;
  15:  
  16:         /// <summary>
  17:         /// 根据异常生成返回到客户端的错误信息的方法代理.
  18:         /// </summary>
  19:         public readonly Func<Exception, object> FnGetFaultDetails;
  20:  
  21:         /// <summary>
  22:         /// 创建一个GeneralErrorHandler新实例.
  23:         /// </summary>
  24:         /// <param name="fnGetFaultDetails">根据异常生成返回到客户端的错误信息的方法代理.</param>
  25:         /// <param name="fnHandleError">
  26:         /// 处理异常的方法代理. 
  27:         /// <para>通常情况下这个方法代理应该返回false, 以使其他处理异常的方法代理能继续处理这个异常.</para>
  28:         /// </param>
  29:         public GeneralErrorHandler(Func<Exception, bool> fnHandleError, Func<Exception, object> fnGetFaultDetails)
  30:         {
  31:             FnHandleError = fnHandleError;
  32:             FnGetFaultDetails = fnGetFaultDetails;
  33:         }
  34:  
  35:         #region IErrorHandler Members
  36:  
  37:         public bool HandleError(Exception error)
  38:         {
  39:             if (FnHandleError == null)
  40:                 return false; //returns false so the other Error Handlers can do sth with the error.
  41:  
  42:             return FnHandleError(error);
  43:         }
  44:  
  45:         public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
  46:         {
  47:             //如果你只是想捕获并记录所有的异常, 并打算向客户端发送所有的错误信息,
  48:             //你可以把以下代码用一句fault = null;代替
  49:  
  50:  
  51:             //if we set fault = null, raw error will be sent to client(http status won't be 200 OK);
  52:             //  otherwise, if you provided fault message, http status will be 200 OK, and client will receive the fault message instead an error.
  53:  
  54:             var isJson = true;
  55:             var context = WebOperationContext.Current;
  56:             if (null != context && null != context.OutgoingResponse && null != context.OutgoingResponse.Format)
  57:                 isJson = context.OutgoingResponse.Format.Value == WebMessageFormat.Json;
  58:  
  59:             var faultDetails = null == FnGetFaultDetails ? null : FnGetFaultDetails(error);
  60:             var bodyWriter = new FaultBodyWriter(faultDetails, isJson);
  61:             fault = Message.CreateMessage(version, string.Empty, bodyWriter);
  62:  
  63:             var bodyFormatProperty = new WebBodyFormatMessageProperty(isJson ? WebContentFormat.Json : WebContentFormat.Xml);
  64:             fault.Properties[WebBodyFormatMessageProperty.Name] = bodyFormatProperty;
  65:  
  66:             if (isJson)
  67:                 context.OutgoingResponse.ContentType = "application/json";
  68:         }
  69:  
  70:         #endregion
  71:     }
  72: }
以上代码59行中有一个FaultBodyWriter, 这是为了向客户端返回JSON格式的错误信息而实现的. 如果不使用它, 返回的错误信息将是XML格式.

 

   1: using System.Runtime.Serialization;
   2: using System.Runtime.Serialization.Json;
   3: using System.ServiceModel.Channels;
   4: using System.Xml;
   5:  
   6: namespace Test
   7: {
   8:     public class FaultBodyWriter : BodyWriter
   9:     {
  10:         private object FaultDetails;
  11:         private XmlObjectSerializer Serializer;
  12:  
  13:         public FaultBodyWriter(object faultDetails, bool isJson)
  14:             : base(true)
  15:         {
  16:             FaultDetails = faultDetails;
  17:  
  18:             var type = faultDetails.GetType();
  19:             if (isJson)
  20:                 Serializer = new DataContractJsonSerializer(type);
  21:             else
  22:                 Serializer = new DataContractSerializer(type);
  23:         }
  24:  
  25:         protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
  26:         {
  27:             Serializer.WriteObject(writer, FaultDetails);
  28:         }
  29:     }
  30: }

 

然后是最关键的一步, 把我们的GeneralErrorHandler"注册"(或者你可以说绑定)到WCF服务中, 我们用实现一个IServiceBehavior接口的ErrorHandlerBehaviorAttribute完成.

   1: using System;
   2: using System.Collections.ObjectModel;
   3: using System.ServiceModel;
   4: using System.ServiceModel.Channels;
   5: using System.ServiceModel.Description;
   6: using System.ServiceModel.Dispatcher;
   7:  
   8: namespace Test
   9: {
  10:     [AttributeUsage(AttributeTargets.Class, Inherited = true)]
  11:     public class ErrorHandlerBehaviorAttribute : Attribute, IServiceBehavior
  12:     {
  13:         public readonly GeneralErrorHandler ErrorHandler;
  14:  
  15:         public ErrorHandlerBehaviorAttribute()
  16:         {
  17:             ErrorHandler = new GeneralErrorHandler(error =>
  18:             {
  19:                 //...在这里记录并处理异常
  20:                 return false; //返回false, 以使其他处理异常的方法代理能继续处理这个异常.
  21:             },
  22:  
  23:             error => new SampleItem //在这里, 我们返回异常的Message. 实际情况下的处理可能会很复杂, 取决于你的需求.
  24:             {
  25:                 Id = 20110509,
  26:                 StringValue = error.Message
  27:             });
  28:         }
  29:  
  30:         #region IServiceBehavior Members
  31:  
  32:         public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase,
  33:             Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
  34:         { }
  35:  
  36:         public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
  37:         {
  38:             //在这里注册我们的GeneralErrorHandler
  39:             foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
  40:                 dispatcher.ErrorHandlers.Add(ErrorHandler);
  41:         }
  42:  
  43:         public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
  44:         { }
  45:  
  46:         #endregion
  47:     }
  48: }

最后, 把它注册到Service上.

   1: using System;
   2: using System.Collections.Generic;
   3: using System.ServiceModel;
   4: using System.ServiceModel.Activation;
   5: using System.ServiceModel.Web;
   6:  
   7: namespace Test
   8: {
   9:     [ServiceContract]
  10:     [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
  11:     [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
  12:     [ErrorHandlerBehavior] //注册ErrorHandler到基类
  13:     public class Service1
  14:     {
  15:         [WebGet(UriTemplate = "")]
  16:         public List<SampleItem> GetCollection()
  17:         {
  18:             throw new Exception("这是一个演示WCF REST 4.0 Exception Handling的例子");
  19:         }
  20:     }
  21: }

 

好了, 一切搞定. 现在你只要在任何新的Service上面添加一个[ErrorHandlerBehavior]属性, 就可以避免在每个方法中都添加try…catch了.

参考: http://tothsolutions.com/Blog/post/WCFSilverlight-Exception-Handling.aspx

示例: WcfRestService1.rar

posted on 2011-05-09 18:08  BFL  阅读(1045)  评论(1)    收藏  举报

导航