I’ve been struggling with getting some of our backend components to play nice over WCF for the past couple of days.  Our situation is a little more complex than most I think; our systems have dependencies on a variety of COM libraries, uses some Java APIs via IKVM.NET, and has differing capabilities as far as 64-bit goes. 

What didn’t work

So I tried many things over the course of the last week.  The following is basically a brain dump of every issue I ran in to as well as solutions if I ever found one:

IIS Hosting

The services we were trying to host were to be exposed over the NetTcpBinding, which works fine with IIS7+.  I was able to get stripped-down “Hello, World!” prototype working quite quickly.  What didn’t work was hosting our service in IIS7.  As it turns out, there are apparently some major issues with using IKVM.NET in an ASP.NET environment, which is apparently how even NetTcpBinding services are hosted in IIS.  Bummer.  I never found a solution to this problem.

Sending large objects

Some of the objects we were transmitting over the wire were up to 10MB in size.  Out of the box, WCF will explode with helpful messages about buffer sizes and such.  This was easy to fix through configuration, just be sure you put the config on the right end of the channel (the server-side):

<system.serviceModel>
<services>
  <service ... >
    <endpoint binding="netTcpBinding" bindingConfiguration="MyCustomBinding" ...>
    </endpoint>
    ...
  </service>
</services>
...
<bindings>
  <netTcpBinding>
    <binding name="MyCustomBinding" ... maxBufferSize="20971520" maxReceivedMessageSize="20971520" >
      <readerQuotas maxArrayLength="10485760" maxStringContentLength="10485760" />
    </binding>
  </netTcpBinding>
</bindings>
</system.serviceModel>

Sending too many objects too quickly

We ran into some timeout issues when many threads tried to hammer the service with large amounts of data in parallel.  By default, WCF communications will time out after 1:00.  While that should be enough most of the time, it isn’t always the case.  Fortunately again, it’s a pretty easy fix, just increase the timeout values.  The timeout can be configured on the proxy client-side and on the server-side.  You will probably need to change it on both sides (I did). 

IDisposawhat?

It’s well-known that WCF proxies are in fact broken.  See my previous article about how we created a better base class for our proxies. 

Port Sharing

If the service you are trying to expose over WCF implements multiple contracts, you will need to expose multiple endpoints.  You probably don’t want to have each contract on a separate port, but by default, that’s what you will have to do, because you can’t have two listeners on the same port.  Net.Tcp Port Sharing Service to the rescue!  This Windows Service, installed along with the .NET Framework, enables your WCF NetTcpBinding endpoints share ports.  After making sure the service is enabled (it is disabled by default), you can take advantage of it by setting the portSharingEnabled attribute to true on the binding configuration, like so:

<system.serviceModel>
    <services>
        <service ...>
            <endpoint binding="netTcpBinding" bindingConfiguration="MyCustomBinding" ...>
            </endpoint>
            ...
        </service>
    </services>
    ...
    <bindings>
        <netTcpBinding>
            <binding name="MyCustomBinding" portSharingEnabled="true" ...>
                ...
            </binding>
        </netTcpBinding>
    </bindings>
</system.serviceModel>

The Final Host

As I said, our services were not compatible with IIS hosting due to various COM issues as well as IKVM.NET dependencies.  That left us with two options: self-host the services in a custom application, or create Windows Services to serve as hosts.  We decided to go the self-host route so that deploying our services would remain dirt-simple (it doesn’t get much simpler than XCOPY).  Below is a skeleton class that you can use to host a WCF service in a console application.  Pressing Ctrl+C will shut the service down gracefully and exit:

class Program
{
    //Log4net logger
    private static readonly ILog mLogger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

    private static readonly AutoResetEvent mStopFlag = new AutoResetEvent(false);

    static void Main(string[] args)
    {
        //When the user hits Ctrl+C, the server will be shut down. 
        Console.CancelKeyPress += delegate
                                    {
                                        mLogger.Info("Shutting down server thread.");
                                        mStopFlag.Set();
                                    };

        var serverThread = new Thread(ServerThread) {IsBackground = true};
        serverThread.Start();

        serverThread.Join();
    }

    private static void ServerThread()
    {
        //This will grab the WCF configuration from the app.config file.
        using (ServiceHost host = new ServiceHost(new MyService()))
        {
            host.Open();

            mLogger.Debug("Service is online and awaiting requests...");

            Console.WriteLine("running");
            mStopFlag.WaitOne();
        }

        Console.WriteLine("stopped");
    }
}

Suggestions

I confess to being a total WCF newb, so it’s quite likely that I’ve done something wrong.  If you see any incorrect information or have suggestions for things we could do better, please let me know. 🙂

**UPDATE – 10/29/09**

There is actually a bug in the host application I presented.  While it will allow the background thread to shut down gracefully, it will still cause the application to abort abnormally, which means any pending finalizers and things won’t be executed.  To resolve the issue, use the following CancelKeyPress handler instead:

Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs cancelEventArgs)
                            {
                                mLogger.Info("Shutting down server thread.");
                                mStopFlag.Set();
                                //This is IMPORTANT.
                                cancelEventArgs.Cancel = true;
                            };

Thanks go to James for pointing this out.