MonoでのWCF例外処理の問題

必然的に、Mono 2.6.7でWCFサービスクライアントを作成するタスクを処理する必要がありました。
WCFサービスメソッドで発生する可能性のある例外の処理を開始するまで、クライアントは.NETとモノラルの両方で機能していました。
問題は、例外メッセージだけでなく、いくつかの追加情報も含む、独自の例外を処理する必要があるときに始まりました。
尊敬されているRoman RomanNikitinの記事「WCFによる例外」(http://habrahabr.ru/blogs/net/41638/)で説明されているように、例外的な状況の処理を整理することにしました
.NETでは、クライアントは正常に機能していましたが、Monoで起動すると次のエラーが発生しました。
画像


WCFに関するさまざまな書籍、インターネット上の記事を調べ、送信されたSOAPメッセージの診断を分析した結果、エラーはMonoクライアントがメッセージを正しく理解していないことが原因であるという結論に達しました。これはFaultMessageです。
この問題は、自分への「障害」メッセージのシリアル化とシリアル化解除の責任を受け入れる場合に解決できます。

ソリューションスキームは次のとおりです。
画像

WCFサービスを構成する

WCFサービスの場合、IErrorHandlerインターフェイスを実装して、FaultErrorHandlerエラーハンドラーを定義しました。
/// <summary>
/// WCF-
/// </summary>
public class FaultErrorHandler : IErrorHandler
{
WCFExceptionSerializer serializer = new WCFExceptionSerializer();

/// <summary>
/// , , , , .
/// </summary>
/// <param name="error">, - WCF-</param>
/// <returns>/</returns>
public bool HandleError(Exception error)
{
return true ;
}

/// <summary>
///
/// </summary>
/// <param name="error">, - WCF-</param>
/// <param name="version"> SOAP-</param>
/// <param name="fault">, </param>
public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref System.ServiceModel.Channels.Message fault)
{
fault = Message.CreateMessage(version, String .Empty, error, serializer);
fault.Headers.Add(MessageHeader.CreateHeader( "error" , String .Empty, null ));
}
}


* This source code was highlighted with Source Code Highlighter .


エラーメッセージのシリアル化は、XmlObjectSerializer抽象クラスの子孫であるWCFExceptionSerializerクラスのシリアライザーオブジェクトに割り当てられます。 ただし、エラーメッセージはFaultMessageではなく、通常のメッセージです。 クライアントがそのようなメッセージを通常のメッセージと区別できるようにするために、「error」要素がこのメッセージのヘッダーに挿入されます。

/// <summary>
/// -
/// </summary>
public class WCFExceptionSerializer : XmlObjectSerializer
{
public override bool IsStartObject(System.Xml.XmlDictionaryReader reader)
{
return false ;
}

public override object ReadObject(System.Xml.XmlDictionaryReader reader, bool verifyObjectName)
{
return null ;
}

public override void WriteEndObject(System.Xml.XmlDictionaryWriter writer)
{
}

public override void WriteStartObject(System.Xml.XmlDictionaryWriter writer, object graph)
{
}

/// <summary>
///
/// </summary>
/// <param name="writer"> -</param>
/// <param name="graph"> </param>
public override void WriteObjectContent(System.Xml.XmlDictionaryWriter writer, object graph)
{
Exception e = graph as Exception;

writer.WriteStartElement(e.GetType().Name);
writer.WriteElementString( "Message" , e.Message);
if (e is SomeCustomException) writer.WriteElementString( "AddData" , (e as SomeCustomException).AdditionalData.ToString());
writer.WriteEndElement();
}
}

* This source code was highlighted with Source Code Highlighter .


WCFExceptionSerializerシリアライザーは、発生した例外の種類と一致する名前を持つ要素を作成します。 例外メッセージとその特定のデータ(ある場合)は、子要素として記録されます。 このメッセージには、StackTrace、Sourceなどのデータを含めることもできます。 その結果、メッセージは次のようになります。
< s:Envelope xmlns:s ="http://schemas.xmlsoap.org/soap/envelope/" >
< s:Header >
< error i:nil ="true" xmlns:i ="http://www.w3.org/2001/XMLSchema-instance" xmlns ="" ></ error >
</ s:Header >
< s:Body >
< DivideByZeroException xmlns ="" >
< Message > . </ Message >
</ DivideByZeroException >
</ s:Body >
</ s:Envelope >


* This source code was highlighted with Source Code Highlighter .


ErrorHandlingBehaviorサービスアプリケーションコントラクトの動作を使用して、エラーハンドラーをサービスに接続します。

/// <summary>
/// ,
/// </summary>
public class ErrorHandlingBehavior : Attribute , IContractBehavior
{
//
private void ApplyDispatchBehavior(ChannelDispatcher dispatcher)
{
foreach (IErrorHandler errorHandler in dispatcher.ErrorHandlers)
{
if (errorHandler is FaultErrorHandler)
{
return ;
}
}
dispatcher.ErrorHandlers.Add( new FaultErrorHandler());
}

//
// ContractBehavior
//
void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}

void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add( new ErrorInspector());
}
void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
this .ApplyDispatchBehavior(dispatchRuntime.ChannelDispatcher);
}

void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
return ;
}
}

* This source code was highlighted with Source Code Highlighter .


属性メカニズムを使用して、ErrorHandlingBehavior動作をサービスに接続します。

[ServiceContract, ErrorHandlingBehavior]
public interface IMyService
{
[OperationContract]
void MethodWithException();
}

public class MyService : IMyService
{
public void MethodWithException()
{
Random r = new Random ( DateTime .Now.Millisecond);
r.Next(100);
int a = 30 / (r.Next(100) % 2);
throw new SomeCustomException( " " , 12.987);
}
}


* This source code was highlighted with Source Code Highlighter .


唯一のMyServiceサービスメソッドは、通常のDivideByZeroException、または追加データ(浮動小数点数)をスローするSomeCustomExceptionをスローします。 後者の場合、メッセージは次のようになります。
< s:Envelope xmlns:s ="http://schemas.xmlsoap.org/soap/envelope/" >
< s:Header >
< error i:nil ="true" xmlns:i ="http://www.w3.org/2001/XMLSchema-instance" xmlns ="" ></ error >
</ s:Header >
< s:Body >
< SomeCustomException xmlns ="" >
< Message > </ Message >
< AddData > 12,987 </ AddData >
</ SomeCustomException >
</ s:Body >
</ s:Envelope >


* This source code was highlighted with Source Code Highlighter .


これでサービスの準備が整いました。

クライアントに移りましょう。

メッセージインスペクタークライアントのErrorInspectorを定義します。これは、サービスからメッセージを受信し、メッセージヘッダーに「エラー」要素が含まれている場合にメッセージを処理します。
/// <summary>
///
/// </summary>
public class ErrorInspector : IClientMessageInspector
{
/// <summary>
/// -
/// ( Fault-)
///
/// </summary>
/// <param name="reply"></param>
/// <param name="correlationState">,
/// BeforeSendRequest ( )</param>
public void AfterReceiveReply( ref Message reply, object correlationState)
{
int errorHeaderIndex = reply.Headers.FindHeader( "error" , String .Empty);
if (errorHeaderIndex > -1)
{
throw ExtractException(reply.GetReaderAtBodyContents());
}
}

/// <summary>
/// -
///
///
/// </summary>
public object BeforeSendRequest( ref Message request, System.ServiceModel.IClientChannel channel)
{
return null ;
}

/// <summary>
///
///
/// WCFException.
///
/// Data WCFException.
/// - .
/// </summary>
/// <param name="xdr"> </param>
/// <returns> WCFException ( WCF-)</returns>
private WCFException ExtractException(XmlDictionaryReader xdr)
{
WCFException wcfError = new WCFException();
wcfError.ExceptonType = xdr.Name;

xdr.ReadToFollowing( "Message" );
xdr.Read();
wcfError.Message = xdr.Value;

if (wcfError.ExceptonType == WCFException.SomeCustomException)
{
xdr.ReadToFollowing( "AddData" );
xdr.Read();
wcfError.Data[ "AddData" ] = xdr.Value;
}

return wcfError;
}
}

* This source code was highlighted with Source Code Highlighter .


ApplyClientBehaviorメソッドで、メッセージインスペクターをErrorHandlingBehavior動作に追加します。
void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add( new ErrorInspector());
}


* This source code was highlighted with Source Code Highlighter .


「障害」メッセージの処理は、ExtractExceptionメソッドによって実行されます。このメソッドでは、対応するデータがメッセージ本文から読み取られます。 読み取られたデータに基づいて、タイプWCFException(例外の後継)のオブジェクトが作成され、必要に応じてさまざまな追加データがそのデータコレクションに入力されます。
/// <summary>
/// WCF-
/// </summary>
public class WCFException : Exception
{
//
// Data
//
public static readonly string SomeCustomException = "SomeCustomException" ;

/// <summary>
///
/// </summary>
public WCFException()
: base ()
{
}

/// <summary>
///
/// </summary>
public string ExceptonType
{
get ;
set ;
}

/// <summary>
///
/// </summary>
public new string Message
{
get ;
set ;
}
}

* This source code was highlighted with Source Code Highlighter .


ここで、WCFサービスクライアントを作成し、クライアントアプリケーションのコントラクト動作に既にわかっているerrorHandlingBehavior動作を追加します。
MyServiceClient client = new MyServiceClient();
client.Endpoint.Contract.Behaviors.Add( new ErrorHandlingBehavior());

Console .WriteLine( " Enter, " );
Console .ReadKey();

try
{
client.MethodWithException();
}
catch (WCFException wcfEx)
{
Console .WriteLine( ":" );
Console .WriteLine( ": " + wcfEx.Message);
if (wcfEx.ExceptonType == WCFException.SomeCustomException)
Console .WriteLine( " : " + wcfEx.Data[ "AddData" ].ToString());
}
catch (Exception e)
{
Console .WriteLine(e.Message);
}
Console .ReadKey();


* This source code was highlighted with Source Code Highlighter .


WCFException例外はcatchブロックでキャッチされ、例外の種類に応じて処理されます。

新しいErrorHandlingBehavior動作を接続したサービスとクライアントのテスト。
正しく処理されたDivideByZeroException:
画像

および新しいSomeCustomException:
画像

同時に、任意の例外が持つ可能性のある追加データにアクセスできます。
例外処理のために例外記述クラス(FaultContractとして定義されているクラス)を作成する必要がなく、メッセージ量が増加しないため、コードの量も並行して削減されます。 関心のあるデータのみを送信します。
欠点の中で、新しいカスタム例外を追加情報とともにこのシステムに追加することができます。この情報をWCFExceptionSerializerでシリアル化し、ErrorInspectorで逆シリアル化する方法を手動で追加する必要があります。 また、この新しいユーザーメッセージの名前は、WCFExceptionクラスの静的な読み取り専用フィールドに配置する必要があります。
おそらく、この方法をより便利に使用するために、この手順を自動化できます。

ソースコードのダウンロード(標準エラー処理方法)
ソースコードのダウンロード(エラー処理方法の提案)

Source: https://habr.com/ru/post/J127790/


All Articles