WCF [DataContract] установить / получить не выполняется - PullRequest
2 голосов
/ 03 февраля 2009

Я немного новичок в WCF и не думаю, что полностью понимаю, в чем заключается сделка с DataContracts. У меня есть класс RequestArray:

[DataContract]
public class RequestArray
{
  private int m_TotalRecords;
  private RequestRecord[] m_Record;

  [System.Xml.Serialization.XmlElement]
  [DataMember]
  public RequestRecord[] Record
  { 
    get { return m_Record; }
  }

  [DataMember]
  public int TotalRecords
  {
    get { return m_TotalRecords; }
    set {
      if (value > 0 && value <= 100) {
        m_TotalRecords = value;
    m_Record = new RequestRecord[value];
    for (int i = 0; i < m_TotalRecords; i++)
      m_Record[i] = new RequestRecord();
      }
    }
  }
}

Идея состоит в том, что когда клиент говорит requestArray.TotalRecords=6; массив записей будет выделен и инициализирован (я понимаю, что скрываю реализацию за назначением, это вне моего контроля).

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

EDIT: Похоже, я не совсем понял, как работает [DataContract], но имеет смысл, что клиент не будет выполнять этот код. Как я упоминал в комментарии, если я «вручную» выполняю работу установщика, я вижу, что установленный код действительно выполняется, когда я вызываю сервисную функцию.

Сериализация, в которой я все еще не уверен. Содержимое массива RequestRecord [] не переносится. В классе Record есть сеттеры / геттеры, я чувствую, что мне нужна где-то вспомогательная функция, чтобы помочь сериализовать весь класс.

Спасибо всем за помощь!

Ответы [ 5 ]

6 голосов
/ 04 февраля 2009

Предположение о том, что клиентский прокси будет выполнять один и тот же код на клиенте и на сервере, ошибочно, поскольку WCF основан на интерфейсах. Я объясняю этот момент в пуле № 2 ниже.

Правила совместного использования интерфейсов и реализации WCF

Если вы хотите поделиться реализацией контракта данных, вам нужно будет преобразовать класс RequestArray в библиотеку классов, в которой НИЧЕГО не содержится, но классы контракта данных, включая предположительно также класс RequestRecord.

Правила, по которым я живу:

  1. Сгруппируйте все контракты данных по себе (100%) в одну или несколько сборок, без исключений.

  2. Сгруппируйте все сервисные контракты в одну или несколько сборок.

  3. Сгруппируйте все типы сервисов, т. Е. Классы, реализующие сервисный контракт, в одну или несколько сборок.

  4. Сгруппируйте все прокси клиентского канала, то есть класс, который вызывает методы, определенные в интерфейсе контракта на обслуживание, в одну или несколько сборок.

  5. В общей структуре, где все клиентское программное обеспечение работает как служба WCF (я избегаю дуплексных подключений), безопасно объединить правила 2, 3 и 4, чтобы договоры на обслуживание, типы услуг и прокси каналов были сгруппированы вместе в одну сборку.

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

Изучение кода

Есть несколько БОЛЬШИХ проблем с кодом для RequestArray ...

  1. Логика установщика перезапишет любой из измененных элементов переменной массива m_Record при десериализации экземпляра DataContract. Это нарушает принципы десериализации.

  2. Свойство Record невозможно десериализовать, поскольку свойство Record класса RequestArray доступно только для чтения (поскольку в нем отсутствует установщик). Как правило, я считаю, что для классов DataContract лучший подход к свойствам, доступным только для чтения, - это просто метод. Это плохая идея, если вы привыкнете трактовать контракты с данными так, будто они представляют собой нечто большее, чем битовые корзины. То, что в основном делают атрибуты, - это динамическое создание определения интерфейса, специально используемого для сериализации и десериализации данных. Я считаю, что ошибочно думать о данных на проводе как об объектах. Скорее, это дополнительный способ указать соответствующие части данных объекта, которые должны сохраняться по проводам.

  3. Свойство 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
2 голосов
/ 04 февраля 2009

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

Есть способ достичь того, чего вы пытаетесь достичь. Я бы поставил под сомнение это с точки зрения архитектуры, и он будет работать только с клиентами .NET.

  • Изоляция интерфейса службы, типов параметров и типов возврата (т. Е. Ваших DataContracts) в сборку. Я называю это Контракт сборка

  • Переместите реализацию службы в отдельную сборку, я называю это Сборка реализации и ссылаюсь на Контрактную сборку

  • В клиенте создайте ссылку на контрактную сборку

  • В клиентском коде вручную создайте экземпляр канала ... что-то вроде этого (вам нужно найти точный синтаксис): IMyService myService = new ChannelFactory (). CreateChannel ();

Условие post должно заключаться в том, что поведение, записанное в классе DataContract, успешно вызывается на клиенте, поскольку используется ссылка на локальный тип.

Удачного кодирования!

2 голосов
/ 03 февраля 2009

Я только что провел небольшой тест, и он вызвал сеттер. Как вы десериализуете объекты?

class Program
{
    static void Main(string[] args)
    {
        var serializer = new DataContractSerializer(typeof(Employee));

        var employee = new Employee() { Name="Joe" };
        using (var ms = new MemoryStream())
        {
            serializer.WriteObject(ms, employee);

            ms.Flush();
            ms.Position = 0;

            var newE = serializer.ReadObject(ms) as Employee;
        }

        Console.ReadKey();

    }
}
[DataContract]
public class Employee
{
    private string _name;
    [DataMember]
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}
1 голос
/ 03 февраля 2009

Клиент должен использовать ту же реализацию класса. Когда вы добавляете ссылку на службу в конкретное приложение, подробности реализации свойств контракта данных не добавляются в класс, когда VS генерирует класс на стороне клиента. Скомпилируйте открытые классы в отдельную библиотеку классов, создайте ссылку на эту библиотеку классов в клиентском проекте, и при добавлении ссылки на службу в свой проект убедитесь, что на вкладке «Дополнительно» ... «Повторное использование типов в ссылочных сборках» проверено.

1 голос
/ 03 февраля 2009

Похоже, вы путаете функциональность на стороне сервера и на стороне клиента. Если вы сгенерировали клиентские классы через Visual Studio, то ваша логика Set не будет перенесена. Вы можете проверить это, открыв файл ProjectFolder \ Service References \ SomeServiceName \ References.cs и просмотрев определение созданного для вас объекта VS.

Если ваш клиент и сервер совместно используют общую DLL с вашей логикой Set, то это будет действительно странно. Мне нужно было бы увидеть больше кода, вызывающего его.

редактировать: В качестве дополнения, если вы хотите, чтобы ваш сервер и клиент использовали одну и ту же логику для своих контрактных объектов, лучше всего выделить отдельную dll со всеми вашими контрактами на данные и служебными контрактами, а затем включить эту dll в оба проекта. Я также обнаружил, что позволить VS генерировать прокси-классы для вас приятно и быстро, но в долгосрочной перспективе это вызовет серьезные головные боли.

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