利用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
    }
}
這時候,客戶端和服務器端都已經分別能記錄到錯誤的異常日志了。
附: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輸出格式的設定 -->
  <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="------------------------------------------------------------&#xD;&#xA;" />
      <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]&#13;&#10;" />
      <footer value="[Footer]&#13;&#10;" />
      <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]&#13;&#10;" />
      <footer value="[Footer]&#13;&#10;" />
      <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>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM