Предположение о том, что клиентский прокси будет выполнять один и тот же код на клиенте и на сервере, ошибочно, поскольку WCF основан на интерфейсах. Я объясняю этот момент в пуле № 2 ниже.
Правила совместного использования интерфейсов и реализации WCF
Если вы хотите поделиться реализацией контракта данных, вам нужно будет преобразовать класс RequestArray в библиотеку классов, в которой НИЧЕГО не содержится, но классы контракта данных, включая предположительно также класс RequestRecord.
Правила, по которым я живу:
Сгруппируйте все контракты данных по себе (100%) в одну или несколько сборок, без исключений.
Сгруппируйте все сервисные контракты в одну или несколько сборок.
Сгруппируйте все типы сервисов, т. Е. Классы, реализующие сервисный контракт, в одну или несколько сборок.
Сгруппируйте все прокси клиентского канала, то есть класс, который вызывает методы, определенные в интерфейсе контракта на обслуживание, в одну или несколько сборок.
В общей структуре, где все клиентское программное обеспечение работает как служба WCF (я избегаю дуплексных подключений), безопасно объединить правила 2, 3 и 4, чтобы договоры на обслуживание, типы услуг и прокси каналов были сгруппированы вместе в одну сборку.
Основная причина разделения интерфейсов на более гибкую цепочку зависимостей состоит в том, что можно развернуть на клиенте ограниченный набор сборок, не раскрывая ненужных и потенциально запатентованных деталей реализации. Другая причина заключается в том, что это значительно упрощает рефакторинг, особенно в тех случаях, когда вы хотите внедрить или расширить универсальную среду посредством наследования или делегирования.
Изучение кода
Есть несколько БОЛЬШИХ проблем с кодом для RequestArray ...
Логика установщика перезапишет любой из измененных элементов переменной массива m_Record при десериализации экземпляра DataContract. Это нарушает принципы десериализации.
Свойство Record невозможно десериализовать, поскольку свойство Record класса RequestArray доступно только для чтения (поскольку в нем отсутствует установщик). Как правило, я считаю, что для классов DataContract лучший подход к свойствам, доступным только для чтения, - это просто метод. Это плохая идея, если вы привыкнете трактовать контракты с данными так, будто они представляют собой нечто большее, чем битовые корзины. То, что в основном делают атрибуты, - это динамическое создание определения интерфейса, специально используемого для сериализации и десериализации данных. Я считаю, что ошибочно думать о данных на проводе как об объектах. Скорее, это дополнительный способ указать соответствующие части данных объекта, которые должны сохраняться по проводам.
Свойство TotalRecords становится опасным, если оно заканчивается (правильно), просто позволяя установить переменную m_TotalRecords, поскольку оно будет полностью независимым от внутреннего массива. Для того, чтобы это работало приемлемо в моем примере кода (ниже), мне пришлось экранировать набор с помощью if (m_TotalRecords == 0)
. В примере кода, который я сохранил для будущего использования, я полностью закомментировал свойство TotalRecords, но оставляю m_TotalRecords просто для иллюстрации того, что частный объект фактически сохраняется по проводам.
Фиксированный код
Я адаптировал пример кода Бендевея (спасибо!) И придумал этот полный тест. Примечание: я должен был определить RequestRecord. Также смотрите код комментария. Если есть какие-либо ошибки или что-то неясное, пожалуйста, дайте мне знать.
#region WCFDataContractTest
[DataContract] // The enclosed type needs to also be attributed for WCF
public class RequestRecord
{
public RequestRecord() { }
[DataMember] // This is CRUCIAL, otherwise the Name property will not be preserved.
public string Name { get; set; }
}
[DataContract] // Encloses the RequestRecord type
public class RequestArray
{
private int m_TotalRecords; // should be for internal bookkeeping only
private RequestRecord[] m_Record;
[System.Xml.Serialization.XmlElement]
[DataMember]
public RequestRecord[] Record
{
get { return m_Record; }
// deserialization will not work without the set
set { m_Record = value; }
}
[DataMember] // is not really needed
public int TotalRecords
{
get { return m_TotalRecords; }
set
{
if (m_TotalRecords == 0)
m_TotalRecords = value;
}
}
// The constructor is not called by the deserialization mechanism,
// therefore this is the right place to specify the array size and to
// perform the array initialization.
public RequestArray(int totalRecords)
{
if (totalRecords > 0 && totalRecords <= 100)
{
m_TotalRecords = totalRecords;
m_Record = new RequestRecord[totalRecords];
for (int i = 0; i < m_TotalRecords; i++)
m_Record[i] = new RequestRecord() { Name = "Record #" + i.ToString() };
m_TotalRecords = totalRecords;
}
else
m_TotalRecords = 0;
}
}
public static void TestWCFDataContract()
{
var serializer = new DataContractSerializer(typeof(RequestArray));
var test = new RequestArray(6);
Trace.WriteLine("Array contents after 'new':");
for (int i = 0; i < test.Record.Length; i++)
Trace.WriteLine("\tRecord #" + i.ToString() + " .Name = " + test.Record[i].Name);
//Modify the record values...
for (int i = 0; i < test.Record.Length; i++)
test.Record[i].Name = "Record (Altered) #" + i.ToString();
Trace.WriteLine("Array contents after modification:");
for (int i = 0; i < test.Record.Length; i++)
Trace.WriteLine("\tRecord #" + i.ToString() + " .Name = " + test.Record[i].Name);
using (var ms = new MemoryStream())
{
serializer.WriteObject(ms, test);
ms.Flush();
ms.Position = 0;
var newE = serializer.ReadObject(ms) as RequestArray;
Trace.WriteLine("Array contents upon deserialization:");
for (int i = 0; i < newE.Record.Length; i++)
Trace.WriteLine("\tRecord #" + i.ToString() + " .Name = " + newE.Record[i].Name);
}
}
#endregion
Листинг для этого примера программы после запуска TestWCFDataContract:
Содержимое массива после 'new':
Record #0 .Name = Record #0
Record #1 .Name = Record #1
Record #2 .Name = Record #2
Record #3 .Name = Record #3
Record #4 .Name = Record #4
Record #5 .Name = Record #5
Содержимое массива после модификации:
Record #0 .Name = Record (Altered) #0
Record #1 .Name = Record (Altered) #1
Record #2 .Name = Record (Altered) #2
Record #3 .Name = Record (Altered) #3
Record #4 .Name = Record (Altered) #4
Record #5 .Name = Record (Altered) #5
Содержимое массива при десериализации:
Record #0 .Name = Record (Altered) #0
Record #1 .Name = Record (Altered) #1
Record #2 .Name = Record (Altered) #2
Record #3 .Name = Record (Altered) #3
Record #4 .Name = Record (Altered) #4
Record #5 .Name = Record (Altered) #5