02.04.2009

ChildData in Parallel Replicator

Использование Replicator'а бывает очень удобно и нужно. Но есть один очень не удобный момент: доступ к данным внутри дочерних элементов в параллельном репликаторе.
Если использовать последовательный репликатор, то текущий исполняемый экземпляр доступен через свойство репликатора CurrentIndex.
Иначе дело обстоит с параллельным репликатором, где CurrentIndex всегда указывает на один и тот же элемент в списке данных для реплицируемых элементов.
Для решения данной проблемы можно поступить следующим образом:
-определить свой SequenceActivity, содержащий ссылку на данные для экземпляра репликатора. (поле _instanceData)
[Serializable]
public class StoreSequence : SequenceActivity
{
--private object _instanceData = null;

--public object InstanceData
--{
------get { return _instanceData; }
------set { _instanceData = value; }
--}
}
-поместить данный Activity в качестве дочернего элемента в Replicator Activity, а реплицируемые элементы на StoreSequence .
-при выполнение метода ChildInitialized (инициализация дочерних activity) сохранить данные в созданный контейнер
...
private void sendAlertsReplicator_ChildInitialized(object sender, ReplicatorChildEventArgs e)
{
----((StoreSequence)e.Activity).InstanceData = e.InstanceData;
}
...
-для обращения внутри реплицируемых элементов необходимо выполнить операцию приведения типов (Activity)sender , а родительским элементом будет StoreSequence у которого можно получить свойство InstanceData.

31.03.2009

Бинарнарный сериализатор. (BinarySerializer)

Данный сериализатор формирует из типа, байт последовательность. Формат последовательности следующий:

<class_element_count> - количество полей в классе (4 байта)
[<element>, <element>] - поля класса

где <element> - это простой тип данных:
<size> - размер тела (4 байта)
- простой тип переведенный в байты
или массив представление сериализованного массива аналогично классу (см. выше)

Данный сериализатор поддерживает следующие простые типы:
bool, short, int, long(Int64), float(Single), double, Guid.

Механизм содержит три основные части:
1. Атрибуты, которые указывают, что класс или поле необходимо сериализовать.
2. Временный контейнер, который используется для хранения данных при сериализации.
3. Собственно сам механизм сериализации.

Атрибуты:


// Атрибут для класса. Используется в BinarySerializer
[AttributeUsage(
AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class ClassToBinaryAttribute : Attribute
{
}

/// Атрибут для поля класса. Используется в BinarySerializer
[AttributeUsage(
AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class FieldToBinaryAttribute : Attribute
{
}


Временный контейнер:


/// <summary>
/// Контейнер для хранение сериализованных бинарных данных
/// </summary>
public abstract class BinaryContainer
{
protected List<BinaryContainer> _childs =
new List<BinaryContainer>();
/// <summary>
/// Дочерние элементы контейнера
/// </summary>
public List<BinaryContainer> Childs
{
get { return this._childs; }
}

protected byte[] _body;
/// <summary>
/// Сериализованные данные
/// </summary>
public byte[] Body
{
//get { return _body; }
set { _body = value; }
}

/// <summary>
/// Получить размер бинарной информации
/// </summary>
/// <returns></returns>
public abstract int GetBodySize();

/// <summary>
/// Получить бинарные данные
/// </summary>
/// <returns></returns>
public abstract byte[] GetBody();
}

/// <summary>
/// Контейнер для поля
/// </summary>
class BinaryField : BinaryContainer
{
public override int GetBodySize()
{
if (this._body != null)
return this._body.Length + 4;
else
return 0;
}

public override byte[] GetBody()
{
if (this._body != null)
{
// весь размер буфера: размер + данные
int fullSize = this._body.Length + 4;
byte[] newBody = new byte[fullSize];

Buffer.BlockCopy(
BitConverter.GetBytes(this._body.Length), 0, newBody, 0, 4);
Buffer.BlockCopy(
this._body, 0, newBody, 4, this._body.Length);

return newBody;
}
else
return null;
}
}

/// <summary>
/// Контейнер для класса или массива
/// </summary>
class BinaryArray : BinaryContainer
{
public int ArrayElementCount
{
get { return this._childs.Count; }
}

public override int GetBodySize()
{
int size = 4;

if (this._childs.Count > 0)
{
foreach (BinaryContainer child in this._childs)
size += child.GetBodySize();

return size;
}
else
return size;
}

public override byte[] GetBody()
{
if (this._childs.Count > 0)
{
int bodySize = this.GetBodySize();
this._body = new byte[bodySize];

Buffer.BlockCopy(
BitConverter.GetBytes(
this.ArrayElementCount), 0, this._body, 0, 4);

int offset = 4;
foreach (BinaryContainer child in this._childs)
{
int childSize = child.GetBodySize();
byte[] childBody = child.GetBody();
Buffer.BlockCopy(
childBody, 0, this._body, offset, childSize);
offset += childSize;
}

return this._body;
}
else
return new byte[4];
}

}


Контейнер предоставляет два типа:
-тип для временного хранения простых типов (BinaryArray);
-тип для временного хранения сложных типов, таких как классы и массивы (BinaryField).
Два метода GetBodySize и GetBody предоставляют возможность для получения размера тела и сам байтовый массив. Паттерн Dependency Injection позволяет прозрачно, для основного механизма сериализации, использовать типы контейнера.

Сериализатор:


/// <summary>
/// Бинарный сериализатор
/// Поддержка следующих типов данных:
/// классы, массивы,
/// bool, short, int32, long(int64), float(single), double, Guid
/// Сереализация generic типов не поддерживается
/// </summary>
public class BinarySerializer
{
/// <summary>
/// Сериализация объекта
/// </summary>
/// <typeparam name="TType">Тип объекта</typeparam>
/// <param name="obj">Объект</param>
/// <returns>Массив байт</returns>
public byte[] Serialize<TType>(TType obj)
{
BinaryContainer binaryContainer = this.ClassSerializer(typeof(TType), obj);
byte[] result = binaryContainer.GetBody();

return result;
}

#region Хелперы для сериализации
/// <summary>
/// Сериализация объекта
/// </summary>
/// <param name="type">Тип объекта</param>
/// <param name="obj">Объект</param>
/// <returns>Временный контейнер</returns>
private BinaryContainer ClassSerializer(Type type, object obj)
{
// получение полей в типе
FieldInfo[] fInfos = type.GetFields(
BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public);

BinaryContainer classContainer = new BinaryArray();

foreach (FieldInfo fInfo in fInfos)
{
object[] customFieldAttributes =
fInfo.GetCustomAttributes(
ypeof(FieldToBinaryAttribute), false);

if (customFieldAttributes.Length == 1)
{
// получение аттрибута у типа поля
object[] customTypeAttributes =
fInfo.FieldType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);

if (customTypeAttributes.Length == 1) // поле сложного типа
{
classContainer.Childs.Add(
this.ClassSerializer(fInfo.FieldType, fInfo.GetValue(obj)));
}
else // поле простого типа
{
if (fInfo.FieldType.IsArray) // поле массив
{
//сериализация массива
classContainer.Childs.Add(
this.ArraySerializer(fInfo, obj));
}
else // поле простого типа
classContainer.Childs.Add(
this.SimpleTypeSerializer(
fInfo.FieldType, fInfo.GetValue(obj)));
}
}
}

return classContainer;
}

/// <summary>
/// Сериализация массива
/// </summary>
/// <param name="fInfo">Дескриптор поля</param>
/// <param name="obj">Объект</param>
/// <returns>Бинарный контейнер с сереализованным массивом</returns>
private BinaryContainer ArraySerializer(FieldInfo fInfo, object obj)
{
// тип элемента массива
Type elementType = fInfo.FieldType.GetElementType();
Array array = (Array)fInfo.GetValue(obj);

bool isClass = false;
// получение аттрибута у типа поля
object[] customTypeAttributes =
elementType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);

if (customTypeAttributes.Length == 1)
isClass = true;

// контейнер под хранения массива в бинарном виде
BinaryContainer binaryArray = new BinaryArray();

// испульзуем энумератор для передвижения по массиву
IEnumerator arrayEnumerator = array.GetEnumerator();
while (arrayEnumerator.MoveNext() &&
arrayEnumerator.Current != null)
{
BinaryContainer arrayElement = null;
if (isClass)
{
arrayElement =
this.ClassSerializer(
elementType, arrayEnumerator.Current);
}
else
{
// получение бинарного представления для элемента массива
arrayElement =
this.SimpleTypeSerializer(
elementType, arrayEnumerator.Current);
}

// добавление простого типа к бинарному контейнеру
binaryArray.Childs.Add(arrayElement);
}

return binaryArray;
}

/// <summary>
/// Сериализация простых типов
/// bool, short, int32, long(int64), float(single), double, Guid
/// </summary>
/// <param name="fieldType">Тип поля</param>
/// <param name="value">Значение поля</param>
/// <returns>Временный контейнер</returns>
private BinaryContainer SimpleTypeSerializer(Type fieldType, object value)
{
// буфер для хранения сериализовнных данных
byte[] buffer = null;

if (fieldType == typeof(Boolean))
{
buffer = BitConverter.GetBytes((Boolean)value);
}
else if (fieldType == typeof(Int16))
{
buffer = BitConverter.GetBytes((Int16)value);
}
else if (fieldType == typeof(Int32))
{
buffer = BitConverter.GetBytes((Int32)value);
}
else if (fieldType == typeof(Int64))
{
buffer = BitConverter.GetBytes((Int64)value);
}
else if (fieldType == typeof(Single))
{
buffer = BitConverter.GetBytes((Single)value);
}
else if (fieldType == typeof(Double))
{
buffer = BitConverter.GetBytes((Double)value);
}
else if (fieldType == typeof(Guid))
{
buffer = ((Guid)value).ToByteArray();
}

BinaryContainer tempC = null;
if (buffer != null && buffer.Length > 0)
{
tempC = new BinaryField();
tempC.Body = buffer;
}
else
throw new Exception("Ошибка в сериализациия поля типа " + fieldType.Name);

return tempC;
}
#endregion

/// <summary>
/// Десерилазиация
/// </summary>
/// <typeparam name="TType">Тип объекта</typeparam>
/// <param name="byteObj">Массив байт</param>
/// <returns>Объект заданного типа</returns>
public TType Deserialize<TType>(byte[] byteObj)
{
int offset = 0;
TType inst =
(TType)this.ClassDesirealize(
typeof(TType), byteObj, ref offset);

return inst;
}

#region Хелперы для десериализации

/// <summary>
/// Десериализация класса
/// </summary>
/// <param name="type">Тип класса</param>
/// <param name="data">Бинарные данные</param>
/// <returns>Экземпляр объекта</returns>
private object ClassDesirealize(
Type type, byte[] data, ref int offset)
{
// получение полей в типе
FieldInfo[] fInfos = type.GetFields(
BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public);

//создание объекта типа type
object obj = this.CreateInstance(type);

offset += 4; // первые 4 байта класса - количество полей

foreach (FieldInfo fInfo in fInfos)
{
object[] customFieldAttributes =
fInfo.GetCustomAttributes(
typeof(FieldToBinaryAttribute), false);

if (customFieldAttributes.Length == 1)
{
// получение аттрибута у типа поля
object[] customTypeAttributes =
fInfo.FieldType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);

object fieldValue = null;

if (customTypeAttributes.Length == 1) // поле сложного типа
{
fieldValue =
this.ClassDesirealize(
fInfo.FieldType, data, ref offset);
}
else
{
if (fInfo.FieldType.IsArray) // поле массив
{
fieldValue =
this.ArrayDeserializer(fInfo, data, ref offset);
}
else // поле простого типа
{
// массив байт для хранения размера
byte[] sizeByte = new byte[4];
Buffer.BlockCopy(
data, offset, sizeByte, 0, sizeByte.Length);
// получение размера
int size = BitConverter.ToInt32(sizeByte, 0);

// получение данных
byte[] dataBuffer = new byte[size];
Buffer.BlockCopy(
data, offset + sizeByte.Length,
dataBuffer, 0, dataBuffer.Length);

// десериализация поля
fieldValue =
this.SimpleTypeDeserializer(
fInfo.FieldType, dataBuffer);

// увелечени сдвига по буферу
offset += sizeByte.Length + size;
}
}

// помещение значение в объект
fInfo.SetValue(obj, fieldValue);
}
}

return obj;
}

/// <summary>
/// Десериализация массива
/// </summary>
/// <param name="fInfo">Дескриптор поля</param>
/// <param name="data">Массив байт</param>
/// <param name="offset">Смещение в массиве байт</param>
/// <returns>Массив</returns>
private object ArrayDeserializer(
FieldInfo fInfo, byte[] data, ref int offset)
{
// получение количества элементов в массиве
byte[] elCountByte = new byte[4];
Buffer.BlockCopy(data, offset, elCountByte, 0, elCountByte.Length);
int elementsCount = BitConverter.ToInt32(elCountByte, 0);

// увелечение счетчика движения по буферу
offset += elCountByte.Length;

bool isClass = false;
// тип элемента массива
Type elementType = fInfo.FieldType.GetElementType();
// получение аттрибута у типа поля
object[] customTypeAttributes =
elementType.GetCustomAttributes(
typeof(ClassToBinaryAttribute), false);
if (customTypeAttributes.Length == 1)
isClass = true;

// создание экземпляра массива
Array array = Array.CreateInstance(elementType, elementsCount);

//получение элементов массива
for (int i = 0; i < elementsCount; i++)
{
object value = null;

if (isClass)
{
value =
this.ClassDesirealize(
elementType, data, ref offset);
}
else
{
if (elementType.IsArray)
{
throw new NotImplementedException(
"Ошибка при десериализации в массива ");
}
else
{
// получение размера элемента массива
byte[] elSizeByte = new byte[4];
Buffer.BlockCopy(
data, offset, elSizeByte, 0, elSizeByte.Length);
int elementSize = BitConverter.ToInt32(elSizeByte, 0);

offset += elSizeByte.Length;

//получение значения элемента массива
byte[] valueByte = new byte[elementSize];
Buffer.BlockCopy(
data, offset, valueByte, 0, valueByte.Length);
value = this.SimpleTypeDeserializer(elementType, valueByte);

offset += valueByte.Length;
}
}

// заполнение массива
array.SetValue(value, i);
}

return array;
}

/// <summary>
/// Создание экземпляра класса по его типу
/// </summary>
/// <param name="type">Тип</param>
/// <returns>Объект</returns>
private object CreateInstance(Type type)
{
ConstructorInfo constructor =
type.GetConstructor(new Type[] { });

object inst = null;

if (constructor != null)
{
inst = constructor.Invoke(new object[] { });
}
else
throw new Exception(
"Не возможно создать объект типа " +
type.Name +
". Объект должен иметь пустой конструктор");

return inst;
}

/// <summary>
/// Десериализация простых типов
/// bool, short, int32, long(int64), float(single), double, Guid
/// </summary>
/// <param name="fieldType"></param>
/// <param name="data"></param>
/// <returns></returns>
public object SimpleTypeDeserializer(Type fieldType, byte[] data)
{
object value = null;

if (fieldType == typeof(Boolean))
{
value = BitConverter.ToBoolean(data, 0);
}
else if (fieldType == typeof(Int16))
{
value = BitConverter.ToInt16(data, 0);
}
else if (fieldType == typeof(Int32))
{
value = BitConverter.ToInt32(data, 0);
}
else if (fieldType == typeof(Int64))
{
value = BitConverter.ToInt64(data, 0);
}
else if (fieldType == typeof(Single))
{
value = BitConverter.ToSingle(data, 0);
}
else if (fieldType == typeof(Double))
{
value = BitConverter.ToDouble(data, 0);
}
else if (fieldType == typeof(Guid))
{
value = new Guid(data);
}

if (value == null)
throw new Exception(
"Ошибка в десериализациия поля типа " +
fieldType.Name);

return value;
}

#endregion

}

30.03.2009

Каждому экшену - свое место

Итак, определившись с типом экшена, который мы хотим включить в инсталляцию, необходимо определиться с тем местом инсталляции, в котором его необходимо выполнить. В msi существует две таблицы в которые можно размещать экшены - InstallUISequence и InstallExecuteSequence. Действия, определенные в таблице InstallUISequence выполняются в самом начале установки и как правило именно в этой таблице описывается последовательность пользовательских диалогов, которая будет выводиться для сбора сведений об инсталляции от пользователя. При размещении своих экшенов в этой таблице надо помнить об одной важной вещи - существует "тихий" режим инсталляции, в котором таблица InstallUISequence не обрабатывается. Поэтому если экшен должен выполниться в любом случае, то логично разместить его в таблице InstallExecuteSequence. Действия в таблице InstallExecuteSequence по сути отвечают за сам процесс установки, именно они копируют файлы, ведут записи в реестре и т.д. И как правило именно при их выполнении пользователь видит перед собой диалоговое окно с прогресс-баром. Но при размещении в этой таблице своих экшенов надо помнить об опциях выполнения, которые в WIX определяются атрибутом "Execute" тега "CustomAction". Важнейшими из них являются deferred и immediate. Дело в том, что установка продукта не происходит "в один проход". На определенном этапе, а если точнее, то между действиями InstallInitialize и InstallFinalize происходит запись скрипта инсталляции и те экшены, которые помечены как deferred не выполняются сразу, а записываются в скрипт и исполняются после InstallFinalize, в то время как immediate-экшены выполняются всегда в тот момент, когда приходит их очередь по таблице. Отсюда следуют несколько интересных выводов: во-первых, deferred-экшен не может быть поставлен никуда кроме как между InstallInitialize и InstallFinalize. Во-вторых, если поставить immediate-экшен и deferred-экшен в одно место после копирования файлов, то на момент выполнения immediate-действия файлы еще скопированы не будут, потому как копируются во время исполнения скрипта, а не во время его записи, соттветственно deferred-экшен сможет использовать эти файлы. То же самое относится и к другим изменениям, выполняющимся в deferred-фазу.


Также важным моментом является использование rollback-экшенов. Эти экшены создаются с использованием значения "rollback" для атрибута "Execute". Такие экшены выполняются в том случае, если во время инсталляции произошла ошибка и производится откат. Использовать их следует в паре с написанными кастом-экшенами для отката изменений, ибо сам инсталлер не откатывает то, что делают кастом-экшены.
Порядок выполнения экшенов в таблицах задается целыми числами. В WIX эти числа можно определять как напрямую через атрибут "Sequence" тега "Custom", либо через атрибуты "Before" и "After", задавая перед или после какого экшена выполняться текущему действию.

23.03.2009

Custom Actions, какие они?

Экшены - это действия, котоорые выполняются в процессе инсталляции. По своей сути они представляют собой функции, которые и руководят процессом установки от копирования файлов и записей в реестре до сложных пользовательских функций. Существуют как встроенные экшены инсталлятора, например LunchCondition, который осуществляет проверку условий установки, так и CustomActions - пользовательские функции, расширяющие возможности инсталлятора. Существует несколько типов custom-экшенов, подходящих для разных нужд разработчика. Ниже я постарался перечислить все типы экшенов, присутствующих в Wix 3. Данная информация может быть использована как памятка при выборе типа экшена для определенной задачи.

Type 1 – вызов функции из dll, dll находится в таблице Binary инсталляционного пакета.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
BinaryKey="" – id библиотеки в таблице Binary
DllEntry="" – имя вызываемой функции
/>

Type 2 – запуск исполняемого exe-файла, exe находится в таблице Binary инсталляционного пакета.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
BinaryKey="" – id файла в таблице Binary
ExeCommand="" – передаваемые параметры командной строки
/>

Type 5 – запуск скрипта Java, файл JavaScript находится в таблице Binary инсталляционного пакета.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
BinaryKey="" – id файла скрипта в таблице Binary
JScriptCall="" – имя вызываемой функции
/>

Type 6 – запуск скрипта VisualBasic, файл VBScript находится в таблице Binary инсталляционного пакета.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
BinaryKey="" – id файла скрипта в таблице Binary
VBScriptCall="" – имя вызываемой функции
/>

Type 17 – вызов функции из dll, dll является устанавливаемым файлом.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
FileKey="" – id библиотеки в таблице файлов
DllEntry="" – имя вызываемой функции
/>

Type 18 – запуск исполняемого exe-файла, exe является устанавливаемым файлом.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
FileKey="" – id файла в таблице файлов
ExeCommand="" – передаваемые параметры командной строки
/>

Type 19 – отображение сообщения об ошибке, завершение инсталляции.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Error="" – текст сообщения об ошибке
/>

Type 21 – запуск скрипта Java, файл JavaScript является устанавливаемым файлом
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
FileKey="" – id файла скрипта в таблице файлов
JScriptCall="" – имя вызываемой функции
/>

Type 22 – запуск скрипта VisualBasic, файл VBScript является устанавливаемым файлом.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
FileKey="" – id файла скрипта в таблице Binary
VBScriptCall="" – имя вызываемой функции
/>

Type 34 – запуск исполняемого exe-файла, определяемого директорией, в которой расположен файл и командной строкой для запуска.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Directory="" – id директории в таблице Directory, в которой располагается exe
ExeCommand="" – командная строка для запуска приложения
/>

Type 35 – установка физического пути для элемента Directory инсталляционного пакета.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Directory="" - id директории в таблице Directory
Value="" – новое значение пути (форматируемая строка)
/>

Type 37 – запуск скрипта Java, скрипт написан непосредственно в CustomAction
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Script="jscript">
< ![CDATA[< !—здесь находится текст скрипта -- >\]]>
< /customaction>

Type 38 – запуск скрипта Visual Basic, скрипт написан непосредственно в CustomAction
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Script=" vbscript">
< ![CDATA[< !—здесь находится текст скрипта -- >\]]>
< /customaction>

Type 50 – запуск исполняемого exe-файла, exe уже существует на целевой системе. Exe задается через Property.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Property="" – Property, определяющее имя exe-файла
ExeCommand="" – параметры командной строки, передаваемые приложению
/>

Type 51 – установка значения Property.
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
Property="" – id изменяемого Property
Value="" – новое значение (не должно содержать других Property)
/>

Type 53 – запуск скрипта Java, скрипт хранится в Property
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
JScriptCall="" – имя вызываемой функции
Property="" – id Property, хранящей текст скрипта
/>

Type 54 – запуск скрипта Visual Basic, скрипт хранится в Property
Объявление в тегах Wix:
< CustomAction
Id="" – id экшена
VBScriptCall="" – имя вызываемой функции
Property="" – id Property, хранящей текст скрипта
/>

С внедрением в Wix инструмента DTF стал доступен запуск экшенов из .NET сборки. Как создать соответствующую сборку можно поглядеть в примерах в интернете, а объявление такого экшена в Wix имеет вид аналогичный экшенам для dll-библиотек (например type 1).

P.S. пробелов после открывающихся скобок быть не должно - поставлены, чтобы не воспринимались как html-разметка.

05.03.2009

Динамический ServiceKnownType

При использовании DataContractSerializer иногда возникает необходимость задавать динамически ServiceKnownTypes. Сделать это можно с использование класса-хелпера.



public static class ServiceHelper
{
private static List<Type> _knownTypes = new List<Type>();
public static List<Type> KnownTypes
{
get { return ServiceHelper._knownTypes; }
}

public static IEnumerable<Type> GetServiceKnownTypes(
ICustomAttributeProvider provider)
{
return _knownTypes;
}
}

[ServiceKnownType("GetServiceKnownTypes", typeof(ServiceHelper))]
[ServiceContract(SessionMode = SessionMode.NotAllowed)]
public interface IServiceContract
{
// методы сервиса
}


Добавление типов в ServiceHelper на клиенте можно делать до создания объекта реализующего контракт, а в сервисе в global.asax в методе Application_Start.