Титры к видео уроку - "Проектирование и определение контрактов."

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

Видео курс WCF Essential

Урок 3. Проектирование и определение контрактов

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

И давайте дадим определение контракту. Контракт, в WCF, это соглашение между двумя и более сторонами о формате сообщений, которыми эти стороны обмениваются. Мы с Вами знаем, что технически контракт представляет собой интерфейс, помеченный специальными атрибутами. Логически контракт представляет собой некую сущность, которая описывает сообщение, передаваемое конечным точкам службы и те сообщения, которые возвращаются службой. Каждая конечная точка определяется своими A B C (Address, Binding, Contract). То есть адресом в сети, куда посылаются сообщения, привязкой, которая описывает способ передачи сообщений, и, конечно же, контрактом, в котором оговорены форматы сообщений.

Давайте перейдем к следующему слайду и посмотрим, какие же контракты бывают в WCF. WCF, как мы видим, определяет 4 разновидности контрактов: контракты служб, контракты данных, контракты ошибок и контракты сообщений. С контрактами служб мы уже немного знакомы и сегодня познакомимся более детально, а вот с контрактами данных, с контрактами ошибок и с контрактами сообщений мы еще не знакомы вовсе. Мы уже знаем, что контракты служб описывают операции, которые могут вызываться клиентом на сервисе. Ну, с использованием техники RPC (Remote Procedure Call). Контракты данных. Контракты данных у нас определяет, какие типы данных принимаются службой и передаются службе. И WCF определяет косвенные контракты данных для встроенных типов, таких как int, float, string и другие, то есть все примитивные встроенные типы уже имеют по умолчанию этот специальный контракт. Контракты ошибок. Контракты ошибок определяют, какие ошибки инициируются службой, как служба обрабатывает эти ошибки, как служба передает эти ошибки своим клиентам. И здесь используются так называемые fault контракты. У нас будет отдельный урок по такого вида контрактам. И последнее – контракты сообщений. Они позволяют службам напрямую взаимодействовать с сообщениями. Когда мы работаем с контрактами сообщений мы формируем некое объектно-ориентированное представление сообщений. В первую очередь, это добавляем нам немножко удобства, но, конечно же, отводит нас от, так сказать, вот этой сервисной парадигмы, точнее даже от предметной парадигмы, когда мы каждый сервис представляем собой как некую автономную сущность и при работе с сообщениями нам приходиться переключатся на техническое представление. Это как, например, если бы мы представляли взаимодействие друг с другом, то есть представляли бы людей и говори «Люди не общаются друг с другом, не говорят что-то друг другу, а обмениваются сообщениями в формате звуковых волн». Я говорю «Вот, пошли мне звуковые волны, в которых закодирована информация, допустим, о том-то том-то том-то». Не совсем удобно, но иногда требуется рассматривать человеческую речь и в формате звуковых волн.

И давайте перейдем к следующему слайду, и рассмотрим один интересный язык, который называется WSDL, то есть Web Service Description Language. По сути, этот язык является языком описания веб служб. Что бы контракты были интероперабельными, ну, интероперабельные, это значит, что бы могли взаимодействовать с максимально широким диапазоном систем, они должны быть выражены на языке WSDL. Дело в том, что при работе с WCF, если наши сервисы будут располагаться только на системах Windows, то нам даже не надо обращать внимание на WSDL формат. То есть он является для нас необязательным, потому что в нашем случае контракт выступает как набор определений типов .NET. Так вот, WSDL, в первую очередь, это формат xml для описания сетевых служб. Но контракты WCF являются, в первую очередь, определениями типов .NET, которые аннотированы специальными атрибутами, мы это уже видели. И эти аннотированные определения, определения типов, могут быть использованы для генерации документа в стандарте WSDL, в том случае, если нам надо наладить именно интероперабельность, то есть взаимодействие с какими-то другими системами, отличными от Windows. По этому, мы не будем делать сегодня акцент на WSDL. Раньше, когда мы использовали ASP веб сервисы, там надо было делать акцент на WSDL, сегодня же с ним работа полностью скрыта и нам даже не надо обращать внимание на то, что он существует. Ну, скажу так, в большинстве случаев не нужно обращать на него внимание. Но знать, что такой язык существует, требуется.

И дальше мы рассмотрим некоторые особенности создания контрактов служб. Уже самые простые контракты служб мы создавали в предыдущих уроках и давайте теперь вспомним основные моменты. Мы видим, что контракты служб описывают операции, которые могут вызываться клиентом на сервисе. Сервисные контракты, мы знаем, описывают сам сервис. Это описание включает в себя определений операций сервиса и, так называемый, MEP (Message Exchange Patterns), то есть шаблоны обмена сообщений. Вот такой термин, мы с ним будем знакомиться немножко позже.

И начнем мы с рассмотрения такой особенности, как работа перегрузки методов в контрактах. Обратите внимание на этот слайд, этот слайд нам говорит о том, что в WCF чистая перегрузка операций недопустима. Давайте вот посмотрим. Мы создаем контракт и пытаемся создать два одноименных метода, которые отличаются типом аргументов. В обычном объектно-ориентированном программировании это вполне допустимая конструкция. Но, как мы видим, если мы попытаемся с таким контрактом выполниться, то у нас будет исключение InvalidOperationException, «Нельзя иметь две операции с одинаковым именем в одном контракте. Методы Add и Add в типе Client.IInterface нарушают это правило. Можно изменить имя одной из операций путем изменения имени метода или с помощью » внимание « Name атрибута OperationContractAttribute». Вот, давайте посмотрим.

А вот как раз то, что нам посоветовало описание, мы это и делаем. То есть мы, в данном случае, решили организовать перегрузку методов вручную. При этом имена методов, непосредственно имена методов в контракте мы оставили такие, как и были – Add и Add, но мы указали именуемые параметры атрибута OprationContract, видите, вот такими. Мы сказали, что хотим, что бы этот метод в итоге назывался как AddInt, а этот метод как AddDouble. Еще раз посмотрите, для, так сказать, ручной перегрузки в свойстве Name атрибута OperationContract мы определили, так называемые, псевдонимы. И важно понимать, желательно, что бы такой псевдоним должен был быть определен как на стороне клиента, так и на стороне службы.

И давайте перейдем к рассмотрению примера и посмотрим, как же мы организовываем перегрузки. Зайдем в Visual Studio и посмотрим на серверную сторону. Заходим в файл MyService.cs и смотрим. На 19 строке мы создаем интерфейс с именем IInterface, помечаем его контрактом. И обратите внимание, 22 и 25 строки у нас имеются два одноименных метода, то есть две перегрузки метода Add. И мы, следуя правилу, свойством Name атрибута OperationContract присваиваем вот такие значения, как бы говоря, что настоящее имя метода будет вот таким AddInt, AddDouble. Это мы говорим для исполняющей среды WCF, для тех типов, которые будут пользоваться нашими методами. И если мы будем пользоваться вот такими именами методов Add и Add, то WCF среда будет пользоваться вот такими именами AddInt и AddDouble, создавая иллюзию, как будто она нас понимаем, как будто она понимает перегрузки. Смотрим, на 30 строке мы создаем класс Service, реализуем интерфейс и реализуем два метода. И в итоге возвращаем возвращаемые значения этих методов как сумму аргументов. Давайте посмотрим в Program.cs, в Program.cs мы создаем хост, добавляем конечную точку, открываемся. Заходим теперь на сторону клиента. Клиент имеет такой же самый контракт. Обратите внимание, тоже имеются два одноименных метода и здесь снова же используются атрибуты OperationContract и их именованным параметрам присваиваются те же значения, что и были на стороне сервиса. Давайте посмотрим. И далее мы создаем фабрику каналов, создаем собственно сам канал, мы создали канал. Мы на классе объекте вызвали статический метод CreateChannel. Это еще один способ создания прокси, то есть создания удаленного объектно-ориентированного представления сервиса. И вот на 32 строке мы как раз на сервисе вызываем метод Add, передаем ему значения «2», «3». Мы понимаем, что этот метод связывается с сервисом провайдером, отправляет ему эти аргументы, и соответственно получает возвращаемые значения, потому что тело вот этого метода Add, оно очень техничное и в нем находятся только механизмы, которые организовывают соединения с сервисом провайдером. Ну и на 35 строке мы вызываем вторую перегрузку метода. Давайте выполнимся и посмотрим, как у нас работает. Вот, обратите внимание, сервер показываем нам, что он получил сообщение, мы там использовали специальные конструкции, которые нам показывают эти моменты. Так вот, мы видим, что у нас произошел вызов первого метода, видите, в Body сообщения, это у нас сообщение в формате SOAP. Вот у нас в Body имеется вызов метода AddInt и вот идут его аргументы, обратите внимание. Вот ниже у нас идет второй вызов – AddDouble. Видите, как переходит. А вот это возвращаемые значения, которые мы получили от сервиса – «5», «5.5». Видим, в такой форме наши перегрузки сработали.

А мы переходим к следующему примеру, который описывает еще разновидности перегрузок методов. И вот следующий пример, который описывает работу с перегрузками. Смотрим, на 17 строке у нас имеется уже знакомый нам интерфейс, две перегрузки методов, снова же, свойством Name атрибута OperationContract мы присваиваем настоящие имена. Далее, сервис не изменился. Единственное, можете посмотреть, вот эта строка. Она и позволяет нам вывести на стороне сервера именно SOAP сообщение в том формате, в котором оно и передается. Это такие технические моменты, которые Вы можете просто посмотреть и поизучать SOAP сообщения. Далее, Program.cs самый стандартный. И заходим теперь в Client. Обратите внимание, на стороне клиента, давайте откроем Client и Service, и сравним их контракты. Вот смотрите, у нас имеется контракт на стороне сервиса и контракт на стороне клиента. Обратите внимание, что на стороне сервиса у нас идет полноценная перегрузка с точки зрения языка разработки. Единственное, мы ее немного корректируем через атрибуты, а точнее через их свойство Name. Видите, да. Посмотрите, что делает клиент. А клиент берет и просто на своей стороне создает контракт вот с такими именами методов (AddInt, AddDouble). Видите, да. И получается, что когда клиент делает запрос к сервису, то ничего страшного не происходит. Почему? Потому что сервис технически воспринимает их вот эти имена. То есть допустима вот такая ситуация, когда контракты отличаются настоящими именами. Но в этом случае, замете, что нам нужно их корректировать в атрибутах OperationContract. Насколько это хорошо? Ну, нельзя сказать, потому что легко запутаться и Вам приходится держать разные версии контрактов. И только вы уберете вот здесь это и у Вас уже ничего не получится, у Вас уже программа не выполнится, потому что контракты будут разными. И давайте посмотрим еще одну разновидность. То есть мы видим, что на стороне сервиса контракт у нас нормальный, вручную откорректирован через атрибуты, а на стороне клиента вообще написали контракт с вот такими именами, как, в общем-то, предполагается.

И давайте посмотрим еще один пример, который показывает работу с перегрузками. И вот заходим в Visual Studio и смотрим. А теперь на стороне сервиса у нас отсутствует перегрузка методов, а на стороне клиента та же ситуация, которая была только что на стороне сервиса. То есть видите, на стороне клиента имеется перегрузка и идет ручная корректировка этой перегрузки через свойство Name атрибута OperationContract. А вот на стороне сервиса просто методы с такими полноценными именами. Ну, думаю, что лучше постараться вообще избавится от перегрузки методов, лучше постараться избавится. Либо постараться воспользоваться самым первым способом, что бы мы корректировали методы в атрибутах, как на стороне сервиса провайдера, так и на стороне сервиса consumer. И тогда программисты, которые будут работать с сервисом, они действительно ощутят, так сказать, удобства работы с перегрузками. Ну, потому что представьте себе, что это будет слишком технично, если я буду вызывать AddInt, AddDouble... не очень красиво, правда? И так же разрывать контракты не очень хорошо. Уж лучше придержаться какого-то, так сказать, одного стиля. Ну и естественно рекомендуется придержаться такого стиля, как с одной стороны, так и другой стороны. Ну, снова же, мы можем выполниться и посмотреть, что здесь все работает. Вот, пожалуйста, у нас клиенту вернулись возвращаемые значения, а видим, если мы почитает сообщение, которые присылаются от сервиса consumer сервису провайдера, видите, это вывел сервер, сервис провайдер. Мы видим, что он в теле сообщения получил вызов вот такого метода, он получил «Вот, клиент хочет вызвать вот такой-то метод с вот такими-то параметрами». И вот второй метод, видите, AddDouble с вот такими-то параметрами. Более детально формат SOAP сообщений Вы уже будите изучать на курсе Advanced. А сейчас мы смотрим на WCF легко, просто, просто с ней знакомимся в легкой манере, что бы у Вас сформировалась действительно легкое восприятие этой технологии. Это технология действительно простая, легкая и приятная в работе. Хорошо, мы с Вами переходим дальше.

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

И давайте мы с Вами перейдем к примеру и посмотрим уже в программном коде реализации техник наследования контрактов.

И в этом примере мы видим, что у нас на 10 строке интерфейс с именем IInterface1, мы помечаем его атрибутом ServiceContract, в нем имеется один метод. Далее смотрим, что на 17 строке у нас имеется IInterface2, который наследуется от IInterface1. В данном случае идет наследование интерфейсов, а не реализация. И мы понимаем, что производный интерфейс, он в себя будет включать как свой метод, так и унаследованный от базового интерфейса. Далее, на 26 строке мы создаем сервис с именем MyService, который реализует вот эти два контракта – IInterface1, IInterface2. И теперь смотрим, мы здесь имеем методы реализации Method1 и Method2. Ну, здесь идет, в теле метода, возвращение конкатенации значения аргумента метода и вот такой оттеняющей строки. Если мы зайдем в Program.cs, посмотрим, что у нас здесь идет стандартное создание хоста. И перейдем теперь на сторону клиента, и посмотрим. На стороне клиента у нас имеется тоже определение контрактов. На 12 строке создаем IInterface1, на 19 создаем IInterface2, который наследуется от первого интерфейса. Ну и далее в теле main, обратите внимание, мы создаем канал и далее, на 39 строке, мы обращаемся, через использование техники RPC к Method1, а вот здесь мы обращаемся к Method2, видим, да. Так как мы создали канал согласно второго контракта, а второй контракт, мы помним, у нас наследуется от первого. Поэтому в данном случае нам доступны оба метода. И здесь важно сказать, важно вспомнить, что у нас есть понятие узкого и широкого интерфейса. Так вот, самый базовый интерфейс, он называется узким. А вот интерфейс, который ниже, он называется широким. Почему? Потому что он расширяется вот этим базовым, который стоит выше, и в нем уже имеется методов больше чем в самом базовом интерфейсе. Еще раз. Узкий и широкий интерфейс. Так вот здесь мы привелись к, так сказать, широкому контракту. Мы здесь воспользовались широким контрактом, который включает в себя и возможности узкого контракта. Давайте посмотрим еще один вариант использования контрактов.

И вот смотрим, в этом примере у нас имеется так же базовый интерфейс, видите, базовый контракт, узкий, имеется широкий контракт, широкий интерфейс. Далее мы создаем сервис, в котором реализуем все методы. Program.cs у нас не изменился. Ну, нет, немножко изменился. Смотрим, здесь мы создаем сервис хост, и обратите внимание, на 16 и на 17 строках мы добавляем две конечные точки. Одна для узкого контракта, вторая для широкого контракта. Теперь у нас имеются две конечные точки, и клиент может присоединяться либо к одной точке, либо к другой. Обратите внимание на порты. Узкий на 80 порту висит, а широкий на 81.

Давайте теперь зайдем на сторону клиента и посмотрим. Здесь у нас так же создается узкая версия и широкая версия контракта. И в теле метода main, обратите внимание, мы создаем два канала. Один узкий, второй широкий. Канал под узкий контракт, канал под широкий контракт. Вот мы обращаемся к узкому контракту, как видим, у нас здесь доступный только Method1, Method2 у нас скрыт. Ну а здесь, соответственно, когда мы создали широкий канал, мы можем обращаться сразу к двум методам. Можем выполниться и посмотреть. Здесь, снова же, у нас отображаются SOAP сообщения, которые пришли на сервер, мы видим, какие методы вызываются. Вот здесь вызывается метод один, видите, в Body вызов Method1, вот пошли сообщения уже по широкому каналу. А вот в клиенте у нас идет вывод того, что нам вернул сервис.

И давайте посмотрим еще одну разновидность работы с контрактами. И вот, давайте рассмотрим этот пример. Здесь мы так же создаем узкий контракт, создаем широкий контракт, казалось бы, ничего не изменилось. Сервис, все то же самое. Вот так строка, которая выводит содержимое сообщения, Вы можете тоже посмотреть в своих примерах, когда Вы выполняетесь. Заходим в Program.cs. Видим, что здесь снова же создается две конечные точки, одна под узкий контракт, вторая под широкий. Обратите внимание, они сейчас обе находятся на 80 порту, только имеются здесь вот такие суффиксы. Для узкого мы выбрали А, для широкого мы выбрали B.

И теперь давайте зайдем на клиентскую сторону. Видим, что здесь тот же самый контракт, те же самые контракты – узкий и широкий контракт. Создаем два канала. Канал для узкого контракта, канал для широкого контракта. Снова же, здесь мы на классе объекте вызываем статический метод CreateChannel, но уже это как вариант. Обратите внимание, на согласование адресов. И что мы здесь видим? Мы видим, что у нас, снова же, вызывается Method1, на узком канале и Method1, Method2 вызываются на широком канале. И давайте мы сейчас зайдем в сервис, и посмотрим, что в данном случае сервис у нас наследуется только от широкого, в чем разница этого примера, от остальных. Потому что в предыдущих примерах у нас было и IInterface1 и IInterface2, то здесь мы спокойно в сервисе реализуем только IInterface2. И мы видим, что здесь автоматически, сразу же, учитывается все наследование интерфейсов. То есть, что бы ни возникало вопросов «Обязательно ли реализовывать оба интерфейса?», все как в ООП – можно реализовывать широкий интерфейс, а узкий реализуется автоматически. Вот это единственный отличительный момент от предыдущих примеров.

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

Так вот, какие вопросы могут возникнуть у разработчиков контрактов во время разработки этих контрактов, и какие рекомендации по выделению контрактов можно дать? Так вот, вопросы какие:

Как проектировать контракты служб?

Как определить, какие операции должны быть выделены в тот или иной контракт службы?

Сколько операций должен содержать каждый контракт?

Так вот, у факторинга имеются свои метрики, так скажем. Вот давайте их посмотрим. При проектировании сервис ориентированных систем, требуется учитывать взаимодействие двух факторов. То есть мы должны понять, что правильный факторинг приведет к созданию более специализированных пригодных к повторному использованию контрактов с неплотным соединением, то есть это значит, что там будет слабая логическая связанность. Соответственно, такие преимущества и распространятся на всю нашу систему в целом. И, естественно, факторинг должен привести к сокращению числа операций в контрактах. Так вот, у нас имеется, так сказать, два фактора. Давайте посмотрим. Это первых фактор, который определяет затраты на реализацию контрактов служб. Затраты, как по часовые, так и денежные, для бизнеса, сколько же времени и труда уйдет на реализацию контрактов, на обдумывание их. И второй фактор определяет затраты на объединение этих контрактов, то есть как мы можем объединить их в узкие, широкие контракты создать и соответственно, важно понимать, что в итоге эти контракты должны быть интегрированы в приложение. Как? Они должны быть реализованы в определенных сервисах. Давайте посмотри на одну интересную диаграмму, которая и описывает баланс между количеством контрактов и размером их, ну конечно, количество и размер контрактов скажется на количестве и размере служб. Мы здесь видим вот такое правило, что если контракты служб слишком маленькие, вот, допустим, маленькие-маленькие контрактики, видите, вот они, буквально по одному, по два метода, то реализация каждого такого контракта упрощается, их просто будет создать. Но общее, видите, затраты на реализацию очень низкие, но затраты на интеграцию, на реализацию таких контрактов, окажутся очень большими. Мы здесь практически ничего не выиграем. Если же имеется большой контракт, большой и сложный контракт, в котором 150 методов, такого нельзя конечно делать, то затраты на реализацию, обдумывание такого контракта конечно же окажутся высокими, но при этом такой контракт будет легко интегрировать, ну что, взяли, сделали один большой сервис, который все делает, но уже как его потом сопровождать это другое дело. Снова же, не очень хорошая идея. И мы видим, что нужно выбрать нечто среднее. У нас не должны быть контракты слишком большие, у нас не должно быть очень много маленьких контрактов, мы должны руководствоваться каким-то здравым смыслом, что бы контракты были средними. И тогда, мы видим на диаграмме, что мы подойдем к вот этой черте, где у нас будут минимальные затраты, усредненные затраты, скажем так, относительно общих затрат, относительно этой пунктирной линии, общих затрат на создание систем. Но мы это уже смотрим как проектировщики, как архитекторы, вот нам нужно посчитать стоимость системы. Мы видим, что нам нужно как-то усредниться и тогда будет более-менее легко и контракты создавать, определятся с высоко… а что такое контракты? Это набор интерфейсов, а что такое интерфейс? Это и есть тот высокоуровневый интерфейс, открытый интерфейс, потому что мы создаем открытые методы в интерфейсах, которые и будут являться интерфейсами взаимодействия с системой, а наша система, сервис ориентированная система, будет составлена из множества сервисов. Поэтому, давайте определять какие-то разумного размера контракты. И тут Вы сразу же спросите «А что значит разумные?». Ну, разумность мы можем выразить, снова же, в количестве используемых в контракте методов.

Давайте перейдем к следующему слайду и посмотрим, на то оптимальное количество операций, которые мы можем помещать в контракты служб. И вот смотрите, как бы не показалось странным, но оптимальное количество это 3-5 операций в контракте. 6-9 – нормальное, допустимое количество. И вот рекомендация – есть смысл проанализировать операции и определить возможность их объединения. Что значит возможность объединения операций? Ну, возможно сформировать более высокоуровневый интерфейс, что бы две операции вызывались в контексте одной операции, и вот эти две низкоуровневые операции они не входили в контракт, а вот эта высокоуровневая операция входила в контракт. Около 12 операций – это уже считается большое количество и вот рекомендация – стоит поискать способы выделения операций в отдельный контракт. Дело в том, что во втором случае, когда было 6-9 операций, то ради двух операций создавать отельный контракт, мы снова же получим много мелких контрактов, поэтому здесь лучше постараться объединить эти операции, воспользовавшись таким понятием как высокоуровневость и низкоуровневость операций. Но вот когда их уже 12, то нам сложно будет формировать высокоуровневый и низкоуровневый интерфейсы в контексте одного контракта. И поэтому нам лучше попробовать выделить определенные операции в отдельный контракт, возможно, нам есть смысл рассмотреть понятие узкого и широкого контракта, с какой-то стороны, а может быть сделать два узких контракта, два базовых интерфейса, от которых наследуется производный широкий контракт. И в каком-то случае, может даже будет и 12 операций использовать, Вы скажете. И вот четвертая картинка, Вы видите, здесь 20 или более операций. Ну, это, конечно же, недопустимо, то есть ни при каких условиях. Поэтому здесь однозначно стоит выделять эти операции в отдельные контракты и пересматривать, что-то мы здесь неправильно себе намыслили, и скорее всего не только в отдельные контракты, но и пытаться объединять операции, то есть одним словом полностью перепроектировать такой контракт.

И так же хотелось бы рассмотреть один важный момент как использование операций минимальной инкапсуляции. Вот, обратите внимание на определение – операции являющиеся аналогом свойств, известны под название «операции минимальной инкапсуляции». Если Вы в контракт включаете такие операции, такие методы, как Set_Method и Get_Method, как методы доступы мутатор и аксессор (Mutator , Accessor). Ну конечно, подобных операций следует избегать. Почему? Потому что, собственно, бизнес логику, задания и чтения значения переменной можно инкапсулировать на стороне службы, но в идеале, клиент вообще не должен иметь дело со свойствами служб, почему? Потому что служба, это уже такое более высокоуровневое представление какой-то такой виртуальной живой сущности. Представьте, что я подхожу, вижу, что у Вас в глазу ресница и начинаю лезть к Вам в глаз. Зачем, Вы спросите. Уберите, пожалуйста, это ресницу, я скажу, мы же уже такие высокоуровневые, цивилизованные существа, правда? То есть, зачем мне лезть напрямую и изменять значение Ваших переменных. Вот. И когда клиент вызывает операции, служба уже сама будет заботиться об управлении своими переменными, то есть своим состоянием. И вот взаимодействие должно вестись только в контексте вызовов, такого типа как «служба, выполни определенное действие». Все. Но мы не лезем к каким-то свойствам этой службы, ни в коем случае. И поэтому вот такие методы, методы минимальной инкапсуляции, каких-то полей службы делать открытыми не очень хорошо. А как служба будет решать задачу, какие переменные будет задействованы, клиента не касается. Как Вы будите убирать эту ресницу из глаза это уже Ваша проблема. Вы можете придумать какие-то подобные ситуации, когда человек не должен касаться состояния другого человека. Мы просто посылаем друг другу сообщения и вызываем друг на друге методы. Вот это важно заметить и подобных операций при работе с сервисами следует избегать.

И мы с Вами перейдем к рассмотрению еще одной разновидности контрактов – это контракты данных. Что это такое, сейчас мы с Вами рассмотрим более детально и подробно, с примерами и объяснениями. Давайте посмотрим на этот слайд. Мы видим, что контракты данных определяют, какие типы данных принимаются и передаются службой. И вот мы видим, что WCF определяет косвенные контракты данных для встроенных типов. Ну что это значит, контракт данных? Ну, представьте, когда мы вызываем удаленно метод, и мы с Вами в качестве аргумента метода передавали, помним, string, или int, например. А представьте, что в качестве аргумента потребуется передать ссылку на экземпляр класса MyClass, к примеру. Вот я вызываю метод на сервисе и в качестве аргумента этого метода передаю ссылку на экземпляр своего класса. Вот здесь, вот в этом случае, и придется создавать полноценные контракты данных. И прежде чем перейти к рассмотрению примеров контрактов данных, нельзя не сказать о таком понятии как сериализация и десериализация, которая используется при вызове операции. Когда мы на стороне клиента вызываем удаленно метод через технику RPC (Remote Procedure Call), то невидимо для нас как раз и происходят такие техники маршалинга и демаршалинга. Что такое маршалинг и демаршалинг? Это меж процессная, меж доменная и меж уровневая, между компьютерами, сериализация и десериализация. Еще раз. Меж процессная, меж доменная и меж уровневая сериализация и десериализация называется соответственно маршалинг и демаршалинг. И на этом слайде мы видим, вот последовательность шагов, которые происходят при вызове метода с аргументом. Сначала происходит сериализация входных параметров метода, далее транспортировка сообщения службе, в SOAP формате, на стороне службы, когда сообщение доставлено сервису, происходит десериализация входных параметров, то есть демаршалинг. На стороне клиента маршалинг, далее транспортируем службе сообщение, на строне сервиса десериализация. Далее создается экземпляр сервиса, выполняется операция, а у этой операции например есть возвращаемое значение. Маршалинг, то есть сериализация возвращаемого значения, возвращение сообщения клиенту, и клиент уже делать демаршалинг, то есть десериаоищацию возвращаемого значения метода, который был выполнен на стороне сервиса. Еще раз. Мы должны понимать, что при передаче какого-то объекта ссылочного типа или экземпляра структурного типа в параметре удаленно вызываемой операции в действительности надо передать лишь его состояние, то есть зачем нам передавать методы, если они на самом деле там находятся. То есть мы передаем лишь состояние этого объекта. А принимающая сторона, сервис провайдер, уже должна преобразовать этот объект обратно к своему родному представлению. И вот такая передача состояния называется маршалингом, демаршалингом. И вот он реализуется по средствам сериализации, когда пользовательские типы, MyClass и другие, переводятся из CLR представлений, в xml представления, то есть в SOAP формат. И, по сути, мы понимаем, что при приеме таких параметров, на стороне сервис провайдера происходит десериализация, или как мы называем – демаршалинг. То есть набор параметров, который хранится сейчас в xml формате, в SOAP формате, преобразуется обратно в обычные классы, в стандартный объект, который понимает CLR и дальше передается самому сервису для обработки.

И давайте перейдем и посмотрим, как же контракты данных могут выглядеть в программном коде. И на этом слайде мы с Вами видим, что если мы хотим создать класс Point, это наш класс, и передать на сторону сервис провайдера экземпляр этого класса, то мы понимаем, что произойдет его сериализация на стороне клиента и десериализация на стороне сервис провайдера. Или как мы говорим маршалинг и демаршалинг. И для этого мы создаем класс Point, далее мы создаем два поля, обратите внимание, x,y – координаты этой точки. Помечаем их атрибутами DataMember – член данных. А сам класс Point мы помечаем атрибутом DataContract и именованному параметру Namespace вот некоторое значение. Мы понимаем, что вот этот конструктор уже не передастся. Почему? Потому что Вы скажите Ну если мы передаем на сторону сервиса провайдера вот такое состояние, класса Point, то наверно где-то у него в библиотеке имеется этот класс Point и при десериализации, что произойдет? Создастся экземпляр класса Point и из xml сообщения, которое пришло по сети, достанутся значения x и y и запишутся стандартно в поля созданного экземпляра класса Point на стороне сервиса провайдера. То есть, по сути, стандартные сериализация, десериализация, только, так сказать, меж уровневая. И меж уровневая сериализация, десериализация мы ее будем называть маршалинг, демаршалинг.

И давайте перейдем к рассмотрению программных кодов, которые показывают работу с контрактами данных. Мы перейдем в Visual Studio и посмотрим на сторону сервера. Обратите внимание, заходим в файл IContract. На 10 строке у нас создается стандартный сервисный контракт. Обратите внимание, в теле этого контракта имеется абстрактный метод Add, который принимает два аргумента типа Point и возвращает значение тоже типа Point. То есть мы хотим взять и сложить координаты двух точек – точки а, точки b и получить, ну, сумму координат по x и по y, к примеру. И теперь смотрим, когда мы создаем класс Point, мы помечаем сам класс атрибутом DataContract и задаем ему вот такой уникальный Namespace. И далее смотрим, на 20 и на 22 строках, мы поля x и y помечаем атрибутам DataMember, то же самое, что мы с Вами видели в презентации. Заходим в Service. И теперь мы видим, что на 9 строке мы создаем Service, класс Service, который реализует обычный контракт IContract. И на 11 строке мы реализуем метод Add, который будет работать с экземплярами класса Point, а как мы уже видели, что сам класс Point представляет собой контракт данных, то есть он является контрактом данных, выражает этот контракт. Здесь мы выводим xml сообщение, мы можем его сейчас закомментировать, а Вы уже комментарий снимите и почитаете это xml сообщение. И в итоге на 14 строке, смотрите, мы возвращаем новую точку, видите, возвращаем новую точку, новый экземпляр Point, каждая координата этой точки будет являться суммой других координат. Ну, мы можем здесь взять a.x, к примеру, плюс b.x… а нет, мы здесь просто суммируем a.y, a.x, b.y, b.x. Ну, здесь уже не важно. Давайте теперь перейдем в code behind. Смотрим, здесь мы подготавливаем конечную точку, создаем сервис хост, добавляем конечную точку, ну и стандартно стартуем сервис.

Давайте зайдем на клиентскую сторону. Обратите внимание, у клиента тот же сервисный контракт и тот же контракт данных. Обратите внимание, тот же класс Point, который помечен атрибутом DataContract, и каждое поле, значение которого будет сериализоваться и передаваться на сторону сервера, тоже помечается атрибутами DataMember. Вот это такая простая техника создания контрактов данных. А когда мы работаем с простейшими типами, с такими как double, int, byte, string, то у них уже имеется вот такой контракт по умолчанию, WCF им, так сказать, предоставляет такой контракт для примитивных типов по умолчанию. Давайте зайдем в code behind клиентского приложения. И видим, что здесь мы создаем фабрику каналов, создаем канал, и вот собственно наша логика. На 33, 34 строке мы создаем две точки, обратите внимание. На 36 строке мы на канале вызываем метод Add, передаем ему две точки. Понятно, что их состояние, состояние каждой точки, x, y каждой точки, сериализуется и отправляется на сторону сервиса. Сервис десериализует, считает, сериализует результат, возвращает нам, и вот на 36 строке в результате в переменной Res будет ссылка на результат класса Point, который нам создаст метод Add, он уже создаст его пустой экземпляр, экземпляра класса Point, и далее откроет конверт, который пришел как ответ от сервиса, он достанет оттуда значение x и y, запишет и отдаст нам. Мы можем сейчас попробовать выполнится. Давайте выполнимся и посмотрим, как работает. Мы здесь интерфейс не стали менять. Стартуем сервис. Вот сейчас нажимаем на эту кнопку и, по сути, у нас отработает эта логика по созданию точек, которую мы с Вами смотрели. Видим, да. Вот, пожалуйста, то, что нам вернулось от сервера. Мы туда послали две точки, нам их просуммировали, вернули, и вот мы вывели результат.

И давайте посмотрим тот же самый пример с хостингом в IIS, что бы закрепить работу с хостингом в IIS. И давайте перейдем в Visual Studio и посмотрим тот же самый контракт. Обратите внимание, вот сторона сервиса. Мы создаем сервисный контракт, видите, да, все знакомо, тот же метод Add, который принимает два аргумента типа Point, возвращает значение типа Point. У нас имеется здесь контракт данных, для этой точки. И вот ниже мы организуем сервис. На 32 строке мы создаем класс Service, который реализует интерфейс IContract. И обратите внимание, на 34 строке мы реализуем метод из этого интерфейса и в теле метода мы возвращаем ссылку на экземпляр Point, где суммируем значение координат. Зайдем теперь в файл svc. Он, по сути, у нас стандартный. Web.config тоже у нас стандартный, здесь мы указываем обычную конечную точку и mex конечную точку. Зачем mex? Что бы мы могли более гибко работать, что бы могли удаленно получать, так сказать, интерфейс. Сейчас мы это посмотрим, как это делается. И давайте теперь попробуем захостится в IIS. Запускаем IIS сервер, берем здесь создаем новую виртуальную директорию, традиционно назовем ее My, выберем физический путь, вот, выбираем сервис, тестируем соединение. Все сработало, ОК. Теперь нам нужно преобразовать в приложение, ОК. И теперь мы можем попробовать к нему обратится. И мы обратимся к нему из клиента. Посмотрим в клиент. Обратите внимание, что у нас делается в клиенте. Мы здесь используем обертку. Смотрите, CalculatorClient, создаем две точки, и далее вызываем метод Add на калькуляторе и получаем результирующею точку и выводим ее значение на экран. Здесь у нас появились какие-то странные классы. Откуда они взялись? Вот у нас идет класс Point, у него вот здесь идет два поля, вот у нас идут свойства, видите, помечены атрибутами DataMemberAttribute. Это выглядит немножко пугающе, да? Не переживайте, это автоматически сгенерированный код, мы сейчас сделаем тоже самое. Сначала выполнимся, а потом так же руками все сгенерируем, что бы посмотреть, как мы можем на стороне клиента все также организовать. И даже клиент использует конечную точку через App.config, через конфигурационный файл. Хорошо, давайте зайдем в Client.cs, выполнимся, посмотрим, что он у нас работает. Обратите внимание, вот это консольное приложение, которое является клиентом, обратилось к IIS серверу, а точнее к нашему сервису, который сейчас захощен в IIS. И, по сути, вот получили результат. А теперь хотелось бы самостоятельно сгенерировать вот такой код, посмотреть, как нам Visual Studio помогает это сделать. И для этого мы создадим новый проект, назовем его, к примеру, Test. И теперь мы правой кнопкой мыши… зайдем, посмотрим в App.config, здесь как раз имеется адрес, подготовим его сразу же, куда мы будем обращаться. Ну, скопируем его в буфер обмена. Теперь, правой кнопкой мыши по References, Add Service References. Здесь нам рекомендуется указать тот адрес сервиса, который сейчас имеется в IIS, вставляем из буфера обмена адрес. Обратите внимание, сейчас вот эта программа, вот эта утилита, обратится к mex конечной точки, вытащит интерфейс взаимодействия с нашим сервисом и получит его, и она на основании этого интерфейса, использую автоматическую кодогенерацию, сама напишет нам клиентский прокси, клиентскую версию сервиса. Все, что мы раньше с Вами делали руками, она сейчас сделает сама. Ну, и давайте попробуем. Делаем Go. Вот, смотрите, она обнаружила наш сервис, вот и наш контракт здесь показывается. Теперь ниже, мы нажимаем кнопку ОК. И, обратите внимание, вот у нас произошла генерация сервисов. Здесь мы сейчас все позакрываем, что бы у нас не висело, будем работать с новым вариантом сервиса. Мы здесь пока ничего не видим, но если мы нажмем вот эту команду, Show all files, у нас сразу же откроются все возможности, все что спрятано в Service References. И мы с Вами зайдем в Program, он пока пустой, как Вы видите. Но, мы хотим сейчас посмотреть, мы хотим создать экземпляр вот этого прокси, который автоматически был сгенерирован. И вот, давайте мы с Вами сейчас посмотрим в старом клиенте, где у нас было. Заходим в client. Видите, у нас здесь был сформирован калькулятор клиент, вот generated код. Этот код нам тоже был сгенерирован автоматически, но мы его просто взяли и почистили, почистили от лишнего. Теперь, если мы зайдем в наш пример и попытаемся, допустим, сделать точку. Смотрите, Point, и нам здесь предлагается подключится. Смотрите, using Test.ServiceReference1. Вот мы подключаемся и можем перейти, и посмотреть. Видите, где находится наша точка. Она была автоматически сгенерирована. Видите, вот в этом файлике Reference. Сейчас мы опять в эту точку перейдем. Потому что здесь работает автоматическая кодогенерация. Видите, вот это нам все автоматически сгенерировало. Вот клиентский контракт, это все нам автоматически было, автоматически было сгенерировано. Вот у нас имеется класс ContractClient, видите, да. И вот давайте посмотрим, что у нас здесь имеется. Пусть Вас не пугает этот код, потому что генерировался автоматически, Вы, если хотите, можете его почистить. Ну, в большинстве случаев чистить его не надо, потому что если Вы будите перегенерировать обращение к сервису, он у Вас просто убьет всю Вашу работу. Поэтому довольствуйтесь тем, что Вы получили в результате автоматической генерации программных текстов, и если вдруг в дальнейшем, там, контракт изменится, то Вы просто, буквально, два нажатия, две команды, что мы с Вами делали, быстро перегенерируете себе клиентский сервис и будите с ним продолжать работать. Вот имеется интерфейс, обратите внимание, метод Add, как Вы видите, вот в таком формате он был сгенерирован. ContractClient, смотрим, что у нас имеется в ContractClient, конструкторы имеются… Вот как раз наш метод Add, то есть его реализация. Видите, мы через канал обращаемся к сервису, видите, вот, какая реализация этого метода. Ну и давайте теперь попробуем взять и создать экземпляр класса ContractClient. Сначала создадим экземпляр, пишем ContractClient, допустим, назовем его service… вот. Теперь, нам нужно создать две точки. Создаем Point, точку а, то, что у нас было, Point b. Это мы сейчас будем их заполнять координатами и отправлять на сервер. Вот, возьмем а.х, присвоим, допустим, 1. а.у так же присвоим 1. Допустим, b.x = 2, b.y = 2, что бы они были похожи. И теперь создаем переменную, допустим, Point с, к примеру, и присваиваем ей возвращаемое значение service.add, и в качестве аргументов метода мы передаем точку a и точку b. Вот, обратите внимание. И дальше мы берем и выводим на консоль значение координаты точки с, с.x например. И дальше сделаем задержку. Выполнимся и смотрим. Видите, мы получили тот же результат. Мы знаем, что у нас сейчас сервис хостится под IIS, мы быстренько создали клиент, буквально минутка, автоматически сгенерировался, и мы уже обращаемся к этому сервису через вот такую обертку. Так вот этот класс, ContractClient, он, по сути, называется удаленным прокси. Конечно же, он удаленный, он находится на клиентской машине, удаленно от сервиса. И в паттернах проектирования GOF, при рассмотрении паттерна прокси рассматривается такая его разновидность – удаленный прокси, или, у него есть сленговое название, посол. Это вот и есть одна из разновидностей этого паттерна, который вот здесь генерируется автоматически. То есть, по сути, здесь произошла автоматическая генерация клиентской части сервиса по шаблону проектирования удаленный прокси, или его называют послом.

А мы с Вами переходим дальше к рассмотрению контрактов данных. И мы перейдем к следующему слайду и рассмотрим еще одну интересную особенность, которую требуется учесть при работе с контрактами данных. Обратите внимание, мы здесь используем атрибут KnownType. Давайте посмотрим на его описание. Атрибут KnownType примененный к базовому контракту, видите, к вот этому базовому контракту, позволяет сервисам принимать производные классы вместо базовых. И теперь смотрите, если мы хотим вызвать метод удаленно и передать ему ссылку на экземпляр класса Customer, то мы можем этот экземпляр класса Customer привести к базовому типу Contact, видите, да, мы экземпляр Customer можем привести к Contact. Потому что здесь мы видим, у нас имеется класс Contact, он у нас является полноценным контрактом данных, так же мы создаем производный класс Customer от Contact, и вот расширяем уже Contact еще одним полем. И мы видим, по сути, у нас имеется два контракта данных. Так вот, для того, что бы мы могли передать ссылку на экземпляр класса Customer и он безболезненно привелся к базовому типу, то есть произошел произошел правильный абкаст привидения к базовому типу, мы должны базовый класс пометить атрибутом KnownType. Давайте мы перейдем к коду и посмотрим, как это выглядит.

Зайдем в Visual Studio. И обратите внимание, вот у нас имеется клиент, на стороне клиента у нас имеется контракт IContractManager и здесь имеется такие методы как AddCustomer, AddContact и GetContacts. Добавить клиента, добавить контакт, и получить контакты. Далее мы создаем контракты данных. Вот у нас имеется класс Contact, два поля. Он является контрактом данных, мы его пометили еще атрибутом KnownType. Давайте посмотрим, тоже самое у нас происходит и на серверной стороне. Вот это у нас серверная сторона идет. И мы смотрим, что мы создаем производный класс Customer, который наследуется от базового класса Contact, видите, да. И давайте теперь посмотрим, как у нас будет происходить вызов. Зайдем в code behind клиента. И смотрим, здесь мы создаем фабрику каналов, настраиваем конечную точку. И вот смотрите, на 34 строке мы создаем экземпляр класса Customer, даем ему имя, фамилию, указываем OrderNumber. Обратите внимание, мы создаем экземпляр производного класса. Далее, мы берем, обращаемся к каналу и говорим «Сервис у себя далеко, добавь, пожалуйста, этот контакт». И мы, обратите внимание, в качестве аргумента метода передаем экземпляр производного класса, который здесь приводится к базовому типу. И на 40 строке мы получаем все контакты. Говорим «Канал, дай нам, пожалуйста, все контакты» и выводим их на экран. Давайте выполнимся и посмотрим. Вот у нас имеется клиент, имеется сервер. Мы стартуем сервер, сейчас он запустится. И теперь отправляем сообщение. Обратите внимание, мы с серверной стороны получаем клиента, вот, Ivan Petrov. Видим, да. Что же у нас произойдет, если мы не придержемся такого правила, если мы возьмем и закомментируем KnownType. Выполнимся сейчас. Стартуем сервер. Вот сервер стартанул и посылаем сообщение. Обратите внимание, у нас получилось исключение, видите, да. То есть в данном случае сервис отказался сработать. Поэтому, будьте внимательны, и если Вам предстоит выполнить вот такие операции, в которых используется привидение к базовому типу, то Вы должны такие контракты данных помечать атрибутом KnownType.

И давайте мы посмотрим еще одну разновидность атрибутов. Вот у нас имеется атрибут ServiceKnownType. Он, можно сказать, немножко альтернативный кому же атрибуту KnownType. И давайте посмотрим описание – атрибут ServiceKnownType примененный к базовому контракту данных или операции, замете, его уже можно применить либо к самому контракту, либо к операции, позволяет сервисам принимать производные классы вместо базовых, корректно приводя их к базовому типу. И вот, давайте посмотрим, первый вариант. Вот у нас имеется, так сказать, сервисный контракт, и мы его уже применяем не к контрактам данных, а применяем непосредственно к операциям описываемых в контрактах сервисах. Видите, мы метод AddContact помечаем атрибутом ServiceKnownType и указываем, видите, производный тип от базового Contact. И так же здесь – мы указываем, что у нас будет использоваться Customer, как массив элементов возвращаемых значений типа Customer. А вот вторая версия. Мы можем не помечать каждый метод отдельно, а пометить сразу же весь сервисный контракт. Вот такие еще два способа использования. И давайте перейдем к программному коду и посмотрим, как выглядит работа с использованием вот таких атрибутов.

И обратите внимание, вот у нас имеется сервисный контракт на стороне клиента. Мы здесь берем и каждый из методов помечаем атрибутом ServiceKnownType. А вот у нас идет серверный контракт. Здесь мы просто берем и помечаем весь сервисный контракт вот этим атрибутом ServiceKnownType. Уже просто атрибут KnownType, видите, в контрактах данных, мы можем не использовать. Видите, мы его закомментировали. То есть это, по сути, тот же самый пример, видите, да, что мы рассматривали предыдущий, но теперь у нас имеется альтернативный способ организовать, так сказать, абкаст параметра метода, когда этот параметр передается на сторону сервиса провайдера. Еще раз. Когда мы на стороне клиента обращаемся к сервису провайдеру и в качестве аргумента метода передаем ему…давайте зайдем, посмотрим, передаем ему, в code behind зайдем, передаем ему Customer, вот, обратите внимание, у нас на 33 строке создается Customer, мы его сразу абкастим к Contact, заполняем его значениями и, на 36 строке, вызываем удаленно метод AddContact и передаем сюда contact. На самом деле мы понимаем, что contact представляет собой ссылку на экземпляр класса Customer, который намного больше, чем Contact, и все привидения типов являются всего лишь игрой компилятора. То есть, вообще все основные парадигмы ООП это не что иное, как просто игра компилятора. А сервис на той стороне говорит «А почему я должен страдать от того, что у Вас вот такие игры Visual Studio?», что она так настроена. На самом-то деле, contact будет содержать в себе и дополнительное поле. Вот давайте посмотрим. Нажмем и сделаем его Customer, переименуем contact на customer. И теперь, если мы возьмем и вот здесь обратимся к customer, мы видим, что у нас имеется OrderNumber и мы можем ему присвоить вот такое-то значение. И теперь здесь, ну, мы можем, снова же, организовать привидение к Contact. По сути, сюда то передается, на ту сторону полный Customer, а метод, на стороне сервиса провайдера, готов принять обрезанного Customer до Contract, потому что мы должны Customer привести к базовому типу. Ну, думаю здесь с понимание у Вас тоже все в порядке, потому что техника достаточно простая.

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

И давайте перейдем к программному коду и посмотрим, как же выглядят контракты сообщений. Давайте зайдем на сторону сервера и посмотрим. На 10 строке у нас имеется контракт ICalculator. Обратите внимание, он помечен как ServiceContract, в нем имеется метод Calculate, вычислить что-то, и этот метод принимает сообщение, видите, мы здесь создаем MyMessage, и возвращает сообщение. Вы скажите «Как-то не предметно ориентировано». Да, здесь достаточно технично. Но зато на 18 строке мы создаем класс MyMessage, который и представляет собой объектно-ориентированное представление сообщения. А Вы скажите «Ага, он очень похож на контракт данных». Да, чем-то он похож. Есть много сходств. И вот мы как раз такой класс помечаем атрибутом MessageContract. Обратите внимание, в теле этого класса у нас имеется набор полей: operation, n1, n2, result. Далее мы видим здесь конструктор по умолчанию, пользовательские конструкторы. И теперь смотрим, на 47 строке у нас имеется Operation, и мы ее помечаем как MessageHeader, заголовок нашего сообщения. Далее у нас идут, так сказать, члены тела нашего сообщения, это N1, N2 и Result это тоже член тела сообщения. И получается, что Operation, помеченный атрибутом MessageHeader, он и будет описывать основную операцию, которая должна быть выполнена при получении такого сообщения, скорее всего мы здесь будем помещать арифметическую операцию ( «+», «-»…), как раз ту операцию, которую мы должны будем выполнить над вот этими двумя операндами – N1, N2, и результат арифметической операции поместить вот в это свойство Result. Ну, тоже достаточно просто. Давайте зайдем в Service и посмотрим. На 10 строке мы создаем класс CalculatorService, который реализует контракт ICalculator. В нем мы создаем метод Calculate, который принимает сообщение и возвращает сообщение. В теле метода мы создаем экземпляр Message, передаем ему сюда request, мы видели тела конструктора, он все так распределит. И далее, на 16 строке мы создаем переключатель switch, который в качестве значения выражения селектора принимает значение свойства Operation. И вот мы видим, что если значение выражения селектора совпадет с одним из постоянных выражений операторов case, то выполнится соответствующее тело, иначе мы перейдем в блок default. Поэтому, если мы здесь, допустим, Operation будет равен «-», то мы выполним вот эту операцию, мы result присвоим разность N1 и N2. Если будет «+», то мы присвоим сумму N1 и N2. Если будет умножить, присвоим произведение. Если будет знак разделить, присвоим частное от N1 и N2. Далее мы можем зайти в code behind сервиса. Смотрим, здесь стандартный код, который просто запускает сервис.

И теперь давайте зайдем в клиент. И мы видим, что клиент тоже на 10 строке создает сервисный контракт и ниже создает контракт сообщений. Вот, контракты полностью совпадают. И зайдем в code behind клиентского приложения. Тоже стандартно у нас создается канал, через фабрику каналов. И вот смотрите, мы здесь создаем экземпляр класса Message, присваиваем операндам определенные значения, указываем, что операция будет «+», и на 38 строке мы создаем переменную response типа MyMessage, и на канале вызываем метод Calculate, передавая ему request, говорим «Вот наше подготовленное сообщение», калькулятор на стороне сервиса провайдера считает, и когда сообщение возвращается обратно к нам мы берем, и извлекаем из полученного сообщения значение операндов, результат извлекаем, ну, вдруг операнды тоже могли измениться в ходе выполнения арифметических операций, и выводим на экран. Тоже самое происходит с операцией вычитания, с умножением и делением. И мы можем выполниться, и посмотреть, как работает этот пример. Вот окошко клиента, окно сервера. Стартуем сервер. Вот видим, сервер запущен. Отправляем ему все эти 4 сообщения. Обратите внимание, сейчас они вернутся. Видите, да, вернулись все результаты арифметических операций, которые калькулятор на стороне сервера, то есть калькулятор сервис провайдер, нам их посчитал. Вот, можем использовать подход с сообщениями. Но снова же, использование сообщений, это достаточно технический подход, который может нас увести от работы с предметной областью и нам покажется немного необычным такой подход. Тем не менее, как подход.

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

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

И давайте мы снова же перейдем к программному коду и посмотрим, как в программном коде организуются двухсторонние каналы. И зайдем сразу на серверную сторону, и посмотрим, какой у сервиса имеется контракт. Обратите внимание, у нас здесь теперь имеется два контракта. Один контракт для клиента, потому что у нас теперь и клиент является провайдером, в каком-то случае. Смотрим, для клиента у нас имеется клиентский контракт. Обратите внимание, на 12 строке мы указываем свойству IsOneWay, атрибута OperationContract, true. Говорим, пусть он будет односторонний, мы не будем ничего друг другу отвечать. По каналу сообщения идут только в одну сторону, никаких обратных возвращаемых значений. И вот на клиенте будет ClientMethod, который будет там вызываться, и ему будет передаваться некое сообщение. Видите, возвращаемое значение даже void.

А вот сервисный контракт. Мы, на 22 строке используем атрибут ServiceContract, но здесь мы уже используем, так сказать, в аргументе этого атрибута свойству CallBackContract, контракт обратного вызова, присваиваем ссылку на экземпляр класса type, который содержит информацию о клиентском контракте. И теперь мы видим, что сервисный контракт содержит в себе некую информацию о клиентском контракте, то есть к какому же клиенту нам нужно обращаться. И снова же, сервисную операцию, на 27 строке, мы помечаем атрибутом OperationContract и ее конфигурируем как IsOneWay, как одностороннею, что не ожидайте, что она что-то вернет по каналу вызова, по нижнему каналу.

Давайте зайдем и посмотрим теперь в клиентский контракт. Смотрим, клиентский контракт полностью совпадает. На 9 строке создаем клиентский контракт, тот же IsOneWay в OperationContract, сервисный контракт, тот же контракт обратного вызова IClientContract. То есть вот у нас контракты полностью совпадают.

Давайте посмотрим теперь на реализацию сервиса. Заходим в сервис, смотрим сервис, он стандартно организует свой сервисный контракт. В нем реализуется его метод, вот мы выводим, что этот метод отработал, на 11 строке. И смотрите, что мы делаем, мы полю callback, статическому полю, которое находится в Program, присваиваем возвращаемое значение метода GetCallbakcChannel, получить канал обратного вызова. Смотрите он теперь, сервис, будет строить зеленую трубу от себя к клиенту. Вы видите, получится теперь две трубы. Одна труба, один канал, от клиента к сервису, и второй обратно. И по одному каналу будут идти сообщения на сервис, по другому к клиенту. Вы скажите «Зачем так? Почему не вернуть обратно?». А очень просто, представьте, что сервис будет очень долго выполнять эту операцию, допустим, 30 минут. И поэтому, возможно Вы скажите «Ну хорошо, ты там выполняй эту операцию, я буду выполнять что-то дальше, а потом когда ты, сервис, выполнишь эту операцию, просто вызови на мне нужный метод, и я уже качественно отреагирую». Представляете, да. То есть, сервис провайдер получил сообщение, долго что-то с ним делает, обрабатывает сообщение, и потом когда он его обработал, он соединяется с клиентом, проводит к нему сам трубу, канал, и отправляет ему результат по этой трубе. Представляете, да. И вот это позволяет нам сделать вот эта строка.

Давайте зайдем в Program теперь и посмотрим. Мы здесь создаем стандартный сервис хост, добавляем стандартную конечную точку, обратите внимание, хостимся, запуск хоста, и вот, пожалуйста, на 28 строке мы вызываем метод на клиенте, клиентский метод. Смотрите, Program.callback, а вот этот callback как раз был создан здесь, внутри сервиса. Я вот тут рядом поставлю, что бы Вы видели. Видите, инициализация вот этого callback, с 8 строки, инициализация этого callback произошла внутри сервисного метода, видите, вот здесь она вот произошла, и уже потом пользователь нажимает клавишу, и мы пользуемся уже созданным каналом внутри сервиса, и отправляем клиенту сообщение. Просто? Просто.

Давайте теперь зайдем на сторону клиента и посмотрим, как же у нас работает клиент. Клиентский контракт мы уже видели, он полностью совпадает с сервисным контрактом. Зайдем теперь в Context. Ага, контекст представляет собой тоже некую версию сервиса, мы бы могли его назвать ClientService, то есть клиентский сервис. Ну, мы его называем Context. И видите, реализуем в нем сервисный контракт. То есть это как клиент сервис провайдер. Видите, да. Вот его реализация клиентского метода, вот здесь выводится его сообщение. Повторюсь, мы этот Context могли назвать как ClientServiceProvider. И заходим теперь в Program, и смотрим. Здесь мы создаем экземпляр класса InstanceContext, видите, да. Это системный класс, который нам поможет сработать с клиентским Context. И конфигурируем его экземпляром клиентского Context, то есть это, по сути, клиентская версия сервиса. Далее, на клиенте мы создаем DuplexChannelFactory, мы создаем фабрику, которая создаем такие дуплексные каналы, двойные каналы. И при этом обратите внимание, мы здесь указываем адрес, binding, context и контракт. Уже не три A B C, а A B C C (Address, Binding, Context, Contract). 21 строка, говорим «Фабрика двойных каналов, построй нам, пожалуйста, такой канал». Построила. И теперь мы ждем, когда пользователь нажмет клавишу, вызовется на сервисе метод, видите, да, на сервисе провайдере, улетит туда сообщение. Смотрим, сервер его получает, сервис получает, создает канал… зайдем в Program… создает канал, и мы теперь ожидаем, когда на стороне сервера админ нажмет клавишу и полетит ответ, видите. Давайте выполнимся и посмотрим. Вот у нас имеется сервер, вот у нас имеется клиент. Они все готовы отправлять сообщения. Делаем клиента активным и давайте отправим на сервер сообщение. Я нажимаю Enter, обратите внимание, на сервере появилось сообщение. Админ увидел, и администратор теперь нажимает Enter. Видите, пришел ответ на клиенте по другому каналу. То есть по одному каналу клиент послал на сервер, а сервер по другому каналу послал сообщение на клиента.

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

Теперь было бы хорошо зайти и посмотреть на сторону сервиса. Вот у нас сервис реализует этот контракт, здесь же у нас создается сразу же на стороне сервиса канал, только вызовется клиентом этот сервисный метод, здесь у нас сразу же вызовем метод на клиенте. Смотрим в хост, заходим в code behind. Видим, что здесь мы хост, добавляем конечную точку, открываем хост. И вот когда мы нажмем кнопку на форме, то у нас как раз и полетит сообщение по второму каналу, который сгенерирует сервер, для клиента. Мы содержимое textBox отправляем на клиента.

Давайте зайдем теперь в клиента. Вот клиентский Context, мы уже говорили, что это аналог сервиса, который реализует клиентский контракт. Мы здесь обращаемся, используем WindowClient, сейчас мы зайдем в него. Вот у нас идет WindowClient. Смотрим, здесь мы создаем InstanceContext, это обертка для нашего Context, для нашего сервиса системная, создаем DuplexChannelFactory, которая будет создавать двойные каналы, и указываем здесь A B C C (Address, Binding, Context, Contract). Здесь создаем канал в сторону сервиса провайдера, но при этом мы готовы принять его вызов, потому что, вот, видите, мы передали сюда context, мы говорим, что да, мы готовы, что он в ответ протянет к нам другую трубу. Вызываем его метод. И после того, как произойдет connect, мы выведем на экран, что мы законнектились. Переходим к Context и видим, что когда вызовется клиентский метод, а вызовется он кем? Сервисом провайдером, по сети будет вызван этот метод, это не мы будем его у себя вызывать, а сервис удаленный вызовет. И соответственно он нам передаст какое-то сообщение, а мы выведем его на экран. Давайте выполнимся и посмотрим. Вот у нас имеется сервер, вот у нас имеется клиент. Сначала нажимаем, допустим, на клиенте connect, хотим подконнектится к серверу. Соединение установлено. И теперь на сервера мы пишем, допустим, «Hello User», сервер здоровается с клиентом. Видите, да, клиент получил сообщение от сервера по другому каналу, потому что соединение прошло по одному каналу, а пересылка вот этого сообщения, из textbox, произошла по другому каналу. Скажу сразу, для начинающих логика может показаться немножко запутанной. Но, мы так рано, на вот таком стартовом курсе по WCF рассматриваем такую сложную технику, мы эту технику будем дальше рассматривать на курсе Advance и на других курсах по WCF. Но, Вы должны знать, что такая разновидность вызовов существует, такие дуплексные, двойные каналы. Здесь бы надо было убрать вот эти сообщения, можно сказать, что диаграмма не очень хорошо отображает, если мы использовали только одностороннею передачу. Но Вы уже себе подкорректируете.

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

1.

Контракты служб

2.

Контракты данных

3.

Контракты сообщений

4.

Контракты ошибок

Контракты ошибок мы договорились посмотреть на отдельном уроке, который будет посвящен исключениям.

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

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