利用Attribute和IErrorHandler處理WCF全局異常
在處理WCF異常的時(shí)候,有大概幾種方式:
第一種是在配置文件中,將includeExceptionDetailInFaults設(shè)置為true
<behavior name="serviceDebuBehavior"><serviceDebug includeExceptionDetailInFaults="true" /></behavior>
但是這種方式會(huì)導(dǎo)致敏感信息泄漏的危險(xiǎn),一般我們僅僅在調(diào)試的時(shí)候才開(kāi)啟該屬性,如果已經(jīng)發(fā)布,為了安全,我們一般會(huì)設(shè)置成false。
第二種方法是自定義錯(cuò)誤,通過(guò)FaultException直接指定錯(cuò)誤信息。
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class CalculatorService : ICalculator
{
throw new FaultException("被除數(shù)y不能為零!");
}
但這樣我們還必須為WCF的方法里顯式的去拋出異常,如果能像.NET一樣,在Global里直接捕獲全局的異常,并寫(xiě)入log4net日志多好,有沒(méi)有方法在wcf里如果出現(xiàn)異常,異常日志寫(xiě)入服務(wù)器斷,同時(shí)拋給客戶(hù)端呢?
要實(shí)現(xiàn)這個(gè),需要三步
第一步: 我們需要實(shí)現(xiàn)IErrorHandler接口,實(shí)現(xiàn)他的兩個(gè)方法
bool HandleError(Exception error);
void ProvideFault(Exception error, MessageVersion version, ref Message fault);
其中HandleError是true表示終止當(dāng)前session
在ProvideFault方法里我們可以寫(xiě)我們要拋給客戶(hù)端的自定義的錯(cuò)誤,同時(shí)也可以在服務(wù)器端寫(xiě)異常日志。
我們實(shí)現(xiàn)一個(gè)GlobalExceptionHandler ,繼承自IErrorHandler,同時(shí)在ProvideFault里通過(guò)log4net寫(xiě)服務(wù)器端日志,如下:
namespace WcfService
{
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
/// <summary>
/// GlobalExceptionHandler
/// </summary>
public class GlobalExceptionHandler : IErrorHandler
{
/// <summary>
/// 測(cè)試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)
{
//// 寫(xiě)入log4net
log.Error("WCF異常", ex);
var newEx = new FaultException(string.Format("WCF接口出錯(cuò) {0}", ex.TargetSite.Name));
MessageFault msgFault = newEx.CreateMessageFault();
msg = Message.CreateMessage(version, msgFault, newEx.Action);
}
#endregion
}
}
第二步:現(xiàn)在我們需要?jiǎng)?chuàng)建一個(gè)自定義的Service Behaviour Attribute,讓W(xué)CF知道當(dāng)WCF任何異常發(fā)生的時(shí)候,我們通過(guò)這個(gè)自定義的Attribute來(lái)處理。實(shí)現(xiàn)這個(gè)需要繼承IServiceBehavior接口,并在此類(lèi)的構(gòu)造函數(shù)里,我們獲取到錯(cuò)誤類(lèi)型。
一旦ApplyDispatchBehavior行為被調(diào)用時(shí),我們通過(guò)Activator.CreateInstance創(chuàng)建錯(cuò)誤handler,并把這個(gè)錯(cuò)誤添加到每個(gè)channelDispatcher中。
ApplyDispatchBehavior
channelDispatcher中文叫信道分發(fā)器,當(dāng)我們的ServiceHost調(diào)用Open方法,WCF就會(huì)創(chuàng)建我們的多個(gè)信道分發(fā)器(ChannelDispatcher),每個(gè)ChannelDispatcher都會(huì)擁有一個(gè)信道監(jiān)聽(tīng)器(ChannelListener),ChannelListener就有一直在固定的端口監(jiān)聽(tīng),等到Message的到來(lái),調(diào)用AcceptChannel構(gòu)建信道形成信道棧,開(kāi)始對(duì)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的類(lèi)上,加上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
}
}
這時(shí)候,客戶(hù)端和服務(wù)器端都已經(jīng)分別能記錄到錯(cuò)誤的異常日志了。
附:log4net配置
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler, log4net, Version=1.2.10.0, Culture=Neutral, PublicKeyToken=bf100aa01a5c2784" />
</configSections>
<log4net>
<!-- 日志文件部分log輸出格式的設(shè)定 -->
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="./Logs\Log_" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<datePattern value="yyyyMMdd'.txt'" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<header value="------------------------------------------------------------
" />
<ConversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline%newline%newline" />
</layout>
</appender>
<appender name="WcfService.Api_Error" type="log4net.Appender.RollingFileAppender" LEVEL="ERROR">
<file value="./Logs\API\logError_" />
<appendToFile value="true" />
<datePattern value="yyyyMMdd'.txt'" />
<rollingStyle value="Date" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<header value="[Header] " />
<footer value="[Footer] " />
<conversionPattern value="%date{dd/MM/yyyy-HH:mm:ss} %m%newline%exception" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="ERROR" />
<param name="LevelMax" value="ERROR" />
</filter>
</appender>
<appender name="WcfService.Api_Info" type="log4net.Appender.RollingFileAppender" LEVEL="INFO">
<file value="./Logs\API\logInfo_" />
<appendToFile value="true" />
<datePattern value="yyyyMMdd'.txt'" />
<rollingStyle value="Date" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<header value="[Header] " />
<footer value="[Footer] " />
<conversionPattern value="%date{dd/MM/yyyy-HH:mm:ss} %m%newline%exception" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="INFO" />
<param name="LevelMax" value="INFO" />
</filter>
</appender>
<root>
<level value="All" />
<appender-ref ref="RollingLogFileAppender" />
</root>
<logger name="WcfService" additivity="false">
<appender-ref ref="WcfService.Api_Info" />
<appender-ref ref="WcfService.Api_Error" />
</logger>
</log4net>
</configuration>

浙公網(wǎng)安備 33010602011771號(hào)