Бросок FaultException с использованием обработчика пользовательских исключений EL WCF

Поэтому я пытаюсь использовать Enterprise Library в своем сервисе WCF, чтобы выполнить для меня часть работы, связанной с исключениями.

Моя идея состоит в том, чтобы установить "Обработчик пользовательских исключений", скажем, "NullReferenceException" и в "Обработчике пользовательских исключений" создать исключение FaultException.

Насколько я понимаю, это "новое" исключение затем будет распространяться по проводам, и я поймаю его на клиенте.

Некоторый код для лучшего понимания:

WCF сервис:

[ServiceContract(Name="MyService", ConfigurationName="MyNamespace.MyService")]
    [ExceptionShielding("PolicyName")]
    public interface IMyService
    {
        [OperationContract]
        [FaultContract(typeof(MyFaultContract))]
        string Method(String Param);
}

public class MyService : IMyService
    {
        public string Method(String Param)
        {
          throw new  NullReferenceException("code-created null message here");
        }
}

Пользовательский обработчик исключений: (Корпоративная библиотека)

public class MyExceptionHandler : IExceptionHandler
    {
        public MyExceptionHandler(NameValueCollection collection) { }

        public MyExceptionHandler() { }

        public System.Exception HandleException(System.Exception exception, 
                              Guid handlingInstanceId)
        {
                MyFaultContract details = new MyFaultContract();
                if (exception is NullReferenceException)
                {
                    details.ErrorCode = MyFaultCode.NullReferenceException;
                    details.OriginalMessage = exception.Message;
                    details.MyMessage = "Null Reference exception here!";
                }
                return new FaultException<MyFaultContract>(details);
            }
}

Файл конфигурации приложения, который отображает исключение NullReferenceException на "Обработчик пользовательских исключений":

<exceptionPolicies>
      <add name="PolicyName">
        <exceptionTypes>
          <add name="NullReferenceException" type="System.NullReferenceException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
            postHandlingAction="ThrowNewException">
            <exceptionHandlers>
              <add type="MyExceptionHandler, MyApplication, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
                name="MyExceptionHandler" />
            </exceptionHandlers>
          </add>
        </exceptionTypes>
      </add>
    </exceptionPolicies>

И, наконец, код клиента, который ожидает перехват этого FaultException:

MyService.MyServiceClient client = new MyService.MyServiceClient();
            client.Open();
            try
            {
                string result = client.Method(string parameter);
            }

            catch (System.ServiceModel.FaultException<MyService.MyFaultContract> ex)
            {
                // where I think exception should end up
            }
            catch (System.ServiceModel.FaultException ex)
            {
                // general FaultException
            }

Но вместо этого я получаю ProtocolException, заявляя, что требуется ответное действие:

Получено ответное сообщение для операции "Метод" с действием ". Однако ваш клиентский код требует действия "http://tempuri.org/MyService/MethodResponse".

Что я делаю неправильно? Можно ли вернуть (не явно выдать) FaultException с пользовательским FaultContract в "Обработчик пользовательских исключений"?

Любой совет приветствуется.

Обновление: Как вы можете видеть, действие постобработки в "Обработчике пользовательских исключений" установлено в "ThrowNewException". Если я изменяю его на "NotifyRethrow", я больше не получаю "ProtocolException"! Вместо этого клиент ловит обычное "FaultException" (не пользовательский тип).

Теперь вопрос заключается в том, почему оригинальное FaultException с пользовательским типом не проходит по проводам.

Обновление 2 Одна вещь, которую я забыл упомянуть, это то, что моя служба WCF работает в ServiceHost, а не в IIS. Так что в основном у меня есть служба Windows, которая создает ServiceHost и предоставляет интерфейс Service1 через этот ServiceHost.

Причина, по которой я думаю, что это может быть как-то связано с этой проблемой, заключается в том, что Enterprise Library утверждает, что имеет дело с исключениями во всем приложении, а не только в границах служб. Может быть, это приведет к тому, что исключение будет выдано слишком поздно? Или не на правильном уровне?

Обновление 3
Спасибо за пост! Вы правы, единственная причина, по которой я возлюсь с пользовательским обработчиком, заключается в том, что я хочу установить значение MyFaultCode. Я попробовал ваш совет - настроил 2 обработчика.

Первый - это пользовательский обработчик, который перехватывает исключение NullReference. Затем создается новое исключение - MyApplicationException с полем MyFaultContract.

Затем я настроил второй обработчик - встроенный "обработчик исключений Fault Contract", который перехватывает MyApplicationException, создает новое FaultException и автоматически сопоставляет MyFaultcontract из MyApplicationException с вновь созданным FaultException.

Клиент WCF по-прежнему перехватывает общее исключение FaultException, а не пользовательское соглашение.

2 ответа

Решение

Я хотел бы рассмотреть возможность использования пользовательской реализации IErrorHandler WCF. Он предоставляет возможность централизованно обрабатывать все необработанные исключения, возникающие в ваших методах обслуживания.

По моему мнению, это в конечном итоге чище, чем при использовании Ent Lib, на 1 меньше зависимости, и вам не нужно такое отображение ошибок в вашей конфигурации, а значит меньше XML.

Пример:

public class MyServiceErrorHandler : IErrorHandler
{
    /// <summary>
    /// Central error handling for WCF services.
    /// Whenever a service encounteres an unhandled exception, it will end up here.
    /// This method will log the error and continue with normal error processing.
    /// </summary>
    /// <param name="error">The unhandled exception thrown from a service method.</param>
    /// <returns>true if the exceptions has been handled, false if normal error processing should continue. This implementation will always return false.</returns>
    public virtual bool HandleError(Exception error)
    {
        return false; // returning false so that WCF will still process the exception as usual.
    }

    /// <summary>
    /// Create a custom Fault message to return to the client.
    /// </summary>
    /// <param name="error">The 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 Message object that is returned to the client, or service, in the duplex case.</param>
    public virtual 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;

        // pack the exception info into the Fault Contract
        MyFaultContract details = new MyFaultContract();
        if (exception is NullReferenceException)
        {
            details.ErrorCode = MyFaultCode.NullReferenceException;
            details.OriginalMessage = error.Message;
            details.MyMessage = "Null Reference exception here!";
        }
        var faultException = new FaultException<MyFaultContract>(details);

        // turn the fault contract into the response WCF Message.
        var messageFault = faultException.CreateMessageFault();
        fault = Message.CreateMessage(version, messageFault, faultException.Action);
    }
}

Конечно, если у вас есть другие причины хотеть использовать Ent Lib, тогда продолжайте. Вы, вероятно, можете как-то связать их обоих.

Эта ссылка также имеет хорошее чтение по обработке ошибок, включая добавление пользовательского поведения IErrorHandler: http://www.codeproject.com/KB/WCF/WCFErrorHandling.aspx

Я собирался сделать это комментарий, но это слишком долго.

Рассматривали ли вы использование защиты от исключений для блоков обработки исключений в обработчике исключений контракта отказа? Я думаю, что это делает то, что вы хотите. http://msdn.microsoft.com/en-us/library/ff953192(v=PandP.50).aspx

Подход заключается в создании собственного обработчика для сопоставления всех свойств с вашим пользовательским типом исключения. Затем настройте Fault Contract Exception Handler для сопоставления свойств в вашем исключении со свойствами в договоре об ошибках.

Если вам не нужно устанавливать значение для MyFaultCode, вы можете избежать написания собственного обработчика.

Другие вопросы по тегам