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 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 of download or upload in connected Client - Server architecture. Hence, there will be no 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, that made it very easy for me to adjust the code and enhance it further with more flexibilities.
Let's 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 methods, one for
FileDownload
and another for FileUpload
. Let's 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 that 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 in 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 be transferred in MessageBody
and you cannot add any other member into it. Thus, you can see that 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 downloading should start and hence helps in resuming a half downloaded file. Let's 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.
ServerHost
After you create the Server application, let's create the ServiceHost
application. For the time being, I have used a Console
application to demonstrate the thing. Let's 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, let's configure the application. The configuration settings that I am going to use for the service is BasicHttpBinding
with MTOM support. Let's 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 let's 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. Let's create an application which has this functionality.
While creating the client, you need to follow the steps:
- Add a service reference to the Client, so that you can call it. The Service Reference will automatically create the discovery files.
- Once you added, design your UI, I have used WPF to design my UI.
- 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;
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 that 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.
I hope you will like the adjustments. Thank you for reading.