Из цикла лекций «Internet-технологии разработки приложений» для студентов 4-го курса кафедры Компьютерных технологий физического факультета Донецкого национального университета проф. В. К. Толстых WCF-службы Обработка ошибок Исключение FaultException Передача нестандартных деталей в исключении Централизованная (расширенная) обработка ошибок ДонНУ, кафедра КТ, проф.В.К.Толстых Концепция обработки ошибок Если в сервисе происходит исключение, то клиенту не обязательно знать обо всех тонкостях этого исключения, ему важен факт ошибки и основная причина ее возникновения. Все тонкости важны только на этапе отладки. Исключения, возникающие в WCF-сервисе, не нарушают работу хостового процесса, а также работу других запущенных сервисов и клиентов, не имеющих отношения к этим исключениям. Когда необработанное исключение выходит из области видимости сервиса, диспетчер канала перехватывает и обрабатывает его, возвращая в сериализованном виде в SOAP-сообщении клиенту. Когда сообщение попадает к посреднику (к прокси клиента), последний инициирует исключение на стороне клиента. Ex0 – первоначальное исключение, возникшее в сервисе, Ex1 – исключение, предназначенное для передачи клиентской стороне. Ошибки, получаемые клиентом сервиса При обращении к сервису, клиент может столкнуться с тремя типами ошибок: • Коммуникационные ошибки. Возникают, например, когда нет подключения к сети, когда указан неправильный адрес сервиса, когда не запущен host-процесс и т.д. Эти исключения определены на клиентской стороне классом исключения CommunicationException. • Ошибки состояния канала. Связаны с состоянием каналов и прокси клиента. Такой ошибкой может быть, например, попытка доступа к уже закрытому прокси, которая оканчивается исключением класса ObjectDisposedException, или, например, несоответствие между контрактом и уровнем безопасности привязки. • Ошибки запросов. Происходят при работе сервисов. Возникающие исключения CLR не передаются за пределы сервисов, они преобразуются в сбои (faults) – нейтральная информация для клиентов. Для возврата клиенту в SOAPсообщении сбои сервисов сериализуются. Иерархия наследования от System.ServiceModel.CommunicationException в клиентах сервисов ActionNotSupportedException AddressAccessDeniedException AddressAlreadyInUseException Channels.RedirectionException Channels.RetryException ChannelTerminatedException CommunicationObjectAbortedException CommunicationObjectFaultedException Dispatcher.MessageFilterException EndpointNotFoundException FaultException Persistence.PersistenceException PoisonMessageException ProtocolException Security.MessageSecurityException Security.SecurityAccessDeniedException SSecurity.SecurityNegotiationException ServerTooBusyException ServiceActivationException Исключение FaultException возникает когда прокси клиента получает не типизированную (не указанную в контракте операции) ошибку в SOAP. Оно должно перехватываться до обработки CommunicationException. Не типизированные ошибки отправляются клиенту при соответствующей настройке отладки поведения службы в файле конфигурации (см. далее). Исключения и экземпляры службы Воздействие, оказываемое исключениями на клиента и на экземпляр службы, зависит от режима управления экземплярами. Службы уровня вызова: Экземпляр службы уничтожается, посредник инициирует исключение FaultException на стороне клиента, канал связи закрывается. Даже если клиент перехватит исключение, то последующие вызовы приведут к CommunicationObjectFaultedException. Клиент только может закрыть посредника. Сеансовые службы: - аналогично службам уровня вызова, сеанс завершается. Синглетные службы: Экземпляр службы не уничтожается и продолжает работать, канал связи закрывается, клиент только может закрыть посредника. Прокси клиента, при получении сериализованных ошибок запроса, десериализует их и генерирует иерархические исключения классов: FaultException<TDetail>, FaultException и CommunicationException. Ошибки запросов принимаются клиентом при двухстороннем взаимодействии т.е. при [OperationContract(IsOneWay = false)] соответствующего метода. Для односторонней привязки WSHttpBinding , если не задано явно IsOneWay=true, то автоматически организуется второй HTTPканал. Контракты сбоев При помощи контрактов сбоев служба перечисляет типы ошибок которые она может инициировать. WCF-клиент по контракту сбоев отличает контрактные ошибки от других. Атрибут FaultContract применяется непосредственно к контрактным операциям, и в нём указывается тип детализации ошибки, например: [ServiceContract] public interface IServiceName { [OperationContract(IsOneWay = false)] [FaultContract(typeof(FaultException))] string GetData(int value); [OperationContract(IsOneWay = false)] [FaultContract(typeof(DivideByZeroException))] int DivideInt(int n1, int n2); } Исключения в методах служб должны использовать точно такой же тип как указано в контракте сбоев. Для данного примера метод DivideInt() может инициировать только исключение FaultException<DivideByZeroException>. Не забывайте после изменения методов и контрактов настраивать посредника у клиента, например, удалить и заново создать ссылку на WCF-службу. Настройка поведения службы для FaultException Для передачи исключений типа FaultException клиенту необходимо в поведении службы настроить следующий «отладочный» режим: <behaviors> <serviceBehaviors> <behavior name="MyBehavior"> <serviceMetadata httpGetEnabled="true"/> <!-- Передавать клиенту исключения --> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> Не типизированные ошибки службы (клиентские исключения типа FaultException) возвращают объект-строку, которая не требуют сериализации. Клиент получает информацию об ошибке сервиса в свойстве Message исключения FaultException. Обычно, этих средств достаточно для отладки сервиса и нет необходимости создавать и организовывать сериализацию специализированного объекта типа TDetail. Пример клиента SampleServiceClient wcfClient = new SampleServiceClient(); try { // Вызов сервиса… } catch (TimeoutException timeProblem){ Console.WriteLine("Timed out " + timeProblem.Message); wcfClient.Close(); Console.ReadLine(); } // Перехватить неизвестное исключение типа FaultException catch (FaultException faultEx){ Console.WriteLine(" Неизвестное исключение "+faultEx.Message+faultEx.StackTrace); Console.Read(); wcfClient.Close(); } // Перехватить стандартное исключение типа CommunicationException catch (CommunicationException commProblem){ Console.WriteLine("Ошибка подключения " + commProblem.Message + commProblem.StackTrace); Console.Read(); wcfClient.Close(); } Пример 1 службы Контракт службы: Помещаем службу в пространство имён WcfTest Создаём для SOAP службы непредвиденные сбои метода GetData() Метод службы Генерируем исключение Проект службы: Конфигурация службы Блок в конфигурация <behaviors> идентифицирован как ServiceNameBehaviors Служба ServiceName в пространстве имён WcfTest Идентичность рабочей точки Рабочая конечная точка службы Дополнительные (нестандартные) настройки привязки Блок в конфигурация поведения ServiceNameBehaviors Передавать клиенту подробности ошибки, возникающие в службе ServiceName В случае false клиент получит сообщение: Серверу не удалось обработать запрос из-за внутренней ошибки. Клиент для примера службы Код клиента Не используйте оператор using(ServiceRef.ServiceNameClient proxy = ...) для создания proxy при возврате ошибок, канал посредника будет закрыт раньше времени. Никогда не используйте экземпляр посредника после исключения, даже если оно было перехвачено. Пример 2 службы Метод службы: Контракт службы: Клиент службы: Не забудьте заранее компилировать службу. Передача нестандартных деталей в исключении Через исключение FaultException можно вернуть клиенту любой объект с деталями сбоя. Например, вы сохраняете детали сбоя посредством объекта Data типа IDictionary, представляющего собой массив пар key/value: exception.Data.Add("code", "205"); exception.Data.Add("message", "Внутренняя ошибка"); В нужном месте WCF необходимо сгенерировано исключение FaultException с передачей объекта типа IDictionary: throw new FaultException<System.Collections.IDictionary>(exception.Data); Контракт сбоев данной операции при этом должен иметь атрибут того же типа: [FaultContract(typeof(System.Collections.IDictionary))] Теперь клиент может получить детали исключения посредством: catch (FaultException<System.Collections. Generic.Dictionary<object,object>> e) { Label1.Text = e.Detail["code"]; } Расширение обработки ошибок WCF позволяет разработчикам не только настраивать заданную по умолчанию обработку исключений, но и определять собственные обработчики в различных точках расширения функциональности WCF во время выполнения. Для создания собственного обработчика ошибок необходимо реализовать интерфейс IErrorHandler: public class MyErrorHandler: IErrorHandler { void ProvideFault( Exception error, MessageVersion version, ref Message fault); bool HandleError(Exception error); } Этот обработчик связывается с диспетчером канала, после чего появляется возможность перехватывать и обрабатывать все исключения, которые возникают в сервисе. Методы ProvideFault и HandleError Метод ProvideFault() вызывается сразу после того, как происходит исключение. Если реализация метода будет «пустой», то входное исключение не будет изменено. Для предоставления альтернативного исключения необходимо создать собственное сообщение об ошибке, которая и будет передана клиенту. Суть метода заключается в преобразовании входного исключения в некоторую альтернативную форму, которая распространится дальше до клиента. Метод HandleError() вызывается после передачи управления клиенту и выполняется в отдельном потоке, нежели клиентский запрос и преобразование исключений ProvideFault(). В связи с этим, обработка ошибок может осуществляться даже после передачи управления клиенту, поэтому время её выполнения не столь критично. Метод HandleError() не оказывает никакого воздействия на клиентское приложение . Это удобный способ централизованного ведения лога ошибок сервиса. Пример реализации обработчиков ошибок public class MyErrorHandler : IErrorHandler { bool HandleError(Exception error) { try { MyServiceLogging.Log(error); } catch { } // не останавливать вызов расширенной обработки исключений: finally { return false; } } public void ProvideFault( Exception error, MessageVersion version, ref Message fault) { // Пример – анализ входного исключения – деление на ноль: if (error is DivideByZeroException) { // формирование сообщения с исключением для клиента (детали не включаем) FaultException<DivideByZeroException> faultException = new FaultException<DivideByZeroException>(null, error.Message); MessageFault messageFault = faultException.CreateMessageFault(); fault = Message.CreateMessage(version, messageFault, faultException.Action); } ... } } Установка расширенной обработки ошибок Диспетчер канала WCF имеет коллекцию обработчиков ошибок Collection<IErrorHandler>. Чтобы установить собственную реализацию интерфейса IErrorHandler, требуется лишь добавить ее в эту коллекцию. Для этого в реализации сервиса должен быть использован атрибут ErrorHandlerBehavior : [ErrorHandlerBehavior(typeof(MyErrorHandler))] public class MyService : IMyContract { ... } Код реализации данного атрибута необходимо добавить в класс сервиса. Исходный код можно взять, например, в статье http://www.rsdn.ru/article/dotnet/FaultsWCF.xml Источники • Джувел Лёве. Создание служб Windows Communication Foundation. – СПб.: Питер, 2008 . – 592 с.: ил. • http://msdn.microsoft.com • http://www.rsdn.ru/article/dotnet/FaultsWCF.xml