📜 ⬆️ ⬇️

Problems Exception Handling in WCF under Mono

For business reasons, I had to deal with the task of creating a client of a WCF service under Mono 2.6.7.
Everything seemed to be going well - the client worked both under .NET and under Mono - until I started to handle exceptions that may arise in the methods of the WCF service.
The problems started when I needed to process my own exception, containing not only the exception message, but also some additional information.
I decided that I would organize the handling of exceptional situations as described in the article “Exceptions through WCF” (http://habrahabr.ru/blogs/net/41638/) of the respected Roman RomanNikitin .
In .NET, the client worked as it should, but when launching under Mono, the following error occurred:
image


Having studied various books on WCF, articles on the Internet, after analyzing the diagnostics of transmitted SOAP messages, I came to the conclusion that the error is due to the fact that the Mono-client incorrectly understands the message, which is FaultMessage.
This problem can be solved by taking over the responsibility for serializing and deserializing “fault” messages to itself.

The solution scheme is as follows:
image
')
Configure WCF service

For the WCF service, I defined the FaultErrorHandler error handler, implementing the IErrorHandler interface.
/// <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 .


Serialization of the error message is assigned to the serializer object of the WCFExceptionSerializer class, the successor of the abstract class XmlObjectSerializer. However, the error message is not a FaultMessage, but an ordinary Message. In order for the client to distinguish such a message from a regular message, the element “error” is inserted into the header of this message.

/// <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 .


The WCFExceptionSerializer serializer creates an element with the name corresponding to the type of exception that occurred. An exception message and its specific data (if any) are recorded as child elements. You can also include data such as StackTrace, Source, etc. in this message. As a result, the message is as follows:
< 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 .


We connect our error handler to the service using the behavior for the ErrorHandlingBehavior service application contract.

/// <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 .


We connect the behavior of ErrorHandlingBehavior to the service using the attribute mechanism:

[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 .


The only method of the MyService service throws out the usual DivideByZeroException exception, then the created SomeCustomException exception, which contains some additional data (a floating-point number). In the latter case, the message will look like this:
< 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 .


Now the service is ready to work.

Let's go to the client.

We define the ErrorInspector message inspector for the client, who will receive messages from the service and process them if the message header contains the element “error”.
/// <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 .


Adding our message inspector to ErrorHandlingBehavior behavior occurs in the ApplyClientBehavior method.
void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add( new ErrorInspector());
}


* This source code was highlighted with Source Code Highlighter .


The “fault” messages are processed by the ExtractException method, and the corresponding data is read from the message body. Based on the read data, an object of the WCFException type is created (the successor of Exception), various additional data are entered into its Data collection, if necessary.
/// <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 .


Now it remains to create a client of the WCF service and add the already familiar ErrorHandlingBehavior behavior to the contract application for the client application.
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 .


In a catch block, a WCFException exception is caught and processed depending on what type of exception it represents.

We test the service and the client with the new ErrorHandlingBehavior behavior connected.
DivideByZeroException is handled correctly:
image

and a new SomeCustomException:
image

At the same time, we have access to any additional data that our arbitrary exception may have.
The amount of code is also reduced in parallel, since we do not need to create exception description classes (defined as FaultContract) to handle exceptions, and the volume of messages does not increase; we send only the data that interests us.
Among the shortcomings, it is possible to note that in order to add a new user exception with its own additional information to this system, you must manually add how this information will be serialized into WCFExceptionSerializer and deserialized into ErrorInspector. Also, the name of this new custom message will have to be placed in a static, read-only field of the WCFException class.
Perhaps this procedure can be automated to make using this method more convenient.

Download source code (standard error handling method)
Download source code (suggested error handling method)

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


All Articles