Tuesday 2 March 2010

Serialising Enumerations with WCF

Introduction

There are occasions when the exceptions thrown by Windows Communication Foundation (WCF) are clear and unambiguous. There are also occassions where they are not. In this post I'll describe a real-world problem which a colleague encountered recently together which the steps which you might reasonably perform to diagnose it.

The problem manifested itself in a System.ServiceModel.CommunicationException being thrown with nothing particularly useful in the Exception message itself.

System.ServiceModel.CommunicationException: An error occurred while receiving th
e HTTP response to http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary
/Service1/. This could be due to the service endpoint binding not using the HTTP
 protocol. This could also be due to an HTTP request context being aborted by th
e server (possibly due to the service shutting down). See server logs for more d
etails. ---> System.Net.WebException: The underlying connection was closed: An u
nexpected error occurred on a receive. ---> System.IO.IOException: Unable to rea
d data from the transport connection: An existing connection was forcibly closed
 by the remote host. ---> System.Net.Sockets.SocketException: An existing connec
tion was forcibly closed by the remote host

Clearly something very bad happened. But the WCF service itself was still up and running, so one of the theories put forward in the message above "possibly due to the service shutting down" can be quickly dismissed. I'm sure it's obvious, but the above message was generated by my mock-up of the problem rather than the original problem itself.

Mock-up of Original Problem

Let me quickly run through the mock-up of the problem which I put together. It consists of three projects: WcfServiceLibrary contains a single WCF service called Service1, WcfConsoleHost provides a host for Service1 in the form of a Console Application, and WcfClient consumes Service1.

Solution Explorer view of SerialisingEnumerationsWithWCF.sln

WcfServiceLibrary consists of a Service1 class which implements an IService1 interface. Here's the interface:

using System;
using System.Runtime.Serialization;
using System.ServiceModel;

namespace WcfServiceLibrary
{
    public enum Gender
    {
        Male = 1,
        Female = 2
    }

    [DataContract]
    public class MyCompositeType
    {
        [DataMember]
        public bool MyBool
        {
            get;
            set;
        }

        [DataMember]
        public string MyString
        {
            get;
            set;
        }

        [DataMember]
        public Gender MyEnum
        {
            get;
            set;
        }
    }

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        MyCompositeType GetMyCompositeType(bool initialised);
    }
}

And the class:

using System;

namespace WcfServiceLibrary
{
    public class Service1 : IService1
    {
        public MyCompositeType GetMyCompositeType(bool initialised)
        {
            MyCompositeType myCompositeObject = new MyCompositeType();

            if (initialised)
            {
                myCompositeObject.MyBool = true;
                myCompositeObject.MyString = "Hello World";
                myCompositeObject.MyEnum = Gender.Male;
            }

            return myCompositeObject;
        }
    }
}

WcfConsoleHost just contains a Program class which hosts Service1:

using System;
using System.ServiceModel;

namespace WcfConsoleHost
{
    class Program
    {
        static void Main(string[]args)
        {
            try
            {
                ServiceHost serviceHost = new ServiceHost(typeof(WcfServiceLibrary.Service1));
                serviceHost.Open();

                foreach (System.ServiceModel.Description.ServiceEndpoint serviceEndPoint in serviceHost.Description.Endpoints)
                {
                    Console.WriteLine("Listening at {0}...", serviceEndPoint.Address.ToString());
                }
            }
            catch (System.Exception ex)
            {
                System.Console.WriteLine(ex.ToString());
            }

            Console.ReadLine();
        }
    }
}

Finally, we have WcfClient which again just consist of a Program class. Upon execution, WcfClient simply retrieves an initialised instance of MyCompositeType via WCF and writes its details to the console.

using System;

namespace WcfClient
{
    class Program
    {
        static void Main(string[]args)
        {
            try
            {
                DisplayMyCompositeObject(true);
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex);
            }

            Console.ReadLine();
        }

        static void DisplayMyCompositeObject(bool initialised)
        {
            Service1Reference.MyCompositeType myCompositeObject = GetMyCompositeType(initialised);

            Console.WriteLine("MyBool={0}", myCompositeObject.MyBool);
            Console.WriteLine("MyString={0}", myCompositeObject.MyString);
            Console.WriteLine("MyString={0}", myCompositeObject.MyEnum);
            Console.WriteLine();
        }

        static Service1Reference.MyCompositeType GetMyCompositeType(bool initialised)
        {
            Service1Reference.Service1Client service1Client = null;
            Service1Reference.MyCompositeType myCompositeObject = null;

            try
            {
                service1Client = new Service1Reference.Service1Client();
                myCompositeObject = service1Client.GetMyCompositeType(initialised);
                service1Client.Close();
            }
            catch
            {
                if(service1Client != null)
                {
                    service1Client.Abort();
                }
                throw;
            }

            return myCompositeObject;
        }
    }
}

For the sake of completeness, I'll also show you the configuration files I'm using. First, the server-side configuration file in WcfConsoleHost:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="WcfServiceLibrary.Service1" behaviorConfiguration="WcfServiceLibrary.Service1Behavior">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary/Service1/"/>
          </baseAddresses>
        </host>
        <endpoint address="" binding="basicHttpBinding" contract="WcfServiceLibrary.IService1">
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="WcfServiceLibrary.Service1Behavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

And the client-side configuration file in WcfClient:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_IService1" closeTimeout="00:01:00"
          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
          allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
          maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
          messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
          useDefaultWebProxy="true">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <security mode="None">
            <transport clientCredentialType="None" proxyCredentialType="None" realm="" />
            <message clientCredentialType="UserName" algorithmSuite="Default" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary/Service1/"
        binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
        contract="Service1Reference.IService1" name="BasicHttpBinding_IService1" />
    </client>
  </system.serviceModel>
</configuration>

Testing the Application

With all this in place we can start the WcfConsoleHost:

Listening at http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary/Servi
ce1/...
Listening at http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary/Servi
ce1/mex...

Firing up WcfClient the gives us a nice simple:

MyBool=True
MyString=Hello World
MyString=Male

So what's the problem? Well the problem is that if we pass false to WcfClient.Program.DisplayMyCompositeObject (and hence to WcfClient.Program.GetMyCompositeType and hence to WcfServiceLibrary.Service1.GetMyCompositeType), to ask that an uninitialised instance of MyCompositeObject be returned, we get the following output:

System.ServiceModel.CommunicationException: An error occurred while receiving th
e HTTP response to http://localhost:8731/Design_Time_Addresses/WcfServiceLibrary
/Service1/. This could be due to the service endpoint binding not using the HTTP
 protocol. This could also be due to an HTTP request context being aborted by th
e server (possibly due to the service shutting down). See server logs for more d
etails. ---> System.Net.WebException: The underlying connection was closed: An u
nexpected error occurred on a receive. ---> System.IO.IOException: Unable to rea
d data from the transport connection: An existing connection was forcibly closed
 by the remote host. ---> .Net.Sockets.SocketException: An existing connec
tion was forcibly closed by the remote host
   at System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size,
 SocketFlags socketFlags)
   at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 s
ize)
   --- End of inner exception stack trace ---
   at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 s
ize)
   at System.Net.PooledStream.Read(Byte[] buffer, Int32 offset, Int32 size)
   at System.Net.Connection.SyncRead(HttpWebRequest request, Boolean userRetriev
edStream, Boolean probeRead)
   --- End of inner exception stack trace ---
   at System.Net.HttpWebRequest.GetResponse()
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpCha
nnelRequest.WaitForReply(TimeSpan timeout)
   --- End of inner exception stack trace ---

Server stack trace:
   at System.ServiceModel.Channels.HttpChannelUtilities.ProcessGetResponseWebExc
eption(WebException webException, HttpWebRequest request, HttpAbortReason abortR
eason)
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpCha
nnelRequest.WaitForReply(TimeSpan timeout)
   at System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeS
pan timeout)
   at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message messag
e, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean on
eway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan tim
eout)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean on
eway, ProxyOperationRuntime operation, Object[] ins, Object[] outs)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCall
Message methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)

Exception rethrown at [0]:
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage req
Msg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgDa
ta, Int32 type)
   at WcfClient.Service1Reference.IService1.GetMyCompositeType(Boolean initialis
ed)
   at WcfClient.Service1Reference.Service1Client.GetMyCompositeType(Boolean init
ialised) in C:\Users\Ian.Picknell\Documents\Blog\WCF\Serialising Enumerations wi
th WCF\WcfClient\Service References\Service1Reference\Reference.cs:line 140
   at WcfClient.Program.GetMyCompositeType(Boolean initialised) in C:\Users\Ian.
Picknell\Documents\Blog\WCF\Serialising Enumerations with WCF\WcfClient\Program.
cs:line 48
   at WcfClient.Program.DisplayMyCompositeObject(Boolean initialised) in C:\User
s\Ian.Picknell\Documents\Blog\WCF\Serialising Enumerations with WCF\WcfClient\Pr
ogram.cs:line 23
   at WcfClient.Program.Main(String[] args) in C:\Users\Ian.Picknell\Documents\B
log\WCF\Serialising Enumerations with WCF\WcfClient\Program.cs:line 11

I've highlighted what I believe to be the first exception thrown. One thing you may have noticed in the server-side configuration file earlier in the post was that I'm asking for exception details to be included within faults, which is generally a good idea during development. The inner-most exception (according to the client) was .Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host.

Diagnosing the Problem

Lets enable logging on both client and server to obtain more details. We update both configuration files to add the following within the <system.serviceModel> element:

<diagnostics>
  <messageLogging logEntireMessage="true" logMalformedMessages="true" logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true" />
</diagnostics>

We then direct the messages to an appropriate listener by adding a <system.diagnostics> element directly within the <configuration> element.

<system.diagnostics>
  <sources>
    <source name="System.ServiceModel" switchValue="Information, ActivityTracing" propagateActivity="true" >
      <listeners>
        <add name="xml"/>
      </listeners>
    </source>
    <source name="System.ServiceModel.MessageLogging">
      <listeners>
        <add name="xml"/>
      </listeners>
    </source>
  </sources>
  <sharedListeners>
    <add initializeData="WcfClient.svclog" type="System.Diagnostics.XmlWriterTraceListener" name="xml"/>
  </sharedListeners>
</system.diagnostics>

The only difference between the new entries in the client-side and server-side configuration files is the name of the file to which diagnostic messages will be written: WcfClient.svclog and WcfConsoleHost.svclog respectively.

Now we can both re-start WcfConsoleHost and WcfClient and re-produce the error. Having done this we need to check the logs for any additional information. So we fire-up Microsoft Service Trace Viewer, within the Microsoft Windows SDK and open the client-side log file, WcfClient.svclog.

In the Activity tab on the far left we immediately notice that one of the activities is highlighted in red. This is a 'Process action' activity for 'http://tempuri.org/IService1/GetMyCompositeType'. Clicking on the activity populates the panel in the top-right, which has a 'Throwing an exception' message highlighted in red. Clicking this text populates the panel in the bottom-right with details of the exception. Studying the exception doesn't really provide any additional information - it simply re-iterates that An existing connection was forcibly closed by the remote host. Checking the messages and activities immediately prior to the failure provides no additional insight.

Let's check the server-side log file. After all, if the client is saying that the server terminated the connection then hopefully the server is aware that it did this and knows why. So we open WcfConsoleHost.svclog with Microsoft Service Trace Viewer.

We again notice in the Activity tab on the far left that one of the activities is highlighted in red. This is a 'Process action' activity for 'http://tempuri.org/IService1/GetMyCompositeType'. Clicking on the activity populates the panel in the top-right, which has a 'Throwing an exception' message highlighted in red. This all sounds worrying familiar so far. Clicking on this text populates the panel in the bottom-right with details of the exception. But the exception is different to that experience by the client. The exception message is:

There was an error while trying to serialize parameter http://tempuri.org/:GetMyCompositeTypeResult. The InnerException message was 'Enum value '0' is invalid for type 'WcfServiceLibrary.Gender' and cannot be serialized. Ensure that the necessary enum values are present and are marked with EnumMemberAttribute attribute if the type has DataContractAttribute attribute.'.  Please see InnerException for more details.

Of course. Silly me. The Gender enumeration has values 1 and 2 (for Male and Female respectively) but as it's uninitialised it'll have the value 0 which simply isn't valid. It's a shame that WCF doesn't propagate the above exception to the client, especially as I've set includeExceptionDetailInFaults to true in my server-side configuration file. But at least the message is plain and simple once we switch on server-side logging.

Moral of the story? Always ensure that your enumerations support 0, even if you map that to something generic like Unknown or Undefined. You'll still have to handle these values at some point, but at least WCF won't thrown exceptions when attempting to pass them across the wire.

See Also

1 comment:

  1. thanks for the post ,, I tried to set a 0 value for one of the enum values , and adding the MemberShip attribute this did not solve the issue , i worked around it by setting a default value in object constructor ,,, hope this may help another one ,,

    ReplyDelete