利用Attribute和IErrorHandler处理WCF全局异常
在处理WCF异常的时候,有大概几种方式:
第一种是在配置文件中,将includeExceptionDetailInFaults设置为true
<behavior name="serviceDebuBehavior"><serviceDebug includeExceptionDetailInFaults="true" /></behavior>
但是这种方式会导致敏感信息泄漏的危险,一般我们仅仅在调试的时候才开启该属性,如果已经发布,为了安全,我们一般会设置成false。
第二种方法是自定义错误,通过FaultException直接指定错误信息。
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class CalculatorService : ICalculator
{
throw new FaultException("被除数y不能为零!");
}
但这样我们还必须为WCF的方法里显式的去抛出异常,如果能像.NET一样,在Global里直接捕获全局的异常,并写入log4net日志多好,有没有方法在wcf里如果出现异常,异常日志写入服务器断,同时抛给客户端呢?
要实现这个,需要三步
第一步: 我们需要实现IErrorHandler接口,实现他的两个方法
bool HandleError(Exception error); void ProvideFault(Exception error, MessageVersion version, ref Message fault);
其中HandleError是true表示终止当前session
在ProvideFault方法里我们可以写我们要抛给客户端的自定义的错误,同时也可以在服务器端写异常日志。
我们实现一个GlobalExceptionHandler ,继承自IErrorHandler,同时在ProvideFault里通过log4net写服务器端日志,如下:
namespace WcfService
{ using System; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Dispatcher;
/// <summary> /// GlobalExceptionHandler /// </summary> public class GlobalExceptionHandler : IErrorHandler { /// <summary> /// 测试log4net /// </summary> private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(GlobalExceptionHandler));
#region IErrorHandler Members /// <summary> /// HandleError /// </summary> /// <param name="ex">ex</param> /// <returns>true</returns> public bool HandleError(Exception ex) { return true; }
/// <summary> /// ProvideFault /// </summary> /// <param name="ex">ex</param> /// <param name="version">version</param> /// <param name="msg">msg</param> public void ProvideFault(Exception ex, MessageVersion version, ref Message msg) { //// 写入log4net log.Error("WCF异常", ex); var newEx = new FaultException(string.Format("WCF接口出错 {0}", ex.TargetSite.Name)); MessageFault msgFault = newEx.CreateMessageFault(); msg = Message.CreateMessage(version, msgFault, newEx.Action); } #endregion } }
第二步:现在我们需要创建一个自定义的Service Behaviour Attribute,让WCF知道当WCF任何异常发生的时候,我们通过这个自定义的Attribute来处理。实现这个需要继承IServiceBehavior接口,并在此类的构造函数里,我们获取到错误类型。
一旦ApplyDispatchBehavior行为被调用时,我们通过Activator.CreateInstance创建错误handler,并把这个错误添加到每个channelDispatcher中。
ApplyDispatchBehavior
channelDispatcher中文叫信道分发器,当我们的ServiceHost调用Open方法,WCF就会创建我们的多个信道分发器(ChannelDispatcher),每个ChannelDispatcher都会拥有一个信道监听器(ChannelListener),ChannelListener就有一直在固定的端口监听,等到Message的到来,调用AcceptChannel构建信道形成信道栈,开始对Message的处理。
using System; using System.Collections.ObjectModel; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher; namespace WcfService { public class GlobalExceptionHandlerBehaviourAttribute : Attribute, IServiceBehavior { private readonly Type _errorHandlerType; public GlobalExceptionHandlerBehaviourAttribute(Type errorHandlerType) { _errorHandlerType = errorHandlerType; } #region IServiceBehavior Members public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase) { } public void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters) { } public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase) { var handler = (IErrorHandler) Activator.CreateInstance(_errorHandlerType); foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers) { var channelDispatcher = dispatcherBase as ChannelDispatcher; if (channelDispatcher != null) channelDispatcher.ErrorHandlers.Add(handler); } } #endregion } }
第三步:在我们的WCF的类上,加上GlobalExceptionHandlerBehaviour
using System; namespace WcfService { [GlobalExceptionHandlerBehaviour(typeof (GlobalExceptionHandler))] public class SomeService : ISomeService { #region ISomeService Members public string SomeFailingOperation() { throw new Exception("Kaboom"); return null; } #endregion } }