WCF: предоставить универсальное исключение FaultException в IErrorHandler

Немного контекста: у нас есть собственный XSD и мы генерируем код WSDL и C#, используя WSCF.blue. Клиентская сторона использует ChannelFactory<T> и разделяет интерфейс, который включает все атрибуты, добавленные WSCF.blue, чтобы соответствовать тому, что находится в XSD.

Я пытаюсь реализовать IErrorHandler.ProvideFault где он предоставляет общий FaultException<T>, но на стороне клиента я получаю не универсальный FaultContract, Это то, что мой ProvideFault Метод выглядит так:

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);
    }
}

В каждом методе обслуживания, если я делаю попытку / поймать с throw FaultExceptionFactory.CreateFaultException(ex) это работает, как и ожидалось, поэтому я думаю, [FaultContract], фабрика, крепления и т. д. все правильно. На всякий случай вот так работает фабрика:

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())
);

Я думаю, что проблема в том, как сообщение создается в IErrorHandler возможно в CreateMessageFault(), Я прочитал, что действие должно быть faultException.Action вместо null, но по факту faultException.Action является null, Может быть, это вызывает проблему. Я могу установить действие на заводе, но каким должно быть это действие, и почему оно не появится при ручном броске?

Любые другие идеи, которые я мог бы упустить?

Изменить: я проверил WSDL и нашел конкретную операцию, которую я вызывал, и ее действие:

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

Я старался кодировать действие: Message.CreateMessage(..., "http://myNamespace/MyMethodBusinessRuleFaultExceptionTypeFault") и установив его на .Action на фабрике, но это все еще не сработало.

Редактировать 2: throw/catch генерирует следующий XML, который позволяет перехватить общее исключение на клиенте:

<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 генерирует следующее, что идет к неуниверсальному 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>

Изменить 3: если я добавлю [DataContract] а также [DataMember] к BusinessRuleFaultExceptionType тогда я получаю почти правильный 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>

В нем отсутствует пространство имен для кода и причины. По крайней мере, я думаю, что это сужает это. Регулярная сериализация использует [XmlType] а также [XmlElement] в то время как IErrorHandler использует [DataContract] а также [DataMember], к несчастью [DataMember] не позволяет вам установить пространство имен, поэтому я думаю, что теперь вопрос заключается в том, как использовать XMLSerializer в IErrorHandler, Это описывает мою проблему, но исправление не будет работать по причине, указанной выше: http://twenty6-jc.blogspot.com/2011/05/ierrorhandlerprovidefault-serialization.html

Редактировать 4: я частично нашел проблему. Мы используем XmlSerializer, но с тех пор IErrorHandler находится за пределами действия операции, возвращается к значению по умолчанию DataContractSerializer, Решение состоит в том, чтобы либо изменить свой сервис для использования DataContractSerializer везде или вручную выбрать XmlSerializer при создании неисправностей. Эти две статьи предоставили то, что мне было нужно:

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

Это сближает меня. Он такой же, как рабочий XML, за исключением того, что отсутствует пространство имен типа исключения:

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

Я предполагаю, что это не добавляет xmlns="http://myNamespace/Services" потому что у него нет контекста запроса. Это пространство имен определено в интерфейсе, но не в контракте данных. Я действительно буду вынужден оставаться в контексте запроса (надеюсь, IOperationInvoker работает) вместо использования IHttpHandler?

2 ответа

У меня нет опыта работы с WSCF.blue, поэтому я попытался создать пример приложения для демонстрации рабочего сценария с использованием стандартного клиента и сервера WCF. Возможно, это поможет вам найти недостающие соединения, чтобы ваш сценарий работал.

Я использовал ваш BusinessRuleFaultExceptionType со свойствами Code и Reason. BusinessRuleFaultExceptionType является контрактом WCF по вине.

Я был немного ленив и реализовал весь код в одном консольном приложении. Клиент Wcf использует те же Datacontracts и ICalculator интерфейс как сервис Wcf. Извините за код. Это будет длинный пост.

Первый Datacontracts и сервисный интерфейс

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; }
}

Теперь реализация сервиса:

[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;
    }
}

И клиентская реализация:

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();
    }
}

Основная программа для демонстрации этого примера

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();
    }

}

Реализация обработки ошибок:

/// <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's 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

И app.config моего консольного приложения:

<?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>

Я использовал тестовый клиент WCF, чтобы отправить этот запрос:

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

и получил этот ответ:

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

Когда я позвонил

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

Я поймал FaultException<BusinessRuleFaultExceptionType> ex который я вошел в консоль

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

РЕДАКТИРОВАТЬ: я изменил пространства имен в BusinessRuleFaultExceptionType и установите решение для использования [XmlSerializerFormat(SupportFaults = true)],

Изменен интерфейс, дата-контракты и реализация сервиса:

[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;
    }
}

Я нашел статью о причинах возникновения проблем с использованием XmlSerializer в IErrorHandler. Поэтому я изменил реализацию службы, добавив FaultException в реализацию метода и не полагаясь на IErrorHandler.

Я также нашел другую (относительно старую) статью о том, как использовать XmlSerializer в IErroHandler, и через некоторое время я сделал возможным сериализацию даже из IErrorHandler. Я изменил выброшенное исключение обратно на ArgumentException. Вот изменения (я продолжил предыдущий пример, поэтому, возможно, не весь код и атрибуты обязательны):

[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;
    }
}

В обоих случаях сериализованная ошибка была:

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

Я бы предложил использовать IOperationInvoker вместо IErrorHandler. С IOperationInvoker:

  • Вы можете использовать один оператор try/catch. Это будет работать так, как будто все операции заключены в try/catch.
  • Вам не нужно будет вручную создавать результирующее Сообщение.

Реализация в вашем случае может выглядеть так:

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);
    }
}
Другие вопросы по тегам