WCF: интерфейсы, дженерики и ServiceKnownType

у меня есть следующие:

[ServiceContract]
[ServiceKnownType(typeof(ActionParameters))]
[ServiceKnownType(typeof(SportProgram))]
[ServiceKnownType(typeof(ActionResult<SportProgram>))]
public interface ISportProgramBl  
{
    [OperationContract]
    IActionResult<ISportProgram> Get(IActionParameters parameters);
}

когда я запускаю метод Get, я получаю следующую ошибку:

произошла ошибка при попытке сериализации параметра http://tempuri.org/:GetResult. Сообщение InnerException было "типом" PPS.Ядро.Файле domainmodel.Поддержка.Действие.ActionResult ' 1[ [PPS.Ядро.Файле domainmodel.SportProgram.ISportProgram, PPS.Ядро.DomainModel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' с именем контракта данных 'ActionResultOfanyType:http://schemas.datacontract.org/2004/07/PPS.Core.DomainModel.Support.Action ' не ожидается. Добавьте любые типы, не известные статически, в список известных типов - например, с помощью атрибута KnownTypeAttribute или путем добавления их в список известных типов, переданных DataContractSerializer.'. Дополнительную информацию см. В разделе InnerException.

из этой ошибки я вижу, что он может решить ActionResult, но он не может решить ISportProgram, хотя у меня есть ServiceKnownType (typeof (ActionResult )) на мой интерфейс сервиса...

Примечание это ссылка заглушка, которая генерируется выглядит так, поэтому я вижу, что известные типы переносятся правильно:

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="SportProgramStb.ISportProgramBl")]
public interface ISportProgramBl {

    [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/ISportProgramBl/Get", ReplyAction="http://tempuri.org/ISportProgramBl/GetResponse")]
    [System.ServiceModel.ServiceKnownTypeAttribute(typeof(PPS.Core.DomainModel.SportProgram.SportProgram))]
    [System.ServiceModel.ServiceKnownTypeAttribute(typeof(PPS.Core.DomainModel.Support.Action.ActionParameters))]
    [System.ServiceModel.ServiceKnownTypeAttribute(typeof(PPS.Core.DomainModel.Support.Action.ActionResult<PPS.Core.DomainModel.SportProgram.SportProgram>))]
    object Get(object parameters);
}

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

наконец ActionResult выглядит вот так:

public interface IActionResult<T> 
{
    T Result { get; set; } 
}

Ура Энтони!--4-->

5 ответов


Ну, я думаю, что это еще один случай SOA против ООП "несоответствие импеданса". Два мира совершенно разделены.

в WCF, все, что передается от клиента к серверу передается как сериализованного сообщения - ссылки не используются.

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

Это имеет много последствий:

  • нет интерфейсов - вы не можете передавать интерфейсы-вам нужно работать с конкретными типами
  • нет" автоматического " наследования - вы не можете просто определить базовый класс и передать производные классы на его основе - они должны быть указаны слишком (это то, для чего атрибут ServiceKnownType)
  • нет автоматических дженериков-опять же, вам нужно использовать конкретные типы вместо

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

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

Марк

PS: Я сделал еще несколько исследований, и вопреки всему моему пониманию, кажется, есть способ сериализовать все, что основано на интерфейсе и / или абстрактном базовом классе по проводам, если вы можете быть уверены, что это всегда только .NET на любом конце провода (т. е. это не совместимый с, например, Java).

посмотреть Аарон Skonnard сообщение в блоге на Помощью netdatacontractserializer и еще один блоге и еще один показывает, как использовать NetDataContractSerializer, чтобы иметь возможность передавать такие вещи, как IPerson как параметры ваших методов.


Это одна из проблем, которую я решаю с помощью ServiceStack.NET - Моя Платформа веб-служб с открытым исходным кодом .NET и MONO.

на стек услуг сильно повлияло Мартин Фаулерс Передача Данных Шаблон Объекта поскольку это позволяет вам просто использовать DTO для определения ваших веб-сервисов-т. е. SOA way :).

Я избегаю этого ограничения, присущего WCF, создавая свои собственные WSDL, которые ведут себя так, как вы ожидаете. В качестве преимущества замена сложной конфигурации WCF / модели ServiceContract - веб-службы SOAP также работают на MONO -посмотреть видео.


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

[ServiceContract]
[ServiceKnownType(typeof(CollectionWrapper<IAssociation>))]
public interface IService : 
{
    [OperationContract]
    ICollectionWrapper<IAssociation> FindAssociation(string name, int pageSize, int page);
}

public interface ICollectionWrapper<TModel>
{
    int TotalCount { get; set; }
    IEnumerable<TModel> Items { get; set; }
}

[KnownType(typeof(OrganizationDto))]
[KnownType(typeof(CompanyDto))]
public class CollectionWrapper<TModel> : ICollectionWrapper<TModel>
{
    [DataMember]
    public int TotalCount { get; set; }
    [DataMember]
    public IEnumerable<TModel> Items { get; set; }
}

public class CompanyDto :  IAssociation
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class OrganizationDto :  IAssociation
{
    public int Id { get; set; }
    public string Name { get; set; }
}

ключ здесь должен использовать комбинацию KnownType и ServiceKnownType.

так что в вашем случае вы можете сделать что-то вроде этого:--6-->

[ServiceContract]
[ServiceKnownType(typeof(ActionParameters))]
[ServiceKnownType(typeof(ActionResult<ISportProgram>))] // Actual implementation of container, but interface of generic.
public interface ISportProgramBl  
{
    [OperationContract]
    IActionResult<ISportProgram> Get(IActionParameters parameters);
}

[KnownType(typeof(SportProgram))] // Actual implementation here.
public class ActionResult<T>
{
    // Other stuff here
    T FooModel { get; set; }
}

это будет работать, если у вас есть общий контракт (доступ к фактическому интерфейсу службы) и потреблять контракт с ChannelFactory<ISportProgramBl>. Я не знаю, работает ли он с service-reference.

однако, похоже, есть некоторые проблемы с реализацией, как упоминалось здесь:

WCF с интерфейсом и общей моделью

и еще один подобный вопрос задан и ответил здесь:

общие возвращаемые типы с параметрами типа интерфейса в WCF


вы возвращаете IList T. возможно, у системы есть проблемы с выяснением того, что такое T.

Не уверен, что нормально возвращать интерфейс, а не тип.


вы указываете интерфейсы в объектах, а не конкретные типы?

PPS.Core.DomainModel.Support.Action.ActionListResult<IList<PPS.Core.DomainModel.SportProgram.ISportProgram>>

изменить:

Я говорю, что все конкретные типы, которые вы передаете в дженериках (в том числе в подобъекте через интерфейсы), передаются в списке известных типов. У нас были проблемы serialasation, где все типы не были известны.