File Transfer with WCF

3 minute read

This is the first post on a small series about transferring large files with WCF using streaming :

WCF streaming is helpful in all those cases when you need to move a lot of data between the client and the service in a way that keeps memory consumption under control (see Large Data and Streaming on MSDN for more details if you are not familiar with the topic).

Streaming does impose some restrictions. Among them is that your operations can only have one input and/or output parameter and they can only be only one of the following:

  • a Stream
  • a Message
  • a class implementing IXmlSerializable

I wanted to expose a very simple service to upload and download files through WCF. And because I wanted to be able to pass both the file content as a stream and at least the file name, I could not just use a Stream but had to resort to using Messages: the headers would convey the file information (such as the file type) and the body the content of the file itself through a stream.

I therefore defined the service as follows:

[ServiceContract(Namespace = "http://schemas.acme.it/2009/04")]
public interface IFileTransferService
{
    [OperationContract(IsOneWay = true)]
    void UploadFile(FileUploadMessage request);
    [OperationContract(IsOneWay = false)]
    FileDownloadReturnMessage DownloadFile(FileDownloadMessage request);
}

[MessageContract]
public class FileUploadMessage
{
    [MessageHeader(MustUnderstand = true)]
    public FileMetaData Metadata;
    [MessageBodyMember(Order = 1)]
    public Stream FileByteStream;
}

[MessageContract]
public class FileDownloadMessage
{
    [MessageHeader(MustUnderstand = true)]
    public FileMetaData FileMetaData;
}

[MessageContract]
public class FileDownloadReturnMessage
{
    public FileDownloadReturnMessage(FileMetaData metaData, Stream stream)
    {
        this.DownloadedFileMetadata = metaData;
        this.FileByteStream = stream;
    }

    [MessageHeader(MustUnderstand = true)]
    public FileMetaData DownloadedFileMetadata;
    [MessageBodyMember(Order = 1)]
    public Stream FileByteStream;
}

[DataContract(Namespace = "http://schemas.acme.it/2009/04")]
public class FileMetaData
{
    public FileMetaData(
        string localFileName,
        string remoteFileName)
    {
        this.LocalFileName = localFileName;
        this.RemoteFileName = remoteFileName;
        this.FileType = FileTypeEnum.Generic;
    }

    public FileMetaData(
        string localFileName,
        string remoteFileName,
        FileTypeEnum fileType)
    {
        this.LocalFileName = localFileName;
        this.RemoteFileName = remoteFileName;
        this.FileType = fileType;
    }

    [DataMember(Name="FileType", Order=0, IsRequired=true)]
    public FileTypeEnum FileType;</p>
    [DataMember(Name="localFilename", Order=1, IsRequired=false)]
    public string LocalFileName;
    [DataMember(Name = "remoteFilename", Order = 2, IsRequired = false)]
    public string RemoteFileName;
}

public class FileTransferService : IFileTransferService
{
    public void UploadFile(FileUploadMessage request)
    {
          // implementation here

    }

    public FileDownloadReturnMessage DownloadFile(FileDownloadMessage request)
    {
          // implementation here

    }
}

I am omitting the details of the implementation, they are pretty straightforward (if you are interested, I will gladly share it. UPDATE: I have put the basic scheleton of the server implementation is a separate post).

And this is how the service was configured on the server side within the web.config.

<system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="serviceBehavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"
                              httpHelpPageEnabled="true" />
          <dataContractSerializer maxItemsInObjectGraph="2147483647"/>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service behaviorConfiguration="serviceBehavior"
                  name="FileTransferService">
        <endpoint address=""
                  name="basicHttpStream"
                  binding="basicHttpBinding"
                  bindingConfiguration="httpLargeMessageStream"
                  contract="IFileTransferService" />
        <endpoint address="mex"
                      binding="mexHttpBinding"
                      contract="IMetadataExchange"/>
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="httpLargeMessageStream"
                    maxReceivedMessageSize="2147483647"
                    transferMode="Streamed"
                    messageEncoding="Mtom" />
      </basicHttpBinding>
    </bindings>
  </system.serviceModel>

This is the base of the plumbing that it's needed to create the service.

While I was developing it, I have lost quite some time for an error which was really obscure to decipher:
Server Error in '/' Application. Operation 'UploadFile' in contract 'IFileTransferService' uses a MessageContract that has SOAP headers. SOAP headers are not supported by the None MessageVersion.

Googling and binging around I found almost no relevant information on the topic, so I was pretty on my own.

I thought the problem was in the HTTP binding that I was using (maybe it was not compatible with SOAP headers?). Unfortunately the HTTP binding did not have any way to specify a different MessageVersion. So I ended up creating a custom binding, like the following, but that didn't help either:

      <customBinding>
        <binding name="customHttpBindingStream">
          <textMessageEncoding messageVersion="Default"/>
          <httpTransport
               transferMode="Streamed"
               maxReceivedMessageSize="2147483647" />
        </binding>
      </customBinding>

I made several other attempts with no luck. I then tried self hosting the service and that did work. Then I was certain that it was something with the web hosting. Tried IIS6, IIS7, etc... Same exception all the time.

To make a long story short, at the end, while reducing my web.config to the bare bones, I found the culprit: it was the name of the service in the service configuration.

Instead of writing:

      <service behaviorConfiguration="serviceBehavior"
                  name="FileTransferService">

I had written by mistake:

      <service behaviorConfiguration="serviceBehavior"
                  name="FileTransfer">

Talk about meaningful error messages!

See more at File Transfer with WCF II

Updated:

Leave a Comment