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.
Let's create a streamed WCF application, which allows you to download/upload a stream resource to clients:
WCFCommunicateService
.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.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); }
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.
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.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.
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); }
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
.<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
.
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!"); }
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(); } }
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.
13.59.48.161