Using Interfaces With WCF

2019-02-25 07:03发布

问题:

I have Googled and read for hours now and I can't find anyone that deals with my specific scenario...

I want to use interfaces in my WCF service contracts to loosely couple the service from the classes used on each end of the wire. This will enable us to have a low-level assembly that contains just the Service and Data Contracts (just interfaces) that we can hand to a consultant. On their end of the wire they can instantiate their data classes that implement our Data Contract interface, send it over the wire to us, and our WCF service will then translate/cast/whatever that incoming data into our version of a data class that implements the same interface.

Here's an example. IDataContract contains the bare information I want to transmit over the wire. The endpoints and other WCF-specific config are all default stuff (my problems may lie in that, so I can include more of it if that's where I need to change things).

EDIT: I've included more of the code and renamed a couple classes to help it be less confusing. The Name & Namespace additions to the DataContractAttributes, as well as the two sections in the config files are new additions based on information from this blog post. If I switch to an abstract base class instead of an interface, it works. However, I'd like to get this working with an interface if possible.

Shared library (my code, shared with client authors):

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

Client code (their's):

[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);
}

Client config:

<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>

Server code (mine):

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; }
}

Server config:

<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>

I am getting a serialization error on the client call complaining about known types. Am I just missing some metadata markup in that client class? I'm at a loss as to where to even know the problem even lies, as I've tried all the searches I can think of and no one seems to have dealt with this specific scenario.

Basically, I want ClientDataClass to serialize to <IDataContract><MyProperty>client data</MyProperty></IDataContract> and then be able to deserialize that into a ServerDataClass instance. This seems like it should be possible.

回答1:

If your data contracts are interfaces WCF can't know what object to instantiate for an incoming request. There is no need for the class to be the same as in the service, after all the Add Service Reference reads the WSDL and generates new classes based on the type info in the WSDL.



回答2:

This blog gives me the right direction to find the solution for my problem. Actually I have exactly the same scenario like sliderhouserules describes in his post.

But in my scenario I can't use any abstract or base class to inherit from. So I used a TypesHelper class to read the dataContractSerializer section by myself and pass the relevant types to the WCF service.

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;
          }
        }
      }
    }
  }
}


回答3:

You could create a BaseContract that your ClientContract and ServerContract can provide (as property) and that you can use in the respective constructor when creating new instances of the ClientContract or ServerContract. Then you only have to add the BaseContract to your shared lib.