Сообщений 0    Оценка 330        Оценить  
Система Orphus

Распределенные системы

Сериализация

Автор: Барабаш Михаил Александрович
Перевод:
Источник:
Материал предоставил:
Опубликовано: 02.05.2012
Исправлено: 10.12.2016
Версия текста: 1.1
Службы промежуточного уровня
Распределенные объекты
Сервис ориентированная архитектура
Взгляд со стороны .NET Framework
SOAP Web Services и XmlSerializer
XmlSerializer
.NET Remoting и BinaryFormatter
BinaryFormatter
WCF и DataContractSerializer
Заключение
Список литературы

Службы промежуточного уровня

В общем случае распределенной системой называют, програмно-аппаратное решение, состоящее из компонентов, функционирующих на физически удаленных и независимых друг от друга гетерогенных узлах, предоставляющееся пользователям единой объединенной системой.


Рис 1. Взаимодействие через промежуточный уровень

Для обеспечения требуемого уровня прозрачности, масштабируемости, а также открытости взаимодействия гетерогенных узлов, используется специальный промежуточный уровень, находящийся между прикладным и транспортным уровнем модели OSI.

Таблица 1


Как видно из таблицы 1, к промежуточному уровню относятся достаточно независимые по своей природе понятия, которые служат отдельными строительными блоками более специализированных приложений. Службы промежуточного уровня формально разделяются на парадигмы [1,2] приведенные на рисунке 2.


Рис 2. Парадигмы построения распределенных систем

Распределенные объекты

Одной из первых парадигм построения распределенных систем, был подход удаленного вызова процедур RPC (Remote procedure call). Отличительной особенностью RPC семейства, является высочайшая степень прозрачности, достигающаяся за счет подстановок средой исполнения соответствующих клиентских и серверных заглушек, а также максимально полной информации об используемых типах данных. С развитием ООП, на базе RPC были разработаны технологии получившие название Распределенных Объектов (Distributed objects). Основным их отличием от RPC, является использование заместителя (proxy), реализующего интерфейс удаленного серверного объекта. Назначение заместителя во многом аналогично RPC заглушке, это маршалинг аргументов при обращении к методам удаленного объекта, посылка сообщения удаленному объекту и демаршалинг результатов из ответных сообщений с последующим возвращением их клиенту[1].

Существует достаточно большое количество технологий реализующих парадигму распределенных объектов, наиболее распространенными из которых являются:

Сервис ориентированная архитектура

Термин SoA, как правило, используется для обозначения архитектурного стиля построения надежных распределенных систем, предоставляющие некую функциональность в виде набора слабосвязанных между собой сервисов. Наиболее полное понятие сервиса, дает консорциум всемирной паутины W3C, утверждая, что сервис – это абстрактное представление конкретных прикладных программ, или баз данных, определенное с точки зрения, что именно они делают[2].

Сервис должен обладать следующим набором характеристик:

Более подробно по каждому из требований к сервису, можно почитать в документе[2].

Взгляд со стороны .NET Framework

Начиная с первой версии .NET framework, инженеры из Microsoft предоставили разработчикам достаточно широкий выбор средств и парадигм построения распределенных приложений. Это были уже достаточно плотно прижившиеся: RPC - .NET Remoting, SoA - SOAP WebServices и MoM – MSMQ. Все они являются зрелыми технологиями, обладающие достаточным уровнем гибкости, расширяемости и конфигурирования, но в тоже время требующие от разработчиков совершенно разных моделей программирования и знаний.

Достаточно частым сценарием при проектировании распределенных систем является одновременное использование RPC модели при взаимодействии внутренних жестко связанных частей, и SoA для предоставления внешних интерфейсов. До выхода WCF, .NET Remoting и SOAP WebServices не обладали общей платформой, поэтому реализация новой конечной точки (endpoint) сводилась к написанию достаточно большого количества специфичного для каждой технологии кода. С выходом Windows Communication Foundation (WCF), разработчикам была предложена унифицированная платформа, позволяющая эффективно разрабатывать распределенные приложения, абстрагирующая существующие технологии (службы) промежуточного уровня.

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

Формально процесс сериализации можно разделить на два этапа:

Существует два вида сериализации: текстовая и двоичная, однако с точки зрения распределенных приложений, такая классификация считается устаревшей. Намного важнее не форма представления состояния объекта в выходном потоке, а наличие платформо-зависимых данных или же наоборот стандартизированных данных с одновременным доступом к необходимым метаданным. Двоичное представление сильно зависит от платформы, архитектуры компьютера, даже от версии компилятора и используемых библиотек времени выполнения (runtime). Оно хорошо подходит для построения закрытых, монолитных систем, с непосредственным доступом к общим типам данных (когда все части распределенной системы имеют общий код содержащий описание передаваемых типов данных). Как правило, двоичная сериализация полностью сохраняет информацию о типах и граф объектов.

Текстовая сериализация в основном записывается выходной поток в XML формате, а для предоставления метаданных, как правило, используется XSD схема. Преимуществом XSD схемы, перед альтернативными форматами (OWL семейство) является ее стандартизация и поддержка со стороны основных участников рынка поставщиков средств для разработки ПО.

ПРИМЕЧАНИЕ

Наличие метаданных позволяет при помощи специальных утилит кодогенерации, получить все необходимые типы данных для соответствующей платформы или языка программирования.

Основные механизмы построения распределенных систем и применяемые механизмы сериализации для каждого из них приведены в таблице 2.

Таблица 2


ПРИМЕЧАНИЕ

двоичные сериализаторы отмечены курсивом

Целью данной статьи является рассмотрение наиболее часто используемых механизмов сериализации представленных в среде .NET 4.0, в контексте их применения в стеке той или иной технологии. Привести основные требования к каждому из них, описать наиболее частные случаи их использования и способы изменения их поведения.

SOAP Web Services и XmlSerializer

В большинстве случаев, когда речь идет об открытых распределенных системах, оперируют таким понятием как веб-служба (web service). Веб службы стоят на таких столпах, как WSDL и SOAP[3,4]. Первая технология описывает способ использования службы, а вторая - способ и формат передачи сообщений.

Для того, что бы глубже разобраться в этих понятиях, рассмотрим простой пример. Предположим, что есть веб-служба, содержащая метод GetUser, который принимает два параметра и возвращает экземпляр класса User.

public class User
{
    publicint Id { get; set; }
    publicstring Name { get; set; }
}
…
User GetUser(int id, string name)

WSDL описывает SOAP веб-службу, предоставляя следующую информацию:


Рис. 3 Схематическое описание операции GetUser

Внутри элемента wsdl:types находится описание всех используемых сообщениями типов данных, для чего, как правило, используется XSD схема. Содержимое этой секции зависит от типа привязки (binding), который определяется в секции wsdl:bindings. Структура всех входящих и исходящих сообщений, определяется внутри элементов wsdl:message. Как видно на рисунке 3, определено два сообщения: GetUserSoapIn – передающее параметры запроса, результат исполнения которого будет передан внутри сообщения GetUserSoapOut. Как и в случае элемента wsdl:types, структура сообщений зависит от способа привязки. Веб-методы входящие в состав веб-службы, определяются внутри элементов wsdl:portType, здесь же для каждого веб-метода, задается ссылка на описание входящего и исходящего сообщения.

ПРИМЕЧАНИЕ

На самом деле WSDL спецификация определяет еще третий тип сообщений – FaultMessage, но входящие в состав .NET - SOAP WebServices их не поддерживают.

Наиболее интересной частью WSDL, является описание привязки. Именно внутри секции wsdl:binding, определяется формат сообщений и протокол используемый операциями для приема и передачи сообщений.

<wsdl:binding name="UsersServiceSoap" type="tns:UsersServiceSoap">
    <soap:bindingtransport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="GetUser">
      <soap:operation soapAction="http://www.mb.com/UsersService" style="rpc"/>
      <wsdl:input>
        <soap:bodyuse="literal"namespace="http://www.mb.com/Request" />
      </wsdl:input>
      <wsdl:output>
        <soap:bodyuse="literal"namespace="http://www.mb.com/Response" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>

Из описания привязки UsersServiceSoap, видно, что в качестве транспортного протокола, используется SOAP over HTTP, что следует из значения обязательного атрибута transport. Внутри привязки определяются доступные через нее операции. Операция – это реализация абстрактного веб-метода (сигнатура которого дается в секции wsdl:portType), в контексте привязки, которой она принадлежит. Для каждой операции определяется способ ее вызова, это может быть: rpc или document, а также формат сериализации входящих и исходящих сообщений: literal или encoded. На практике возможны любые комбинации этих параметров, каждая из которых имеет свои сильные, так и слабые стороны. Наиболее определяющим в данной привязке фактором является именно SOAP протокол, описывающий структуру сообщений. Каждое SOAP сообщение может содержать список заголовков и обязательно само тело сообщения, внутри которого находятся сериализированные данные.

Если привязка определяет вызов операции через rpc (style=rpc), то soap:body передаваемого при запросе сообщения должен содержать один единственный дочерний элемент с именем, совпадающим с названием вызываемого веб метода, внутри которого содержатся передаваемые параметры. Исходящее сообщение, при ответе будет иметь аналогичную структуру. В случае (style=document) , тело каждого сообщения полностью определяется XSD схемой, находящейся в секции types.

Как следствие из всего вышесказанного, можно сделать вывод, что привязки имеющие стиль document , имеют преимущество при верификации входящих и исходящих сообщений, а rpc – в значительной степени упрощают разработку, так как не требуют XSD схемы для примитивных типов данных, однако негативно сказываются на расширяемости и производительности системы.

Для каждой операции можно определить формат сериализации передаваемых ею сообщений, задав одно из двух возможных значений атрибута use=encoded|literal. Encoded формат отличается тем, что четко определен спецификацией SOAP 1.1[4] и как правило используется привязками использующими rpc вызовы. Когда же операция использует Literal формат, для сериализации тела сообщения, то это говорит о том, что необходимо смотреть описание соответствующей схемы предоставляемой внутри секции types. Наиболее часто literal формат используется операциями, вызываемыми через document привязки. В таблице 3, приведены все возможные варианты запроса к веб-методу GetUser, в зависимости от вышеперечисленных параметров (use и style).

Таблица 3


ПРИМЕЧАНИЕ

Часть служебных пространств имен убрана из описаний, для экономии места

ПРЕДУПРЕЖДЕНИЕ

На практике возможна реализация любой конфигурации, в зависимости от предоставленных требований к веб службе, но лишь Literal-wrapped привязки, совместимые с WS-I [5].

ASP.NET WebServices и WCF WebServices максимально абстрагируют разработчиков от необходимости вникать во все вышеперечисленные детали, и стоит признать, что для большинства бизнес задач, прекрасно с этим справляются. Как правило, проблемы начинаются, когда проектировщики распределенных систем, начинают использовать общий домен нескольких слоев приложения. Например, что бы бизнес логика, слой доступа к данным и веб-службы использовали общие типы данных. Зачастую в таких системах абстракции начинают течь [6] еще начиная, с попытки объединения любого слоя с контрактами веб-служб и чем раньше проектировщик заглянет за кулисы и вникнет, с чем же на самом деле имеет дело, тем меньше шансов получить технического гротеска на выходе. Поэтому необходимо всегда отдавать себе отчет, что любой класс, используемый как параметр или возвращаемый результат веб-метода, должен поддерживать XML сериализацию, формат которой должен соответствовать привязке, через который данный метод будет вызывается.

XmlSerializer

До выхода WCF, основным механизмом создания веб-служб, было использование ASP.NET WebServices или же в более редких случаях Spring.NET [7] web services. В данной технологии для сериализации/десериализации применяется XmlSerializer. Кроме того он используется для сохранения данных в промежуточные файлы (например, файлы конфигурации), или специализированные хранилища, не сохраняя в выходной поток информацию о сериализируемых типах. Как показала многолетняя практика наиболее часто используемый сериализатор.

Что бы сериализировать класс в соответствии SOAP спецификации (use=encoded), необходимо определить и передать в сериализатор соответствующий экземпляр XmlTypeMapping. Пример приведен в листинге 1, однако поведение такого сериализатора отличается не только форматом записи, но и обладает достаточно большим количеством функциональных отличий и ограничений, например SOAP формат поддерживает полноценное сохранение графа объектов.

Листинг 1. XmlSerializer формирующий SOAP результат

public static byte[] XmlSoapSerialize<T>(this T source)
{
   SoapReflectionImporter soap = new SoapReflectionImporter();
   XmlTypeMapping mapping = soap.ImportTypeMapping(typeof(T));
   XmlSerializer serializer = new XmlSerializer(mapping);
   using (MemoryStream ms = new MemoryStream())
   {
      serializer.Serialize(ms, source);
      return ms.ToArray();
   }
}

<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="id1">
  <Ssn xsi:type="xsd:int">123</Ssn>
  <IsSmoke xsi:type="xsd:boolean">false</IsSmoke >
</Person>

Листинг 2. XmlSerializer формирующий Xml Literal результат

public static byte[] XmlSoapSerialize<T>(this T source)
{
   XmlSerializer serializer = new XmlSerializer(typeof(T));
   using (MemoryStream ms = new MemoryStream())
   {
      serializer.Serialize(ms, source);
      return ms.ToArray();
   }
}
<Person xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Ssn>123</Ssn>
  <IsSmoke>false</IsSmoke>
</Person>

Требования к типу

Поведение по умолчанию

Сериализирует открытые свойства, с гетерами и сетарами. В случае, если отсутствует открытый сетер, будет исключение во время сериализации.

ПРИМЕЧАНИЕ

Начиная с .NET 3.5 свойства, помеченные как [Obsolete] полностью игнорируются. Достаточно спорное решение проектировщиков из Microsoft. Ведь на практике, очень часто бывает, что типы распространяются в виде отдельной .NET 2.0 сборки, которая используется гетерогенными узлами распределенной системы. Вполне возможна следующая ситуация:

(.NET 2.0)->(.NET 3.5)->/Все [Obsolete] свойства пропали/->(.NET 2.0)

Такое поведение может привести к достаточно неожиданным последствиям.

IXmlSerializable

Если класс реализует интерфейс IXmlSerializable, то тогда поведение по умолчанию изменяется и сериализация/десериализация выполняется строго по сценарию определенному в методах WriteXml/ReadXml. Однако нельзя забывать про обязательное наличие конструктора по умолчанию, иначе будет выброшено исключение еще в момент создания сериализатора.

ПРИМЕЧАНИЕ

В случае если выходной поток формируется в соответствии с SOAP спецификацией, то необходимо что бы тело SOAP сообщения было в формате Literal Soap encoded.

Атрибуты

Пространство имен System.Xml.Serialization содержит набор специализированных атрибутов, которые можно использовать для управления XML-сериализацией объекта или для создания альтернативного потока XML из того же набора классов.

XmlOverrides and SoapOverrides

Первые два способа переопределения механизма сериализации подразумевают то, что у разработчика есть доступ и возможность изменения сериализируемых типов. В случае, когда такая возможность отсутствует, можно воспользоваться возможностью передачи набора атрибутов при создании сериализатора, или же специализированного импортера, например для переопределения семейства SoapAttribute соответственно:

XmlSerializer(Type type, XmlAttributeOverrides overrides);
SoapReflectionImporter(SoapAttributeOverrides attributeOverrides);

ПРЕДУПРЕЖДЕНИЕ

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

В целом можно сделать вывод, что XmlSerializer – отлично зарекомендовавший себя сериализатор, который действительно делает, для чего и был спроектирован (поддерживает все сценарии, приведенные в таблице 3). Даже с выходом WCF, он активно продолжает использоваться и прекрасно интегрирован в стек WCF, достаточно лишь отметить контракт всего сервиса или конкретной операции атрибутом [XmlFormat].

.NET Remoting и BinaryFormatter

Технология .NET Remoting базируется на двух основных понятиях: передаваемые по ссылке MBR (Marshall by Reference) объекты и передаваемые по значению MBV (Marshall by Value) объекты. Как уже было сказано выше, .NET Remoting относится к RPC семейству и обладает достаточно большой степенью прозрачности.


Рис. 4 Упрощенная модель взаимодействия .NET Remoting

Передаваемые по ссылке объекты обладают возможностью исполнения собственных методов за границами своего домена. При этом сам объект и его состояние, как правило, не передается за границы а, передаются только аргументы вызываемых методов и их возвращаемые значения. Все объекты, пересекающие границы домена, называются передаваемые по значению, и они обязаны быть явно помечены атрибутом [Serializable]. Процесс передачи по значению представляет сбой сериализацию объекта отправителем и десериализацию – получателем. С выходом WCF, .NET Remoting официально считается устаревшей технологией, однако один из используемых им сериализаторов (см. Таблицу 2) BinaryFormatter на сегодняшний день, все еще активно продолжает использоваться разработчиками. Например, при реализации обычных прикладных задач, связанных с необходимостью временного сохранения состояния системы или части ее данных, с полным сохранением информации о сериализированных типах данных.

BinaryFormatter

Требования к типу

ПРЕДУПРЕЖДЕНИЕ

Атрибут [Serializable] не распространяется на наследников. Важно запомнить, что им необходимо пометить каждый тип, который будет сериализироваться с помощью BinaryFormatter или пересекать границы домена.

Поведение по умолчанию

Сериализует все поля класса, независимо от их области видимости.

ПРЕДУПРЕЖДЕНИЕ

Будьте осторожны, если ваш класс хранит внутри закрытого поля секретные данные и помечен атрибутом [Serializable] , то это поле будет также сериализировано и может быть выявлено при передаче объекта по сети.

Атрибуты

Что бы исключить поле, его можно пометить атрибутом [NonSerializable]

ISerializable

Позволяет определить вручную процесс сериализации внутри метода GetObjectData. Для десериализации необходимо определить конструктор, принимающий два аргумента.

.ctor(SerializationInfo info, StreamingContext context)

ПРЕДУПРЕЖДЕНИЕ

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

BinaryFormatter не требует конструктора по умолчанию, это значит, что при десериализации экземпляр создается без вызова конструктора. Соответственно автоинициализируемые поля помеченные атрибутом [NonSerializable] будут содержать значения по умолчанию. Кроме класса BinaryFormatter, подобным сценарием создания объектов отличается метод Object.MemberwiseClone.

СОВЕТ

Для этого используется вызов метода FormatterServices.GetUninitializedObject

Если же тип реализует интерфейс ISerializable, то, как уже было написано выше, при десерилизации экземпляр создается явным вызовом конструктора принимающего SerializationInfo и SerializingContext, соответственно автоинициализируемые поля будут иметь свои значения по умолчанию.

IDeserializationCallback

Данный интерфейс присутствует с .NET 1.0 и позволяет разработчику модифицировать результат десериализации внутри метода OnDeserialization.

IObjectReference

В случае необходимости возврата совершенно другого экземпляра после десериализации или вызова конструктора (наиболее частый сценарий при сериализации класса - одиночки), можно реализовать интерфейс IObjectReference и вернуть экземпляр любого типа из GetRealObject.

Методы обработчики событий

Начиная с версии .NET 2.0 появилась более гибкая поддержка специальных обработчиков событий происходящих во время сериализации и десериализации.

Явной подписки на эти события нет, вместо этого предлагается создать внутри сериализируемого класса метод с любой областью видимости и именем, но с обязательной сигнатурой следующего вида:

[OnDeserialized] //метод помеченый таким атрибутом, будет вызван после десериализации
private void OnDeserialized(StreamingContext context)
{
   FangsCount = 34;
} 

Разрешается создать не более одного обработчика на каждый вид события.

ПРЕДУПРЕЖДЕНИЕ

Тип может реализовывать ISerializable и иметь обработчики событий, в таком случае могут быть неприятные сюрпризы. Метод, помеченный атрибутом OnDeserializing, вызывается перед вызовом конструктора, и если внутри этого метода изменить значение автоинизиализируемого поля, то оно будет перезаписано в момент десериализации, при явном вызове конструктора, что может изменить ожидаемое программистом поведение программы.

На рисунке 5, приведены блок-схемы сериализации и десериализации BinaryFormatter, на которых видна последовательность вызова перечисленных выше механизмов управления процессом сериализации.


Рис. 5 Процесс сериализации и десериализации BinaryFormatter

WCF и DataContractSerializer

Стремясь собрать в рамках одной новой технологии WCF все существующие ранее подходы создания распределенных приложений, инженеры из Microsoft столкнулись с задачей создания нового сериализатора, способного решить задачу сериализации и десериализации передаваемых данных. В 2006 году общественности было представлено два новых сериализатора: DataContractSerializer и NetDataContractSerializer. Первый был интегрирован в стек WCF, как сериализатор по умолчанию и обладает широким спектром использования и кастомизации, но самое главное, прекрасно подходит для создания распределенных систем без общего доступа к типам. NetDataContractSerializer в свою очередь, отлично подходит для использования внутри систем с общими типами, однако требует что бы все клиенты и сервер, использовали одинаковую версию .NET framework.

С первого взгляда может показаться, что DataContractSerializer в некоторой степени универсальный сериализатор, совмещающий в себе практически все ранее известные сценарии сериализации, что в достаточной степени уменьшает диверсификацию используемых внутри системы механизмов сериализации. Можно сказать, что его создателям удачно удалось уместить в одном классе все существующие ранее сериализаторы.

Ниже приводится анализ требований к типам, которые могут использоваться DataContractSerializer, а также используемый сценарий работы, для каждого случая. Очень важно правильно понимать алгоритм и приоритеты работы данного сериализатора.

Требования к типу
DataContractSerializer практически полностью повторяет поведение BinaryFormatter, если тип помечен атрибутом Serializable. Существенным отличием является то, то в выходной поток не сохраняется информация о типах. Если тип не помечен никаким атрибутом, но имеет конструктор по умолчанию, то DataContractSerializer будет дублировать поведение XmlSerializer, однако атрибуты XmlElement, XmlAttribute, XmlIgnore… будут проигнорированы. Допускается реализация интерфейса IXmlSerializable, при этом в отличие от XmlSerializer для сериализации отсутствует требование наличия конструктора по умолчанию.

На рисунке 6, приведена блок-схема сериализации, отображающая последовательность выбора способа сериализации. Очередность выбора стратегии десериализации пропущена, так как она – симметрична.


Рис. 6 Алгоритм сериализации DataContractSerializer
Поведение по умолчанию

В отличие от BinaryFormatter, если поля или свойства класса явно не помечены атрибутом DataMember, то они не будут сериализированы, это делает использование DataContractSerializer более безопасным с точки зрения сокрытия внутренних данных класса.

Переопределение

Как уже было написано выше, данный сериализатор не сохраняет информацию о типе, поэтому если не подразумевается использование общих контрактов, то крайне не рекомендуется переопределять работу сериализатора отличными от DataMemberAttribute способами.

СОВЕТ

Не помечайте класс одновременно двумя атрибутами [Serializable] и [DataContract], это собьет с толку других разработчиков, а возможно приведет к багу в программе.

Заключение

Данная статья изначально задумывалась, как небольшое практическое руководство по существующим в .NET framework сериализаторам. Однако без описания технологий, для которых они изначально предназначались, данное повествование было бы оторванным от реальности. Основное внимание ,было уделено именно требованиям к самим сериализируемым типам данных, поэтому такие разделы как: суррогаты, известные типы, сериализация коллекций, были преднамеренно пропущены.

Список литературы

  1. Э. Таненбаум, М. ван Стеен "Распределенные системы. Принципы и парадигмы": Питер 2003
  2. World Wide Web Consortium "Web Services Architecture"
    http://www.w3.org/TR/ws-arch/
  3. World Wide Web Consortium "Web Services Description Language" http://www.w3.org/TR/wsdl
  4. World Wide Web Consortium "Simple Object Access Protocol" http://www.w3.org/TR/2000/NOTE-SOAP-20000508/
  5. Web Services Interoperability Organisation "Basic Profile Version 1.1"
    http://www.ws-i.org/Profiles/BasicProfile-1.1.html
  6. Joel Spolsky "The Law of Leaky Abstractions" http://www.joelonsoftware.com/articles/LeakyAbstractions.html
  7. Spring.NET application framework
    http://springframework.net/doc-latest/reference/html/webservices.html


    Сообщений 0    Оценка 330        Оценить