WCF

Today I encountered a problem with accessing the metadata for a WCF service that was deployed on a Windows Server 2003 machine.

The WSDL part worked just fine for the metadata exchange endpoint (url?wsdl, url?wsdl=wsdl0, etc.). These WSDL files refer to XSD files for the message types. Requesting these files (url?xsd=xsd0, url?xsd=xsd1, etc.) resulted in an empty response from the webserver. Checking the IIS logs indicated a HTTP 200 OK response with 0 bytes transferred. A very weird problem. Checking the config files did not lead anywhere.

Eventually I found a hint in a reply by James Zhang in this MSDN Forum post. The identity that is used for the application pool that hosts the WCF service must have the correct NTFS permissions on the %WINDIR%temp folder. The identity that I used is a domain account. After setting the right NTFS permissions, the problem disappeared.

The funny thing was that this particular answer wasn't the answer for the original question in this forum post.

James Zhang does not indicate what type of permissions are needed, so I had to experiment a little.

First I added the account to the local Users group. This gives it special access permissions: Traverse folder/execute file, create files/write data, create folders/append data  on this folder and subfolders. This is not enough. Then I realized, the domain account is already implicitly a member of this group because the Users group contains the NT AuthorityAuthenticated Users group. Next, I duplicated the extra rights that the NETWORK SERVICE account had for the domain account. These are list folder/read data and delete permissions for this folder, subfolders and files. This was enough. But it doesn't seem very secure. Now the service account can access temporary files created by other accounts.

So I experimented a bit more. I tuned back the NTFS permissions for the service account on %WINDIR%temp to list folder/read data on this folder only. This is just enough. This allows the account to see which files are in the temp folder, but it doesn't allow it to read the data in files that are owned by other accounts.

It is very unfortunate that WCF didn't give any clue about why it couldn't generate metadata in this case. It is also unfortunate that it needs just slightly more permissions that a standard user on the folder for temporary files.

Note that if you run your WCF service in an IIS application pool under the default NETWORK SERVICE account you won't run into this problem, because it has more than enough permissions.

PS: Best practices indicate you shouldn't deploy your services with metadata enabled. We will turn this off eventually. However, of course it should work if you do want to enable this.

1 Comment

When you create a Windows Communication Foundation (WCF) service proxy class for a service by using svcutil.exe, it creates a proxy that derives from System.ServiceModel.ClientBase<TChannel>. This class implements IDisposable so you think you can use your proxy safely in a C# using statement and get guaranteed clean-up in the face of exceptions.

However, the System.ServiceModel.ClientBase<TChannel> class can throw exceptions from its Dispose method, because it calls its Close method. This can happen when it is in a faulted state. In that case you have to call Abort for clean-up. Most times you are not interested in knowing whether the Dispose fails or not and you just want to have guaranteed clean up of any resources held by the proxy.

Check out this discussion on the WCF forum for why Microsoft implemented the dispose in this way.

To prevent you from writing a lot of code to accomplish this clean-up, I have written a generic ServiceProxyHelper class that does it for you. You can download it here or view the code below. It only swallows expected exceptions in its Dispose method but it always cleans up by calling Abort.

The idea is that you derive a class from my ServiceProxyHelper<TProxy, TChannel> class. Insert your proxy class (that should derive from ClientBase<TChannel>) name for TProxy, and the service interface name for TChannel. The helper class wraps the proxy and doesn't directly expose its methods. From your derived class you can use the protected Proxy property to get access to the service proxy. You should add methods to your derived class that delegate to the proxy.

For our project we added convenience method to the derived helper that create request messages and unpack response messages. So our helper methods have a different signature than the methods on the proxy class itself.

This code is provided "AS-IS" without any warranties. You can change it to fit your needs.

 
using System;
using System.Collections.Generic;
using System.ServiceModel;

namespace LogicaCMG.ServiceAccess
{
    /// <summary>
    /// Generic helper class for a WCF service proxy.
    /// </summary>
    /// <typeparam name="TProxy">The type of WCF service proxy to wrap.</typeparam>
    /// <typeparam name="TChannel">The type of WCF service interface to wrap.</typeparam>
    public class ServiceProxyHelper<TProxy, TChannel>: IDisposable
        where TProxy : ClientBase<TChannel>, new()
        where TChannel : class
    {
        /// <summary>
        /// Private instance of the WCF service proxy.
        /// </summary>
        private TProxy _proxy;

        /// <summary>
        /// Gets the WCF service proxy wrapped by this instance.
        /// </summary>
        protected TProxy Proxy
        {
            get
            {
                if (_proxy != null)
                {
                        return _proxy;
                }
                else
                {
                        throw new ObjectDisposedException("ServiceProxyHelper");
                }
            }
        }

        /// <summary>
        /// Constructs an instance.
        /// </summary>
        protected ServiceProxyHelper()
        {
            _proxy = new TProxy();
        }

        /// <summary>
        /// Disposes of this instance.
        /// </summary>
        public void Dispose()
        {
            try
            {
                if (_proxy != null)
                {
                    if (_proxy.State != CommunicationState.Faulted)
                    {
                        _proxy.Close();
                    }
                    else
                    {
                        _proxy.Abort();
                    }
                }
            }
            catch (CommunicationException)
            {
                _proxy.Abort();
            }
            catch (TimeoutException)
            {
                _proxy.Abort();
            }
            catch (Exception)
            {
                _proxy.Abort();
                throw;
            }
            finally
            {
                _proxy = null;
            }
        }
    }
}