tags:

views:

779

answers:

3

I'm trying to overcome a problem with WCF and enums, where I'm trying to pass an object from the server to the client (or another server), which contains an enum. The enum starts with 1, on purpose. Everything goes fine when the enum is initialized and when the value is defined in it, but when it's not defined in the enum, I get this wonderful (and very descriptive(...)) error message:

"The underlying connection was closed: The connection was closed unexpectedly."

What I try to achieve is when I get this scenario, either from corrupted data in the database (which is being casted anyway to the enum, which is pretty wierd altogether) or when a developer forgot to set the enum value when initiating the object, to get a meaningful message, something like "Enum value is not valid, type: {0}, value: {1}".

I've tried to use the "Enum.IsDefined" on the setter and getter of the enum in the class, and throw the meaningful exception to the client (or the other server), but still got the "connection close" error (when allowing to debug the server I get the meaningful message but on the server side only).

Here's a snippet of the enum setter & getter:

    private TestEnum m_TestEnum;

    [DataMember]
    public TestEnum TestEnum 
    {
        get
        {
            if (Enum.IsDefined(typeof(TestEnum), m_TestEnum))
            {
                return m_TestEnum;
            }
            else
            {
                throw new ApplicationException("Enum value is not valid: " + m_TestEnum);
            }
        }
        set
        {
            if (Enum.IsDefined(typeof(TestEnum), value))
            {
                m_TestEnum = value;
            }
            else
            {
                throw new ApplicationException("Enum value is not valid: " + value);
            }
        }
    }

Starting the enum from 0 (with an "Unknown" value) is not a good enough, since I can still get values which don't exist in the enum. I can combine both solutions, where I check the "IsDefined" and set the enum to the "Unknown" value, but still - this is not the ideal solution, since we want to know about those cases in order to solve them in development cycle.

What do you say? Thanks, Nir.

+4  A: 

When sending enums over WCF, the string representation of the enum value is sent, not the numeric value. This allows both sides to map the same enum label to different numbers.

If a label is sent that is not understood by the recipient, then the channel will be closed. I believe this extends to values for enums that are not defined (which it looks like you're catering for.)

So, just because the enum label is valid on the sender's side doesn't mean the client would necessarily understand it.

Are you sharing the same class definition between both sender and recipient?

To debug this issue further, you should enable service tracing in client and server (this is true of many frustrating WCF issues.) It will help you identify the source of the problem where otherwise you have no indication.

Add the following XML to your App.config (on both client and server):

<configuration>
   <system.diagnostics>
      <sources>
            <source name="System.ServiceModel" 
                    switchValue="Information, ActivityTracing"
                    propagateActivity="true">
            <listeners>
               <add name="traceListener" 
                   type="System.Diagnostics.XmlWriterTraceListener" 
                   initializeData= "WcfTrace.svclog" />
            </listeners>
         </source>
      </sources>
   </system.diagnostics>
</configuration>

There are several options for this configuration.

With this config added, you will create a .svclog file that can be viewed with the Service Trace Viewer Tool. This log contains a lot of information about everything that occurs, including warnings and errors.

Note that the log files generated can get quite large so be sure to remove or comment out this config section once you have finished your debugging.


Note that you can specify different string values for enum members to be used on the wire.

For example, the following two enums are equivalent over WCF:

public enum CarCondition
{
    New,
    Used,
    Rental,
    [NonSerialized]
    Lost
}    

[DataContract(Name = "CarCondition")]
public enum CarConditionWithDifferentNames
{
    [EnumMember(Value = "New")]
    BrandNew,
    [EnumMember(Value = "Used")]
    PreviouslyOwned,
    [EnumMember]
    Rental
}

You can read more information about using enums over WCF here.

Drew Noakes
good points - since the enum value is passed in a message, one could devise a plan to handle deserialization errors on the receiving end (e.g. the server) and somehow react to those. Or one could catch these exceptions on the server and turn them into SOAP FaultExceptions and thus save the connection from being destroyed.
marc_s
Marc,Can you please elaborate on your idea? -It seems this is what we're aiming for.Drew - to answer your question, the client and server are sharing the same enum definition, the problem is that the enum value set is not a part of it (either no one set it or we got corrupted data from somewhere).Thanks,Nir.
nirpi
A: 

I'm not sure why the error happens, but I'm getting it too.

I was looking at the wsdl and the problem seems to be there. Setting the enum to start with a specific value implies many changes in the wsdl.

I'm still looking the reason behind this. If you find it out, and figure out a clean workaround, please post it.

Thanks

Jose

Jose
+1  A: 

Well, the problem is that the serialization cannot serialize a value that isn't defined in the enum, and this includes the (default) 0 value.

For example:

enum E { New = 1, Used }

[OperationContract] public E Method() { E e; return e; }

A call to Method will fail as "e" is initialized as "e = (E)0". When the serialization tries to do its job, it doesn't find a field in E that matches 0, so it fails (or generates a xml that doesn't match the wsdl which restricts the values to the enum members).

Thus, we have to make sure that our service method always returns valid values (according to the enum definition).

I didn't see nothing talking about this in MSDN, so it is my own conclusion after my tests.

Hope it helps, Jose

Jose