Использование интерфейсов с WCF

Я гуглил и читал часами, и я не могу найти никого, кто занимается моим конкретным сценарием...

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

Вот пример. IDataContract содержит голую информацию, которую я хочу передать по сети. Конечные точки и другая конфигурация, специфичная для WCF, — это все по умолчанию (мои проблемы могут заключаться в этом, поэтому я могу включить больше, если мне нужно что-то изменить).

РЕДАКТИРОВАНИЕ: я добавил больше кода и переименовал пару классов, чтобы было меньше путаницы. Дополнения Name & Namespace к DataContractAttributes, а также два раздела в файлах конфигурации являются новыми дополнениями, основанными на информации из этот пост в блоге. Если я переключусь на абстрактный базовый класс вместо интерфейса, это сработает. Однако я бы хотел, чтобы это работало с интерфейсом, если это возможно.

Общая библиотека (мой код, общий с авторами клиента):

public interface IDataContract
{
    string MyProperty { get; set; }
}
[ServiceContract]
public interface ITestService
{
    [OperationContract]
    IDataContract TestSharedInterface(IDataContract clientData);
}

Код клиента (их):

[DataContract(Name = "IDataContract", Namespace = "http://services.sliderhouserules.com")]
public class ClientDataClass : IDataContract
{
    [DataMember]
    public string MyProperty { get; set; }
}
private static void CallTestSharedInterface()
{
    EndpointAddress address = new EndpointAddress("http://localhost/ServiceContractsTest.WcfService/TestService.svc");
    ChannelFactory<ITestService> factory = new ChannelFactory<ITestService>("ITestService", address);
    ITestService proxy = factory.CreateChannel();
    ((IClientChannel)proxy).Open();

    IDataContract clientData = new ClientDataClass() { MyProperty = "client data" };
    IDataContract serverData = proxy.TestSharedInterface(clientData);
    MessageBox.Show(serverData.MyProperty);
}

Конфигурация клиента:

<system.runtime.serialization>
    <dataContractSerializer>
        <declaredTypes>
            <add type="ServiceContractsTest.Contracts.DataContracts.IDataContract, ServiceContractsTest.Contracts">
                <knownType type="ServiceContractsTest.WcfClient.ClientDataClass, ServiceContractsTest.WcfClient"/>
            </add>
        </declaredTypes>
    </dataContractSerializer>
</system.runtime.serialization>

Код сервера (мой):

public class TestService : ITestService
{
    public IDataContract TestSharedInterface(IDataContract clientData)
    {
        ServerDataClass convertedClientData = (ServerDataClass)clientData;
        IDataContract serverData = new ServerDataClass() { MyProperty = convertedClientData.MyProperty + " + server data added" };
        return serverData;
    }
}
[DataContract(Name = "IDataContract", Namespace = "http://services.sliderhouserules.com")]
public class ServerDataClass : IDataContract
{
    [DataMember]
    public string MyProperty { get; set; }
}

Конфигурация сервера:

<system.runtime.serialization>
    <dataContractSerializer>
        <declaredTypes>
            <add type="ServiceContractsTest.Contracts.DataContracts.IDataContract, ServiceContractsTest.Contracts">
                <knownType type="ServiceContractsTest.WcfService.ServerDataClass, ServiceContractsTest.WcfService"/>
            </add>
        </declaredTypes>
    </dataContractSerializer>
</system.runtime.serialization>

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

По сути, я хочу, чтобы ClientDataClass сериализовался в <IDataContract><MyProperty>client data</MyProperty></IDataContract>, а затем имел возможность десериализовать его в экземпляр ServerDataClass. Кажется, это должно быть возможно.


person sliderhouserules    schedule 16.11.2010    source источник
comment
Я должен отметить, что я много читал об атрибуте ServiceKnownTypeAttribute, и здесь он не подходит. Мне нужно, чтобы сборка с сервисным интерфейсом ничего не знала о классах, реализующих интерфейс. Я не знаю, ясно ли это из моего вопроса выше.   -  person sliderhouserules    schedule 16.11.2010
comment
С какой стати вам нужны разные названия для одних и тех же контрактов? Контракт есть контракт...   -  person Gerrie Schenck    schedule 16.11.2010
comment
Имя не является важной частью, ключом являются две разные конкретные реализации. Данные, передаваемые туда и обратно, представляют собой просто пакет свойств. Почему это не может быть класс 1 на одном конце провода, а затем стать классом 2 на другом конце провода, если они оба реализуют один и тот же интерфейс?   -  person sliderhouserules    schedule 16.11.2010
comment
Я уточнил код и добавил больше. Надеюсь, это устранит путаницу, которую испытала Джерри.   -  person sliderhouserules    schedule 16.11.2010
comment
WCF существует с 2006 года. Если вы используете Google и не можете найти, как что-то сделать, вероятно, есть веская причина, по которой вы не можете найти эту информацию.   -  person John Saunders    schedule 16.11.2010
comment
И тем не менее, в своих поисках я нашел множество других случаев, когда объяснялись несколько иные сценарии, и люди говорили, что это невозможно. Но я не смог найти никого, кто обратился бы к моему конкретному сценарию и сказал, что вы не можете этого сделать. Итак, у вас есть реальные отзывы по моему вопросу? Или вы просто думали, что критики по поводу того, что я действительно спросил, было достаточно?   -  person sliderhouserules    schedule 16.11.2010
comment
Я публикую решение: ServiceKnownType Динамическое разрешение Надеюсь, это поможет   -  person Moshe Levi    schedule 06.02.2017


Ответы (3)


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

person Maurice    schedule 16.11.2010
comment
Как я могу указать WCF, какой класс конкретно создавать экземпляр, не определяя интерфейс службы с конкретным именем класса? App.config в порядке, атрибуты в моем фактическом файле .svc... что угодно. Пока сборка, в которой определен интерфейс службы, не должна этого делать. - person sliderhouserules; 16.11.2010
comment
Я должен упомянуть, что ваш ответ помог мне, по крайней мере, заставить работать абстрактные базовые классы. Я взглянул на то, что сгенерировал Add Service Reference, и использовал некоторые из них. Тогда спасибо. - person sliderhouserules; 17.11.2010

Этот блог дает мне правильное направление, чтобы найти решение моей проблемы. На самом деле у меня точно такой же сценарий, как описывает sliderhouserules в своем посте.

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

namespace ExampleNamespace
{
  public interface IJustAInstance { }

  [ServiceContract]
  [ServiceKnownType("GetKnownTypes", typeof(ExampleNamespace.TypesHelper))]
  public interface ICreateInstance
  {
    IJustAInstance CreateInstance();
  }

  public static class TypesHelper
  {
    public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
    {
      DataContractSerializerSection section = (DataContractSerializerSection)
        ConfigurationManager.GetSection(
        "system.runtime.serialization/dataContractSerializer");
      if (dataContractSerializerSection != null)
      {
        foreach (DeclaredTypeElement item in dataContractSerializerSection.DeclaredTypes)
        {
          foreach (TypeElement innterItem in item.KnownTypes)
          {
            Type type = Type.GetType(innterItem.Type);
            if (typeof(IJustAInstance).IsAssignableFrom(type ))
              yield return type;
          }
        }
      }
    }
  }
}
person LEitZ    schedule 31.07.2011

Вы можете создать BaseContract, который ваши ClientContract и ServerContract могут предоставлять (как свойство) и который вы можете использовать в соответствующем конструкторе при создании новых экземпляров ClientContract или ServerContract. Затем вам нужно только добавить BaseContract в вашу общую библиотеку.

person flayn    schedule 16.11.2010