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
- Create a Service Application and name it as WCFCommunicationService
- 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.
- 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