Click here to Skip to main content
Click here to Skip to main content

Progress Streamed File download and Upload with Resume facility

, 24 Sep 2010
Rate this:
Please Sign up or sign in to vote.
For distributed applications, WCF is the most easy and rich component.I like to use WCF as it is very easy to implement and also provides built in functions to handle with Complex problems. One of such interesting thing that I found in WCF is the support for Streaming while sending data from the ser

For distributed applications, WCF is the most easy and rich component. I like to use WCF as it is very easy to implement and also provides built in functions to handle with Complex problems. One of such interesting thing that I found in WCF is the support for Streaming while sending data from the service.

WCF allows you to send Byte streams through the communication channel or even allows you to implement a service that might take the stream start location from the service call and send the stream from a certain location. It allows you to use normal HttpBinding to support streams which is capable download or upload in connected Client - Server architecture. Hence there will be not timeouts for sending or receiving large files. In this article I will show you how you could use Streaming to ensure your files are downloaded or uploaded correctly.

Once I read Demitris solution for File upload and download it made me very easy to adjust the code and enhance it further with more flexibilities.

Lets first create the service step by step :

Service

  1. Create a Service Application and name it as WCFCommunicationService
  2. The first thing that you need to do in order to create a service is ServiceContract. So once you create a Service Library, you need to delete the existing Service1 and IService1 files from the solution and add a new Interface. 
  3. Create Two method, one for FileDownload and another for FileUpload. Lets see how the service definition looks like :
Contract

[ServiceContract]
public interface IFileTransferLibrary
{

    [OperationContract()]
    void UploadFile(ResponseFile request);

    [OperationContract]
    ResponseFile DownloadFile(RequestFile request);
}

The Contract defines the methods that are exposed to outside. The ServiceContract defines the interface which will have the members which are available to outside. The OperationContract identifies the exposed members. In the above contract the IFileTransferLibrary has two methods, UploadFile which takes an object of ResponseFile and DownloadFile which returns an object of ResponseFile.

[MessageContract]
public class ResponseFile : IDisposable
{
    [MessageHeader]
    public string FileName;

    [MessageHeader]
    public long Length;

    [MessageBodyMember]
    public System.IO.Stream FileByteStream;

    [MessageHeader]
    public long byteStart = 0;

    public void Dispose()
    {
        if (FileByteStream != null)
        {
            FileByteStream.Close();
            FileByteStream = null;
        }
    }
}

[MessageContract]
public class RequestFile
{
    [MessageBodyMember]
    public string FileName;

    [MessageBodyMember]
    public long byteStart = 0;
}

If you see the classes RequestFile and ResponseFile, you will see that I have used MessageContract instead of DataContract for my complex types. It is important. AS I am going to send the data streamed to the client, I need to send the data into packets. DataContract will send the whole object at a time while Messagecontract sends them into small Messages.It is also important to note that I have used IDisposable for ResponseFile to ensure that the Stream is closed whenever the connection is terminated.

Another important note is you need to use Stream as MessageBodyMember. The stream data will always transferred in MessageBody and you cannot add any other member into it. Thus you can see I have put all other members as MessageHeader.

For Both UploadFile and DownloadFile the Stream is taken from the ResponseFile object. The byteStart element of RequestFile ensures from where the the downloading should start and hence helps in Resuming a half downloaded file.  Lets see how to implement this:

public ResponseFile DownloadFile(RequestFile request)
{
    ResponseFile result = new ResponseFile();

    FileStream stream = this.GetFileStream(Path.GetFullPath(request.FileName));
    stream.Seek(request.byteStart, SeekOrigin.Begin);
    result.FileName = request.FileName;
    result.Length = stream.Length;
    result.FileByteStream = stream;
    return result;

}
private FileStream GetFileStream(string filePath)
{
    FileInfo fileInfo = new FileInfo(filePath);

    if (!fileInfo.Exists)
        throw new FileNotFoundException("File not found");

    return new FileStream(filePath, FileMode.Open, FileAccess.Read);
}
public void UploadFile(ResponseFile request)
{
           
    string filePath = Path.GetFullPath(request.FileName);
            
    int chunkSize = 2048;
    byte[] buffer = new byte[chunkSize];

    using (FileStream stream = new FileStream(filePath, FileMode.Append, FileAccess.Write))
    {
        do
        {
            int readbyte = request.FileByteStream.Read(buffer, 0, chunkSize);
            if (readbyte == 0) break;

            stream.Write(buffer, 0, readbyte);
        } while (true);
        stream.Close();
    }
}

If you see the code, you can see how I have used byteStart to start sending Bytes for the file. This ensures the file download to have resume facility. You can also implement the same for UploadFile too.

Download FileTransfer Sample - 161KB

ServerHost

After you create the Server application lets create the ServiceHost application. For the time being, I have used a Console application to demonstrate the thing. Lets add a new application to the solution and add the following lines in the Main method.

static void Main(string[] args)
{
    using (ServiceHost host = new ServiceHost(typeof(FileTransferLibrary)))
    {
        host.Open();
        Console.WriteLine("Service Started!");

        foreach (Uri address in host.BaseAddresses)
            Console.WriteLine("Listening on " + address);
        Console.WriteLine("Press any key to close...");
        Console.ReadKey();

        host.Close();
    }
}

Here I have used ServiceHost to host the Service. If you are going to use a Windows Service which I would probably like for this situation, I would have been writing the same code as you see above and use host.Open during the OnStart of the Service and host.Close during the OnStop of the service.

Finally lets configure the application. The configuration settings that I am going to use for the service is BasicHttpBinding with MTOM support. Lets see how my ServiceModel section of the service looks like :

<system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="WCFCommunicationLibrary.FileTransferServiceBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <basicHttpBinding>
        <binding name="FileTransferServicesBinding"
          transferMode="Streamed"
          messageEncoding="Mtom"
          sendTimeout="01:05:00"
          maxReceivedMessageSize="10067108864">
        </binding>
      </basicHttpBinding>
    </bindings>
    <services>
      <service behaviorConfiguration="WCFCommunicationLibrary.FileTransferServiceBehavior"
        name="WCFCommunicationLibrary.FileTransferLibrary">
        <clear />
        <endpoint address="http://localhost:8080/FileDownloaderLibrary/" binding="basicHttpBinding"
          bindingConfiguration="FileTransferServicesBinding" contract="WCFCommunicationLibrary.IFileTransferLibrary">
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange">
        </endpoint>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080/FileDownloader/" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>

Just as I said, the service config is very simple with Streamed as TransferMode of ServiceBinding. I have used BasicHttpBinding for simplicity but you can easily use TCPBinding if you like to. The endpoint defines the address as http://localhost:8080/FileDownloaderLibrary/ the Binding as BasicHttpBinding and the contract with the WCFCommunicationLibrary.IFileTransferLibrary which is the basic contract for our service.

If you run the console you will see the service started running with the following message, which indicates that the service is available for Download or upload of Files.


You can host your Service class library anywhere you want. The probable hosting environment could be IIS, Windows Activation Service, Windows Service, .NET programs, etc.

Now lets create the client application to download/ upload the files to the server. Client is actually a consumer of the Service which lets you to add the service reference to the client and will produce the discovery files which helps you to access the Streamed services and also allows you to produce progress behaviour for the file. Lets create an application which has this functionality.



 While creating the client, you need to follow the steps :

  1. Add a service reference to the Client, so that you can call it. The Service Reference will automatically create the discovery files.
  2. Once you added, design your UI, I have used WPF to design my UI,
  3. Use server methods directly to store or send Stream or data.


public void StartDownloading()
{
    try
    {

        string filePath = System.IO.Path.Combine("Download", this.FileName);
        string fileName = this.FileName;

        Stream inputStream;

        long startlength = 0;
        FileInfo finfo = new FileInfo(filePath);
        if (finfo.Exists)
            startlength = finfo.Length; // If File exists we need to send the Start length to the server

        long length = this.Client.DownloadFile(ref fileName, ref startlength, out inputStream);

        using (FileStream writeStream = new System.IO.FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write))
        {

            int chunkSize = 2048;
            byte[] buffer = new byte[chunkSize];

            do
            {
    
                int bytesRead = inputStream.Read(buffer, 0, chunkSize);
                if (bytesRead == 0) break;

    
                writeStream.Write(buffer, 0, bytesRead);

    
                this.ProgressValue = (int)(writeStream.Position * 100 / length);
            }
            while (true);


            writeStream.Close();
        }

        inputStream.Dispose();
    }
    catch
    {

    }
}

The code allows you to save the file to the disk. You can see we can easily stop this operation by clicking on Pause Button. The notion of Pause button is actually to stop the Thread completely. Hence the operation will be stopped and the file will remain in the disk until it is downloaded. When you resume, the process is called again. As in the function we check the size of the file and send it back to the server, this will ensure that the server will start on sending the file from a specified point.

Note : I have modified Demitris Solution of FileDownload to add new flexibilities to the solution.

Download FileTransfer Sample - 161KB

I hope you would like the adjustments. Thank you for reading.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Abhishek Sur
Architect
India India
Did you like his post?
 
Oh, lets go a bit further to know him better.
Visit his Website : www.abhisheksur.com to know more about Abhishek.
 
Abhishek also authored a book on .NET 4.5 Features and recommends you to read it, you will learn a lot from it.
http://bit.ly/EXPERTCookBook
 
Basically he is from India, who loves to explore the .NET world. He loves to code and in his leisure you always find him talking about technical stuffs.
 
Presently he is working in WPF, a new foundation to UI development, but mostly he likes to work on architecture and business classes. ASP.NET is one of his strength as well.
Have any problem? Write to him in his Forum.
 
You can also mail him directly to abhi2434@yahoo.com
 
Want a Coder like him for your project?
Drop him a mail to contact@abhisheksur.com
 
Visit His Blog

Dotnet Tricks and Tips



Dont forget to vote or share your comments about his Writing
Follow on   Twitter   Google+

Comments and Discussions

 
QuestionBroken Source code link Pinmemberronaldcst20-Feb-14 4:30 
QuestionMy vote of 5 starts Pinmembervalamas4-Dec-13 16:52 
QuestionNeed formating ur code PinmemberTridip Bhattacharjee24-Jan-13 21:14 
QuestionNeeds formatting PinmvpRichard MacCutchan21-Jan-13 3:59 
GeneralGood One .. PinmemberSushant Joshi25-Sep-10 5:13 
GeneralRe: Good One .. PinmvpAbhishek Sur25-Sep-10 8:35 
GeneralRe: Good One .. PinmemberKanasz Robert6-Oct-10 2:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 25 Sep 2010
Article Copyright 2010 by Abhishek Sur
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid