Клиент WCF испытывает проблемы с распознаванием ServiceKnownTypes? - PullRequest
7 голосов
/ 01 октября 2010

Как мне сообщить службе WCF, какие KnownTypes использовать при передаче данных обратно клиенту?

Я знаю, что могу использовать атрибут [ServiceKnownType], который заставляет вызов службы нормально выполняться из теста WCFСервер, однако он по-прежнему отказывает от клиента.Я что-то здесь упускаю?

[OperationContract]
[ServiceKnownType(typeof(SubClassA))]
[ServiceKnownType(typeof(SubClassB))]
BaseClassZ GetObject();

Сообщение об ошибке от клиента:

{"Элемент" http://schemas.datacontract.org/2004/07/BaseClassZ' содержит данные из типа, который сопоставляется с именем 'http://schemas.datacontract.org/2004/07/SubClassA'. Десериализатор не знает ни о каком типе, который сопоставляется с этим именем. Рассмотрите возможность использования DataContractResolver или добавьте тип, соответствующий SubClassA, в список известных типов - например, используя атрибут KnownTypeAttribute или добавив его в список.известных типов, передаваемых в DataContractSerializer. "}

Сериализация / десериализация объекта на сервере WCF с использованием DataContractSerializer и списка KnownTypes работает нормально.

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

[DataContract]
[KnownType(typeof(SubClassA))]
[KnownType(typeof(SubClassB))]
public class BaseClassZ 
{
    ...
}

Ответы [ 3 ]

10 голосов
/ 02 октября 2010

Чтобы не сдерживать ваш сервисный код, поместите известные типы в web.config сервиса:

<system.runtime.serialization>
    <dataContractSerializer>
        <declaredTypes>
            <add type="SomeNs.BaseClassZ, SomeAssembly">
                <knownType type="SomeNs.SubClassA, SomeAssembly" />
                <knownType type="SomeNs.SubClassB, SomeAssembly" />
            </add>
        </declaredTypes>
    </dataContractSerializer>
</system.runtime.serialization>

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

[ServiceContract]
[ServiceKnownType(typeof(SubClassA))]
[ServiceKnownType(typeof(SubClassB))]
public interface IFoo
{
    [OperationContract]
    BaseClassZ GetObject();
}

UPDATE:

Я создал пример проекта , иллюстрирующий использование web.config для настройки известных типов, что является моим предпочтительным подходом. И еще один пример проекта , демонстрирующий второй подход.


ОБНОВЛЕНИЕ 2:

Посмотрев на ваш обновленный код с помощью приложения-клиента Silverlight, мы увидим следующее определение:

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceReference1.IService1")]
public interface IService1 {

    [System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IService1/GetMany", ReplyAction="http://tempuri.org/IService1/GetManyResponse")]
    System.IAsyncResult BeginGetMany(System.AsyncCallback callback, object asyncState);

    System.Collections.ObjectModel.ObservableCollection<MyCommonLib.BaseClassZ> EndGetMany(System.IAsyncResult result);

    [System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IService1/GetSingle", ReplyAction="http://tempuri.org/IService1/GetSingleResponse")]
    [System.ServiceModel.ServiceKnownTypeAttribute(typeof(MyCommonLib.SubClassA))]
    [System.ServiceModel.ServiceKnownTypeAttribute(typeof(MyCommonLib.SubClassB))]
    System.IAsyncResult BeginGetSingle(System.AsyncCallback callback, object asyncState);

    MyCommonLib.BaseClassZ EndGetSingle(System.IAsyncResult result);
}

Обратите внимание, что метод BeginGetSingle содержит атрибуты известного типа, а метод BeginGetMany - нет. Фактически, эти атрибуты должны быть помещены в определение сервиса, чтобы класс выглядел следующим образом.

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceReference1.IService1")]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(MyCommonLib.SubClassA))]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(MyCommonLib.SubClassB))]
public interface IService1
{
    [System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IService1/GetMany", ReplyAction="http://tempuri.org/IService1/GetManyResponse")]
    System.IAsyncResult BeginGetMany(System.AsyncCallback callback, object asyncState);

    System.Collections.ObjectModel.ObservableCollection<MyCommonLib.BaseClassZ> EndGetMany(System.IAsyncResult result);

    [System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IService1/GetSingle", ReplyAction="http://tempuri.org/IService1/GetSingleResponse")]
    System.IAsyncResult BeginGetSingle(System.AsyncCallback callback, object asyncState);

    MyCommonLib.BaseClassZ EndGetSingle(System.IAsyncResult result);
}

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

Похоже, что если у вас есть следующее определение сервиса:

[ServiceContract]
[ServiceKnownType(typeof(SubClassA))]
[ServiceKnownType(typeof(SubClassB))]
public interface IService1
{
    [OperationContract]
    BaseClassZ[] GetMany();

    [OperationContract]
    BaseClassZ GetSingle();
}

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

1 голос
/ 21 сентября 2011

Я потратил часы сегодня на то, что, насколько я могу судить, является точно такой же проблемой.Для меня решение было использовать метод AddGenericResolver из библиотеки ServiceModelEx IDesign.

ПРИМЕЧАНИЕ. Требуется .NET 4.0, поскольку он использует DataContractResolver

Вы можете найти его на Страница загрузки IDesign .

Все, что мне нужно было сделать в моем случае, это добавить следующую строку кода:

Client.AddGenericResolver( typeof ( K2Source ) );

Надеюсь, это поможет кому-то еще сэкономить несколькочасов!

Дополнительную информацию можно найти в книге Юваля Лоуи «Программирование служб WCF: освоение WCF и служебной шины Azure AppFabric».

0 голосов
/ 02 октября 2010

Есть еще один способ сделать это.Вместо того, чтобы использовать «добавить ссылку на сервис», вы кодируете прокси-классы.Сначала это немного больше кода, но дает вам гораздо более стабильное и надежное решение.Мы обнаружили, что это экономит наше время в долгосрочной перспективе.

См .: http://www.dnrtv.com/default.aspx?showNum=122

Примечание: это работает, только если у вас есть контроль над сервером и клиентом.

...