Skip to content

File Transfer with WCF

2009 August 28
tags: , ,
by stefanoricciardi

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

Bookmark and Share

Technorati Tags: , , ,
  • Amit

    Stefano,

    I have a question.

    Can i use datacontracts/datamembers for streaming (download) instead of message contract.

    If yes then will it improve performance?

    Please advise

    thanks
    Amit

  • Amit

    Stefano,

    I have a question.

    Can i use datacontracts/datamembers for streaming (download) instead of message contract.

    If yes then will it improve performance?

    Please advise

    thanks
    Amit

  • stefanoricciardi

    As far as I know, the answer is no, you cannot mix streaming and “regular” data contracts.

    See this: http://msdn.microsoft.com/en-us/library/ms733742%28v=VS.90%29.aspx

  • stefanoricciardi

    As far as I know, the answer is no, you cannot mix streaming and “regular” data contracts.

    See this: http://msdn.microsoft.com/en-us/library/ms733742%28v=VS.90%29.aspx

  • James

    Stefano,

    Please, Can you share the code of SaveFile(response.FileByteStream, localFile); methos as well. I want to see how the stream is converted to file back again. Also can i use the your technique of WCF service for downloading zip files

    Thanks
    James

  • James

    Stefano,

    Please, Can you share the code of SaveFile(response.FileByteStream, localFile); methos as well. I want to see how the stream is converted to file back again. Also can i use the your technique of WCF service for downloading zip files

    Thanks
    James

  • stefanoricciardi

    James,

    you should be able to send any file type. I’ll try to share the code you’re asking for shortly.

    Stefano

  • stefanoricciardi

    James,

    you should be able to send any file type. I’ll try to share the code you’re asking for shortly.

    Stefano

  • http://www.filesdirect.com Frank

    Hey Stefano,

    Great work, but out of curiosity, why not use an online file transfer service like http://www.filesdirect.com?

  • Nadia

    Stephano, I implemented an MTOM webservice that transfers files using webservices.
    I used Axis2 and Java. I’m trying to make a decision if this could be an architecture for a large project where clients will be sending files using web services. I’d like your opinion about the feasability of MTOM and possible problems. Is there a size limitation? How do I make sure the file transferred completely to the client/server? Is there a way to send it with chunks.. Any help will be appreciated.
    Thanks
    Nadia

  • Anonymous

    Nadia,

    I haven’t got a first-hand experience interoperating with other stacks other than .NET. However MTOM is an open standard which is a few years old now, so I wouldn’t expect surprises there.

    If reliability is a concern, then you might want to try chunked transfer (see http://msdn.microsoft.com/en-us/library/aa717050.aspx). Like streaming, it will keep memory consumption to a minimum and there are no size restrictions.

  • Raghu

    Dear Stefano,
    Thank you verymuch for sharing your knowledge on WCF file transfer. Three months I googled a lot on the topic. I couldnot relevant information except in WCF samples by MSDN. Can you please provide the code in VB as well. I tried to convert the code ,but it is showing errors.

  • Anonymous

    Raghu,

    glad to know that you’ve found these sample code useful in some way.
    Unfortunately I am not fluent in VB, therefore I cannot help you there…

    Stefano

  • Raghu

    Dear Stefano,
    Thank for your reply. I tried in C# itself. Same error both in C# and VB.
    Error:
    “The type or namespace ‘FileTypeEnum’ could not be found(are you missing a using directive or an assembly reference)”

  • Raghu

    Can you please intimate the namespace or adding reference. Honestly, I tried my best. And then I had to refer the issue to you. Thank you

  • Anonymous

    Raghu,

    unfortunately these are not “copy and paste into your app” snippets. They
    are somehow extracted from the larger context of our line-of-business
    application.

    The compiler is complaining because you have not defined the FileTypeEnum.
    That enumeration is only meaningful within our own application (it describes
    which kind of file is being transferred; according to the file type a
    specific post-upload operation may be performed).

    My suggestion is to remove it wherever it is referenced if that is not
    something you need, or create your own enumeration.

    Stefano

  • Davide Benvegnu

    Hi. I’m italian but I write in English so everyone can understand :)

    I’ve implemented your solution for transferring files across WCF, by I have a problem that’s making me crazy…

    When I upload small files, all works without problems. But if the file I try to upload has more then 48840 bytes (Byte[].lenght() > 48840) I receive an error:

    Unexpected answer from server: (400) Bad Request.

    Googling i found a lot of people with the same issue, but incresing client-side values in app.config (max* values and readerQuotas values) doens’t work for me.

    Have u got an idea for my situation?

    Thanks a lot

  • Anonymous

    Davide,

    this looks like a server issue.
    Davide,

    this looks like a server configuration issue. Try increasing the maxRequestLegth of the httpRuntime.

    Mine is set as follows:

    Reference: http://msdn.microsoft.com/en-us/library/e1f13641(v=VS.90).aspx

  • Anonymous

    Davide,

    try changing the maxRequestLength of the httpRuntime.

  • Basharat

    I am from islamababd, great article! I am impressed by your active support in multiple issues addressed here. I implemented service and it is now working fine with upload and download. Upload is working fine even with same file mutiple times.
    Download first time is OK. If I download same file second time, it fails on client side. I debuged service download method, at the time of opening file stream exception is: The process cannot access the file ‘C:workTest.txt’ because it is being used by another process.
    Any clue?
    I am closing FileByteStream on client side. closing IClientChannel proxy too.
    Basharat Hussain

  • Msysurani3

    davide,

    do increase the array length,maxbuffersize at the server side as well.
    Also place the behaviour configuration that is at the client app.config in the service under endpoint tag.

    hope this helps you !!
    thanks
    Mumtaz

  • Adam O

    Hi Stefano,

    Great article – I am also experiencing the same issues as Basharat .. The first time I access a file using the service it is fine, but the file is then locked by the process and cannot be written to subsequently.

    Do you know of a way to close the stream on the download method (as it gets returned in the method it cannot be closed).

    Many thanks in advance,

    Adam

  • Anonymous

    Basharat, Adam,

    I have tried to reproduce the issue you are experiencing, but I am not able to.

    I have got the following integration test and it passes:


    [Test()]
    public void it_should_be_possible_to_download_a_generic_file_twice()
    {
    IFileTransferProxy proxy = new FileTransferProxy();
    string localFile = Path.Combine(basePath, "localFoo.txt");

    proxy.DownloadFileWithName(localFile, "foo.txt", serviceUrl);

    using (FileStream s = new FileStream(localFile, FileMode.Open, FileAccess.Read))
    {
    Assert.IsTrue(s.CanRead);
    }

    // try it again
    proxy.DownloadFileWithName(localFile, "foo.txt", serviceUrl);
    using (FileStream s = new FileStream(localFile, FileMode.Open, FileAccess.Read))
    {
    Assert.IsTrue(s.CanRead);
    }

    // cleanup
    File.Delete(localFile);
    }

  • Anonymous

    And for me it works also if I try to write to the file just downloaded:

    [Test()]
    public void it_should_be_possible_to_download_a_generic_file_twice_can_be_written()
    {
    IFileTransferProxy proxy = new FileTransferProxy();
    string localFile = Path.Combine(basePath, “localFoo.txt”);

    proxy.DownloadFileWithName(localFile, “foo.txt”, serviceUrl);

    using (FileStream s = new FileStream(localFile, FileMode.Open, FileAccess.Write))
    {
    Assert.IsTrue(s.CanWrite, “cannot write the first time”);
    }

    // try it again
    proxy.DownloadFileWithName(localFile, “foo.txt”, serviceUrl);
    using (FileStream s = new FileStream(localFile, FileMode.Open, FileAccess.Write))
    {
    Assert.IsTrue(s.CanWrite, “cannot write the second time”);
    }

    // cleanup
    File.Delete(localFile);
    }

  • Adam O

    Interesting – although I wonder if your server code for the DownloadFile method differs to mine.
    Could you shed a bit of light as to what happens inside your proxy.DownloadFileWithName?

    Again – many thanks for your help on this .. there is almost nothing on the web to help with this kind of stuff!

    I am doing something like this (similar to the article) :-

    public FileDownloadReturnMessage DownloadFile(FileDownloadMessage request)
    {
    try
    {
    string rootPath = ConfigurationManager.AppSettings["MercuryRootFolder"];
    FileStream fileStream = new FileStream(Path.Combine(rootPath, Path.GetFileName(Path.GetFileName(request.FileMetaData.FileName))), FileMode.Open);
    return new FileDownloadReturnMessage(new PublishingMetaData(), fileStream);
    }
    catch (IOException ex)
    {
    throw new FaultException(ex);
    }
    }

  • Anonymous

    Your server side code looks very similar to my own.

    On the client side, I have something like the following:

    private void IssueDownloadRequest(string localFile, string serviceUrl, FileDownloadMessage request)
    {
    this.service = this.proxy.OpenProxy(serviceUrl);

    try
    {
    using (FileDownloadReturnMessage response = this.service.DownloadFile(request))
    {
    if (response != null && response.FileByteStream != null)
    {
    SaveFile(response.FileByteStream, localFile);
    }
    }

    this.proxy.CloseProxy(this.service);
    }
    catch (Exception e)
    {
    throw new FileTransferProxyException(“Error while downloading the file”, e);
    }
    finally
    {
    // we expect the stream returned from the server to be closed by the
    // server itself so nothing to be done with it here. Just abort
    // the proxy if needed.
    this.proxy.AbortProxyIfNeeded(this.service);
    }
    }

    // this will throw if something is wrong
    private static void SaveFile(Stream saveFile, string localFilePath)
    {
    const int bufferSize = 65536; // 64K

    using (FileStream outfile = new FileStream(localFilePath, FileMode.Create))
    {
    byte[] buffer = new byte[bufferSize];
    int bytesRead = saveFile.Read(buffer, 0, bufferSize);

    while (bytesRead > 0)
    {
    outfile.Write(buffer, 0, bytesRead);
    bytesRead = saveFile.Read(buffer, 0, bufferSize);
    }
    }
    }

    The proxy you see in the integration tests is just a COM-visible wrapper around these features, it adds no special handling to the request.

  • Adam O

    I managed to fix this problem by keeping a reference to the stream as it is downloaded – then closing it once the download is finished .. it is not a perfect solution but works in my scenario.

    Thanks again for the help.

    static Dictionary OpenStreams { get; set; }
    public void AttemptToCloseStream(PublishingMetaData metaData)
    {
    if (ExecutionResearchService.OpenStreams != null)
    {
    if (ExecutionResearchService.OpenStreams.ContainsKey(Path.GetFileName(metaData.FileName)))
    {
    Stream stream = ExecutionResearchService.OpenStreams[Path.GetFileName(metaData.FileName)];
    stream.Flush();
    stream.Close();
    OpenStreams.Remove(Path.GetFileName(metaData.FileName));
    }
    }
    }

  • Basharat Hussain

    Basharat: I could not fix the problem either way. by calling IssueDownloadRequest() twice or Adam’s trick.
    First way same behavior, stream is not closed on second time and raise exception. Server code is similar in WCF. Client code is bit different like “FileDownloadReturnMessage response” is not disposable in my case so not have “using” block. Here is mine code:

    In Adam’s method, When I call stream.Flush it creashes with “Specified method is not supported” exception.

    Here is my code:

    private void IssueDownloadRequest(string baseFolder, FileDownloadMessage request)
    {
    var proxy = OpenProxy(“http://localhost/LargeDataService/FileTransferService.svc”);
    FileDownloadReturnMessage response = null;
    try
    {
    //AttemptToCloseStream(request.FileMetaData.RemoteFilename);
    response = proxy.DownloadFile(request);
    if (response != null && response.FileByteStream != null)
    {
    string localFilePath = Path.Combine(baseFolder, request.FileMetaData.RemoteFilename);
    SaveFile(response.FileByteStream, localFilePath);
    response.FileByteStream.Close();
    //OpenStreams.Add(request.FileMetaData.RemoteFilename, response.FileByteStream);
    MessageBox.Show(string.Format(“File downloaded successfully to path : {0}”, localFilePath));
    }

    }
    catch (Exception e)
    {
    throw e;// new FileTransferProxyException(e);// { InnerException = e, Source = “Download File Exception”};
    }
    finally
    {
    // we expect the stream returned from the server to be closed by the
    // server itself so nothing to be done with it here. Just abort
    // the proxy if needed.
    //this.proxy.AbortProxyIfNeeded(this.service);
    CloseProxy(proxy);
    }
    }

    and here is proxy
    public IFileTransferService OpenProxy(string serviceUrl)
    {
    Debug.Assert(!string.IsNullOrEmpty(serviceUrl));

    var binding = new BasicHttpBinding();
    binding.Name = “basicHttpStream”;
    binding.MaxReceivedMessageSize = 2147483647;
    binding.TransferMode = TransferMode.Streamed;
    binding.MessageEncoding = WSMessageEncoding.Mtom;

    var channelFactory = new ChannelFactory(
    binding,
    new EndpointAddress(serviceUrl));

    return channelFactory.CreateChannel();
    }

    public void CloseProxy(object sender)
    {
    IClientChannel channel = (IClientChannel)sender;
    try
    {
    channel.Close();
    }
    catch
    {
    channel.Abort();
    }
    }
    Any suggestion will be greatly appriciated

  • Anonymous

    Basharat,

    as I mentioned earlier I am not facing the same issues you are experiencing with the following sequence:
    1) download file x
    2) open file x for write
    3) close x
    4) goto 1

    There must be something different in your scenario that causes the stream not to be closed properly. Unfortunately, it’s quite difficult to understand the behavior without stepping into the code with a debugger.

  • Anonymous

    Basharat,

    as I mentioned earlier I am not facing the same issues you are experiencing with the following sequence:
    1) download file x
    2) open file x for write
    3) close x
    4) goto 1

    There must be something different in your scenario that causes the stream not to be closed properly. Unfortunately, it’s quite difficult to understand the behavior without stepping into the code with a debugger.

  • razzi

    For those, still having problems with the Streams not closing properly, just change the code in the DownloadFile method to

    Stream fs = new FileStream(serverFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

    Note the ‘FileAccess.Read, FileShare.ReadWrite’

    This makes the code work fine

  • Basharat Hussian

    @razzi
    Thanks a lot, yes it worked:)
    Basharat

  • raghu

    You should use idisposable interface to close the stream properly. http://www.codeproject.com/KB/WCF/WCF_FileTransfer_Progress.aspx. PLEAE CHECK IT OUT

  • raghu

    I would like to implement multiple downloads for single client. For example there are certain files intended for download to particular client every day. These files are uploaded by subordinate offices.
    For the present I am sending a query to the webserver to find if there are any files. If there are files, array of file names will be returned to client.
    Each file name in the array will be passed to download file contract till all the filenames in the array are downloaded.

    Can I pass array of file names at server side itself. Please help…..

  • http://twitter.com/stefanoric Stefano Ricciardi

    Raghu,
    that is certainly possible. You need to extend the service with a new operation contract and a message contract to suite your needs.

  • 07raghuram

    I am new in this technology could you please provide small hint how to achieve this.

  • raghu

    Dear Stefano ,

    I think you would like to just write a word a two if some one makes some post. Not interested to solve their exact problem. Please intimate what is the use of providing a provision to post a comment when you are not interested to provide some sort of solution.

    I have been waiting for your response for the past one week.
    If you are not interested to post anything, Please remove this comment box altogether and just share your experience.

  • Anonymous

    Raghu,

    as you can see from the number of replies I have made in this post, I *am* trying to help. However this is just a personal blog and not a question & answer site (like, say, StackOverflow).

    I have presented here a possible solution to a problem (sending files via WCF), hoping that this would inspire other people with similar problems. I am not a Microsoft MVP.

    It seems, though, that you need something more and you expect me to provide the solution to your specific problem. I don’t have code ready to solve it and it would take me hours just to understand your scenario, put the code together and test it until it works correctly.

  • razzi

    a correct version of the DownloadFile method is this one:

    public FileDownloadReturnMessage DownloadFile(FileDownloadMessage request)
    {
    Logger.AddLine(TraceLevel.Verbose, String.Format(“Calling DownloadFile(FileDownloadMessage[ {0}, {1}]“, request.FileMetaData.LocalFileName, request.FileMetaData.RemoteFileName));

    string localFileName = request.FileMetaData.LocalFileName;

    try
    {
    string serverFileName = request.FileMetaData.RemoteFileName;

    Stream fs = new FileStream(serverFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

    // Streams should always be closed after usage to free the resources behind them.
    // WCF web service functions returning values like streams are no exception. You should never rely on your client to do this
    // Using the OperationCompleted event, we handle the disposal of the streal
    OperationContext clientContext = OperationContext.Current;
    clientContext.OperationCompleted += new EventHandler(delegate(object sender, EventArgs args)
    {
    if (fs != null)
    fs.Dispose();
    });

    return new FileDownloadReturnMessage(new FileMetaData(localFileName, serverFileName), fs);
    }
    catch (Exception e)
    {
    Logger.AddLine(TraceLevel.Error, e.Message);
    FileAccessFault fault = new FileAccessFault();
    fault.AdditionalDetails = e.Message;
    throw new FaultException(fault);
    }
    }

  • tugberk

    can you provide ‘FileTypeEnum’ Enum code?

  • Anonymous

    Hi, see my reply to this comment.

  • Pks

    Hi, I am interested to know if you can pass the stream across multiple WCF services i.e., read the file and create stream in asp.net – pass it to wcf service – this wcf service in turn passes it to another wcf service which saves it to file system. I have a similar type of requirement but I want to know if it is possible to stream across multiple wcf services without storing it in memory in every level.

  • Rockkyy

    where is FileTypeEnum
    FileTypeEnum??

  • Anonymous

    This has been asked and replied above.
    Cheers,
     Stefano

0 visitors online now
0 guests, 0 bots, 0 members
Max visitors today: 0 at 12:02 am CET
This month: 0 at 02-01-2012 12:00 am CET
This year: 48 at 01-13-2012 02:02 pm CET
All time: 82 at 01-24-2011 04:41 pm CET