前面幾篇爛文中所介紹到的錯誤方式,都是在操作協定的實作代碼中抛出 FaultException 或者帶泛型參數的detail方案,有些時候,錯誤的處理方法比較相似,可是要每個操作協定去處理,似乎也太麻煩,此時就應當考慮統一處理了。
在 System.ServiceModel.Dispatcher 命名空間下,有一個 IErrorHandler 接口,這個接口就是讓我們統一處理錯誤的。
先來認識一下這個接口。
public interface IErrorHandler
{
bool HandleError(Exception error);
void ProvideFault(Exception error, MessageVersion version, ref Message fault);
}
HandleError 方法是先判斷一下,這個錯誤是否應該讓會話中止,通常我們直接傳回 true,表示會話不會中止,如果傳回false,通信會話會被中止,這會使得服務執行個體被釋放(單個執行個體模式除外,因為單例模式下如果把執行個體釋放,就導緻其他用戶端無法調用服務了),進而使資料丢失,是以,還是傳回true好一點,表示錯誤由你來處理。
ProvideFault 方法就是用來産生 SOAP 錯誤消息的,可以在這裡對異常進行統一封裝。
IErrorHandler 的實作類隻能插入到 Channel Dispatcher中,這表明它是跟通道層相關的。最簡單的方法就是把實作了 IErrorHandler 接口的類型執行個體直接通過 ServiceHost 來擷取通道排程程式并加入到 ErrorHandlers 集合中。但,為了便于擴充和配置,老周還是建議甯可麻煩一點,擴充一個 behavior 來添加這個 handler。
按照老周一向的風格,理論和原理部分已經說完了,下面就是上菜,哦不,是上示例時間。
照舊,我們得先弄個示例服務。下面代碼定義了一個計算加法運算和二次方運算的協定。
[ServiceContract(Namespace = "sp-test", Name = "demo", ConfigurationName = "testContract")]
public interface ITest
{
[OperationContract]
int Add(int a, int b);
[OperationContract]
int SQR(int n);
}
然後,實作它。
[ServiceBehavior(ConfigurationName = "sv")]
class TestSvr : ITest
{
public int Add(int a, int b)
{
if (a < 0) throw new ArgumentException("值不能小于0", nameof(a));
if (b < 0) throw new ArgumentException("值不能小于0", nameof(b));
return a + b;
}
public int SQR(int n)
{
if (n == 0) throw new Exception("參數不能為0");
return n * n;
}
}
在實作代碼中,都對傳入參數進行驗證,并抛出對應的異常,這個相信大夥能看懂。
接下來才是主角出場。
來,實作一個自定義的 Error Handler。
public class CustErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
// 1、封裝異常
string errText = error.Message;
FaultException fex = new FaultException(errText);
// 2、産生 MessageFault
MessageFault msg_fault = fex.CreateMessageFault();
// 3、産生新的消息
fault = Message.CreateMessage(version, msg_fault, "cust-fault");
}
}
用 FaultException 來封裝錯誤資訊是關鍵一步,如果必要可以用 FaultException<TDetail> ,因為這裡沒有定義錯誤協定類,故而用 FaultException 就OK了。
随後,通過 FaultException ,可以生成一個 MessageFault 對象,最後就是産生新的 Message ,這條消息是要傳回用戶端的。此處咱們用的是以下CreateMessage 方法。
public static Message CreateMessage(MessageVersion version, MessageFault fault, string action);
version 參數好辦,隻直接引用 ProvideFault 方法中傳入的值即可,第二個參數就是剛剛産生的 MessageFault 執行個體,最後還得指定一個 action,這個你可以自己取,反正一個字元串就行。
下面實作 behavior,這裡以實作End Point 層面的 behavior 為例。
public class CustEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
return; //無需處理
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
ChannelDispatcher cndisp = endpointDispatcher.ChannelDispatcher;
// 加入自定義的錯誤處理程式
CustErrorHandler cehdlr = null;
cehdlr = (CustErrorHandler)cndisp.ErrorHandlers.FirstOrDefault();
if (cehdlr == null)
{
cehdlr = new CustErrorHandler();
}
cndisp.ErrorHandlers.Add(cehdlr);
}
public void Validate(ServiceEndpoint endpoint)
{
return;
}
}
ApplyClientBehavior 方法是用戶端上面調用的,此處我們不需要,隻要實作 ApplyDispatchBehavior 方法即可,它是用在伺服器上的。
按理說,到這裡就完事了,但是,如果用配置檔案來配置的話,還是不夠的,當然了,用代碼來配置是沒問題的。
為了能讓這個自定義的behavior能在配置檔案中用,需要實作抽象類 BehaviorExtensionElement。
public class CustBehaviorExtElement : BehaviorExtensionElement
{
public override Type BehaviorType => typeof(CustEndpointBehavior);
protected override object CreateBehavior()
{
return new CustEndpointBehavior();
}
}
這個我不解釋了,簡單。
打開配置檔案,我們需要在配置檔案中注冊這個自定義的元素。
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="custerr" type="TestSvrSample.CustBehaviorExtElement, TestSvrSample"/>
</behaviorExtensions>
</extensions>
</system.serviceModel>
注意,type 屬性指定的是我們剛剛實作抽象類 BehaviorExtensionElement 的那個類,不僅要寫上完整類型路徑,還要寫上所在程式集的名字,不然會找不到。name 屬性用來指定在配置中使用時,其元素的名稱。
比如,可以這樣配置。
<behaviors>
<endpointBehaviors>
<behavior name="bhv">
<custerr />
</behavior>
</endpointBehaviors>
</behaviors>
其中的 custerr 元素就是剛才在 behaviorExtensions 中所指定的 name 屬性。
最後可以在終結點配置中引用這個 behavior。
<service name="sv">
<endpoint address="http://127.0.0.1:3655/test" binding="basicHttpBinding" contract="testContract" behaviorConfiguration="bhv"/>
</service>
好了,大功告成,現在,可以測試一下調用,調用中,我故意傳個不合适的參數值。
ITest ch = factory.CreateChannel();
try
{
int r = ch.Add(-1, 6);
Console.WriteLine(r);
}
catch(FaultException faultex)
{
FaultReason reason = faultex.Reason;
string text = reason.GetMatchingTranslation().Text;
Console.WriteLine($"錯誤:{text}");
}
運作後,用戶端捕捉到的錯誤資訊如下圖所示。
OK,本文到此結束,開飯。
示例代碼下載下傳:https://files.cnblogs.com/files/tcjiaan/IErrorHandlerSample.zip