Streaming data over a network using a WCF service

WCF is an easy way to implement a distributed application and also provides built-in functions to handle complex problems. Streaming is one of the most important problems we always need to consider while working over a network in distributed applications. Applications deployed over a cloud sometimes need download or upload capabilities. Downloading a large file over a network using a buffered read technique such as a SOAP-based response can really be a bottleneck to the system. Reading a very large file from the memory can terminate the server process with OutofMemoryException.

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 that have download or upload capabilities in the connected client-server architecture. Hence, there will be no timeouts to send or receive large files. In this recipe, I will show you how to use streaming to ensure your files are downloaded or uploaded correctly.

How to do it...

Let's create a streamed WCF application, which allows you to download/upload a stream resource to clients:

  1. Create a service application and name it WCFCommunicateService.
  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 methods inside the ServiceContract file; one for FileDownload and another for FileUpload. Let's see what the service definition looks like:
    [ServiceContract]
    public interface IFileTransferLibrary
    {
    
        [OperationContract]
        void UploadFile(ResponseFile request);
    
        [OperationContract]
        ResponseFile DownloadFile(RequestFile request);
    }
  4. The contract defines the methods that are exposed outside. The ServiceContract attribute defines the interface that will have members available outside. The OperationContract attribute identifies the exposed members. In the preceding contract, IFileTransferLibrary has two methods: UploadFile, which takes an object of ResponseFile, and DownloadFile, which returns an object of ResponseFile. In the following code, you will see that we have used MessageContract instead of DataContract. The DataContract attribute is used to specify XML representation for the data that is transferred over the network, while MessageContract follows the SOAP standard to communicate data and therefore, this type of service can be accessed by any client that follows SOAP standards:
    [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;
    
        [MessageHeaderMember]
        public long byteStart = 0;
    }

    The RequestFile and ResponseFile files are attributed with MessageContract instead of DataContract for the complex types. It is important to note that as we are going to send data using streaming, we need to break all of the data into a sequence of packages. The DataContract attribute sends the whole object at a time and is not capable of producing packet information about the data that is sent over the network. The MessageContract attribute on the other hand sends them as small messages and is capable of producing the packet data for the streams. Also, note that we have used a disposable pattern to ensure that the file gets closed when the client gets disconnected.

  5. The MessageBodyMember attribute is added to the message body of the envelope it sends over the network. There can be only one body element for the whole message. The body holds the data that needs to be streamed. For every request, we have placed byteStart, which indicates where the download/upload should start from.
  6. To download a file, we need to first open the file and return the Stream as the result. Let's see what the code will look like:
    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);
    }

    You can see that we keep the Stream open for the file and seek the byte location from where the Stream needs to start. We set FileByteStream for the current Stream and let the response return to the client.

  7. Similarly, in the case of UploadFile, we need to write the file to disk at a certain position as follows:
    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);
                }
  8. In the UploadFile method, we are actually using Stream to extract data from the client side. We are reading from FileByteStream and writing the bytes back to the server disk location. We should also remember that we need to close the stream after the write operation is completed. In the previous code, we are using a Using block to automatically call the Dispose method, releasing/closing Stream.
  9. Let's now configure the service endpoint to support streaming:
      <bindings>
          <basicHttpBinding>
            <binding name="FileTransferServicesBinding"
              transferMode="Streamed"
              messageEncoding="Mtom"
              sendTimeout="01:05:00"
              maxReceivedMessageSize="10067108864">
            </binding>
          </basicHttpBinding>
        </bindings>

    Here, we have specified the transferMode binding called Streamed and the messageEncoding property called Mtom. The streamed transferMode ensures that the server sends the message response in streams and encoded to Mtom.

  10. Finally, host the service using ServiceHost. We create a console application and use ServiceHost to host the service.
    using (ServiceHost host = new ServiceHost(typeof(FileTransferLibrary)))
    {
            host.Open();
            Console.WriteLine("Service Started!");
    }
  11. Now let's create the client application to download/upload files to the server. A client is actually a consumer of the service. The client can add a reference to the services and can produce the discovery files that will help you access the streamed services. It will also allow you to produce progress behavior for the file.
  12. Add a service reference to the client so that you can call it. The service reference will automatically create the discovery files.
  13. Use server methods directly to store or send stream or data:
    public void StartDownloading()
    {Stream inputStream;
        try
        {
            string filePath = System.IO.Path.Combine("Download", this.FileName);
            string fileName = this.FileName;
                    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);
                        }
                }
        catch
        {
    //Log file errors here. Generally when file not found //or edited or Channel is faulted.
        }
        finally{if(inputStream != null) inputStream.Dispose();
        }
    }
  14. The code allows you to save the file to the disk. You can easily stop this operation by killing the thread that is downloading the file. 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 sending the file from a specific point.

How it works...

WCF communicates between two communication channels using sockets. Sockets are low-level implementations of communicating between two machines where one remains a SocketListener, which is stated here as the server side where the WCF service is hosted; another one is the socket client, which is used to connect to SocketListener and send/receive data. The sockets work at the TCP level and send serialized raw data between two communication channels.

WCF is a framework implemented on top of the socket API, which lets a programmer create contracts and based on it, the WCF API will understand how to serialize and communicate between the sockets.

Sockets support streaming internally. One can easily implement logic such that after sending a fixed set of bytes from one socket, it waits for acknowledgment from the other socket before the next set of bytes are sent. WCF implements this feature inside its API. It allows breaking the whole stream into a number of data packets that are defined as MessageContract and can be sent through a channel.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.48.161