Displaying of progress of downloading data in WCF services

Platform Windows Communication Foundation can be used not only for a simple operations call, but also for transfer of big parts of data (for example, files in some mbyte). Sometimes such scenarios are applicable and on slow channels. In this case an indicator of executing of operation (progress bar) is necessary.

For simplicity we will use BasicHttpBinding binding. The key moment at implementation of the given scenario is setting of TransferMode parameter to Streamed value. Thus, data will be transferred as a stream.

After that we will define the contract. It is required to transfer a file name, its size and contents to the client side.

[ServiceContract]
public interface IFileTransferService
{
    [OperationContract]
    RemoteFileInfo DownloadFile(DownloadRequest request);
}
 
[MessageContract]
public class DownloadRequest
{
    [MessageBodyMember]
    public string FileName;
}
 
[MessageContract]
public class RemoteFileInfo : IDisposable
{
    [MessageHeader(MustUnderstand = true)]
    public string FileName;
 
    [MessageHeader(MustUnderstand = true)]
    public long Length;
 
    [MessageBodyMember(Order = 1)]
    public Stream FileByteStream;
 
    public void Dispose()
    {
        if (FileByteStream != null)
        {
            FileByteStream.Close();
            FileByteStream = null;
        }
    }
}

Apparently, DownloadFile operation returns RemoteFileInfo object which contains the necessary information.

public class FileTransferService : IFileTransferService
{
    public RemoteFileInfo DownloadFile(DownloadRequest request)
    {
        var filePath = request.FileName;
        var fileInfo = new FileInfo(filePath);
 
        if (fileInfo.Exists==false)
        {
            throw new FileNotFoundException("File not found", request.FileName);
        }
 
        var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
 
        var result = new RemoteFileInfo
                         {
                             FileName = request.FileName, 
                            Length = fileInfo.Length, 
                            FileByteStream = stream
                         };
 
        return result;
    }
}

Now it is necessary to host service

class Program
{
    static void Main()
    {
        var myServiceHost = new ServiceHost(typeof(FileService.FileTransferService));
        myServiceHost.Open();
 
        Console.ReadKey();
 
        myServiceHost.Close();
    }
}

Thus the configuration file will have the following data.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="FileTransferServicesBinding" transferMode="Streamed" messageEncoding="Mtom" maxReceivedMessageSize="20134217728" />
            </basicHttpBinding>
        </bindings>
        <services>
            <service behaviorConfiguration="MyServiceTypeBehaviors" name="FileService.FileTransferService">
                <endpoint address="getfile"
                  binding="basicHttpBinding"
                  bindingConfiguration="FileTransferServicesBinding"
                  contract="FileService.IFileTransferService" />
                <host>
                    <baseAddresses>
                        <add baseAddress="http://localhost/" />
                    </baseAddresses>
                </host>
            </service>
        </services>
        <behaviors>
            <serviceBehaviors>
                <behavior name="MyServiceTypeBehaviors">
                    <serviceMetadata httpGetEnabled="true" />
                    <serviceDebug includeExceptionDetailInFaults="true" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
    </system.serviceModel>
</configuration>

Now it is necessary to create a client side.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="fileBinding" maxReceivedMessageSize="20134217728" messageEncoding="Mtom" transferMode="Streamed" />
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost/getfile"
                                binding="basicHttpBinding"
                                bindingConfiguration="fileBinding"
                                contract="Client.FileTransferClient.IFileTransferService" />
        </client>
    </system.serviceModel>
</configuration>

After generation of a client proxy it looks as follows.

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
public partial class FileTransferServiceClient : 
    System.ServiceModel.ClientBase<Client.FileTransferClient.IFileTransferService>, 
    Client.FileTransferClient.IFileTransferService
{
   public FileTransferServiceClient()
   {
   }
   
   public FileTransferServiceClient(string endpointConfigurationName) : 
           base(endpointConfigurationName)
   {
   }
   
   public FileTransferServiceClient(string endpointConfigurationName, string remoteAddress) : 
           base(endpointConfigurationName, remoteAddress)
   {
   }
   
   public FileTransferServiceClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : 
           base(endpointConfigurationName, remoteAddress)
   {
   }
   
   public FileTransferServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 
           base(binding, remoteAddress)
   {
   }
   
   Client.FileTransferClient.RemoteFileInfo Client.FileTransferClient.IFileTransferService.DownloadFile(Client.FileTransferClient.DownloadRequest request)
   {
       return base.Channel.DownloadFile(request);
   }
}

Let's create more convenient DownloadFile method.

public long DownloadFile(ref string fileName, out System.IO.Stream fileByteStream)
{
    Client.FileTransferClient.DownloadRequest inValue = new Client.FileTransferClient.DownloadRequest();
    inValue.FileName = FileName;
    Client.FileTransferClient.RemoteFileInfo retVal = ((Client.FileTransferClient.IFileTransferService)(this)).DownloadFile(inValue);
    FileName = retVal.FileName;
    FileByteStream = retVal.FileByteStream;
    return retVal.Length;
}

Now we should receive this stream and read out from it the information in the portions.

var client = new FileTransferClient.FileTransferServiceClient();
 
Stream inputStream;
long length = client.DownloadFile(ref fileName, out inputStream);
 
using (var writeStream = new FileStream(fileName, FileMode.CreateNew, FileAccess.Write))
{
    const int bufferSize = 2048;
    var buffer = new byte[bufferSize];
 
    do
    {
        int bytesRead = inputStream.Read(buffer, 0, bufferSize);
        if (bytesRead == 0)
        {
            break;
        }
 
        writeStream.Write(buffer, 0, bytesRead);
 
        progressBar1.Value = (int)(writeStream.Position * 100 / length);
    }
    while (true);
 
    writeStream.Close();
}
 
inputStream.Dispose();
client.Close();

Great! Now we have received the application which will display progress of downloading data on the client. Similarly it is possible to make uploading data to a server. If this infomation is interesting to you I recommend to read more detailed article.

Source code:

No Comments