tags:

views:

862

answers:

2
+1  Q: 

WCF type collision

I have a problem with WCF. I think I understand what the issue is, but I need to test that understanding and ask if anyone has seen this before or can suggest a workaround or an alternate approach. Please bear with me: this is a sizeable problem which is difficult to break down to a minimal test case.

The issue: I'm writing a C# interface to a legacy messaging system. I have implemented the interop assembly for a direct interface and that all works fine. The basis of it is that I have a class for each message type, and a generic class for passing around that just occupies the same space as a maximal size message.

Each message class implements cast operators to cast into and out of this generic class, called QOD_Message. The generic class and the individual message classes are both derived from a bodyless minimal message class. A client C# app wanting to send a message just calls a send method in an interface assembly which takes a QOD_Message, casting into the generic type for sending. The receiver event passes the receiving C# app a generic type, which be cast according to a common member which has a one-to-one relationship with the message type.

I have implemented the local direct interface assembly, and this all works fine and I can now message between the C# test application and existing native legacy applications. Wonderful. The basic class structure looks like this:

public class QOD_Message_Minimal
{
   a message header
}

public class QOD_Message : QOD_Message_Minimal
{
   generic message - defines an empty body
}

public class QOD_WcfDialout : QOD_Message_Minimal
{
   ... some irrelevant code

   // cast operators to convert to and from the abstract message class.

   public static implicit operator QOD_Message (QOD_WcfDialout m)
   {
      ... some irrelevant code
   }

   public static implicit operator QOD_WcfDialout (QOD_Message m)
   {
      ... some irrelevant code
   }
}

Note the cast operators.

Now comes the problem. I want to extend this interface to support WCF, so another group of developers overseas can communicate via WCF with an adapter WCF service I have written. This is called, with an admirable lack of imagination, the WCF_Adapter. The WCF_Adapter exposes for test purposes a single method which takes a string (Dialout), and converts this into a message for the legacy code. This also works fine, with a call to the function in my WCF_Client application being converted into a legacy message and sent to the (fixed) recipient.

So I tried to extend the interface to add an entrypoint which would accept a QOD_Message (Dispatch), in the hope that this would work the same way as the direct interface, i.e the sender would cast their message into a QOD_Message, call the sending function with this type, and the adapter would simply pass the received generic message into its local interface assembly for onward dispatch. So the WCF code would look pretty much exactly like the normal messaging code.

[ServiceContract]
public interface IDialout
{
   [OperationContract (IsOneWay = true)]
   void Dialout (string NumberToDial);

   [OperationContract (IsOneWay = true)]
   void Dispatch (QOD_Message msg);
}

So I extended the client and the WCF_adapter service, generating my proxy code for the WCF client and.... Oh. The client won't compile.

It seems that the client app cannot understand the cast operator, and throws a compilation error. Even if I include the messaging assembly with the code above as a reference, the client code "prefers" the type defined in the generated proxy and ignores the original message type, so it cannot "see" the cast and won't compile. This is the error:

warning CS0436: The type 'QOD_Messaging.QOD_Message' in 'blah-blah\generatedProxy.cs' conflicts with the imported type 'QOD_Messaging.QOD_Message' in 'blah-blah\QOD_Messaging.dll'. Using the type defined in 'blah-blah\generatedProxy.cs'.

As far as I can tell, the class defined in the generated proxy is correct, but of course the generated proxy class only defines data members, not functionality. It contains two classes

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="QOD_Message_Minimal", Namespace="http://schemas.datacontract.org/2004/07/QOD_Messaging")]
[System.Runtime.Serialization.KnownTypeAttribute(typeof(QOD_Messaging.QOD_Message))]
public partial class QOD_Message_Minimal : object, System.Runtime.Serialization.IExtensibleDataObject
{
   whole slew of data members.
}

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="QOD_Message", Namespace="http://schemas.datacontract.org/2004/07/QOD_Messaging")]
public partial class QOD_Message : QOD_Messaging.QOD_Message_Minimal
{
   extra stuff to define the added body.
}

So.....

I can leave the assembly reference out, so the code can't see the cast and fails to compile, or I can leave the reference in and the compiler wilfully ignores the assembly's type definition and uses the proxy definition... and fails to compile.

Have other WCF developers seen this kind of thing before? Or does everyone stick to simple value parameters when calling WCF endpoints? Am I just going to have to declare an endpoint with a unique class parameter for every kind of message I might want to send? This is a maintenance nightmare.

+1  A: 

The conversion operators are not part of the generated mex, so they won't exist at the client.

Given the complexity here - would it be possible to use assembly sharing? i.e. rather than generated (proxy) types, can you reference the original assembly for the types? This is supported both in the IDE (on the Advanced dialog, although it seems enabled by default, so might work already if you have a reference to the assembly with the message types) and via svcutil (the /r switch). Then you have your original types, conversions included, at the client.

If you want to keep the proxy approach, you can add partial classes for each of your messages, and add the extra conversion there - but this has an obvious maintenance cost.

Marc Gravell
Excellent - I hadn't thought of using the /r flag, being a horrendous newbie at this C# lark. That did the trick. Many thanks.
Bob Moore
+1  A: 

The only kind of class member that can be exposed in a web service is an method. Properties, operators, casts, indexers, delegates, events, nor anything else can be exposed.

Recall that, one way or antoher, a web service is described by one or more XML documents: WSDL, XML Schema, WS-Policy, etc. None of these documents has a way to describe any programming language operators of any kind, including cast operators.

One may expose class methods as web service operators, and one may expose classes as data contracts or fault contracts, to be exposed as XML schema, and message contracts, to be exposed as message types. There is nothing else that can be exposed.

John Saunders