Transferring large files using WCF

Recently I wanted to send large files (several GB) from a rich desktop client to a web service. The client and service communicate using WCF and I thought that this would be quite easy. As it turns out it is but there are a few gotchas on the way.

Streaming

The first gotcha is that you do not want to buffer the message as you send it. Depending on the amount of RAM and the size of the file this may or may not be a problem on the client but it's almost certainly going to be a problem on the server with multiple clients sending files at the same time.

To solve this you can either write a service contract that accepts the file in chunks or use WCF streaming. In this instance I decided to use streaming.

Streaming in WCF does have some drawbacks which I'm not going to go into here so it's not suitable for everything but it was fine in this situation.

To use streaming you simply pass parameters of type 'Stream' and change the config to use streaming. This is a per endpoint setting so I set up a separate endpoint for the upload service.

Passing Metadata

The next problem I found was how to pass metadata with streams. Since I'm uploading via HTTP I want to make sure the file gets there intact so I'd like to transfer a checksum with the file (along with other details). WCF will not let you have any other parameters if using streams so how do you pass it.

Since using sessions with streaming can result in unpredictable behaviour there are a couple of options open. The first is to return a GUID (or other unique ID) after uploading the file and then allow a separate operation to set the metadata for that GUID and then link it to the previous file upload.The other option, and the one I chose, is to explicitly specify the message and send the metadata in the message header.

Max Message Sizes

The next issue I faced was the size of the messages, you need to ensure that the maxReceivedMessageSize is set large enough for your largest file. Since headers are always buffered, even when streaming, you want to ensure that this doesn't result in DOS attacks by settings maxBufferSize to something reasonable such as 64K. This will allow large streamed bodies but limits the size of the headers.

Gotchas

So that's it, all set up and working now. Well not quite. As I mentioned at the start there are a few gotchas with this.

First, the VS webdev server (Cassini) cannot handle streaming over HTTP. This is simple to fix, use IIS or self host in a console app or windows service.

Second, IIS uses the ASP.NET maxRequestLength setting for the max length not the WCF setting. You need to add this to your web config.

Third, IIS cannot transfer more than 2GB of data. You'll need to self host to get around this if you need to send more data than that.

Fourth, timeouts can occur so you need to increase the send/receive timeouts in WCF.

Code

So here are samples of the code bits I used.

web.config - server

<system.web>
    <httpRuntime maxRequestLength="2097151" />
</system.web>

<basicHttpBinding>
    <binding name="FileSenderService.StreamedBinding"
                 transferMode="StreamedRequest" maxBufferSize="65536"
                 maxReceivedMessageSize="2000000000" messageEncoding="Mtom"
                 receiveTimeout="00:10:00">
    </binding>
</basicHttpBinding>

 

app.config - client

<basicHttpBinding>
    <binding name="BasicHttpBinding_IFileTransfer" sendTimeout="00:10:00"
             messageEncoding="Mtom"transferMode="StreamedRequest" />
</basicHttpBinding> 

Contracts

[MessageContract]
public class SendFileRequestMessage
{
    [MessageHeader(MustUnderstand = true)]
    public FileTransferInfo FileInfo;

    [MessageBodyMember(Order = 1)]
    public Stream FileData;
}

[ServiceContract]
public interface IFileTransfer
{
    [OperationContract]
    void SendFile(SendFileRequestMessage request);
}

[DataContract]
public class FileTransferInfo
{
    [DataMember(Order = 1, IsRequired = true)]
    public string Name { get; set; }

    [DataMember(Order = 2, IsRequired = true)]
    public byte[] Checksum { get; set; }
}

The following blogs / articles were useful in sorting this out.

 

Print | posted on Monday, June 23, 2008 3:17 PM

Feedback

# RE: Transferring large files using WCF

left by at 6/12/2009 5:57 PM Gravatar

Thanks so much, this saved my life today.


# re: Transferring large files using WCF

left by Bruno Melo at 8/20/2009 12:51 PM Gravatar
Thanks thanks thanks. We need people thats share information (important and easy) with us. So simple, so difficult to find on net ever on MSDN. Thank you again.

# re: Transferring large files using WCF

left by admin at 8/20/2009 1:01 PM Gravatar
Thanks for the comments and glad the article helped you out. I really must start posting some more snippets like this one.

# re: Transferring large files using WCF

left by Stefano Ricciardi at 8/27/2009 12:18 PM Gravatar
I am also trying to put together something like this, using the same links as you did as a base framework.

However, I keep getting an error like the following:

Operation 'UploadFile' in contract 'IFileTransferService' uses a MessageContract that has SOAP headers. SOAP headers are not supported by the None MessageVersion.

I have tried using both basicHttpBinding and a customBinding, but the error is still there.

Have you faced anything like that while developing your own service?

# re: Transferring large files using WCF

left by admin at 8/28/2009 9:30 AM Gravatar
Thats odd Stefano. It's quite correct that you can't use SOAP headers with a message version of None as that enables POX messaging and thus disables SOAP.

Try explicitly setting the MessageVersion property on the basic HTTP binding to something other than None such as Soap11 to see if that helps. Unfortunately it looks like the basicHttpBindingElement in the configuration file doesn't expose that property so you'll probably have to do it in code.

However, you could also try adding a textMessageEncoding element to your custom binding with a messageVersion attribute of Soap11 as I believe you can do that in config. I've not tested any of these things since it all worked ok with what I did above.

# re: Transferring large files using WCF

left by Stefano Ricciardi at 9/1/2009 10:53 AM Gravatar
I finally found out what the issue was with my service... nothing to do with message version, despite what WCF was trying to tell me. I had simply mispelled the name of one of the classes implementing the service.

I have posted my experience should any other people incur in the same strange error message: http://wp.me/puaLG-58

# re: Transferring large files using WCF

left by admin at 9/2/2009 3:05 PM Gravatar
Glad to see you got it sorted, as you said, an odd error message for the issue you had.
Title  
Name
Email (never displayed)
Url
Comments   
Please add 5 and 7 and type the answer here: