Титры к видео уроку - "Исключения и ошибки. "

Ссылка на полный видео урок

Видео курс WCF Essential

Урок 5. Исключения и ошибки

Здравствуйте. Меня зовут Александр Шевчук. Я сертифицированный тренер Microsoft, работаю в компании CyberBionic Systematics. Я рад Вас приветствовать на информационном видео ресурсе для разработчиков программного обеспечения ITVDN. Тема данного урока «Исключения и ошибки в WCF».

Мы знаем, что в традиционной модели программирования .NET необработанные исключения немедленно завершают процесс, в котором оно произошло. В WCF вот такая традиционная модель обработки ошибок как в .NET не используется. В WCF если вызов службы от одного из клиентов приводит к исключению на стороне сервиса, это на самом деле не нарушает работоспособность всего хостового процесса. Давайте посмотри на этот слайд. Мы знаем, что клиент, прежде чем связаться с сервисом, формирует канал, инициирует, так сказать, формирование коммуникационного канала, по которому и будут циркулировать сообщения. Так вот, представьте себе, что клиент запрашивает на выполнение какую-то операцию, на стороне сервиса, и вот версия одного из экземпляров сервисов приводит к исключению. То, обратите внимание, эта проблема будет распространяться только на одного этого клиента. На всех других клиентов это исключение не распространится. И хостовый процесс, при этом, будет продолжать работать и обслуживать всех остальных клиентов, тех клиентов, у которых исключительной ситуации не произошло. Единственная проблема возникнет вот с тем сервисом консьюмером, который и вызвал такую исключительную ситуацию. При этом есть, так сказать, два варианта. Либо канал падает, либо канал остается функционировать. Чаще всего – канал падает. Мы с Вами дальше посмотрим, как сделать так, что бы удержать канал, что бы на стороне сервиса консьюмера нам не приходилось заново создавать этот канал или не перегенерировать, так сказать, прокси.

Давайте перейдем к следующему слайду. И вот смотрим, здесь имеется такое правило – все исключения, возникшие на стороне службы, по умолчанию всегда достигают клиента в виде так называемого FaultException. Мы понимаем, что когда необработанное исключение выходит из области видимости сервиса, то есть сервис говорит «Вот, у меня возникло исключение» и там, на хосте, имеется механизм, который называется диспетчером. И поэтому вот этот поврежденный сервис говорит диспетчеру «Диспетчер, вот, у меня возникло исключение, пользователь меня о чем-то попросил». И диспетчер берет это исключение, перехватывает его, обрабатывает его и говорит «Ага, сейчас я свяжусь с клиентом и верну ему это исключение, что бы клиент мог понимать, какие его действия привели к ошибке». И вот в итоге, когда возвращаемое исключение, а точнее сообщение, в котором находится информация об исключении, возвращается так называемому посреднику… мы уже говорили, что на стороне клиента вот этот посредник называется послом или удаленным прокси. То уже в этом случае вот этот посредник, вот этот, так сказать, прокси, и инициирует исключение на клиентской стороне и клиент уже получает какое-то уведомление об этом исключении. И исключение, возникающее в результате изъянов в реализации логики службы, или самого владельца, это на самом деле объекты классов, производных от типа Exception, который идет в поставке .NET Framework. И вот такая интероперабельность, то есть сетевое, межсервисное взаимодействие, я бы сказал, достигается за счет сериализации зависящих от платформы деталей исключений согласно общей схеме, которая описываются в спецификации протокола SOAP. Давайте зайдем и посмотрим на следующий слайд. И в этой спецификации упоминается элемент отказа, то есть Fault, который может присутствовать в теле сообщения SOAP. И вот мы видим на слайде, что в отказе SOAP должно быть задано два значения – причина, то есть описание ошибки, и код ошибки. Код может быть либо индикатором, либо одним из предопределенных значений, перечисленных в спецификации SOAP. Подробней со спецификацией Вы можете ознакомиться на этом ресурсе, а мы идем дальше.

И на этом слайде мы с Вами видим три возможных типа исключений, которые могут быть в WCF, точнее три разновидности исключений.

Давайте посмотрим первое – это коммуникационные ошибки, или исключения типа CommunicationException. В каком случае они возникают? Когда недоступна сеть, или сервис консьюмер обратился по несуществующему, неверному адресу, или, например, когда даже адрес правильный, но, к сожалению, хостовый процесс был не запущен. Вот в таком случае будут возникать такого типа исключения. И они называются ошибки коммуникаций, то есть ошибки связи.

Далее, ошибки связанные с состоянием посредника и каналов. Например, упал канал, что-то с посредником, допустим, какие-то проблемы на стороне посредника. Например, попытка обращения к уже закрытому посреднику, когда Вы закрыли прокси, а прокси соответственно мог закрыть и канал, и Вы обращаетесь снова к прокси, а прокси уже всю свою работу закрыл. Это на стороне клиента. Или, например, несоответствие между контрактом. Представьте, что на стороне клиента Вы берете какой-то контракт, который не соответствует контракту на стороне сервиса провайдера. Конечно же, Вам скажут «Нет, мы не будем с Вами общаться по такому контракту». Ну и несоответствие уровням безопасности привязки. Представьте, сервис провайдер работает по TCP протоколу, а Вы пытаетесь к нему обратиться по HTTP, снова же, это неправильно. И вот в таком случае возникают исключения типа ObjectDisposedException.

И последний – это ошибки вязаные с работой самой службы, то есть FaultException. Что это значит? Дело в том, что на стороне службы у нас могут возникать самые разнообразные ситуации. Деление на ноль, попытка открыть несуществующий файл на диске и много-много чего. Допустим, OutOfRangeException – вышли за пределы массива, ну, самые разнообразные исключения, с которыми мы встречаемся в повседневной жизни. Плюс ко всему, если какие-то пользовательские исключения. Вот такая разновидность.

Снова же, давайте посмотрим снова. Коммуникационные ошибки, то есть ошибки связи. Ошибки связанные с состоянием посредника и каналов, то есть на стороне клиента закрыли посредника, закрыли канал и потом пытаемся как-то обратиться. Это такие, на стороне клиента, проблемы клиента. А вот это уже проблемы на стороне сервиса. Именно проблемы, связанные с некой логикой. Либо с пользовательской логикой…ну, вот такие три разновидности ошибок.

И давайте мы сейчас с Вами посмотрим в программном коде на все три разновидности ошибок и попробуем их искусственно инициировать. Перейдем в Visual Studio и посмотрим на этот проект. У нас имеется контракт. Обратите внимание, в теле контракта у нас имеется один абстрактный метод Divide, аргументы делимое, делитель и получаем частное. Заходим в сервис. На 11 строке создаем класс Calculator – это и есть класс нашего сервиса, который реализует контракт ICalculator. И в теле сервиса мы реализуем метод Divide и, обратите внимание, на 15 строке мы выводим хэш-код экземпляра сервиса и на 17 строке мы пытаемся частное от деления аргумента dividend на divisor. И на 10 строке мы указываем, что вот этот сервис будет уровня сессии. Зайдем теперь в хост. Стандартный хост. И смотрите, мы на 19 строке забываем открыть хост, забываем запустить хостовый процесс. Заходим в клиент. А в клиенте у нас все правильно. Обратите внимание, мы здесь создаем канал на классе объекте ChannelFactory, вызываем метод CreateChannel и передаем ему Address, Binding и закрываем его контрактом. И далее в блоке try, на 26 строке, мы пытаемся вызвать метод Divide на стороне сервиса и пытаемся 4 поделить на 0. Если мы посмотрим на аргументы, аргументы у нас целочисленные, и мы понимаем, что арифметика не позволяет нам какие-то целые числа делить на 0. Поэтому, соответственно, мы будем ожидать исключения ошибка деление на 0. Далее мы пытаемся вывести результат деления, но мы уже не дойдем до этой строки, потому что здесь будет сформировано исключение и мы его проглотим в блоке catch. Вот мы принимаем… ну, если бы у нас хост был открыт, мы бы получили исключение деления на 0, но мы-то помним, что у нас хост не открытый, поэтому мы получим какое исключение? Именно коммуникационное исключение. И вот мы выводим его на экран, проверяем, имеется ли у нас какой-то вложенное исключение, и тоже выводим информацию о нем на экран. Поэтому в данном случае не дойдет до исключения деления на 0. Давайте выполнимся через Ctrl F5 и посмотрим, что у нас происходит. На сервере у нас ничего не вывелось, хотя мы там пытались вывести хэш, но, к сожалению, мы ничего не вывели. Посмотрите, вот такое у нас получилось исключение. Внешнее исключение, это ProtocolException, и вложенное исключение, это remote server вернул Method Not Allowed. Мы можем проверить, допустим, тот же самый тип вложенного исключения, можем взять и вывести информацию о нем на экран. ex.InnerException.GetType(), и сейчас выведем на экран, какого же типа вложенное исключение. Еще раз выполнимся. Видим, да. То есть вложенное исключение типа WebException.

Хорошо. А мы перейдем к рассмотрению следующей разновидности исключений, то есть исключений, связанных с состоянием посредника и каналов. И перейдем в Visual Studio. Посмотрим на сторону сервиса, сторона сервиса простейшая, имеется контракт, абстрактный метод, здесь же мы сразу же совместили класс HelloWCF, вот в классе HelloWCF реализовали контракт, в методе main мы создаем хост, запускаем сервис, здесь даже у нас создается блок метаданных. Ну, такая стандартная логика. Вот реализация метода контракта. То есть сервис запускается и работает. Давайте теперь зайдем на сторону клиента. Обратите внимание, что на 11 строке мы создаем экземпляр посредника, вот, мы можем сюда зайти, в HelloWCF, этот код сгенерирован автоматически, мы уже делали такую генерацию с Вами, вот уже идет и реализация метода Say, вот ContractClient – класс, автоматически сгенерирован. И смотрите, что происходит – создаем экземпляр этого класса, вызываем метод Say, он успешно соединяется, отправляет сообщение серверу, сервер что-то с ним делает, и вот на 15 строке мы берем и закрываем посредника, потому что в данном случае вот этот ContractClient является посредником. И далее мы пытаемся обратиться к посреднику, мы его закрыли на 15 строке, и говорим «Посредник, давай снова для нас что-то сделай». Но посредник, к сожалению, уже закрыл канал, он уже не может ничего выполнить. Вы можете теперь только создать нового посредника, который Вам откроет канал. И в блоке catch мы как раз и обрабатываем такую ситуацию. Давайте выполнимся и посмотрим. И вот смотрим, на стороне сервиса, что у нас произошло, и на стороне клиента. Вот «Приложение готово к приему сообщений, объект HelloWCF создан, сообщение получено, тело Hi from sender». И вот у нас имеется, так сказать, SOAP представление сообщения, которое пришло от клиента сервису. Вот, обратите внимание на сторону клиента, мы получили исключение ObjectDisposedException, то есть это исключение связано непосредственно с состоянием самого посредника и его канала. И вот Cannot access a disposed object.

И мы перейдем к рассмотрению следующего, последнего, типа исключений – FaultException. Посмотрим на сторону контракта. Здесь имеется один метод Divide. Сторона сервиса. Обратите внимание, стандартная реализация метода Divide. И на 17 строке мы первый аргумент делим на второй и пытаемся вернуть частное от деления. Зайдем на сторону хоста. Здесь все правильно. Видите, создаем хост, добавляем конечную точку и открываем хост. И заходим на клиент. В клиенте на классе объекте Channel мы создаем канал и пытаемся в блоке try выполнить операцию деления на 0. И далее обрабатываем получившееся исключение в случае, если у нас тут что-то произошло на стороне сервиса. Понятно, что на стороне сервиса у нас возникнет исключение DivideByZeroException. И далее, в следующем блоке try, мы пытаемся еще один раз поделить на ноль. Зачем? Что бы проверить, обратите внимание, мы на том же канале, на одном канале, пытаемся дважды вызвать один и тот же метод. Но здесь, на 24 строке, мы понимаем, что у нас 100% возникнет какие-то исключение в этой программе, и теперь нам интересно, удастся ли нам повторно обратится к сервису по существующему каналу, или канал уже упал. Давайте выполнимся. Вот у нас серверная сторона. Вот у нас клиентская сторона. Обратите внимание, метод был вызван, вывели на экран, на стороне сервера, хэш-код экземпляра сервиса, но, к сожалению, мы получили исключение FaultException. Сказать точно, что мы здесь получили сложно. Мы не знаем, что произошло на стороне сервиса. И вот смотрите – вторая попытка. Вот она, вторая попытка обратится к посреднику, что бы он попытался еще выполнить ту же операцию, снова же привела к исключению. То есть, здесь у нас уже, мы говорим ServiceChannel cannot be used for communication because it is in the Fault state, он находится в состоянии падения, то есть он упал и больше не работает. Ну, что интересно, что мы не знаем, что же произошло на стороне сервиса. Здесь вывелась такая, так сказать, невразумительная информация. В дальнейшем мы научимся оповещать пользователя о тех ситуациях, о тех проблемах, которые произошли на стороне сервиса провайдера. А мы идем дальше.

И рассмотрим исключения, возникающие в службах разного уровня. Обратите внимание, воздействие, оказываемое на клиента и экземпляр службы, зависит от режима управления экземплярами. Мы все помним, что у нас бывают 3 разновидности уровня экземпляров сервисов – это уровень сессии, уровень вызова и синглетный. И так же важно понимать, что WCF не уничтожает хостовый процесс, когда возникает исключение. Но когда экземпляр службы сталкивается с исключение, то ошибка может повлиять на его работу и, соответственно, на способность клиента продолжить использование посредника и связанного с ним канала, который связывает посредника со службой. Поэтому если на стороне сервера возникает какая-то ошибка, то ее нужно обработать очень-очень, так сказать, аккуратно, потому что неизвестно, если смысл нам продолжать работу, а вдруг там поменялись какие-то данные, или наоборот не изменились, и мы продолжаем, например, перечислять деньги или сводить какой-то баланс, и это может в итоге привести к каким-то неприятностям. Например, недоплатить налоги или перечислить больше денег чем положено, все что угодно можно ожидать. Поэтому Microsoft говорит «Мы не будем даже шутить с этим, мы просто завершаем работу, если Вы не можете корректно обработать это исключение».

И давайте перейдем к рассмотрению первой разновидности исключения в службах уровня вызова. Мы помним, что на каждый вызов создается новый экземпляр сервиса. То есть на каждый вызов сервиса консьюмера создается новый экземпляр сервиса провайдера на другой стороне. И вот если в этом экземпляре происходит какое-то исключение, то экземпляр службы уничтожается, а посредник генерирует исключение, FaultException, на стороне клиента. По умолчанию все исключения, которые инициируются службами, они приводят к отказу канала, в итоге канал падает. Все исключения, кроме производных от FaultException. Мы вот чуть-чуть попозже как раз и научимся удерживать канал от падения, что бы он ни упал. Это не сложно. Но по умолчанию канал должен упасть. Почему? Потому что если удаляется экземпляр сервиса и здесь сформируется новый экземпляр, а вот тут уже вызвали 5 методов, ожидаете, что состояние объекта такое вот, в полях записаны такие вот значения, а он уже упал и у него в полях все нули. А Вы продолжаете вызывать пятый, шестой метод, рассчитывая на истинность каких-то значений, а здесь уже все другое, и Вы запутались, и в итоге результаты Ваших расчетов будут неверными и это может повлечь за собой даже штрафные санкции со стороны государства, а кому это нужно. То есть даже если клиент у нас и перехватит исключение, то он не сможет потом делать последующие вызовы на имеющемся канале, потому что такое взаимодействие, вот такое в данном случае взаимодействие, приводит к выдачи исключения, как мы с Вами рассматривали, CommunicationObjectFaultedException. В данном случае клиент, наша программа, может только закрыть вот этого посредника, создать новый и начать с ним работать заново.

И следующая разновидность – исключения в сеансовых службах, то есть службы уровня PerSession. Мы помним, что для каждого консьюмера создается сеанс, или мы сеанс называем сессией, и на хосте живет объект, который в течение определено заданного времени обслуживает консьюмера. И при использовании любой из сеансовых привязок WCF… вот при использовании любой из сеансовых привязок, какие бы у нас небыли, все исключения по умолчанию, снова же, завершают сеанс, так же кроме исключений, производных от FaultException. Мы знаем, что FaultException – это разновидность наших, так сказать пользовательских, обычных исключений, которые не связаны с проблемами конечной точки и хоста, потому что все связанное с проблемами вот здесь, с неправильными настройками, с этим ящиком, это все CommunicationException, так сказать, первая разновидность исключений. Потом могут быть исключения связанные непосредственно с этой стороной, со стороной консьюмера, здесь вторая разновидность исключений. И вот исключения, которые связаны непосредственно с логикой вот этих ящиков, вот этих сервисов, это третья разновидность исключений, по сути, FaultException. И в случае, если здесь возникает исключение, и если клиент даже не перехватит возникшее исключение, то клиент не сможет выдать последующие вызовы, он не сможет обращаться к сервису. Почему? Потому что ему здесь уже отфутболиться исключение CommunicationObjectFaultException, но уже поймет, что все, соединение закрыто, все повреждено и нужно начинать все заново. Когда мы создадим новый экземпляр консьюмера, на стороне, вот здесь, посредника, то новый посредник свяжется с конечной точкой и у него будет вот такой чистенький синенький экземпляр сервиса, а не поврежденный. Поэтому помним, что клиент в этом случае может только закрыть посредника и сеанс дальше уже использоваться не будет. Открываем новый сеанс, или по-другому – новую сессию.

И смотрим еще одну разновидность исключений – это исключения в синглетных службах. А здесь, если исключение происходит при вызове синглетной службы, то синглетный экземпляр службы, он не уничтожается и продолжает работать. Почему? Потому что на нем большая ответственность, на нем еще много других консьюмеров. Вы скажите «Что-то здесь подозрительно». Ничего не надо подозревать. Так как здесь находится синглтон, а мы знаем, что синглтон может быть как нестатический класс, условно, это мы пока просто поковыряемся в это стороне немножко, может быть, допустим, одна из разновидностей синглтонов, одно из выражений паттерна синглтон, это вот статические классы. И соответственно здесь уже идет такая особенная синглетная логика, я не буду углубляться в дескрипторные таблицы прерываний и исключений, это слишком глубоко, нам главное понимать, что в итоге этот сервис не падает. Видите, здесь молния произошла, проблема только на уровне, молния как исключение, проблема только на уровне вот этого канала, всех остальных этот сервис обслуживает. Он устойчивый и поэтому у него проблем нет. И получается, что по умолчанию все исключения, которые происходят в службах, приводят к отказу канала, кроме третей разновидности, когда мы обрабатываем исключения, или генерируем исключения FaultException. И клиент, снова же, не может выдавать последующие действия вызовы, кроме закрытия вот этого посредника. И в нашем случае, снова же, у нас канал падает, а этот сервис провайдер живет, канал падает, мы снова же генерируем нового консьюмера. Ну, понятно, если у клиента существует сеанс с синглетной службой, то этот сеанс, конечно же, завершается.

И было бы теперь хорошо посмотреть на работу в коде. И перейдем к рассмотрению программных кодов. Посмотрим контракт. Контракт уже нам знакомый. Заходим в сервис. Сервис тоже знакомый, мы его помечаем атрибутом ServiceBehavior, указываем уровень PerCall (на каждый вызов будет создаваться новый экземпляр). Смотрим хостинг. Хостинг тоже не изменился. И в клиенте у нас тоже логика простая. Смотрите что мы здесь делам – на 23 строке пытаемся поделить 4 на 0. Понятно, что в данном случае возникнет исключение. И далее мы пытаемся повторно обратится к службе. И давайте теперь посмотрим, что же произойдет. Еще раз зайдем в сервис. Здесь выводится хэш-код. В клиенте мы на экземпляре, на канале, вызываем метод Divide один раз, и вот снова же пытаемся вызвать второй раз. Теперь нам интересно – упадет канал, или не упадет, что бы мы проверили. Ctrl F5. Давайте проанализируем вывод. Видим, первый раз обращаемся – возникает исключение. Но, возникает какое исключение? Давайте еще раз зайдем в сервис. Здесь возникла ошибка деления на 0, исключение связанное именно с логикой. И вот мы видим, что экземпляр отработал, вот его хэш-код. Вывелось исключение, и мы не понимаем, снова же пока, какое возникло исключение. Попытались обратиться второй раз, снова же возникло исключение, только уже коммуникационного характера, и на стороне сервиса ничего даже не сработало. А вот третий вызов, третий вызов в клиенте произошел на новом канале. Видите, мы на 45 строке создаем новый канал и уже вызываем операцию деления 4 на 2, и она у нас происходит, как мы видим, успешно.

И давайте посмотрим еще один уровень. Этот уровень является уровнем PerSession. Смотрим, так же знакомый контракт, сервис. Единственное, мы указали, что экземпляр сервиса будет PerSession. Хост, хостинг стандартный. И снова же, логика клиента у нас не изменилась, все то же самое. Создаем канал, обращаемся, естественно здесь будет ошибка, пытаемся обратиться на существующем канале, канал закрыт, произойдет коммуникационная ошибка, то есть ошибка, связанная со стороной клиента. И здесь, в итоге, мы создаем канал и уже проводим корректную операцию деления. Давайте выполнимся, посмотрим, что в этом случае у нас происходит. Берем сервер, клиент. Первый раз обращаемся – ошибка, потому что на 0 делить нельзя, вот хэш-код экземпляра начальной сессии. Потом обращаемся – получаем CommunicationObjectFaultException, потому что мы на закрытом канале обращаемся. И когда мы создаем новый канал – операция успешная.

И давайте посмотрим еще синглетный уровень. Заходим в Visual Studio. Контракт не изменился, сервис не изменился. Единственное, мы указали уровень экземпляра сервиса как Single, синглтон. Хостинг стандартный. И клиент, та же самая логика клиента. Давайте выполнимся. И мы видим, что на стороне сервиса, несмотря на то, что у нас первое обращение было как исключение, дальше клиент не смог обратится, и когда клиент уже создает новый канал, то он создает канал к существующему синглетному сервису. То есть мы видим, что на сервере живет синглтон, который реально не упал. Почему? Потому что мы видим, что хэш-коды совпадают. А просто к уже существующему сервису провайдеру произошло обращение через новый канал, был сформирован новый канал.

А мы переходим к рассмотрению разновидностей сбоев, которые позволяют нам оставить канал в неразрушенном состоянии. Обратите внимание, для того что бы канал ни упал, нам требуется воспользоваться классом FaultException параметризированным указателем места заполнения типом Tdetail, который наследуется от класса FaultException. И вот давайте посмотрим – указатель места заполнения типом <T>, ну, Tdetail, у FaultException<T> содержит информацию об ошибке, это может быть любой тип, необязательно производный от Exception. И единственное ограничение – тип должен быть сериализуемым или являться контрактом данных. А зачем? Зачем он должен быть сериализуемым? Да очень просто, потому что в итоге диспетчер со стороны сервиса должен каким-то образом передать это исключение на сторону клиента. И мы понимаем, что все сбои основаны на промышленном стандарте, который независимый от исключений, относящихся к конкретной технологии. То есть мы кратко рассматривали протокол SOAP. И снова же, что бы инициировать этот сбой служба должна создать экземпляр FaultException и определенным образом передать информацию, которая в нем находится, на сторону клиента. И также нам нужно организовать такую технику, что бы клиент мог бы как-то обрабатывать эти сервисные сбои. И дальше мы еще перейдем к рассмотрению контрактов. Ну, пока мы посмотрим вот такой ручной способ.

Давайте перейдем к программному коду и посмотрим, как выглядит такая техника. Зайдем в Visual Studio. Посмотрим на контракт, контракт не изменился. Зайдем на сторону сервиса. Ага, здесь у нас появилось что-то дополнительное, сейчас мы к этому вернемся. Хост не изменился. Клиент тоже не изменился, мы видим, что мы на канале вызываем метод Divide, соответственно должно возникнуть исключение на стороне сервиса провайдера, далее мы проверяем падение канала, как подсказывает комментарий – канал не упал. Теперь возвращаемся в сервис и смотрим, что же нужно сделать для того, что бы канал остался жив. На 14 строке мы выводим хэш-код, что бы проверить, что действительно сервис не умер, что он остался жив. На 16 строке мы создаем условную конструкцию, в условии которой мы указываем выражение divisor равен нулю, если условие удовлетворяет истинности, то мы создаем требуемое нам исключение – DivideByZeroException. И на 19 строке мы бросаем исключение типа FaultException закрытое исключение DivideByZeroException, и в качестве первого аргумента само исключение, и дальше передаем то, что попадет в сообщение – «Попытка деления на ноль!». Ну и здесь, соответственно, у нас уже ничего не выполнится, потому что здесь будет брошено исключение. Клиент его получит, сможет нормально обработать и вывести это сообщение на экран. Как это происходит? Очень просто, потому что мы знаем, что на стороне сервиса есть еще такой механизм, который называется диспетчером, этот диспетчер общается с сервисом, и он от сервиса получит вот это исключение, упакует его, преобразует в SOAP формат, отправит клиенту. Клиентский посредник распакует это сообщение, скажет «Ага, там вот у нас исключение такое-то вот», докопается до DivideByZeroException, используя какие-то системы кодогенерации, например CodeDOM, сгенерирует исключение, бросит его нам, на стороне клиента, а клиент его обработает и выведет информацию на экран. Давайте выполнимся. И смотрим, что у нас происходит. У нас здесь как раз возникла ошибочка. Мы не прошли сюда. Так, еще раз. Остановимся и выполнимся еще раз, и посмотрим. Обратите внимание, вот у нас на сервере возникла ошибка, было брошено исключение. Смотрите, клиент корректно обработал это исключение, обратился повторно к существующему каналу. И мы видим, что в данном случае канал не упал. Поэтому, если мы хотим, что бы у нас не произошло падение канал, мы должны самостоятельно, руками, создать исключение, типа FaultException, закрыть его нужным исключение (FaultException<DivideByZeroException> например), снова же, это наше исключение, из нашей логики, и отправить его на сторону клиента.

А мы переходим к рассмотрению следующего примера. И этот пример нам покажет, как мы можем на клиент отправить всю информацию о возникшем исключении, что бы клиент мог посмотреть уже детали вот этого исключения. Смотрим, контракт знакомый. Хост не изменился. Клиент, смотрите, клиент уже обращается к деталям возникшего исключения, он уже пытается проглотить то исключение, которое мы его будем отправлять. И смотрите, FaultException у нас закрыт ExceptionDetail. И давайте перейдем на калькулятор, в сервис, и посмотрим. Обратите внимание, что мы, снова же, проверяем, если делитель ноль, то мы создаем исключение DevideByZeroException, указываем, например, link сервиса, который должен получить информацию об этом исключении, например можно отправить на этот сервис информацию об этом исключении. И теперь на 20 строке мы создаем экземпляр класса ExceptionDetail. Мы видим, что ExceptionDetail предоставляет детальную информацию об исключении. Можем сделать go to definition, посмотреть на содержимое. И дальше на 22 строке мы создаем экземпляр FaultException и закрываем его уже ExceptionDetail. Не нашим исключением, а ExceptionDetail. И, соответственно, ExceptionDetail уже будет переносить в себе полную информацию о возникшем исключении. И если мы сейчас выполнимся… еще давайте зайдем на сторону клиента и посмотрим на сторону клиента. Вот, пожалуйста, клиент теперь обращается и выводит на экран детали, вплоть до того, что там сообщение, HelpLink, допустим, ссылка на сервис исключений. Ну, например, зачем мне разработчику, что бы приходили исключения, если у нас есть Error Server и службы поддержки, или службы качества, поддержки качества, они уже будут сами разбираться со всеми ошибками и составлять какие-то bug репорты, и уже отправлять на bug fixing, или куда-то разработчикам, которые участвовали в разработке того или иного модуля. StackTrace можем даже отправить. Давайте выполнимся и посмотрим. Вот у нас идет попытка деления на ноль, FaultException. Мы видим, что у нас имеется ExceptionDetail. Вот мы выводим «Попытка деления на ноль!», вот идет HelpLink, и вот пошла вся информация, даже server stack trace. Но насколько хорошо так делать, постоянно возвращать. Здесь может возникнуть ситуация, что те же злоумышленники могут воспользоваться вот такой информацией по исключениям для поиска уязвимости в Ваших сервисах. Поэтому не всегда следует так поступать, выдавать полностью всю информацию о типе исключений.

И мы с Вами в итоге понимаем, что предполагается, что клиент все-таки будет перехватывать и обрабатывать какие-то типы сбоев, и когда служба бросает исключение, указанное в контракте, допустим сбоев, то мы видим, что можно организовать работу сервиса так, что это исключение не приведет к отказу канала связи. И клиент может перехватить это исключение и продолжить пользоваться каналом. Для этого, еще раз давайте зайдем в сервис, и посмотрим, что нужно сделать. Нужно создать экземпляр вот этого класса исключения FaultException и закрыть его либо типом нужного исключения, либо типом ExceptionDetail, если мы хотим выдать всю информацию о существующем исключении.

А мы переходим дальше к рассмотрению такого понятия, как контракты сбоев, что это такое и когда они используются. Важно понимать, что если мы будем использовать контракты сбоев, а что значит контракт сбоя? Посмотрите, у нас имеется обычный сервисный контракт, но метод помечен еще одним атрибутом – FaultContract. И мы в качестве, так сказать, позиционного параметра этого атрибута передаем ссылку на экземпляр класса type, который содержит информацию о каком-то исключении, которое мы предполагаем будет инициировано на стороне сервиса. Так вот, когда мы используем контракты сбоев, то мы перечисляем в этом контракте те типы ошибок, которые мы предполагаем могут возникнуть на стороне сервиса. Мы видим, что этот атрибут, FaultContract, применяется непосредственно к контрактным операциям. Ну и зачем вообще используются эти контракты сбоев? Для того, что бы клиент на своей стороне могу по-разному обрабатывать сервисные сбои, только те, которые мы ему укажем. Но при этом, важно понимать, что произойдет падение канала. Обязательно произойдет падение канала. И если мы хотим, что бы канал ни упал, то мы должны вернуться к вот такому подходу – руками создать экземпляр FaultException и бросить его. То есть контракт сбоев сам не будет сохранять канал, и не будет реализовывать всю вот эту логику вот такого исключения, которая позволяет сохранить канал. Поэтому смотрим, в контракте сбоев мы перечисляем те исключения, которые мы готовы обработать, передать их на сторону клиента, и уже клиент пусть там выводить какую-то информацию, что-то делает с ними, как-то анализирует. Ну, собственно, канал будет разрушен. Почему? Потому что мы не полагаемся и не уверены в том, что действительно не произошло какого-то гистерезиса на стороне сервиса. То есть что все данные действительно корректны, потому что и клиент может не уследить, и сейчас может что-то где-то недоделать, а клиенты даже не знают, что делают сервисы. Потому что сервисы разрабатывают одни разработчики, а клиентов разрабатывают другие разработчики. Одни, например, из компании, которая предоставляет валютные котировки, или температуру, а мы уже обращаемся и используем где-то в своих программах такую информацию. Поэтому, когда мы используем контракты сбоев, то мы предполагаем, что клиент будет все-таки их перехватывать и обрабатывать те сбои, которые произойдут на сервисе. И когда служба будет инициировать исключение, которое указано в контракте сбоев, то исключение все равно приведет к отказу канала связи. Это важно понимать. Но, клиент все равно может перехватить исключение и каналом воспользоваться в дальнейшем он не может.

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

Давайте перейдем к программным кодам и посмотрим на реализацию контрактов сбоев. Смотрим на сторону контракта. Обратите внимание, на 12 строке мы помечаем его как контракт сбоев, то есть мы будем контролировать исключение DivideByZeroException на стороне сервиса. Зайдем в калькулятор, в сервис. Обратите внимание, что на 10 строке мы указываем, что у нас идет PerSession уровень сервиса, и здесь желательно свойству IncludeExceptionDetailInFaults присвоить значение true. Далее выводим хэш-код. И после отправки сообщения об ошибке сервису консьюмеру произойдет разрушение коммуникационного канала. И мы с Вами понимаем, что бы этого не произошло, мы должны, в данном случае, снять комментарии с этого кода. Смотрим, хост самый обычный, знакомый. И клиент так же самый обычный и знакомый. И здесь в клиенте мы обращаемся к методу Divide на стороне сервиса провайдера, конечно же, здесь у нас произойдет исключение, мы его здесь обработаем, сейчас мы увидим, что выведется. И далее мы должны понимать, что коммуникационный канал был разрушен, и мы не сможем более обратиться. Вот, еще раз зайдем в калькулятор. То есть, таким образом, все те типы исключений, которые мы перечислим в fault контракте, мы сможем их, эти исключения, корректно инициировать на стороне сервиса и передавать информацию об них клиенту, а клиент уже сможет обрабатывать их. Но при этом мы разрушаем канал. И поэтому все уже зависит от того, Вы можете комбинировать и устойчивость канала, и fault контракты вместе. Можете использовать что-то одно. Без fault контракта, допустим, просто удерживать канал, можете и то, и другое вместе. Давайте выполнимся и посмотрим. Вот мы видим, что у нас создан был экземпляр сервиса, была создана сессия, Attend to divide by zero, это нам вернулось по умолчанию, благодаря использованию fault контракта, контракта ошибок. И повторное обращение вывело, что мы не можем больше обратиться, потому что уже в состоянии Faulted, канал упал.

И давайте перейдем теперь к выводам, выводам сегодняшнего урока. И самое важное, это понимать, что же произойдет, если не предпринимать никаких действий по обработке исключений, по инициации исключений. Мы с Вами видели, что по умолчанию необработанные исключения может привести к разрыву сеанса и сделать клиентские прокси недействительными, за счет падения канала. Так же мы познакомились с важным исключением Fault Exception, которое помогает нам абстрагировать именно детали схемы отказов SOAP. И эта схема применяется, как мы видели, что бы сериализовать информацию об исключении и передать ее клиенту, здесь активно участвует диспетчер сервиса провайдера, который взаимодействует непосредственно с самим сервисом провайдером. Ну и, конечно же, когда мы создаем WCF службы и клиентские приложения, то для них следует продумывать, так сказать, правильные механизмы обеспечения, такого вот, предсказуемого поведения этой службы и одновременно надежной работы клиентов, которых должно было бы… что бы их было удобно сопровождать.

На этом наш урок закончен. Спасибо за внимание! С Вами был Александр Шевчук. До новых встреч на ITVDN!

© 2017 ITVDN, все права защищены