Exception Handling Application Block与WCF

 

Exception Handling Application BlockWCF

当前的项目同时用到WCFEnterprise Library3.1里的Exception Handling Application Block(EHAB),遇到了如下问题:

1.       如果在WCF客户端调用WCF服务端代码时,WCF服务端发生了异常,如何用EHABWCF服务端处理这个异常使WCF客户端知道?

2.       WCF服务端抛出的异常传递到WCF客户端后如何用EHAB处理

Contract

WCF提供了includeExceptionDetailInFaults配置项,通过配置这个配置项为trueWCF服务端的任何异常都可以传递到WCF的客户端:

    <behaviors >

      <serviceBehaviors >

        <behavior name ="debug">

          <serviceDebug includeExceptionDetailInFaults ="true"/>

        </behavior>

      </serviceBehaviors>

    </behaviors>

但在生产环境,这样做是不安全的。这会使一些敏感信息跨过服务边界,从而对应用程序的安全不利。所以includeExceptionDetailInFaults配置项只能在开发阶段debug时使用,生产环境下includeExceptionDetailInFaults应该设为false

WCF提供了FaultContract,通过它我们可以定义能够在soap中传递的错误类型(即使includeExceptionDetailInFaults配置项设为false)。我们知道下面代码可以定义WCFServiceContractOperationContract

    [ServiceContract]

    public interface IMath

    {

        [OperationContract]

        double Divide(double x, double y);

    }

如果我们想把Divide方法可能的错误从WCF服务端传到WCF客户端,我们就要增加一个FaultContract特性:

    [ServiceContract]

    public interface IMath

    {

        [OperationContract]

        [FaultContract(typeof(BusinessFault))]

        double Divide(double x, double y);

    }

其中BusinessFault是我们自己定义个一个错误类型:

    [DataContract]

    public class BusinessFault

    {

        private string _message;

 

        private IDictionary _data;

 

        public BusinessFault()

        {

        }

 

        [DataMember]

        public string Message

        {

            get

            {

                return _message;

            }

            set

            {

                _message = value;

            }

        }

 

        [DataMember]

        public IDictionary Data

        {

            get

            {

                return _data;

            }

            set

            {

                _data = value;

            }

        }

    }

注意:BusinessFault类型有一个无参数的构造函数,这个是EHAB需要的。另外BusinessFault类型还有一个Data属性,这个是因为一会我们要把一个异常转换成BusinessFault,这个Data属性就是为了Map异常的Data属性。

之所以在WCF服务端和客户端之间传递我们自己定义的BusinessFault类型而不是Exception类型,原因如下:

1.       Exception类型没有DataContractDataMember特性,无法利用WCF序列化

2.       Exception类型中有很多关于异常的详细信息,如调用堆栈等,这些信息没有必要从WCF服务端传递到WCF客户端。这些信息应该记录在WCF服务端的log中。

Service

EHAB提供了ExceptionShielding特性和FaultContractExcpetionHandler类型。我们可以把ExceptionShielding特性应用在WCF服务的实现类上,并指定一个异常处理策略的名字:

    [ExceptionShielding ("WCF Exception Shielding")]

    class MathService : IMath

    {

        #region IMath 成员

 

        public double Divide(double x, double y)

        {

            if (y > -0.01 && y < 0.01)

            {

                DivideByZeroException ex = new DivideByZeroException("The denominator can't be zero!");

                ex.Data["Operation"] = "Divide";

                throw ex;

            }

 

            return x / y;

        }

 

        #endregion

    }

这样在MathService类中的方法抛出未处理的异常时,EHAB就会用WCF Exception Shielding策略处理这个异常。

FaultContractExcpetionHandler用于把WCF服务实现类抛出的异常转换成我们定义的BusinessFault类型,下面是服务端的配置文件片段:

  <exceptionHandling>

    <exceptionPolicies>

      <add name="WCF Exception Shielding">

        <exceptionTypes>

          <add type="System.DivideByZeroException, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

            postHandlingAction="ThrowNewException" name="DivideByZeroException">

            <exceptionHandlers>

              <add faultContractType="Ray.Wcf.EhabWithWcf.Contracts.BusinessFault, Ray.Wcf.EhabWithWcf.Contracts"

                exceptionMessage="" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF.FaultContractExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WCF, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

                name="Fault Contract Exception Handler">

                <mappings>

                  <add source="Message" name="Message" />

                  <add source="Data" name="Data" />

                </mappings>

              </add>

            </exceptionHandlers>

          </add>

        </exceptionTypes>

      </add>

    </exceptionPolicies>

  </exceptionHandling>

我们注意到在WCF Exception Shielding异常处理策略下的DivideByZeroException类型中我们用到了FaultContractExceptionHandler。并且我们在mappings节点中指定了DivideByZeroExceptionBusinessFault属性的Map关系,其中sourceDivideByZeroException的属性,nameBusinessFault的属性。

Client

以上WCF服务端代码会在WCF客户端抛出类型为FaultException<BusinessFault>的异常,现在我们想在WCF客户端也用EHAB统一处理异常,以下是WCF客户端的代码:

        static void Main(string[] args)

        {

            while (true)

            {

                IMath mathProxy = null;

                try

                {

                    ChannelFactory<IMath> factory = new ChannelFactory<IMath>("math");

                    mathProxy = factory.CreateChannel();

                    double x = 4;

                    double y = 0;

                    Console.WriteLine("Press <Enter> to call Math Service.");

                    Console.ReadLine();

                    double result = mathProxy.Divide(x, y);

                    Console.WriteLine("{0} / {1} = {2}", x, y, result);

                    Console.ReadLine();

                }

                catch (Exception ex)

                {

                    if (ExceptionPolicy.HandleException(ex, "UI Exception Policy")) throw;

                }

                finally

                {

                    IChannel channel = mathProxy as IChannel;

 

                    if (null != channel)

                    {

                        if (channel.State == CommunicationState.Opened)

                        {

                            channel.Close();

                        }

                        else

                        {

                            channel.Abort();

                        }

                    }

                }

                Console.ReadLine();

            }

        }

(关于这个片段中关闭通道的代码我很想有人能指点一下,原先我只是简单的close掉通道,但如果通道已损坏就会抛通道处于Fault状态异常,所以改成这个样子了。)

我们注意到这段代码中用UI Exception Policy处理异常,以下是配置文件中的相关片段:

  <exceptionHandling>

    <exceptionPolicies>

      <add name="UI Exception Policy">

        <exceptionTypes>

          <add type="System.ServiceModel.FaultException`1[[Ray.Wcf.EhabWithWcf.Contracts.BusinessFault, Ray.Wcf.EhabWithWcf.Contracts]], System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

            postHandlingAction="None" name="FaultException`1">

            <exceptionHandlers>

              <add type="Ray.Wcf.EhabWithWcf.Client.UIExceptionHandler, Ray.Wcf.EhabWithWcf.Client"

                name="Custom Handler" />

            </exceptionHandlers>

          </add>

        </exceptionTypes>

      </add>

    </exceptionPolicies>

  </exceptionHandling>

这个片段中值得注意的是泛型异常类型的表示“System.ServiceModel.FaultException`1[[Ray.Wcf.EhabWithWcf.Contracts.BusinessFault, Ray.Wcf.EhabWithWcf.Contracts]], System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089和一个自定义异常处理类UIExceptionHandler

UIExceptionHandler的实现非常简单,它只是用于在控制台打出异常信息:

    [ConfigurationElementType(typeof(CustomHandlerData))]

    class UIExceptionHandler : IExceptionHandler

    {

        public UIExceptionHandler(NameValueCollection ignore)

         {

         }

 

        #region IExceptionHandler 成员

 

        public Exception HandleException(Exception exception, Guid handlingInstanceId)

        {

            FaultException<BusinessFault> ex = exception as FaultException<BusinessFault>;

            if (null != ex)

            {

                Console.WriteLine("A business fault is caught.");

                Console.WriteLine("\tType: {0}", ex.GetType().AssemblyQualifiedName);

                Console.WriteLine("\tMessage: {0}", ex.Detail.Message);

                Console.WriteLine("\tOperation: {0}", ex.Detail.Data["Operation"]);

            }

 

            return exception;

        }

 

        #endregion

    }

这个片段中值得注意的地方是应用于UIExceptionHandler上的特性和UIExceptionHandler的构造函数(没用也要有这个构造函数)。
这里是代码

posted on 2007-10-05 16:54  Ray_bihpgh20  阅读(4950)  评论(5编辑  收藏  举报

导航