程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 我的WCF之旅(10):如何在WCF進行Exception Handling

我的WCF之旅(10):如何在WCF進行Exception Handling

編輯:關於.NET

在任何Application的開發中,對不可預知的異常進行troubleshooting時,異常處理顯得尤為重要。對於一般的.NET系統來說,我們簡單地借助try/catch可以很容易地實現這一功能。但是對於 一個分布式的環境來說,異常處理就沒有那麼簡單了。按照面向服務的原則,我們把一些可復用的業務邏輯以Service的形式實現,各個Service處於一個自治的環境中,一個Service需要和另一個Service進行交互,只需要獲得該Service的描述(Description)就可以了(比如WSDL,Schema和Strategy)。借助標准的、平台無關的通信構架,各個Service之間通過標准的Soap Message進行交互。Service Description、Standard Communication Infrastructure、Soap Message based Communication促使各Service以松耦合的方式結合在一起。但是由於各個Service是自治的,如果一個Service調用另一個Service,在服務提供方拋出的Exception必須被封裝在Soap Message中,方能被處於另一方的服務的使用者獲得、從而進行合理的處理。下面我們結合一個簡單的Sample來簡單地介紹我們可以通過哪些方式在WCF中進行Exception Handling。

一、傳統的Exception Handling

我們沿用我們一直使用的Calculator的例子和簡單的4層構架:

1.Service Contract- Artech.ExceptionHandling.Contract

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace Artech.ExceptionHandling.Contract
{
  [ServiceContract]
  public interface ICalculator
  {
    [OperationContract]
    double Divide(double x, double y);
  }
}

定義了一個單一的進行除法運算的Operation。

2.Service:Artech.ExceptionHandling.Service. CalculatorService

using System;
using System.Collections.Generic;
using System.Text;
using Artech.ExceptionHandling.Contract;

namespace Artech.ExceptionHandling.Service
{
  public class CalculatorService:ICalculator
  {
    ICalculator Members#region ICalculator Members

    public double Divide(double x, double y)
    {
      if (y == 0)
      {
        throw new DivideByZeroException("Divide by zero");
      }

      return x / y;
    }

    #endregion
  }
}

如果被除數是零,拋出一個DivideByZeroException Exception。

3.Service Hosting

Configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="calculatorServiceBehavior">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service behaviorConfiguration="calculatorServiceBehavior" name="Artech.ExceptionHandling.Service.CalculatorService">
        <endpoint binding="basicHttpBinding" bindingConfiguration="" contract="Artech.ExceptionHandling.Contract.ICalculator" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8888/Calculator" />
           </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

Program

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using Artech.ExceptionHandling.Service;

namespace Artech.ExceptionHandling.Hosting
{
  class Program
  {
    static void Main(string[] args)
    {
      using (ServiceHost calculatorHost = new ServiceHost(typeof(CalculatorService)))
      {
        calculatorHost.Opened += delegate
        {
          Console.WriteLine("The Calculator service has begun to listen via the address:{0}", calculatorHost.BaseAddresses[0]);
        };
        calculatorHost.Open();
        Console.Read();
      }
    }
  }
}

4.Client

Configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
     <client>
      <endpoint address=http://localhost:8888/Calculator binding="basicHttpBinding" contract="Artech.ExceptionHandling.Contract.ICalculator"
        name="defualtEndpoint" />
    </client>
  </system.serviceModel>
</configuration>

Program

using System;
using System.Collections.Generic;
using System.Text;
using Artech.ExceptionHandling.Contract;
using System.ServiceModel;

namespace Artech.ExceptionHandling.Client
{
  class Program
  {
    static void Main(string[] args)
    {
      ChannelFactory<ICalculator> calculatorFactory = new ChannelFactory<ICalculator>("defualtEndpoint");
      ICalculator calculator = calculatorFactory.CreateChannel();
      try
      {
        Console.WriteLine("Try to invoke Divide method");
        Console.WriteLine("x / y = {2} when x = {0} and y = {1}", 2, 0, calculator.Divide(2,0));
      }
      catch (Exception ex)
      {
        Console.WriteLine("An Exception is thrown.\n\tException Type:{0}\n\tError Message:{1}", ex.GetType(), ex.Message);
      }
      Console.Read();
    }
  }
}

把Service調用放在一個try/catch block中,看看Service端拋出的DivideByZeroException Exception能否被Catch。

我們運行這個程序,看看Client有怎樣的輸出:

我們發現Client catch住的不是我們Service端真正拋出的DivideByZeroException Exception,而是一個比較General的FaultException。Error message也是很general:

"Theserverwasunabletoprocesstherequestduetoaninternalerror.Formoreinformationabouttheerror,eitherturnonIncludeExceptionDetailInFaults(eitherfromServiceBehaviorAttributeorfromthe<serviceDebug>configurationbehavior)ontheserverinordertosendtheexceptioninformationbacktotheclient,orturnontracingaspertheMicrosoft.NETFramework3.0SDKdocumentationandinspecttheservertracelogs."

二、基於ServiceDebug的Exception Handling

很顯然Client端Catch住的Exception對我們進行troubleshooting。為了利於我們進行有效的Debug,WCF提供了ServiceDebug Service Behavior。我們通過includeExceptionDetailInFaults屬性設為true,那麼如果Service拋出Exception,WCF會簡單得包裝這個Exception並把它置於Soap中Response到Service的訪問者。介於此,我修改了Hosting的Configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="calculatorServiceBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service behaviorConfiguration="calculatorServiceBehavior" name="Artech.ExceptionHandling.Service.CalculatorService">
        <endpoint binding="basicHttpBinding" bindingConfiguration="" contract="Artech.ExceptionHandling.Contract.ICalculator" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8888/Calculator" />
           </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

現在再次運行程序,看看現在的運行結果:

可以看到我們我們Catch的是一個FaultException< ExceptionDetail>Type的Exception,不是原來的FaultException。該Exception的Detail屬性就是Service拋出的DivideByZeroException Exception。有興趣的人可以自己測試一下。而且我們在Service端指定的Error Message也被Client獲得。這種方式的Exception Handling方式確實比上面一種具有很強的指示性,對我們進行Debug確實很有幫助。但是這種方式確實不能正式用於我們最終發布的版本中,因為它會把Exception所有的信息返回到Client端,很容易洩露一些很敏感的信息。這也正是WCF把這個列入ServiceDebug Service Behavior的原因。

三、基於Fault Contract 的Exception Handling

既然上面通過定制ServiceDebug只能用於Debug階段。我們必須尋求另外一種Exception Handling的方式。那就是我們現在將要介紹的基於FaultContract的解決方案。我們知道WCF采用一種基於Contract,Contract定義了進行交互的雙方進行消息交換所遵循的准則和規范。Service Contract定義了包含了所有Operation的Service的接口,Data Contract定義了交互的數據的結構,而FaultContract實際上定義需要再雙方之間進行交互的了異常、錯誤的表示。我們現在來看看如何來使用基於FaultContract的Exception Handling。

我們首先來定義一個表示Fault的類:MathError。考慮到這個類需要在Service 和Client使用,我把它定義在Artech.ExceptionHandling.Contract中:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;

namespace Artech.ExceptionHandling.Contract
{
  [DataContract]
  public class MathError
  {
    private string _operation;
    private string _errorMessage;

    public MathError(string operation, string errorMessage)
    {
      this._operation = operation;
      this._errorMessage = errorMessage;
    }

    [DataMember]
    public string Operation
    {
      get { return _operation; }
      set { _operation = value; }
    }

    [DataMember]
    public string ErrorMessage
    {
      get { return _errorMessage; }
      set { _errorMessage = value; }
    }
  }
}

在MathError中定義了兩個成員:表示出錯操作的Operation和出錯信息的ErrorMessage。由於該類的對象需要在Endpoint之間傳遞,所以必須是可序列化的,在WCF中,我們一般用兩個不同的Serializer實現Object和XML的Serialization和Deserialization:Datacontract Serializer和XML Serializer。而對於Fault,只能使用前者。

定義了MathError,我們需要通過FaultContract將其運用到Service Contract中制定的Operation上面,我們通過下面的方式來實現:

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace Artech.ExceptionHandling.Contract
{
  [ServiceContract]
  public interface ICalculator
  {
    [OperationContract]
    [FaultContract(typeof(MathError))]
    double Divide(double x, double y);
  }
}

我們在Divide上運用了FaultContract,並指定了封裝了Fault對應的類型,那麼最終這個基於MathError類型的FaultContract會被寫入Service Description中,Client通過獲取該Service Description(一般是獲取WSDL),它就被識別它,就會將從接收到的Soap中對該Fault的XML Mapping到具體的MathError類型。

接著我們在Service Implementation中以拋出Exception的方式植入這個MathError對象:

using System;
using System.Collections.Generic;
using System.Text;
using Artech.ExceptionHandling.Contract;
using System.ServiceModel;

namespace Artech.ExceptionHandling.Service
{
  public class CalculatorService:ICalculator
  {
    ICalculator Members#region ICalculator Members

    public double Divide(double x, double y)
    {
      if (y == 0)
      {
        MathError error = new MathError("Divide", "Divided by zero");
        throw new FaultException<MathError>(error,new FaultReason("Parameters passed are not valid"),new FaultCode("sender"));
      }

      return x / y;
    }

    #endregion
  }
}

在被除數為0的時候,拋出FaultException<MathError> Exception,並指定具體的MathError對象,以及一個FaultCode(一般指明出錯的來源)和FaultReason(出錯的原因)。

我們現在先不修改Client的Exception Handling的相關代碼,先運行Hosting,看看WSDL中什麼特別之處:

通過上面的Screenshot,我們可以看到,在PortType section中的Divide Operation定義了Message為tns:ICalculator_Divide_MathErrorFault_FaultMessage 的<wsdl:fault>節點。通過查看Message Section,我們發現tns:ICalculator_Divide_MathErrorFault_FaultMessage的Element為q1:MathError,該q1:MathError type實際上是被定義在一個XSD中,其Uri為http://localhost:8888/Calculator?xsd=xsd2,我們定義的所有DataContract都在其中,下面的整個內容:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema elementFormDefault="qualified" targetNamespace="http://schemas.datacontract.org/2004/07/Artech.ExceptionHandling.Contract" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.datacontract.org/2004/07/Artech.ExceptionHandling.Contract">
  <xs:complexType name="MathError">
  <xs:sequence>
   <xs:element minOccurs="0" name="ErrorMessage" nillable="true" type="xs:string"/>
   <xs:element minOccurs="0" name="Operation" nillable="true" type="xs:string"/>
  </xs:sequence>
 </xs:complexType>
 <xs:element name="MathError" nillable="true" type="tns:MathError"/>
</xs:schema>

弄清楚了Fault在WSDL中表示後,我們來修改我們Client端的代碼,來有效地進行Exception Handling:

static void Main(string[] args)
    {
      ChannelFactory<ICalculator> calculatorFactory = new ChannelFactory<ICalculator>("defualtEndpoint");
      ICalculator calculator = calculatorFactory.CreateChannel();
      try
      {
        Console.WriteLine("Try to invoke Divide method");
        Console.WriteLine("x / y = {2} when x = {0} and y = {1}", 2, 0, calculator.Divide(2, 0));
      }
      catch (FaultException<MathError> ex)
      {
        MathError error = ex.Detail;
        Console.WriteLine("An Fault is thrown.\n\tFault code:{0}\n\tFault Reason:{1}\n\tOperation:{2}\n\tMessage:{3}", ex.Code, ex.Reason, error.Operation, error.ErrorMessage);
      }

      catch (Exception ex)
      {
        Console.WriteLine("An Exception is thrown.\n\tException Type:{0}\n\tError Message:{1}", ex.GetType(), ex.Message);
      }
      Console.Read();
    }

下面是運行後的輸出結果:

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved