WCF: provide a general FaultException in IErrorHandler

In some context: We have a custom XSD and generated WSDL and C # code using WSCF.blue. The client side uses ChannelFactory<T> and shares an interface that includes all the attributes added by WSCF.blue according to what is in the XSD.

I am trying to implement IErrorHandler.ProvideFault where it provides a generic FaultException<T> , but on the client side I am returning not a generic FaultContract . Here's what my ProvideFault method looks like:

 public void ProvideFault(Exception error, MessageVersion version, ref Message fault) { if (!(error is FaultException)) { FaultException faultException = FaultExceptionFactory.CreateFaultException(error); MessageFault messageFault = faultException.CreateMessageFault(); fault = Message.CreateMessage(version, messageFault, faultException.Action); } } 

In every service method, if I execute try / catch with throw FaultExceptionFactory.CreateFaultException(ex) , it works as expected, so I think the bindings are [FaultContract] , factory, etc. everything is correct. Just in case, how the factory works:

 BusinessRuleFaultExceptionType businessRuleFaultException = new BusinessRuleFaultExceptionType(); BusinessRuleFaultException.Code = exception.Code.ToString(); BusinessRuleFaultException.Reason = exception.Message; return new FaultException<BusinessRuleFaultExceptionType>( businessRuleFaultException, exception.Message, new FaultCode(exception.Code.ToString()) ); 

I think the problem is with the way the message is created in IErrorHandler , possibly in CreateMessageFault() . I read that the action should be faultException.Action instead of null , but actually faultException.Action is null . Perhaps this is causing the problem. I can set the action in the factory, but what should the action be and why not display with a manual throw?

Any other ideas that I might be missing?

Edit: I checked the WSDL and found the specific operation I was calling and its action:

 <wsdl:operation name="MyMethod"> <wsdl:fault wsaw:Action="http://myNamespace/MyMethodBusinessRuleFaultExceptionTypeFault" name="BusinessRuleFaultExceptionTypeFault" message="tns:..."/> 

I tried hard-coded the action: Message.CreateMessage(..., "http://myNamespace/MyMethodBusinessRuleFaultExceptionTypeFault") and set it to .Action in the factory, but that still didn't work.

Edit 2: The throw / catch function generates the following XML code, which allows catching a general exception on the client:

 <s:Fault> <faultcode xmlns="">s:-100</faultcode> <faultstring xml:lang="en-US" xmlns="">xxx</faultstring> <detail xmlns=""> <BusinessRuleFaultExceptionType xmlns="http://myNamespace/Services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Code xmlns="http://myNamespace/Entitites">-100</Code> <Reason xmlns="http://myNamespace/Entitites">xxx</Reason> </BusinessRuleFaultExceptionType> </detail> </s:Fault> 

IHttpErrorHandler throws the following, which jumps to a non-generic FaultException :

 <s:Fault> <faultcode xmlns="">s:-100</faultcode> <faultstring xml:lang="en-US" xmlns="">xxx</faultstring> <detail xmlns=""> <BusinessRuleFaultExceptionType xmlns="http://schemas.datacontract.org/2004/07/My.Dot.Net.Namespace" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <codeField>-100</codeField> <reasonField>xxx</reasonField> </BusinessRuleFaultExceptionType> </detail> </s:Fault> 

Edit 3: If I add [DataContract] and [DataMember] to BusinessRuleFaultExceptionType , then I will get almost the correct XML:

 <s:Fault> <faultcode xmlns="">s:-100</faultcode> <faultstring xml:lang="en-US" xmlns="">xxx</faultstring> <detail xmlns=""> <BusinessRuleFaultExceptionType xmlns="http://myNamespace/Services" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Code>-100</Code> <Reason>xxx</Reason> </BusinessRuleFaultExceptionType> </detail> </s:Fault> 

There is no namespace for code and reason. At least I think it narrows it down. Regular serialization uses [XmlType] and [XmlElement] , and IErrorHandler uses [DataContract] and [DataMember] . Unfortunately, [DataMember] does not allow you to specify a namespace, so I think the question now is how to use the XMLSerializer in IErrorHandler . This describes my problem, but the fix will not work for the reason mentioned above: http://twenty6-jc.blogspot.com/2011/05/ierrorhandlerprovidefault-serialization.html

Edit 4: I partially found the problem. We use XmlSerializer , but since IErrorHandler is beyond the scope of the operation, it returns to the default DataContractSerializer . The solution is to either change the service to use the DataContractSerializer everywhere, or manually select the XmlSerializer when creating failures. These two articles provided me with what I need:

http://twenty6-jc.blogspot.com/2011/05/ierrorhandlerprovidefault-serialization.html http://zamd.net/2008/08/15/serializing-faults-using-xmlserializer/

This is very close to me. This is the same as working XML, except for the exception type namespace:

 <s:Fault> <faultcode xmlns="">s:-100</faultcode> <faultstring xml:lang="en-US" xmlns="">xxx</faultstring> <detail xmlns=""> <BusinessRuleFaultExceptionType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Code xmlns="http://myNamespace/Entitites">-100</Code> <Reason xmlns="http://myNamespace/Entitites">xxx</Reason> </BusinessRuleFaultExceptionType> </detail> </s:Fault> 

I assume that it does not add xmlns="http://myNamespace/Services" because it does not have a request context. This namespace is defined in the interface, but not in the data contract. Will I really be forced to stay in the context of the request (hope IOperationInvoker works), instead of using IHttpHandler ?

+7
c # wcf xmlserializer wcf-extensions ierrorhandler
source share
2 answers

I have no experience with WSCF.blue, so I tried to make a sample application to demonstrate a working scenario using a standard client and WCF server. Perhaps this will help you find the missing connections for your script to work.

I used BusinessRuleFaultExceptionType using the Code and Reason properties. BusinessRuleFaultExceptionType is a WCF failure contract.

I was a little lazy and implemented all the code in one console application. The Wcf client uses the same Datacontracts and ICalculator as Wcf. Sorry for the code. This will be a long post.

Datacontracts and Services Interface First

 using System; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher; [ServiceContract(Namespace = "http://UE.ServiceModel.Samples")] public interface ICalculator { [OperationContract(IsOneWay = false)] [FaultContract(typeof(BusinessRuleFaultExceptionType))] double Add(double n1, double n2); [OperationContract(IsOneWay = false)] [FaultContract(typeof(BusinessRuleFaultExceptionType))] double Subtract(double n1, double n2); [OperationContract(IsOneWay = false)] [FaultContract(typeof(BusinessRuleFaultExceptionType))] double Multiply(double n1, double n2); [OperationContract(IsOneWay = false)] [FaultContract(typeof(BusinessRuleFaultExceptionType))] double Divide(double n1, double n2); } /// <summary> /// General fault structure. /// </summary> [DataContract(Namespace = "http://someurl.temp")] public sealed class BusinessRuleFaultExceptionType { [DataMember] public int Code { get; set; } [DataMember] public string Reason { get; set; } } 

Now the implementation of the service:

 [ErrorBehavior(typeof(MyErrorHandler))] public class CalculatorService : ICalculator { public double Add(double n1, double n2) { double result = n1 + n2; Console.WriteLine("Received Add({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); throw new ArgumentException("My exception"); return result; } public double Subtract(double n1, double n2) { double result = n1 - n2; Console.WriteLine("Received Subtract({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); return result; } public double Multiply(double n1, double n2) { double result = n1 * n2; Console.WriteLine("Received Multiply({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); return result; } public double Divide(double n1, double n2) { double result = n1 / n2; Console.WriteLine("Received Divide({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); return result; } } 

And client implementation:

 public class Client : ClientBase<ICalculator>, ICalculator { public double Add(double n1, double n2) { try { return base.Channel.Add(n1, n2); } catch (FaultException<BusinessRuleFaultExceptionType> ex) { Console.WriteLine("This is my Code: {0}. This is the reason: {1}", ex.Detail.Code, ex.Detail.Reason); } catch (Exception ex) { throw; } return 0; } public double Subtract(double n1, double n2) { throw new NotImplementedException(); } public double Multiply(double n1, double n2) { throw new NotImplementedException(); } public double Divide(double n1, double n2) { throw new NotImplementedException(); } } 

Main program to demonstrate this example

 internal class Program { private static void Main(string[] args) { ServiceHost myServiceHost = new ServiceHost(typeof(CalculatorService)); // Open the ServiceHostBase to create listeners and start listening for messages. myServiceHost.Open(); // The service can now be accessed. Console.WriteLine("The service is ready."); Console.WriteLine("Press <ENTER> to terminate service."); Console.WriteLine(); Console.ReadLine(); Console.WriteLine("Sending data from client to service."); Client c = new Client(); var res = c.Add(1, 2); Console.ReadLine(); } } 

Error handling execution:

 /// <summary> /// Helper class for exception repackaging. /// </summary> internal class MyExceptionHandler { /// <summary> /// Handles thrown exception into internal exceptions that are being sent over to client. /// </summary> /// <param name="error">Exception thrown.</param> /// <returns>Repackaged exception.</returns> internal static Exception HandleError(Exception error) { // could do something here. return error; } } #region Behaviour /// <summary> /// Control the fault message returned to the caller and optionally perform custom error processing such as logging. /// </summary> public sealed class MyErrorHandler : IErrorHandler { /// <summary> /// Provide a fault. The Message fault parameter can be replaced, or set to null to suppress reporting a fault. /// </summary> /// <param name="error">The <see cref="Exception"/> object thrown in the course of the service operation.</param> /// <param name="version">The SOAP version of the message.</param> /// <param name="fault">The <see cref="System.ServiceModel.Channels.Message"/> object that is returned to the client, or service, in the duplex case.</param> public void ProvideFault(Exception error, MessageVersion version, ref Message fault) { //If it a FaultException already, then we have nothing to do if (error is FaultException) return; error = MyExceptionHandler.HandleError(error); var serviceDebug = OperationContext.Current.EndpointDispatcher.ChannelDispatcher.IncludeExceptionDetailInFaults; BusinessRuleFaultExceptionType f = new BusinessRuleFaultExceptionType { Code = -100, Reason = "xxx" }; FaultException<BusinessRuleFaultExceptionType> faultException = new FaultException<BusinessRuleFaultExceptionType>(f, error.Message); MessageFault faultMessage = faultException.CreateMessageFault(); fault = Message.CreateMessage(version, faultMessage, faultException.Action); } /// <summary> /// Enables error-related processing and returns a value that indicates whether the dispatcher aborts the session and the instance context in certain cases. /// </summary> /// <param name="error">The exception thrown during processing.</param> /// <returns>true if Windows Communication Foundation (WCF) should not abort the session (if there is one) and instance context if the instance context is not Single; otherwise, false. The default is false.</returns> public bool HandleError(Exception error) { // could use some logger like Nlog but as an example it will do. Console.WriteLine("Error occured. {0}", error); return true; } } /// <summary> /// This attribute is used to install a custom error handler for a service /// </summary> public sealed class ErrorBehaviorAttribute : Attribute, IServiceBehavior { /// <summary> /// Type of component to which this error handled should be bound /// </summary> private readonly Type errorHandlerType; /// <summary> /// Initializes a new instance of the ErrorBehaviorAttribute class. /// </summary> /// <param name="errorHandlerType">Type of component to which this error handled should be bound</param> public ErrorBehaviorAttribute(Type errorHandlerType) { this.errorHandlerType = errorHandlerType; } /// <summary> /// Type of component to which this error handled should be bound /// </summary> public Type ErrorHandlerType { get { return errorHandlerType; } } /// <summary> /// Provides the ability to inspect the service host and the service description to confirm that the service can run successfully. /// </summary> /// <param name="description"> /// <para>Type: <see cref="System.ServiceModel.Description.ServiceDescription"/></para> /// <para>The service description.</para> /// </param> /// <param name="serviceHostBase"> /// <para>Type: <see cref="System.ServiceModel.ServiceHostBase"/></para> /// <para>The service host that is currently being constructed.</para> /// </param> void IServiceBehavior.Validate(ServiceDescription description, ServiceHostBase serviceHostBase) { } /// <summary> /// Provides the ability to pass custom data to binding elements to support the contract implementation. /// </summary> /// <param name="description"> /// <para>Type: <see cref="System.ServiceModel.Description.ServiceDescription"/></para> /// <para>The service description.</para> /// </param> /// <param name="serviceHostBase"> /// <para>Type: <see cref="System.ServiceModel.ServiceHostBase"/></para> /// <para>The host of the service.</para> /// </param> /// <param name="endpoints"> /// <para>Type: <see cref="System.Collections.ObjectModel.Collection&lt;ServiceEndpoint&gt;"/></para> /// <para>The service endpoints.</para> /// </param> /// <param name="parameters"> /// <para>Type: <see cref="System.ServiceModel.Channels.BindingParameterCollection"/></para> /// <para>Custom objects to which binding elements have access.</para> /// </param> void IServiceBehavior.AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters) { } /// <summary> /// Provides the ability to change run-time property values or insert custom extension objects such as error handlers, message or parameter interceptors, security extensions, and other custom extension objects. /// </summary> /// <param name="description"> /// <para>Type: <see cref="System.ServiceModel.Description.ServiceDescription"/></para> /// <para>The service description.</para> /// </param> /// <param name="serviceHostBase"> /// <para>Type: <see cref="System.ServiceModel.ServiceHostBase"/></para> /// <para>The host that is currently being built.</para> /// </param> void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase) { IErrorHandler errorHandler; try { errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType); } catch (MissingMethodException e) { throw new ArgumentException("The errorHandlerType specified in the ErrorBehaviorAttribute constructor must have a public empty constructor.", e); } catch (InvalidCastException e) { throw new ArgumentException("The errorHandlerType specified in the ErrorBehaviorAttribute constructor must implement System.ServiceModel.Dispatcher.IErrorHandler.", e); } foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers) { ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher; channelDispatcher.ErrorHandlers.Add(errorHandler); } } } #endregion 

And app.config of my console application:

 <?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.serviceModel> <client> <endpoint address="http://localhost:12345/service/calc" binding="basicHttpBinding" contract="ConsoleApplication2.ICalculator" > </endpoint> </client> <services> <service name="ConsoleApplication2.CalculatorService" behaviorConfiguration="service"> <endpoint address="http://localhost:12345/service/calc" binding="basicHttpBinding" contract="ConsoleApplication2.ICalculator" > </endpoint> <host> <baseAddresses> <add baseAddress="http://localhost:12345/service/calc" /> </baseAddresses> </host> </service> </services> <behaviors> <serviceBehaviors> <behavior name="service"> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration> 

I used the WCF test client to send this request:

 <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://UE.ServiceModel.Samples/ICalculator/Add</Action> </s:Header> <s:Body> <Add xmlns="http://UE.ServiceModel.Samples"> <n1>0</n1> <n2>1</n2> </Add> </s:Body> </s:Envelope> 

and got this answer:

 <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header /> <s:Body> <s:Fault> <faultcode>s:Client</faultcode> <faultstring xml:lang="sk-SK">My exception</faultstring> <detail> <BusinessRuleFaultExceptionType xmlns="http://someurl.temp" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Code>-100</Code> <Reason>xxx</Reason> </BusinessRuleFaultExceptionType> </detail> </s:Fault> </s:Body> </s:Envelope> 

When i called

 Client c = new Client(); var res = c.Add(1, 2); 

I caught the FaultException<BusinessRuleFaultExceptionType> ex which I ran in the console

 Console.WriteLine("This is my Code: {0}. This is the reason: {1}", ex.Detail.Code, ex.Detail.Reason); 

EDIT: I changed the namespaces in BusinessRuleFaultExceptionType and set the solution to use [XmlSerializerFormat(SupportFaults = true)] .

Changed interface, datacontracts and service implementation:

 [ServiceContract(Namespace = "http://UE.ServiceModel.Samples")] [XmlSerializerFormat(SupportFaults = true)] public interface ICalculator { [OperationContract(IsOneWay = false)] [FaultContract(typeof(BusinessRuleFaultExceptionType))] double Add(double n1, double n2); [OperationContract(IsOneWay = false)] [FaultContract(typeof(BusinessRuleFaultExceptionType))] double Subtract(double n1, double n2); [OperationContract(IsOneWay = false)] [FaultContract(typeof(BusinessRuleFaultExceptionType))] double Multiply(double n1, double n2); [OperationContract(IsOneWay = false)] [FaultContract(typeof(BusinessRuleFaultExceptionType))] double Divide(double n1, double n2); } /// <summary> /// General fault structure. /// </summary> [DataContract(Name = "BusinessRuleFaultExceptionType", Namespace = "http://someurl.temp")] [XmlType("BusinessRuleFaultExceptionType", Namespace = "http://someurl.temp")] public sealed class BusinessRuleFaultExceptionType { //[DataMember] [XmlElement(IsNullable = false,Namespace = "http://namespaces2.url")] public int Code { get; set; } [XmlElement(IsNullable = false, Namespace = "http://namespaces2.url")] public string Reason { get; set; } } [ErrorBehavior(typeof(MyErrorHandler))] public class CalculatorService : ICalculator { public double Add(double n1, double n2) { double result = n1 + n2; Console.WriteLine("Received Add({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); throw new FaultException<BusinessRuleFaultExceptionType>(new BusinessRuleFaultExceptionType() { Code = -100, Reason = "xxx" }); //throw new ArgumentException("My exception"); return result; } public double Subtract(double n1, double n2) { double result = n1 - n2; Console.WriteLine("Received Subtract({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); return result; } public double Multiply(double n1, double n2) { double result = n1 * n2; Console.WriteLine("Received Multiply({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); return result; } public double Divide(double n1, double n2) { double result = n1 / n2; Console.WriteLine("Received Divide({0},{1})", n1, n2); Console.WriteLine("Return: {0}", result); return result; } } 

I found an article about the reasons why there are problems using XmlSerializer in IErrorHandler. Therefore, I changed the service implementation to throw a FaultException in the method implementation and not rely on IErrorHandler.

I also found another (relatively old) article on how to use the XmlSerializer in IErroHandler , and after a while I made it possible to serialize even from IErrorHandler. I changed the exception to the trail back to ArgumentException. Here are the changes (I continued with the previous example, so maybe not all codes and attributes are required):

 [DataContract(Name = "BusinessRuleFaultExceptionType", Namespace = "http://someurl.temp")] [XmlType("BusinessRuleFaultExceptionType", Namespace = "http://someurl.temp")] [XmlRoot("BusinessRuleFaultExceptionType", Namespace = "http://someurl.temp")] public sealed class BusinessRuleFaultExceptionType { //[DataMember] [XmlElement(IsNullable = false, Namespace = "http://namespaces2.url")] public int Code { get; set; } [XmlElement(IsNullable = false, Namespace = "http://namespaces2.url")] public string Reason { get; set; } } public class XmlSerializerMessageFault : MessageFault { FaultCode code; FaultReason reason; object details; public XmlSerializerMessageFault(FaultCode code, FaultReason reason, object details) { this.details = details; this.code = code; this.reason = reason; } public override FaultCode Code { get { return code; } } public override bool HasDetail { get { return (details != null); } } protected override void OnWriteDetailContents(System.Xml.XmlDictionaryWriter writer) { var ser = new XmlSerializer(details.GetType()); ser.Serialize(writer, details); writer.Flush(); } public override FaultReason Reason { get { return reason; } } } /// <summary> /// Control the fault message returned to the caller and optionally perform custom error processing such as logging. /// </summary> public sealed class MyErrorHandler : IErrorHandler { /// <summary> /// Provide a fault. The Message fault parameter can be replaced, or set to null to suppress reporting a fault. /// </summary> /// <param name="error">The <see cref="Exception"/> object thrown in the course of the service operation.</param> /// <param name="version">The SOAP version of the message.</param> /// <param name="fault">The <see cref="System.ServiceModel.Channels.Message"/> object that is returned to the client, or service, in the duplex case.</param> public void ProvideFault(Exception error, MessageVersion version, ref Message fault) { BusinessRuleFaultExceptionType f = new BusinessRuleFaultExceptionType { Code = -100, Reason = "xxx" }; // create a fault message containing our FaultContract object FaultException<BusinessRuleFaultExceptionType> faultException = new FaultException<BusinessRuleFaultExceptionType>(f, error.Message); MessageFault faultMessage = faultException.CreateMessageFault(); var msgFault = new XmlSerializerMessageFault(faultMessage.Code, faultMessage.Reason, f); fault = Message.CreateMessage(version, msgFault, faultException.Action); } /// <summary> /// Enables error-related processing and returns a value that indicates whether the dispatcher aborts the session and the instance context in certain cases. /// </summary> /// <param name="error">The exception thrown during processing.</param> /// <returns>true if Windows Communication Foundation (WCF) should not abort the session (if there is one) and instance context if the instance context is not Single; otherwise, false. The default is false.</returns> public bool HandleError(Exception error) { // could use some logger like Nlog but as an example it will do. Console.WriteLine("Error occured. {0}", error); return true; } } 

In both cases, a serialized error:

 <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header /> <s:Body> <s:Fault> <faultcode>s:Client</faultcode> <faultstring xml:lang="sk-SK">My exception</faultstring> <detail> <BusinessRuleFaultExceptionType xmlns="http://someurl.temp" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Code xmlns="http://namespaces2.url">-100</Code> <Reason xmlns="http://namespaces2.url">xxx</Reason> </BusinessRuleFaultExceptionType> </detail> </s:Fault> </s:Body> </s:Envelope> 
+11
source share

I would suggest using IOperationInvoker instead of IErrorHandler. With IOperationInvoker:

  • You can use one try / catch statement. It will work as if all operations were wrapped in try / catch.
  • You do not need to manually create the resulting message.

The implementation in your case might look like this:

 public object Invoke(object instance, object[] inputs, out object[] outputs) { try { return _childInvoker.Invoke(instance, inputs, out outputs); } catch (Exception error) { throw FaultExceptionFactory.CreateFaultException(error); } } 
+2
source share

All Articles